Support _PREFIXED variable names and fail when variable missing

This commit is contained in:
Gregory Schier
2025-03-06 07:15:02 -08:00
parent 0db0cdfd6c
commit 787a0433cb
4 changed files with 26 additions and 10 deletions

View File

@@ -6,6 +6,9 @@ pub enum Error {
#[error("Render Error: {0}")] #[error("Render Error: {0}")]
RenderError(String), RenderError(String),
#[error("Render Error: Variable \"{0}\" is not defined in active environment")]
VariableNotFound(String),
#[error("Render Error: Max recursion depth exceeded")] #[error("Render Error: Max recursion depth exceeded")]
RenderStackExceededError, RenderStackExceededError,
} }

View File

@@ -269,9 +269,9 @@ impl Parser {
while self.pos < self.chars.len() { while self.pos < self.chars.len() {
let ch = self.peek_char(); let ch = self.peek_char();
let is_valid = if start_pos == self.pos { let is_valid = if start_pos == self.pos {
ch.is_alphabetic() // First char has to be alphabetic ch.is_alphabetic() || ch == '_' // First is more restrictive
} else { } else {
ch.is_alphanumeric() || ch == '-' || ch == '_' ch.is_alphanumeric() || ch == '_' || ch == '-'
}; };
if is_valid { if is_valid {
text.push(ch); text.push(ch);
@@ -457,13 +457,13 @@ mod tests {
#[test] #[test]
fn var_prefixes() { fn var_prefixes() {
let mut p = Parser::new("${[ -a ]}${[ _a ]}${[ 0a ]}"); let mut p = Parser::new("${[ -a ]}${[ 0a ]}");
assert_eq!( assert_eq!(
p.parse().tokens, p.parse().tokens,
vec![ vec![
Token::Raw { Token::Raw {
// Shouldn't be parsed, because they're invalid // Shouldn't be parsed, because they're invalid
text: "${[ -a ]}${[ _a ]}${[ 0a ]}".into() text: "${[ -a ]}${[ 0a ]}".into()
}, },
Token::Eof Token::Eof
] ]
@@ -476,8 +476,8 @@ mod tests {
assert_eq!( assert_eq!(
p.parse().tokens, p.parse().tokens,
vec![ vec![
Token::Raw { Token::Tag {
text: "${[ _a ]}".into() val: Val::Var { name: "_a".into() }
}, },
Token::Eof Token::Eof
] ]

View File

@@ -1,4 +1,4 @@
use crate::error::Error::RenderStackExceededError; use crate::error::Error::{RenderStackExceededError, VariableNotFound};
use crate::error::Result; use crate::error::Result;
use crate::{FnArg, Parser, Token, Tokens, Val}; use crate::{FnArg, Parser, Token, Tokens, Val};
use log::warn; use log::warn;
@@ -100,7 +100,7 @@ async fn render_tag<T: TemplateCallback>(
let r = Box::pin(parse_and_render_with_depth(v, vars, cb, depth)).await?; let r = Box::pin(parse_and_render_with_depth(v, vars, cb, depth)).await?;
r.to_string() r.to_string()
} }
None => "".into(), None => return Err(VariableNotFound(name)),
}, },
Val::Bool { value } => value.to_string(), Val::Bool { value } => value.to_string(),
Val::Fn { name, args } => { Val::Fn { name, args } => {
@@ -142,7 +142,7 @@ async fn render_tag<T: TemplateCallback>(
#[cfg(test)] #[cfg(test)]
mod parse_and_render_tests { mod parse_and_render_tests {
use crate::error::Error::{RenderError, RenderStackExceededError}; use crate::error::Error::{RenderError, RenderStackExceededError, VariableNotFound};
use crate::error::Result; use crate::error::Result;
use crate::renderer::TemplateCallback; use crate::renderer::TemplateCallback;
use crate::*; use crate::*;
@@ -200,6 +200,19 @@ mod parse_and_render_tests {
Ok(()) Ok(())
} }
#[tokio::test]
async fn render_missing_var() -> Result<()> {
let empty_cb = EmptyCB {};
let template = "${[ foo ]}";
let vars = HashMap::new();
assert_eq!(
parse_and_render(template, &vars, &empty_cb).await,
Err(VariableNotFound("foo".to_string()))
);
Ok(())
}
#[tokio::test] #[tokio::test]
async fn render_self_referencing_var() -> Result<()> { async fn render_self_referencing_var() -> Result<()> {
let empty_cb = EmptyCB {}; let empty_cb = EmptyCB {};

View File

@@ -160,7 +160,7 @@ const EnvironmentEditor = function ({
const validateName = useCallback((name: string) => { const validateName = useCallback((name: string) => {
// Empty just means the variable doesn't have a name yet, and is unusable // Empty just means the variable doesn't have a name yet, and is unusable
if (name === '') return true; if (name === '') return true;
return name.match(/^[a-z][a-z0-9_-]*$/i) != null; return name.match(/^[a-z_][a-z0-9_-]*$/i) != null;
}, []); }, []);
return ( return (