diff --git a/Cargo.toml b/Cargo.toml index b13f614c..c4fddf57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,13 +38,18 @@ chrono-tz = {version = "0.9", optional = true} # used in get_random function rand = {version = "0.8", optional = true} +# used in async +tokio = {version = "1", optional = true} +async-recursion = {version = "1", optional = true} + [dev-dependencies] serde_derive = "1.0" pretty_assertions = "1" tempfile = "3" [features] -default = ["builtins"] +default = ["builtins", "async"] +async = ["dep:async-recursion", "dep:tokio"] builtins = ["urlencode", "slug", "humansize", "chrono", "chrono-tz", "rand"] urlencode = ["percent-encoding"] preserve_order = ["serde_json/preserve_order"] diff --git a/benches/big_context.rs b/benches/big_context.rs index bca76b94..2b0aa3cd 100644 --- a/benches/big_context.rs +++ b/benches/big_context.rs @@ -71,7 +71,7 @@ fn bench_big_loop_big_object(b: &mut test::Bencher) { )]) .unwrap(); let mut context = Context::new(); - context.insert("objects", &objects); + context.insert("objects", &objects).unwrap(); let rendering = tera.render("big_loop.html", &context).expect("Good render"); assert_eq!(&rendering[..], "0123"); // cloning as making the context is the bottleneck part @@ -93,8 +93,8 @@ fn bench_macro_big_object(b: &mut test::Bencher) { ]) .unwrap(); let mut context = Context::new(); - context.insert("big_object", &big_object); - context.insert("iterations", &(0..500).collect::>()); + context.insert("big_object", &big_object).unwrap(); + context.insert("iterations", &(0..500).collect::>()).unwrap(); let rendering = tera.render("big_loop.html", &context).expect("Good render"); assert_eq!(rendering.len(), 500); assert_eq!(rendering.chars().next().expect("Char"), '1'); @@ -116,7 +116,7 @@ fn bench_macro_big_object_no_loop_with_set(b: &mut test::Bencher) { )]) .unwrap(); let mut context = Context::new(); - context.insert("two_fields", &TwoFields::new()); + context.insert("two_fields", &TwoFields::new()).unwrap(); let rendering = tera.render("no_loop.html", &context).expect("Good render"); assert_eq!(&rendering[..], "\nA\nB\nC\n"); // cloning as making the context is the bottleneck part @@ -143,7 +143,7 @@ fn bench_macro_big_object_no_loop_macro_call(b: &mut test::Bencher) { ]) .unwrap(); let mut context = Context::new(); - context.insert("two_fields", &TwoFields::new()); + context.insert("two_fields", &TwoFields::new()).unwrap(); let rendering = tera.render("no_loop.html", &context).expect("Good render"); assert_eq!(&rendering[..], "A"); // cloning as making the context is the bottleneck part diff --git a/benches/templates.rs b/benches/templates.rs index 5acfaed5..a2a82130 100644 --- a/benches/templates.rs +++ b/benches/templates.rs @@ -24,7 +24,7 @@ pub fn big_table(b: &mut test::Bencher) { let mut tera = Tera::default(); tera.add_raw_templates(vec![("big-table.html", BIG_TABLE_TEMPLATE)]).unwrap(); let mut ctx = Context::new(); - ctx.insert("table", &table); + ctx.insert("table", &table).unwrap(); b.iter(|| tera.render("big-table.html", &ctx)); } @@ -46,7 +46,7 @@ pub fn teams(b: &mut test::Bencher) { let mut tera = Tera::default(); tera.add_raw_templates(vec![("teams.html", TEAMS_TEMPLATE)]).unwrap(); let mut ctx = Context::new(); - ctx.insert("year", &2015); + ctx.insert("year", &2015).unwrap(); ctx.insert( "teams", &vec![ @@ -55,7 +55,8 @@ pub fn teams(b: &mut test::Bencher) { Team { name: "Guangzhou".into(), score: 22 }, Team { name: "Shandong".into(), score: 12 }, ], - ); + ) + .unwrap(); b.iter(|| tera.render("teams.html", &ctx)); } diff --git a/benches/tera.rs b/benches/tera.rs index dc3de3fa..f7bce9bf 100644 --- a/benches/tera.rs +++ b/benches/tera.rs @@ -105,8 +105,8 @@ fn bench_rendering_only_variable(b: &mut test::Bencher) { let mut tera = Tera::default(); tera.add_raw_template("test.html", VARIABLE_ONLY).unwrap(); let mut context = Context::new(); - context.insert("product", &Product::new()); - context.insert("username", &"bob"); + context.insert("product", &Product::new()).unwrap(); + context.insert("username", &"bob").unwrap(); b.iter(|| tera.render("test.html", &context)); } @@ -116,8 +116,8 @@ fn bench_rendering_basic_template(b: &mut test::Bencher) { let mut tera = Tera::default(); tera.add_raw_template("bench.html", SIMPLE_TEMPLATE).unwrap(); let mut context = Context::new(); - context.insert("product", &Product::new()); - context.insert("username", &"bob"); + context.insert("product", &Product::new()).unwrap(); + context.insert("username", &"bob").unwrap(); b.iter(|| tera.render("bench.html", &context)); } @@ -127,8 +127,8 @@ fn bench_rendering_only_parent(b: &mut test::Bencher) { let mut tera = Tera::default(); tera.add_raw_templates(vec![("parent.html", PARENT_TEMPLATE)]).unwrap(); let mut context = Context::new(); - context.insert("product", &Product::new()); - context.insert("username", &"bob"); + context.insert("product", &Product::new()).unwrap(); + context.insert("username", &"bob").unwrap(); b.iter(|| tera.render("parent.html", &context)); } @@ -139,8 +139,8 @@ fn bench_rendering_only_macro_call(b: &mut test::Bencher) { tera.add_raw_templates(vec![("hey.html", USE_MACRO_TEMPLATE), ("macros.html", MACRO_TEMPLATE)]) .unwrap(); let mut context = Context::new(); - context.insert("product", &Product::new()); - context.insert("username", &"bob"); + context.insert("product", &Product::new()).unwrap(); + context.insert("username", &"bob").unwrap(); b.iter(|| tera.render("hey.html", &context)); } @@ -151,8 +151,8 @@ fn bench_rendering_only_inheritance(b: &mut test::Bencher) { tera.add_raw_templates(vec![("parent.html", PARENT_TEMPLATE), ("child.html", CHILD_TEMPLATE)]) .unwrap(); let mut context = Context::new(); - context.insert("product", &Product::new()); - context.insert("username", &"bob"); + context.insert("product", &Product::new()).unwrap(); + context.insert("username", &"bob").unwrap(); b.iter(|| tera.render("child.html", &context)); } @@ -167,8 +167,8 @@ fn bench_rendering_inheritance_and_macros(b: &mut test::Bencher) { ]) .unwrap(); let mut context = Context::new(); - context.insert("product", &Product::new()); - context.insert("username", &"bob"); + context.insert("product", &Product::new()).unwrap(); + context.insert("username", &"bob").unwrap(); b.iter(|| tera.render("child.html", &context)); } @@ -209,7 +209,7 @@ fn bench_huge_loop(b: &mut test::Bencher) { )]) .unwrap(); let mut context = Context::new(); - context.insert("rows", &rows); + context.insert("rows", &rows).unwrap(); b.iter(|| tera.render("huge.html", &context.clone())); } @@ -256,7 +256,7 @@ fn access_deep_object(b: &mut test::Bencher) { .unwrap(); let mut context = Context::new(); println!("{:?}", deep_object()); - context.insert("deep_object", &deep_object()); + context.insert("deep_object", &deep_object()).unwrap(); assert!(tera.render("deep_object.html", &context).unwrap().contains("ornery")); b.iter(|| tera.render("deep_object.html", &context)); @@ -274,7 +274,7 @@ fn access_deep_object_with_literal(b: &mut test::Bencher) { )]) .unwrap(); let mut context = Context::new(); - context.insert("deep_object", &deep_object()); + context.insert("deep_object", &deep_object()).unwrap(); assert!(tera.render("deep_object.html", &context).unwrap().contains("ornery")); b.iter(|| tera.render("deep_object.html", &context)); diff --git a/examples/basic/main.rs b/examples/basic/main.rs index 77652670..95dd5f79 100644 --- a/examples/basic/main.rs +++ b/examples/basic/main.rs @@ -32,10 +32,10 @@ pub fn do_nothing_filter(value: &Value, _: &HashMap) -> Resultalert('pwnd');"); + context.insert("username", &"Bob").unwrap(); + context.insert("numbers", &vec![1, 2, 3]).unwrap(); + context.insert("show_all", &false).unwrap(); + context.insert("bio", &"").unwrap(); // A one off template Tera::one_off("hello", &Context::new(), true).unwrap(); diff --git a/examples/bench/main.rs b/examples/bench/main.rs index 0c42e55a..823d8d33 100644 --- a/examples/bench/main.rs +++ b/examples/bench/main.rs @@ -27,7 +27,7 @@ fn main() { let mut tera = Tera::default(); tera.add_raw_templates(vec![("big-table.html", BIG_TABLE_TEMPLATE)]).unwrap(); let mut ctx = Context::new(); - ctx.insert("table", &table); + ctx.insert("table", &table).unwrap(); let _ = tera.render("big-table.html", &ctx).unwrap(); println!("Done!"); diff --git a/src/builtins/filters/array.rs b/src/builtins/filters/array.rs index f050a6a1..04c5b106 100644 --- a/src/builtins/filters/array.rs +++ b/src/builtins/filters/array.rs @@ -297,7 +297,7 @@ pub fn concat(value: &Value, args: &HashMap) -> Result { arr.push(val.clone()); } } - _ => unreachable!("Got something other than an array??"), + _ => return Err(Error::msg("The `concat` filter can only concat with an array")), } } else { arr.push(value.clone()); diff --git a/src/builtins/filters/object.rs b/src/builtins/filters/object.rs index f9981450..55555f14 100644 --- a/src/builtins/filters/object.rs +++ b/src/builtins/filters/object.rs @@ -1,7 +1,7 @@ /// Filters operating on numbers use std::collections::HashMap; -use serde_json::value::Value; +use serde_json::{to_value, value::Value}; use crate::errors::{Error, Result}; @@ -29,6 +29,29 @@ pub fn get(value: &Value, args: &HashMap) -> Result { } } +/// Merge two objects, the second object is indicated by the `with` argument. +/// The second object's values will overwrite the first's in the event of a key conflict. +pub fn merge(value: &Value, args: &HashMap) -> Result { + let left = match value.as_object() { + Some(val) => val, + None => return Err(Error::msg("Filter `merge` was used on a value that isn't an object")), + }; + let with = match args.get("with") { + Some(val) => val, + None => return Err(Error::msg("The `merge` filter has to have a `with` argument")), + }; + match with.as_object() { + Some(right) => { + let mut result = left.clone(); + result.extend(right.clone()); + // We've already confirmed both sides were HashMaps, the result is a HashMap - + // - so unwrap + Ok(to_value(result).unwrap()) + } + None => Err(Error::msg("The `with` argument for the `get` filter must be an object")), + } +} + #[cfg(test)] mod tests { use super::*; @@ -87,4 +110,27 @@ mod tests { assert!(result.is_ok()); assert_eq!(result.unwrap(), to_value("default").unwrap()); } + + #[test] + fn test_merge_filter() { + let mut obj_1 = HashMap::new(); + obj_1.insert("1".to_string(), "first".to_string()); + obj_1.insert("2".to_string(), "second".to_string()); + + let mut obj_2 = HashMap::new(); + obj_2.insert("2".to_string(), "SECOND".to_string()); + obj_2.insert("3".to_string(), "third".to_string()); + + let mut args = HashMap::new(); + args.insert("with".to_string(), to_value(obj_2).unwrap()); + + let result = merge(&to_value(&obj_1).unwrap(), &args); + assert!(result.is_ok()); + + let mut expected = HashMap::new(); + expected.insert("1".to_string(), "first".to_string()); + expected.insert("2".to_string(), "SECOND".to_string()); + expected.insert("3".to_string(), "third".to_string()); + assert_eq!(result.unwrap(), to_value(expected).unwrap()); + } } diff --git a/src/builtins/functions.rs b/src/builtins/functions.rs index 9c8a1e6e..763f4775 100644 --- a/src/builtins/functions.rs +++ b/src/builtins/functions.rs @@ -74,6 +74,16 @@ pub fn range(args: &HashMap) -> Result { )); } + let expected_num_elements = (end - start) / step_by; + + if expected_num_elements > crate::constraints::RANGE_MAX_ELEMENTS { + return Err(Error::msg(format!( + "Function `range` was called with a range that would generate {} elements, but the maximum allowed is {}", + expected_num_elements, + crate::constraints::RANGE_MAX_ELEMENTS + ))); + } + let mut i = start; let mut res = vec![]; while i < end { @@ -171,29 +181,6 @@ pub fn get_random(args: &HashMap) -> Result { Ok(Value::Number(res.into())) } -pub fn get_env(args: &HashMap) -> Result { - let name = match args.get("name") { - Some(val) => match from_value::(val.clone()) { - Ok(v) => v, - Err(_) => { - return Err(Error::msg(format!( - "Function `get_env` received name={} but `name` can only be a string", - val - ))); - } - }, - None => return Err(Error::msg("Function `get_env` didn't receive a `name` argument")), - }; - - match std::env::var(&name).ok() { - Some(res) => Ok(Value::String(res)), - None => match args.get("default") { - Some(default) => Ok(default.clone()), - None => Err(Error::msg(format!("Environment variable `{}` not found", &name))), - }, - } -} - #[cfg(test)] mod tests { use std::collections::HashMap; @@ -309,33 +296,4 @@ mod tests { assert!(res.as_i64().unwrap() >= 5); assert!(res.as_i64().unwrap() < 10); } - - #[test] - fn get_env_existing() { - std::env::set_var("TERA_TEST", "true"); - let mut args = HashMap::new(); - args.insert("name".to_string(), to_value("TERA_TEST").unwrap()); - let res = get_env(&args).unwrap(); - assert!(res.is_string()); - assert_eq!(res.as_str().unwrap(), "true"); - std::env::remove_var("TERA_TEST"); - } - - #[test] - fn get_env_non_existing_no_default() { - let mut args = HashMap::new(); - args.insert("name".to_string(), to_value("UNKNOWN_VAR").unwrap()); - let res = get_env(&args); - assert!(res.is_err()); - } - - #[test] - fn get_env_non_existing_with_default() { - let mut args = HashMap::new(); - args.insert("name".to_string(), to_value("UNKNOWN_VAR").unwrap()); - args.insert("default".to_string(), to_value("false").unwrap()); - let res = get_env(&args).unwrap(); - assert!(res.is_string()); - assert_eq!(res.as_str().unwrap(), "false"); - } } diff --git a/src/constraints.rs b/src/constraints.rs new file mode 100644 index 00000000..a1f7740a --- /dev/null +++ b/src/constraints.rs @@ -0,0 +1,15 @@ +/// Renderer limits +pub const RENDER_BLOCK_MAX_DEPTH: usize = 5; +pub const RENDER_BODY_MAX_DEPTH: usize = 20; + +/// Stack frame size limit +pub const STACK_FRAME_MAX_ENTRIES: usize = 50; + +/// STACK_FRAME_MAX_SIZE +pub const STACK_FRAME_MAX_SIZE: usize = 1024 * 1024 * 4; + +/// eval_expression max array element size +pub const EXPRESSION_MAX_ARRAY_LENGTH: usize = 100; + +// range max elements +pub const RANGE_MAX_ELEMENTS: usize = 500; diff --git a/src/context.rs b/src/context.rs index 5a874ca4..0dc33ba4 100644 --- a/src/context.rs +++ b/src/context.rs @@ -23,15 +23,19 @@ impl Context { /// Converts the `val` parameter to `Value` and insert it into the context. /// - /// Panics if the serialization fails. - /// /// ```rust /// # use tera::Context; /// let mut context = tera::Context::new(); /// context.insert("number_users", &42); /// ``` - pub fn insert>(&mut self, key: S, val: &T) { - self.data.insert(key.into(), to_value(val).unwrap()); + pub fn insert>( + &mut self, + key: S, + val: &T, + ) -> Result<(), Error> { + self.data.insert(key.into(), to_value(val)?); + + Ok(()) } /// Converts the `val` parameter to `Value` and insert it into the context. @@ -152,7 +156,10 @@ impl ValueRender for Value { } else if let Some(v) = i.as_f64() { write!(write, "{}", v) } else { - unreachable!() + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Number could not be converted to i64, u64 or f64", + )) } } Value::Bool(i) => write!(write, "{}", i), @@ -455,11 +462,11 @@ mod tests { #[test] fn can_extend_context() { let mut target = Context::new(); - target.insert("a", &1); - target.insert("b", &2); + target.insert("a", &1).unwrap(); + target.insert("b", &2).unwrap(); let mut source = Context::new(); - source.insert("b", &3); - source.insert("c", &4); + source.insert("b", &3).unwrap(); + source.insert("c", &4).unwrap(); target.extend(source); assert_eq!(*target.data.get("a").unwrap(), to_value(1).unwrap()); assert_eq!(*target.data.get("b").unwrap(), to_value(3).unwrap()); @@ -474,8 +481,8 @@ mod tests { }); let context_from_value = Context::from_value(obj).unwrap(); let mut context = Context::new(); - context.insert("name", "bob"); - context.insert("age", &25); + context.insert("name", "bob").unwrap(); + context.insert("age", &25).unwrap(); assert_eq!(context_from_value, context); } @@ -486,19 +493,19 @@ mod tests { map.insert("last_name", "something"); let context_from_serialize = Context::from_serialize(&map).unwrap(); let mut context = Context::new(); - context.insert("name", "bob"); - context.insert("last_name", "something"); + context.insert("name", "bob").unwrap(); + context.insert("last_name", "something").unwrap(); assert_eq!(context_from_serialize, context); } #[test] fn can_remove_a_key() { let mut context = Context::new(); - context.insert("name", "foo"); - context.insert("bio", "Hi, I'm foo."); + context.insert("name", "foo").unwrap(); + context.insert("bio", "Hi, I'm foo.").unwrap(); let mut expected = Context::new(); - expected.insert("name", "foo"); + expected.insert("name", "foo").unwrap(); assert_eq!(context.remove("bio"), Some(to_value("Hi, I'm foo.").unwrap())); assert_eq!(context.get("bio"), None); assert_eq!(context, expected); diff --git a/src/filter_utils.rs b/src/filter_utils.rs index 9079ccd8..fae0e145 100644 --- a/src/filter_utils.rs +++ b/src/filter_utils.rs @@ -20,6 +20,7 @@ impl Ord for OrderedF64 { } } +#[allow(clippy::non_canonical_partial_ord_impl)] impl PartialOrd for OrderedF64 { fn partial_cmp(&self, other: &OrderedF64) -> Option { Some(total_cmp(&self.0, &other.0)) diff --git a/src/lib.rs b/src/lib.rs index 151e530d..3029d27a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,6 +61,7 @@ #[macro_use] mod macros; mod builtins; +mod constraints; mod context; mod errors; mod filter_utils; diff --git a/src/parser/ast.rs b/src/parser/ast.rs index 1ff895c0..bb85c533 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -23,6 +23,18 @@ pub enum MathOperator { Div, /// % Modulo, + /// ** + Power, + /// bitor (bitwise or) + BitOr, + /// bitxor (bitwise xor) + BitXor, + /// bitand (bitwise and) + BitAnd, + /// bitlshift (<<) + BitLeftShift, + /// bitrshift (>>) + BitRightShift, } impl fmt::Display for MathOperator { @@ -36,6 +48,12 @@ impl fmt::Display for MathOperator { MathOperator::Mul => "*", MathOperator::Div => "/", MathOperator::Modulo => "%", + MathOperator::Power => "**", + MathOperator::BitOr => "bitor", + MathOperator::BitXor => "bitxor", + MathOperator::BitAnd => "bitand", + MathOperator::BitLeftShift => "bitlshift", + MathOperator::BitRightShift => "bitrshift", } ) } @@ -174,6 +192,8 @@ pub struct Expr { pub val: ExprVal, /// Is it using `not`? pub negated: bool, + /// Is it using `bitnot` + pub bitnot: bool, /// List of filters used on that value pub filters: Vec, } @@ -181,17 +201,22 @@ pub struct Expr { impl Expr { /// Create a new basic Expr pub fn new(val: ExprVal) -> Expr { - Expr { val, negated: false, filters: vec![] } + Expr { val, negated: false, bitnot: false, filters: vec![] } } /// Create a new negated Expr pub fn new_negated(val: ExprVal) -> Expr { - Expr { val, negated: true, filters: vec![] } + Expr { val, negated: true, bitnot: false, filters: vec![] } + } + + /// Create a new bitnot Expr + pub fn new_bitnot(val: ExprVal) -> Expr { + Expr { val, negated: false, bitnot: true, filters: vec![] } } /// Create a new basic Expr with some filters pub fn with_filters(val: ExprVal, filters: Vec) -> Expr { - Expr { val, filters, negated: false } + Expr { val, filters, negated: false, bitnot: false } } /// Check if the expr has a default filter as first filter @@ -247,6 +272,16 @@ pub struct Set { pub global: bool, } +/// Set a variable in the context `{% delete val %}` +#[derive(Clone, Debug, PartialEq)] +pub struct Delete { + /// The name for that value in the context + pub key: String, + /// Whether we want to delete the variable globally or locally + /// global_delete is only useful in loops + pub global: bool, +} + /// A call to a namespaced macro `macros::my_macro()` #[derive(Clone, Debug, PartialEq)] pub struct MacroCall { @@ -324,6 +359,9 @@ pub enum Node { /// The `{% set val = something %}` tag Set(WS, Set), + /// The `{% delete val %}` tag + Delete(WS, Delete), + /// The text between `{% raw %}` and `{% endraw %}` Raw(WS, String, WS), diff --git a/src/parser/mod.rs b/src/parser/mod.rs index a4248ff3..3d8b4d57 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -29,9 +29,11 @@ pub use self::whitespace::remove_whitespace; lazy_static! { static ref MATH_PARSER: PrattParser = PrattParser::new() .op(Op::infix(Rule::op_plus, Assoc::Left) | Op::infix(Rule::op_minus, Assoc::Left)) // +, - + .op(Op::infix(Rule::op_bitor, Assoc::Left) | Op::infix(Rule::op_bitxor, Assoc::Left) | Op::infix(Rule::op_bitand, Assoc::Left) | Op::infix(Rule::op_bitlshift, Assoc::Left) | Op::infix(Rule::op_bitrshift, Assoc::Left)) // bitor, bitxor, bitand, bitlshift, bitrshift .op(Op::infix(Rule::op_times, Assoc::Left) | Op::infix(Rule::op_slash, Assoc::Left) - | Op::infix(Rule::op_modulo, Assoc::Left)); // *, /, % + | Op::infix(Rule::op_modulo, Assoc::Left) + | Op::infix(Rule::op_power, Assoc::Left)); // *, /, %, ** static ref COMPARISON_EXPR_PARSER: PrattParser = PrattParser::new() .op(Op::infix(Rule::op_lt, Assoc::Left) | Op::infix(Rule::op_lte, Assoc::Left) | Op::infix(Rule::op_gt, Assoc::Left) @@ -39,7 +41,7 @@ lazy_static! { | Op::infix(Rule::op_eq, Assoc::Left)| Op::infix(Rule::op_ineq, Assoc::Left)); // <, <=, >, >=, ==, != static ref LOGIC_EXPR_PARSER: PrattParser = PrattParser::new() - .op(Op::infix(Rule::op_or, Assoc::Left)).op(Op::infix(Rule::op_and, Assoc::Left)); + .op(Op::infix(Rule::op_or, Assoc::Left)).op(Op::infix(Rule::op_and, Assoc::Left)); // or, and } /// Strings are delimited by double quotes, single quotes and backticks @@ -62,7 +64,12 @@ fn parse_kwarg(pair: Pair) -> TeraResult<(String, Expr)> { Rule::ident => name = Some(p.as_span().as_str().to_string()), Rule::logic_expr => val = Some(parse_logic_expr(p)?), Rule::array_filter => val = Some(parse_array_with_filters(p)?), - _ => unreachable!("{:?} not supposed to get there (parse_kwarg)!", p.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Got {:?} in parse_kwarg", + p.as_rule() + ))) + } }; } @@ -80,7 +87,12 @@ fn parse_fn_call(pair: Pair) -> TeraResult { let (name, val) = parse_kwarg(p)?; args.insert(name, val); } - _ => unreachable!("{:?} not supposed to get there (parse_fn_call)!", p.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_fn_call", + p.as_rule() + ))) + } }; } @@ -100,7 +112,12 @@ fn parse_filter(pair: Pair) -> TeraResult { Rule::fn_call => { return parse_fn_call(p); } - _ => unreachable!("{:?} not supposed to get there (parse_filter)!", p.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_filter", + p.as_rule() + ))) + } }; } @@ -125,11 +142,21 @@ fn parse_test_call(pair: Pair) -> TeraResult<(String, Vec)> { Rule::array => { args.push(Expr::new(parse_array(p2)?)); } - _ => unreachable!("Invalid arg type for test {:?}", p2.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Invalid arg type for test {:?} in parse_test_call", + p2.as_rule() + ))) + } } } } - _ => unreachable!("{:?} not supposed to get there (parse_test_call)!", p.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_test_call", + p.as_rule() + ))) + } }; } @@ -149,7 +176,12 @@ fn parse_test(pair: Pair) -> TeraResult { name = Some(_name); args = _args; } - _ => unreachable!("{:?} not supposed to get there (parse_ident)!", p.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_test/parse_ident", + p.as_rule() + ))) + } }; } @@ -200,7 +232,12 @@ fn parse_string_concat(pair: Pair) -> TeraResult { } values.push(ExprVal::FunctionCall(parse_fn_call(p)?)) } - _ => unreachable!("Got {:?} in parse_string_concat", p), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_string_concat", + p.as_rule() + ))) + } }; } @@ -228,7 +265,15 @@ fn parse_basic_expression(pair: Pair) -> TeraResult { Rule::op_times => MathOperator::Mul, Rule::op_slash => MathOperator::Div, Rule::op_modulo => MathOperator::Modulo, - _ => unreachable!(), + Rule::op_power => MathOperator::Power, + Rule::op_bitor => MathOperator::BitOr, + Rule::op_bitxor => MathOperator::BitXor, + Rule::op_bitand => MathOperator::BitAnd, + Rule::op_bitlshift => MathOperator::BitLeftShift, + Rule::op_bitrshift => MathOperator::BitRightShift, + _ => { + return Err(Error::msg(format!("Unexpected rule in infix: {:?}", op.as_rule()))) + } }, rhs: Box::new(Expr::new(rhs?)), })) @@ -250,7 +295,7 @@ fn parse_basic_expression(pair: Pair) -> TeraResult { "True" => ExprVal::Bool(true), "false" => ExprVal::Bool(false), "False" => ExprVal::Bool(false), - _ => unreachable!(), + _ => return Err(Error::msg("PARSER ERROR: Unexpected boolean value")), }, Rule::test => ExprVal::Test(parse_test(pair)?), Rule::test_not => { @@ -264,7 +309,12 @@ fn parse_basic_expression(pair: Pair) -> TeraResult { Rule::basic_expr => { MATH_PARSER.map_primary(primary).map_infix(infix).parse(pair.into_inner())? } - _ => unreachable!("Got {:?} in parse_basic_expression: {}", pair.as_rule(), pair.as_str()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unexpected rule in parse_basic_expression: {:?}", + pair.as_rule() + ))) + } }; Ok(expr) } @@ -278,11 +328,16 @@ fn parse_basic_expr_with_filters(pair: Pair) -> TeraResult { match p.as_rule() { Rule::basic_expr => expr_val = Some(parse_basic_expression(p)?), Rule::filter => filters.push(parse_filter(p)?), - _ => unreachable!("Got {:?}", p), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_basic_expr_with_filters", + p + ))) + } }; } - Ok(Expr { val: expr_val.unwrap(), negated: false, filters }) + Ok(Expr { val: expr_val.unwrap(), negated: false, bitnot: false, filters }) } /// A string expression with optional filters @@ -295,11 +350,16 @@ fn parse_string_expr_with_filters(pair: Pair) -> TeraResult { Rule::string => expr_val = Some(ExprVal::String(replace_string_markers(p.as_str()))), Rule::string_concat => expr_val = Some(parse_string_concat(p)?), Rule::filter => filters.push(parse_filter(p)?), - _ => unreachable!("Got {:?}", p), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_string_expr_with_filters", + p + ))) + } }; } - Ok(Expr { val: expr_val.unwrap(), negated: false, filters }) + Ok(Expr { val: expr_val.unwrap(), negated: false, bitnot: false, filters }) } /// An array with optional filters @@ -311,11 +371,16 @@ fn parse_array_with_filters(pair: Pair) -> TeraResult { match p.as_rule() { Rule::array => array = Some(parse_array(p)?), Rule::filter => filters.push(parse_filter(p)?), - _ => unreachable!("Got {:?}", p), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_array_with_filters", + p + ))) + } }; } - Ok(Expr { val: array.unwrap(), negated: false, filters }) + Ok(Expr { val: array.unwrap(), negated: false, bitnot: false, filters }) } fn parse_in_condition_container(pair: Pair) -> TeraResult { @@ -327,7 +392,12 @@ fn parse_in_condition_container(pair: Pair) -> TeraResult { expr = Some(Expr::new(ExprVal::Ident(p.as_str().to_string()))) } Rule::string_expr_filter => expr = Some(parse_string_expr_with_filters(p)?), - _ => unreachable!("Got {:?} in parse_in_condition_container", p), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_in_condition_container", + p + ))) + } }; } Ok(expr.unwrap()) @@ -346,7 +416,12 @@ fn parse_in_condition(pair: Pair) -> TeraResult { // rhs Rule::in_cond_container => rhs = Some(parse_in_condition_container(p)?), Rule::op_not => negated = true, - _ => unreachable!("Got {:?} in parse_in_condition", p), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_in_condition", + p + ))) + } }; } @@ -370,7 +445,18 @@ fn parse_comparison_val(pair: Pair) -> TeraResult { Rule::op_times => MathOperator::Mul, Rule::op_slash => MathOperator::Div, Rule::op_modulo => MathOperator::Modulo, - _ => unreachable!(), + Rule::op_power => MathOperator::Power, + Rule::op_bitor => MathOperator::BitOr, + Rule::op_bitxor => MathOperator::BitXor, + Rule::op_bitand => MathOperator::BitAnd, + Rule::op_bitlshift => MathOperator::BitLeftShift, + Rule::op_bitrshift => MathOperator::BitRightShift, + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unexpected rule in infix: {:?}", + op + ))) + } }, rhs: Box::new(rhs?), }))) @@ -381,7 +467,12 @@ fn parse_comparison_val(pair: Pair) -> TeraResult { Rule::comparison_val => { MATH_PARSER.map_primary(primary).map_infix(infix).parse(pair.into_inner())? } - _ => unreachable!("Got {:?} in parse_comparison_val", pair.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_comparison_val", + pair.as_rule() + ))) + } }; Ok(expr) } @@ -399,7 +490,12 @@ fn parse_comparison_expression(pair: Pair) -> TeraResult { Rule::op_gte => LogicOperator::Gte, Rule::op_ineq => LogicOperator::NotEq, Rule::op_eq => LogicOperator::Eq, - _ => unreachable!(), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unexpected rule in infix: {:?}", + op + ))) + } }, rhs: Box::new(rhs?), }))) @@ -411,7 +507,12 @@ fn parse_comparison_expression(pair: Pair) -> TeraResult { Rule::comparison_expr => { COMPARISON_EXPR_PARSER.map_primary(primary).map_infix(infix).parse(pair.into_inner())? } - _ => unreachable!("Got {:?} in parse_comparison_expression", pair.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_comparison_expression", + pair.as_rule() + ))) + } }; Ok(expr) } @@ -419,21 +520,29 @@ fn parse_comparison_expression(pair: Pair) -> TeraResult { /// An expression that can be negated fn parse_logic_val(pair: Pair) -> TeraResult { let mut negated = false; + let mut bitnot = false; let mut expr = None; for p in pair.into_inner() { match p.as_rule() { Rule::op_not => negated = true, + Rule::op_bitnot => bitnot = true, Rule::in_cond => expr = Some(parse_in_condition(p)?), Rule::comparison_expr => expr = Some(parse_comparison_expression(p)?), Rule::string_expr_filter => expr = Some(parse_string_expr_with_filters(p)?), Rule::logic_expr => expr = Some(parse_logic_expr(p)?), - _ => unreachable!(), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_logic_val", + p.as_rule() + ))) + } }; } let mut e = expr.unwrap(); e.negated = negated; + e.bitnot = bitnot; Ok(e) } @@ -451,10 +560,7 @@ fn parse_logic_expr(pair: Pair) -> TeraResult { operator: LogicOperator::And, rhs: Box::new(rhs?), }))), - _ => unreachable!( - "{:?} not supposed to get there (infix of logic_expression)!", - op.as_rule() - ), + _ => Err(Error::msg(format!("PARSER ERROR: Unexpected rule in infix: {:?}", op.as_rule()))), }; let expr = match pair.as_rule() { @@ -462,7 +568,12 @@ fn parse_logic_expr(pair: Pair) -> TeraResult { Rule::logic_expr => { LOGIC_EXPR_PARSER.map_primary(primary).map_infix(infix).parse(pair.into_inner())? } - _ => unreachable!("Got {:?} in parse_logic_expr", pair.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_logic_expr", + pair.as_rule() + ))) + } }; Ok(expr) } @@ -475,7 +586,12 @@ fn parse_array(pair: Pair) -> TeraResult { Rule::logic_val => { vals.push(parse_logic_val(p)?); } - _ => unreachable!("Got {:?} in parse_array", p.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_array", + p.as_rule() + ))) + } } } @@ -490,7 +606,7 @@ fn parse_string_array(pair: Pair) -> Vec { Rule::string => { vals.push(replace_string_markers(p.as_span().as_str())); } - _ => unreachable!("Got {:?} in parse_string_array", p.as_rule()), + _ => continue, } } @@ -516,7 +632,12 @@ fn parse_macro_call(pair: Pair) -> TeraResult { let (key, val) = parse_kwarg(p)?; args.insert(key, val); } - _ => unreachable!("Got {:?} in parse_macro_call", p.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_macro_call", + p.as_rule() + ))) + } } } @@ -537,7 +658,12 @@ fn parse_variable_tag(pair: Pair) -> TeraResult { } Rule::logic_expr => expr = Some(parse_logic_expr(p)?), Rule::array_filter => expr = Some(parse_array_with_filters(p)?), - _ => unreachable!("unexpected {:?} rule in parse_variable_tag", p.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_variable_tag", + p.as_rule() + ))) + } } } Ok(Node::VariableBlock(ws, expr.unwrap())) @@ -558,7 +684,7 @@ fn parse_import_macro(pair: Pair) -> Node { Rule::tag_end => { ws.right = p.as_span().as_str() == "-%}"; } - _ => unreachable!(), + _ => {} // Don't call unreachable!, instead do nothing, this prevents any potential panic due to abuse of macros }; } @@ -578,7 +704,7 @@ fn parse_extends(pair: Pair) -> Node { Rule::tag_end => { ws.right = p.as_span().as_str() == "-%}"; } - _ => unreachable!(), + _ => {} // Don't call unreachable!, instead return default, this prevents any potential panic due to abuse of macros }; } @@ -603,7 +729,7 @@ fn parse_include(pair: Pair) -> Node { Rule::tag_end => { ws.right = p.as_span().as_str() == "-%}"; } - _ => unreachable!(), + _ => {} // Don't call unreachable!, instead return default, this prevents any potential panic due to abuse of macros }; } @@ -626,13 +752,43 @@ fn parse_set_tag(pair: Pair, global: bool) -> TeraResult { Rule::ident => key = Some(p.as_str().to_string()), Rule::logic_expr => expr = Some(parse_logic_expr(p)?), Rule::array_filter => expr = Some(parse_array_with_filters(p)?), - _ => unreachable!("unexpected {:?} rule in parse_set_tag", p.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_set_tag", + p.as_rule() + ))) + } } } Ok(Node::Set(ws, Set { key: key.unwrap(), value: expr.unwrap(), global })) } +fn parse_delete_tag(pair: Pair, global: bool) -> TeraResult { + let mut ws = WS::default(); + let mut key = None; + + for p in pair.into_inner() { + match p.as_rule() { + Rule::tag_start => { + ws.left = p.as_span().as_str() == "{%-"; + } + Rule::tag_end => { + ws.right = p.as_span().as_str() == "-%}"; + } + Rule::ident => key = Some(p.as_str().to_string()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_delete_tag", + p.as_rule() + ))) + } + } + } + + Ok(Node::Delete(ws, Delete { key: key.unwrap(), global })) +} + fn parse_raw_tag(pair: Pair) -> Node { let mut start_ws = WS::default(); let mut end_ws = WS::default(); @@ -645,7 +801,7 @@ fn parse_raw_tag(pair: Pair) -> Node { match p2.as_rule() { Rule::tag_start => start_ws.left = p2.as_span().as_str() == "{%-", Rule::tag_end => start_ws.right = p2.as_span().as_str() == "-%}", - _ => unreachable!(), + _ => continue, // Don't call unreachable!, instead do nothing, this prevents any potential panic due to abuse of macros } } } @@ -655,11 +811,11 @@ fn parse_raw_tag(pair: Pair) -> Node { match p2.as_rule() { Rule::tag_start => end_ws.left = p2.as_span().as_str() == "{%-", Rule::tag_end => end_ws.right = p2.as_span().as_str() == "-%}", - _ => unreachable!(), + _ => continue, // Don't call unreachable!, instead do nothing, this prevents any potential panic due to abuse of macros } } } - _ => unreachable!("unexpected {:?} rule in parse_raw_tag", p.as_rule()), + _ => continue, // Don't call unreachable!, instead do nothing, this prevents any potential panic due to abuse of macros }; } @@ -686,7 +842,12 @@ fn parse_filter_section(pair: Pair) -> TeraResult { args: HashMap::new(), }); } - _ => unreachable!("Got {:?} while parsing filter_tag", p2), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_filter_section", + p2.as_rule() + ))) + } } } } @@ -702,11 +863,21 @@ fn parse_filter_section(pair: Pair) -> TeraResult { match p2.as_rule() { Rule::tag_start => end_ws.left = p2.as_span().as_str() == "{%-", Rule::tag_end => end_ws.right = p2.as_span().as_str() == "-%}", - _ => unreachable!(), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_filter_section", + p2.as_rule() + ))) + } } } } - _ => unreachable!("unexpected {:?} rule in parse_filter_section", p.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_filter_section", + p.as_rule() + ))) + } }; } Ok(Node::FilterSection(start_ws, FilterSection { filter: filter.unwrap(), body }, end_ws)) @@ -726,7 +897,12 @@ fn parse_block(pair: Pair) -> TeraResult { Rule::tag_start => start_ws.left = p2.as_span().as_str() == "{%-", Rule::tag_end => start_ws.right = p2.as_span().as_str() == "-%}", Rule::ident => name = Some(p2.as_span().as_str().to_string()), - _ => unreachable!(), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_block", + p2.as_rule() + ))) + } }; } } @@ -737,11 +913,21 @@ fn parse_block(pair: Pair) -> TeraResult { Rule::tag_start => end_ws.left = p2.as_span().as_str() == "{%-", Rule::tag_end => end_ws.right = p2.as_span().as_str() == "-%}", Rule::ident => (), - _ => unreachable!(), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_block", + p2.as_rule() + ))) + } }; } } - _ => unreachable!("unexpected {:?} rule in parse_filter_section", p.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_block", + p.as_rule() + ))) + } }; } @@ -765,10 +951,20 @@ fn parse_macro_arg(p: Pair) -> TeraResult { "True" => Some(ExprVal::Bool(true)), "false" => Some(ExprVal::Bool(false)), "False" => Some(ExprVal::Bool(false)), - _ => unreachable!(), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unexpected boolean value: {}", + p.as_str() + ))) + } }, Rule::string => Some(ExprVal::String(replace_string_markers(p.as_str()))), - _ => unreachable!("Got {:?} in parse_macro_arg: {}", p.as_rule(), p.as_str()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_macro_arg", + p.as_rule() + ))) + } }; Ok(val.unwrap()) @@ -829,11 +1025,21 @@ fn parse_macro_definition(pair: Pair) -> TeraResult { Rule::tag_start => end_ws.left = p2.as_span().as_str() == "{%-", Rule::tag_end => end_ws.right = p2.as_span().as_str() == "-%}", Rule::ident => (), - _ => unreachable!(), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_macro_definition [match endmacro_tag]", + p2.as_rule() + ))) + } }; } } - _ => unreachable!("unexpected {:?} rule in parse_macro_definition", p.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_macro_definition", + p.as_rule() + ))) + } } } @@ -863,7 +1069,12 @@ fn parse_forloop(pair: Pair) -> TeraResult { container = Some(parse_basic_expr_with_filters(p2)?); } Rule::array_filter => container = Some(parse_array_with_filters(p2)?), - _ => unreachable!(), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_forloop [match for_tag]", + p2.as_rule() + ))) + } }; } @@ -893,11 +1104,21 @@ fn parse_forloop(pair: Pair) -> TeraResult { Rule::tag_start => end_ws.left = p2.as_span().as_str() == "{%-", Rule::tag_end => end_ws.right = p2.as_span().as_str() == "-%}", Rule::ident => (), - _ => unreachable!(), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_forloop [match endfor_tag]", + p2.as_rule() + ))) + } }; } } - _ => unreachable!("unexpected {:?} rule in parse_forloop", p.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_forloop", + p.as_rule() + ))) + } }; } @@ -919,7 +1140,7 @@ fn parse_break_tag(pair: Pair) -> Node { Rule::tag_end => { ws.right = p.as_span().as_str() == "-%}"; } - _ => unreachable!(), + _ => continue, // Don't call unreachable!, instead do nothing, this prevents any potential panic due to abuse of macros }; } @@ -937,7 +1158,7 @@ fn parse_continue_tag(pair: Pair) -> Node { Rule::tag_end => { ws.right = p.as_span().as_str() == "-%}"; } - _ => unreachable!(), + _ => continue, // Don't call unreachable!, instead do nothing, this prevents any potential panic due to abuse of macros }; } @@ -959,7 +1180,7 @@ fn parse_comment_tag(pair: Pair) -> Node { Rule::comment_text => { content = p.as_str().to_owned(); } - _ => unreachable!(), + _ => continue, // Don't call unreachable!, instead do nothing, this prevents any potential panic due to abuse of macros }; } @@ -994,7 +1215,12 @@ fn parse_if(pair: Pair) -> TeraResult { Rule::tag_start => current_ws.left = p2.as_span().as_str() == "{%-", Rule::tag_end => current_ws.right = p2.as_span().as_str() == "-%}", Rule::logic_expr => expr = Some(parse_logic_expr(p2)?), - _ => unreachable!(), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_if [match if_tag/elif_tag]", + p2.as_rule() + ))) + } }; } } @@ -1016,7 +1242,12 @@ fn parse_if(pair: Pair) -> TeraResult { match p2.as_rule() { Rule::tag_start => current_ws.left = p2.as_span().as_str() == "{%-", Rule::tag_end => current_ws.right = p2.as_span().as_str() == "-%}", - _ => unreachable!(), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_if [match else_tag]", + p2.as_rule() + ))) + } }; } } @@ -1032,12 +1263,22 @@ fn parse_if(pair: Pair) -> TeraResult { match p2.as_rule() { Rule::tag_start => end_ws.left = p2.as_span().as_str() == "{%-", Rule::tag_end => end_ws.right = p2.as_span().as_str() == "-%}", - _ => unreachable!(), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_if [match endif_tag]", + p2.as_rule() + ))) + } }; } break; } - _ => unreachable!("unreachable rule in parse_if: {:?}", p.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_if", + p.as_rule() + ))) + } } } @@ -1055,6 +1296,8 @@ fn parse_content(pair: Pair) -> TeraResult> { Rule::super_tag => nodes.push(Node::Super), Rule::set_tag => nodes.push(parse_set_tag(p, false)?), Rule::set_global_tag => nodes.push(parse_set_tag(p, true)?), + Rule::delete_tag => nodes.push(parse_delete_tag(p, false)?), + Rule::delete_global_tag => nodes.push(parse_delete_tag(p, true)?), Rule::raw => nodes.push(parse_raw_tag(p)), Rule::variable_tag => nodes.push(parse_variable_tag(p)?), Rule::forloop => nodes.push(parse_forloop(p)?), @@ -1068,7 +1311,12 @@ fn parse_content(pair: Pair) -> TeraResult> { Rule::filter_section => nodes.push(parse_filter_section(p)?), Rule::text => nodes.push(Node::Text(p.as_span().as_str().to_string())), Rule::block => nodes.push(parse_block(p)?), - _ => unreachable!("unreachable content rule: {:?}", p.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse_content", + p.as_rule() + ))) + } }; } @@ -1115,6 +1363,7 @@ pub fn parse(input: &str) -> TeraResult> { Rule::op_or => "`or`".to_string(), Rule::op_and => "`and`".to_string(), Rule::op_not => "`not`".to_string(), + Rule::op_bitnot => "`bitnot`".to_string(), Rule::op_lte => "`<=`".to_string(), Rule::op_gte => "`>=`".to_string(), Rule::op_lt => "`<`".to_string(), @@ -1126,6 +1375,12 @@ pub fn parse(input: &str) -> TeraResult> { Rule::op_times => "`*`".to_string(), Rule::op_slash => "`/`".to_string(), Rule::op_modulo => "`%`".to_string(), + Rule::op_power => "`**`".to_string(), + Rule::op_bitor => "`bitor`".to_string(), + Rule::op_bitxor => "`bitxor`".to_string(), + Rule::op_bitand => "`bitand`".to_string(), + Rule::op_bitlshift => "`bitlshift`".to_string(), + Rule::op_bitrshift => "`bitrshift`".to_string(), Rule::filter => "a filter".to_string(), Rule::test => "a test".to_string(), Rule::test_not => "a negated test".to_string(), @@ -1145,6 +1400,8 @@ pub fn parse(input: &str) -> TeraResult> { Rule::filter_section_content => "the filter section content".to_string(), Rule::set_tag => "a `set` tag`".to_string(), Rule::set_global_tag => "a `set_global` tag`".to_string(), + Rule::delete_tag => "a `delete` tag`".to_string(), + Rule::delete_global_tag => "a `delete_global` tag`".to_string(), Rule::block_content | Rule::content | Rule::for_content => { "some content".to_string() }, @@ -1218,7 +1475,12 @@ pub fn parse(input: &str) -> TeraResult> { Rule::macro_definition => nodes.push(parse_macro_definition(p)?), Rule::comment_tag => (), Rule::EOI => (), - _ => unreachable!("unknown tpl rule: {:?}", p.as_rule()), + _ => { + return Err(Error::msg(format!( + "PARSER ERROR: Unsupported rule {:?} in parse", + p.as_rule() + ))) + } } } diff --git a/src/parser/tera.pest b/src/parser/tera.pest index 03c74748..e489e7ce 100644 --- a/src/parser/tera.pest +++ b/src/parser/tera.pest @@ -35,6 +35,7 @@ boolean = { "true" | "false" | "True" | "False" } op_or = @{ "or" ~ WHITESPACE } op_and = @{ "and" ~ WHITESPACE } op_not = @{ "not" ~ WHITESPACE } +op_bitnot = @{ "bitnot" ~ WHITESPACE } // bitwise ones complement cannot use ~ op_lte = { "<=" } op_gte = { ">=" } op_lt = { "<" } @@ -46,6 +47,12 @@ op_minus = { "-" } op_times = { "*" } op_slash = { "/" } op_modulo = { "%" } +op_power = { "**" } +op_bitor = { "bitor" } // bitwise or cannot use | +op_bitxor = { "bitxor" } // bitwise xor cannot use ^ +op_bitand = { "bitand" } // bitwise and cannot use & +op_bitlshift = { "<<" } +op_bitrshift = { ">>" } // ------------------------------------------------- @@ -85,7 +92,7 @@ string_concat = { (fn_call | float | int | string | dotted_square_bracket_ident) // boolean first so they are not caught as identifiers basic_val = _{ boolean | test_not | test | macro_call | fn_call | dotted_square_bracket_ident | float | int } -basic_op = _{ op_plus | op_minus | op_times | op_slash | op_modulo } +basic_op = _{ op_plus | op_minus | op_times | op_slash | op_modulo | op_power | op_bitor | op_bitxor | op_bitand | op_bitlshift | op_bitrshift } basic_expr = { ("(" ~ basic_expr ~ ")" | basic_val) ~ (basic_op ~ ("(" ~ basic_expr ~ ")" | basic_val))* } basic_expr_filter = !{ basic_expr ~ filter* } string_expr_filter = !{ (string_concat | string) ~ filter* } @@ -98,8 +105,8 @@ comparison_expr = { (string_expr_filter | comparison_val) ~ (comparison_op ~ (st in_cond_container = {string_expr_filter | array_filter | dotted_square_bracket_ident} in_cond = !{ (string_expr_filter | basic_expr_filter) ~ op_not? ~ "in" ~ in_cond_container } -logic_val = !{ op_not? ~ (in_cond | comparison_expr) | "(" ~ logic_expr ~ ")" } -logic_expr = !{ logic_val ~ ((op_or | op_and) ~ logic_val)* } +logic_val = !{ (op_not | op_bitnot)? ~ (in_cond | comparison_expr) | "(" ~ logic_expr ~ ")" } +logic_expr = !{ logic_val ~ ((op_or | op_and ) ~ logic_val)* } array = !{ "[" ~ (logic_val ~ ",")* ~ logic_val? ~ "]"} array_filter = !{ array ~ filter* } @@ -189,6 +196,15 @@ set_global_tag = ${ ~ "set_global" ~ WHITESPACE+ ~ ident ~ WHITESPACE* ~ "=" ~ WHITESPACE* ~ (logic_expr | array_filter) ~ WHITESPACE* ~ tag_end } +delete_tag = ${ + tag_start ~ WHITESPACE* + ~ "delete" ~ WHITESPACE+ ~ ident + ~ WHITESPACE* ~ tag_end +} +delete_global_tag = ${ + tag_start ~ WHITESPACE* + ~ "delete_global" ~ WHITESPACE+ ~ ident ~ WHITESPACE* ~ tag_end +} endblock_tag = !{ tag_start ~ "endblock" ~ ident? ~ tag_end } endmacro_tag = !{ tag_start ~ "endmacro" ~ ident? ~ tag_end } endif_tag = !{ tag_start ~ "endif" ~ tag_end } @@ -241,6 +257,8 @@ macro_content = @{ comment_tag | set_tag | set_global_tag | + delete_tag | + delete_global_tag | macro_if | forloop | filter_section | @@ -256,6 +274,8 @@ block_content = @{ comment_tag | set_tag | set_global_tag | + delete_tag | + delete_global_tag | block | block_if | forloop | @@ -271,6 +291,8 @@ for_content = @{ comment_tag | set_tag | set_global_tag | + delete_tag | + delete_global_tag | for_if | forloop | break_tag | @@ -286,6 +308,8 @@ content = @{ comment_tag | set_tag | set_global_tag | + delete_tag | + delete_global_tag | block | content_if | forloop | diff --git a/src/parser/tests/errors.rs b/src/parser/tests/errors.rs index 8b58c440..879a4b08 100644 --- a/src/parser/tests/errors.rs +++ b/src/parser/tests/errors.rs @@ -19,7 +19,7 @@ fn invalid_number() { "{{ 1.2.2 }}", &[ "1:7", - "expected `or`, `and`, `not`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, a filter, or a variable end (`}}`)" + "expected `or`, `and`, `not`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, `**`, `bitor`, `bitxor`, `bitand`, `bitlshift`, `bitrshift`, a filter, or a variable end (`}}`)" ], ); } @@ -35,7 +35,7 @@ fn wrong_start_block() { "{{ if true %}", &[ "1:7", - "expected `or`, `and`, `not`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, a filter, or a variable end (`}}`)" + "expected `or`, `and`, `not`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, `**`, `bitor`, `bitxor`, `bitand`, `bitlshift`, `bitrshift`, a filter, or a variable end (`}}`)" ], ); } @@ -57,7 +57,7 @@ fn unterminated_variable_block() { "{{ hey", &[ "1:7", - "expected `or`, `and`, `not`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, a filter, or a variable end (`}}`)" + "expected `or`, `and`, `not`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, `**`, `bitor`, `bitxor`, `bitand`, `bitlshift`, `bitrshift`, a filter, or a variable end (`}}`)" ], ); } @@ -168,7 +168,7 @@ fn invalid_operator() { "{{ hey =! }}", &[ "1:8", - "expected `or`, `and`, `not`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, a filter, or a variable end (`}}`)" + "expected `or`, `and`, `not`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, `**`, `bitor`, `bitxor`, `bitand`, `bitlshift`, `bitrshift`, a filter, or a variable end (`}}`)" ], ); } @@ -225,7 +225,7 @@ fn invalid_macro_call() { "{{ my:macro() }}", &[ "1:6", - "expected `or`, `and`, `not`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, a filter, or a variable end (`}}`)" + "expected `or`, `and`, `not`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, `**`, `bitor`, `bitxor`, `bitand`, `bitlshift`, `bitrshift`, a filter, or a variable end (`}}`)" ], ); } @@ -282,7 +282,7 @@ fn invalid_test_argument() { r#"{% if a is odd(key=1) %}"#, &[ "1:19", - "expected `or`, `and`, `not`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, or a filter" + "expected `or`, `and`, `not`, `<=`, `>=`, `<`, `>`, `==`, `!=`, `+`, `-`, `*`, `/`, `%`, `**`, `bitor`, `bitxor`, `bitand`, `bitlshift`, `bitrshift`, or a filter" ], ); } diff --git a/src/parser/tests/parser.rs b/src/parser/tests/parser.rs index 73db69b2..8ae76ab2 100644 --- a/src/parser/tests/parser.rs +++ b/src/parser/tests/parser.rs @@ -360,6 +360,95 @@ fn parse_variable_tag_negated_expr() { ); } +#[test] +fn parse_variable_tag_bitor() { + let ast = parse("{{ id bitor id2 }}").unwrap(); + assert_eq!( + ast[0], + Node::VariableBlock( + WS::default(), + Expr::new(ExprVal::Math(MathExpr { + lhs: Box::new(Expr::new(ExprVal::Ident("id".to_string()))), + operator: MathOperator::BitOr, + rhs: Box::new(Expr::new(ExprVal::Ident("id2".to_string()))), + },)) + ) + ); +} + +#[test] +fn parse_variable_tag_bitxor() { + let ast = parse("{{ id bitxor id2 }}").unwrap(); + assert_eq!( + ast[0], + Node::VariableBlock( + WS::default(), + Expr::new(ExprVal::Math(MathExpr { + lhs: Box::new(Expr::new(ExprVal::Ident("id".to_string()))), + operator: MathOperator::BitXor, + rhs: Box::new(Expr::new(ExprVal::Ident("id2".to_string()))), + },)) + ) + ); +} + +#[test] +fn parse_variable_tag_bitand() { + let ast = parse("{{ id bitand id2 }}").unwrap(); + assert_eq!( + ast[0], + Node::VariableBlock( + WS::default(), + Expr::new(ExprVal::Math(MathExpr { + lhs: Box::new(Expr::new(ExprVal::Ident("id".to_string()))), + operator: MathOperator::BitAnd, + rhs: Box::new(Expr::new(ExprVal::Ident("id2".to_string()))), + },)) + ) + ); +} + +#[test] +fn parse_variable_tag_bitlshift() { + let ast = parse("{{ id << id2 }}").unwrap(); + assert_eq!( + ast[0], + Node::VariableBlock( + WS::default(), + Expr::new(ExprVal::Math(MathExpr { + lhs: Box::new(Expr::new(ExprVal::Ident("id".to_string()))), + operator: MathOperator::BitLeftShift, + rhs: Box::new(Expr::new(ExprVal::Ident("id2".to_string()))), + },)) + ) + ); +} + +#[test] +fn parse_variable_tag_bitrshift() { + let ast = parse("{{ id >> id2 }}").unwrap(); + assert_eq!( + ast[0], + Node::VariableBlock( + WS::default(), + Expr::new(ExprVal::Math(MathExpr { + lhs: Box::new(Expr::new(ExprVal::Ident("id".to_string()))), + operator: MathOperator::BitRightShift, + rhs: Box::new(Expr::new(ExprVal::Ident("id2".to_string()))), + },)) + ) + ); +} + +#[test] +fn parse_variable_tag_bitnot() { + let ast = parse("{{ bitnot id }}").unwrap(); + assert_eq!( + ast[0], + Node::VariableBlock(WS::default(), Expr::new_bitnot(ExprVal::Ident("id".to_string()))) + ); +} + #[test] fn parse_variable_tag_negated_expr_with_parentheses() { let ast = parse("{{ (not id or not true) and not 1 + 1 }}").unwrap(); @@ -751,6 +840,24 @@ fn parse_set_global_tag() { ); } +#[test] +fn parse_delete() { + let ast = parse("{% delete hello %}").unwrap(); + assert_eq!( + ast[0], + Node::Delete(WS::default(), Delete { key: "hello".to_string(), global: false },) + ); +} + +#[test] +fn parse_delete_global() { + let ast = parse("{% delete_global hello %}").unwrap(); + assert_eq!( + ast[0], + Node::Delete(WS::default(), Delete { key: "hello".to_string(), global: true },) + ); +} + #[test] fn parse_raw_tag() { let ast = parse("{% raw -%}{{hey}}{%- endraw %}").unwrap(); diff --git a/src/parser/whitespace.rs b/src/parser/whitespace.rs index 1c7c54c4..0d399902 100644 --- a/src/parser/whitespace.rs +++ b/src/parser/whitespace.rs @@ -58,6 +58,7 @@ pub fn remove_whitespace(nodes: Vec, body_ws: Option) -> Vec { | Node::Extends(ws, _) | Node::Include(ws, _, _) | Node::Set(ws, _) + | Node::Delete(ws, _) | Node::Break(ws) | Node::Comment(ws, _) | Node::Continue(ws) => { @@ -110,7 +111,7 @@ pub fn remove_whitespace(nodes: Vec, body_ws: Option) -> Vec { block.body = remove_whitespace(block.body, Some(body_ws)); res.push(Node::Block(start_ws, block, end_ws)); } - _ => unreachable!(), + _ => {} // Do nothing for unsupported }; continue; } diff --git a/src/renderer/call_stack.rs b/src/renderer/call_stack.rs index 2ff364d2..67b7fbb5 100644 --- a/src/renderer/call_stack.rs +++ b/src/renderer/call_stack.rs @@ -128,11 +128,23 @@ impl<'a> CallStack<'a> { } /// Add an assignment value (via {% set ... %} and {% set_global ... %} ) - pub fn add_assignment(&mut self, key: &'a str, global: bool, value: Val<'a>) { + pub fn add_assignment(&mut self, key: &'a str, global: bool, value: Val<'a>) -> Result<()> { if global { - self.global_frame_mut().insert(key, value); + self.global_frame_mut().insert(key, value)?; } else { - self.current_frame_mut().insert(key, value); + self.current_frame_mut().insert(key, value)?; + } + Ok(()) + } + + /// Deletes an assignment value (via {% delete ... %} and {% delete_global ... %} ) + /// + /// This returns the deleted value + pub fn delete_assignment(&mut self, key: &'a str, global: bool) -> Result> { + if global { + self.global_frame_mut().remove(key) + } else { + self.current_frame_mut().remove(key) } } @@ -194,7 +206,7 @@ impl<'a> CallStack<'a> { self.current_frame().active_template } - pub fn current_context_cloned(&self) -> Value { + pub fn current_context_cloned(&self) -> Result { let mut context = HashMap::new(); // Go back the stack in reverse to see what we have access to @@ -207,14 +219,17 @@ impl<'a> CallStack<'a> { ); if for_loop.is_key_value() { context.insert( - for_loop.key_name.clone().unwrap(), + for_loop + .key_name + .clone() + .ok_or_else(|| Error::msg("Expected key name in key-value for loop"))?, Value::String(for_loop.get_current_key()), ); } } // Macros don't have access to the user context, we're done if frame.kind == FrameType::Macro { - return to_value(&context).unwrap(); + return Ok(to_value(&context)?); } } @@ -223,8 +238,9 @@ impl<'a> CallStack<'a> { // We do it this way as we can override global variable temporarily in forloops let mut new_ctx = self.context.inner.clone(); for (key, val) in context { - new_ctx.insert(key, &val) + new_ctx.insert(key, &val)? } - new_ctx.into_json() + + Ok(new_ctx.into_json()) } } diff --git a/src/renderer/for_loop.rs b/src/renderer/for_loop.rs index 30af339b..6c1981b4 100644 --- a/src/renderer/for_loop.rs +++ b/src/renderer/for_loop.rs @@ -40,7 +40,7 @@ impl<'a> ForLoopValues<'a> { pub fn current_key(&self, i: usize) -> String { match *self { ForLoopValues::Array(_) | ForLoopValues::String(_) => { - unreachable!("No key in array list or string") + i.to_string() // Use the index as the key } ForLoopValues::Object(ref values) => { values.get(i).expect("Failed getting current key").0.clone() diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 3f01e153..0437cfb6 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -52,6 +52,14 @@ impl<'a> Renderer<'a> { buffer_to_string(|| "converting rendered buffer to string".to_string(), output) } + /// Async version of [`render()`](Self::render) + #[cfg(feature = "async")] + pub async fn render_async(&self) -> Result { + let mut output = Vec::with_capacity(2000); + self.render_to_async(&mut output).await?; + buffer_to_string(|| "converting rendered buffer to string".to_string(), output) + } + /// Combines the context with the Template to write the end result to output pub fn render_to(&self, mut output: impl Write) -> Result<()> { let mut processor = @@ -59,4 +67,13 @@ impl<'a> Renderer<'a> { processor.render(&mut output) } + + /// Async version of [`render_to()`](Self::render_to) + #[cfg(feature = "async")] + pub async fn render_to_async(&self, mut output: (impl Write + Send + Sync)) -> Result<()> { + let mut processor = + Processor::new(self.template, self.tera, self.context, self.should_escape); + + processor.render_async(&mut output).await + } } diff --git a/src/renderer/processor.rs b/src/renderer/processor.rs index 56c6c486..ad2f41f4 100644 --- a/src/renderer/processor.rs +++ b/src/renderer/processor.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; use std::collections::HashMap; +use std::convert::TryInto; use std::io::Write; use serde_json::{to_string_pretty, to_value, Number, Value}; @@ -20,6 +21,9 @@ use crate::Context; /// Special string indicating request to dump context static MAGICAL_DUMP_VAR: &str = "__tera_context"; +/// Special string indicating request to dump context as the object it is +static MAGICAL_DUMP_VAR_RAW: &str = "__tera_context_raw"; + /// This will convert a Tera variable to a json pointer if it is possible by replacing /// the index with their evaluated stringified value fn evaluate_sub_variables(key: &str, call_stack: &CallStack) -> Result { @@ -147,9 +151,49 @@ impl<'a> Processor<'a> { } } - fn render_body(&mut self, body: &'a [Node], write: &mut impl Write) -> Result<()> { + fn render_body( + &mut self, + body: &'a [Node], + write: &mut impl Write, + body_recursion_level: usize, + ) -> Result<()> { + if body_recursion_level >= crate::constraints::RENDER_BODY_MAX_DEPTH { + return Err(Error::msg(format!( + "Max body depth reached while rendering body ({} > max of {})", + body_recursion_level, + crate::constraints::RENDER_BODY_MAX_DEPTH + ))); + } + + for n in body { + self.render_node(n, write, body_recursion_level + 1)?; + + if self.call_stack.should_break_body() { + break; + } + } + + Ok(()) + } + + #[cfg(feature = "async")] + #[async_recursion::async_recursion] + async fn render_body_async( + &mut self, + body: &'a [Node], + write: &mut (impl Write + Send + Sync), + body_recursion_level: usize, + ) -> Result<()> { + if body_recursion_level >= crate::constraints::RENDER_BODY_MAX_DEPTH { + return Err(Error::msg(format!( + "Max body depth reached while rendering body ({} > max of {})", + body_recursion_level, + crate::constraints::RENDER_BODY_MAX_DEPTH + ))); + } + for n in body { - self.render_node(n, write)?; + self.render_node_async(n, write, body_recursion_level + 1).await?; if self.call_stack.should_break_body() { break; @@ -159,7 +203,13 @@ impl<'a> Processor<'a> { Ok(()) } - fn render_for_loop(&mut self, for_loop: &'a Forloop, write: &mut impl Write) -> Result<()> { + /// Helper method to create a for loop, this makes async for loop rendering easier + #[inline] + fn create_for_loop( + &mut self, + for_loop: &'a Forloop, + container_val: Cow<'a, Value>, + ) -> Result> { let container_name = match for_loop.container.val { ExprVal::Ident(ref ident) => ident, ExprVal::FunctionCall(FunctionCall { ref name, .. }) => name, @@ -170,12 +220,6 @@ impl<'a> Processor<'a> { ))), }; - let for_loop_name = &for_loop.value; - let for_loop_body = &for_loop.body; - let for_loop_empty_body = &for_loop.empty_body; - - let container_val = self.safe_eval_expression(&for_loop.container)?; - let for_loop = match *container_val { Value::Array(_) => { if for_loop.key.is_some() { @@ -221,15 +265,72 @@ impl<'a> Processor<'a> { } }; + Ok(for_loop) + } + + fn render_for_loop( + &mut self, + for_loop: &'a Forloop, + write: &mut impl Write, + body_recursion_level: usize, + ) -> Result<()> { + let for_loop_name = &for_loop.value; + let for_loop_body = &for_loop.body; + let for_loop_empty_body = &for_loop.empty_body; + + let container_val = self.safe_eval_expression(&for_loop.container, body_recursion_level)?; + let for_loop = self.create_for_loop(for_loop, container_val)?; + + let len = for_loop.len(); + match (len, for_loop_empty_body) { + (0, Some(empty_body)) => self.render_body(empty_body, write, body_recursion_level), + (0, _) => Ok(()), + (_, _) => { + self.call_stack.push_for_loop_frame(for_loop_name, for_loop); + + for _ in 0..len { + self.render_body(for_loop_body, write, body_recursion_level)?; + + if self.call_stack.should_break_for_loop() { + break; + } + + self.call_stack.increment_for_loop()?; + } + + self.call_stack.pop(); + + Ok(()) + } + } + } + + #[cfg(feature = "async")] + async fn render_for_loop_async( + &mut self, + for_loop: &'a Forloop, + write: &mut (impl Write + Send + Sync), + body_recursion_level: usize, + ) -> Result<()> { + let for_loop_name = &for_loop.value; + let for_loop_body = &for_loop.body; + let for_loop_empty_body = &for_loop.empty_body; + + let container_val = + self.safe_eval_expression_async(&for_loop.container, body_recursion_level).await?; + let for_loop = self.create_for_loop(for_loop, container_val)?; + let len = for_loop.len(); match (len, for_loop_empty_body) { - (0, Some(empty_body)) => self.render_body(empty_body, write), + (0, Some(empty_body)) => { + self.render_body_async(empty_body, write, body_recursion_level).await + } (0, _) => Ok(()), (_, _) => { self.call_stack.push_for_loop_frame(for_loop_name, for_loop); for _ in 0..len { - self.render_body(for_loop_body, write)?; + self.render_body_async(for_loop_body, write, body_recursion_level).await?; if self.call_stack.should_break_for_loop() { break; @@ -245,15 +346,40 @@ impl<'a> Processor<'a> { } } - fn render_if_node(&mut self, if_node: &'a If, write: &mut impl Write) -> Result<()> { + fn render_if_node( + &mut self, + if_node: &'a If, + write: &mut impl Write, + body_recursion_level: usize, + ) -> Result<()> { for (_, expr, body) in &if_node.conditions { - if self.eval_as_bool(expr)? { - return self.render_body(body, write); + if self.eval_as_bool(expr, body_recursion_level)? { + return self.render_body(body, write, body_recursion_level); } } if let Some((_, ref body)) = if_node.otherwise { - return self.render_body(body, write); + return self.render_body(body, write, body_recursion_level); + } + + Ok(()) + } + + #[cfg(feature = "async")] + async fn render_if_node_async( + &mut self, + if_node: &'a If, + write: &mut (impl Write + Send + Sync), + body_recursion_level: usize, + ) -> Result<()> { + for (_, expr, body) in &if_node.conditions { + if self.eval_as_bool(expr, body_recursion_level)? { + return self.render_body_async(body, write, body_recursion_level).await; + } + } + + if let Some((_, ref body)) = if_node.otherwise { + return self.render_body_async(body, write, body_recursion_level).await; } Ok(()) @@ -266,8 +392,16 @@ impl<'a> Processor<'a> { &mut self, block: &'a Block, level: usize, + body_recursion_level: usize, write: &mut impl Write, ) -> Result<()> { + if level >= crate::constraints::RENDER_BLOCK_MAX_DEPTH { + return Err(Error::msg(format!( + "Max depth of block inheritance reached ({} levels)", + crate::constraints::RENDER_BLOCK_MAX_DEPTH + ))); + } + let level_template = match level { 0 => self.call_stack.active_template(), _ => self @@ -282,29 +416,80 @@ impl<'a> Processor<'a> { if let Some(block_def) = blocks_definitions.get(&block.name) { let (_, Block { ref body, .. }) = block_def[0]; self.blocks.push((&block.name[..], &level_template.name[..], level)); - return self.render_body(body, write); + return self.render_body(body, write, body_recursion_level); } // Do we have more parents to look through? if level < self.call_stack.active_template().parents.len() { - return self.render_block(block, level + 1, write); + return self.render_block(block, level + 1, body_recursion_level, write); } // Nope, just render the body we got - self.render_body(&block.body, write) + self.render_body(&block.body, write, body_recursion_level) } - fn get_default_value(&mut self, expr: &'a Expr) -> Result> { + #[cfg(feature = "async")] + /// The way inheritance work is that the top parent will be rendered by the renderer so for blocks + /// we want to look from the bottom (`level = 0`, the template the user is actually rendering) + /// to the top (the base template). + /// + /// This is the async version of (`render_block`)[Self::render_block] + #[async_recursion::async_recursion] + async fn render_block_async( + &mut self, + block: &'a Block, + level: usize, + body_recursion_level: usize, + write: &mut (impl Write + Send + Sync), + ) -> Result<()> { + if level >= crate::constraints::RENDER_BLOCK_MAX_DEPTH { + return Err(Error::msg(format!( + "Max depth of block inheritance reached ({} levels)", + crate::constraints::RENDER_BLOCK_MAX_DEPTH + ))); + } + + let level_template = match level { + 0 => self.call_stack.active_template(), + _ => self + .tera + .get_template(&self.call_stack.active_template().parents[level - 1]) + .unwrap(), + }; + + let blocks_definitions = &level_template.blocks_definitions; + + // Can we find this one block in these definitions? If so render it + if let Some(block_def) = blocks_definitions.get(&block.name) { + let (_, Block { ref body, .. }) = block_def[0]; + self.blocks.push((&block.name[..], &level_template.name[..], level)); + return self.render_body_async(body, write, body_recursion_level).await; + } + + // Do we have more parents to look through? + if level < self.call_stack.active_template().parents.len() { + return self.render_block_async(block, level + 1, body_recursion_level, write).await; + } + + // Nope, just render the body we got + self.render_body_async(&block.body, write, body_recursion_level).await + } + + fn get_default_value( + &mut self, + expr: &'a Expr, + body_recursion_level: usize, + ) -> Result> { if let Some(default_expr) = expr.filters[0].args.get("value") { - self.eval_expression(default_expr) + self.eval_expression(default_expr, body_recursion_level) } else { Err(Error::msg("The `default` filter requires a `value` argument.")) } } - fn eval_in_condition(&mut self, in_cond: &'a In) -> Result { - let lhs = self.safe_eval_expression(&in_cond.lhs)?; - let rhs = self.safe_eval_expression(&in_cond.rhs)?; + fn eval_in_condition(&mut self, in_cond: &'a In, body_recursion_level: usize) -> Result { + let lhs = self.safe_eval_expression(&in_cond.lhs, body_recursion_level)?; + let rhs = self.safe_eval_expression(&in_cond.rhs, body_recursion_level)?; let present = match *rhs { Value::Array(ref v) => v.contains(&lhs), @@ -336,18 +521,28 @@ impl<'a> Processor<'a> { Ok(if in_cond.negated { !present } else { present }) } - fn eval_expression(&mut self, expr: &'a Expr) -> Result> { + fn eval_expression(&mut self, expr: &'a Expr, body_recursion_level: usize) -> Result> { let mut needs_escape = false; let mut res = match expr.val { ExprVal::Array(ref arr) => { + if arr.len() > crate::constraints::EXPRESSION_MAX_ARRAY_LENGTH { + return Err(Error::msg(format!( + "Max number of elements in an array literal is {}, {:?}", + crate::constraints::EXPRESSION_MAX_ARRAY_LENGTH, + expr.val + ))); + } + let mut values = vec![]; for v in arr { - values.push(self.eval_expression(v)?.into_owned()); + values.push(self.eval_expression(v, body_recursion_level)?.into_owned()); } Cow::Owned(Value::Array(values)) } - ExprVal::In(ref in_cond) => Cow::Owned(Value::Bool(self.eval_in_condition(in_cond)?)), + ExprVal::In(ref in_cond) => { + Cow::Owned(Value::Bool(self.eval_in_condition(in_cond, body_recursion_level)?)) + } ExprVal::String(ref val) => { needs_escape = true; Cow::Owned(Value::String(val.to_string())) @@ -367,7 +562,7 @@ impl<'a> Processor<'a> { i ))), }, - ExprVal::FunctionCall(ref fn_call) => match *self.eval_tera_fn_call(fn_call, &mut needs_escape)? { + ExprVal::FunctionCall(ref fn_call) => match *self.eval_tera_fn_call(fn_call, &mut needs_escape, body_recursion_level)? { Value::String(ref v) => res.push_str(v), Value::Number(ref v) => res.push_str(&v.to_string()), _ => return Err(Error::msg(format!( @@ -375,7 +570,7 @@ impl<'a> Processor<'a> { fn_call.name ))), }, - _ => unreachable!(), + _ => return Err(Error::msg(format!("Unimplemented expression found in line {:?} [{:?}]", s, expr.val))), }; } @@ -391,14 +586,14 @@ impl<'a> Processor<'a> { match self.lookup_ident(ident) { Ok(val) => { if val.is_null() && expr.has_default_filter() { - self.get_default_value(expr)? + self.get_default_value(expr, body_recursion_level)? } else { val } } Err(e) => { if expr.has_default_filter() { - self.get_default_value(expr)? + self.get_default_value(expr, body_recursion_level)? } else { if !expr.negated { return Err(e); @@ -410,18 +605,22 @@ impl<'a> Processor<'a> { } } ExprVal::FunctionCall(ref fn_call) => { - self.eval_tera_fn_call(fn_call, &mut needs_escape)? + self.eval_tera_fn_call(fn_call, &mut needs_escape, body_recursion_level)? } ExprVal::MacroCall(ref macro_call) => { let val = render_to_string( || format!("macro {}", macro_call.name), - |w| self.eval_macro_call(macro_call, w), + |w| self.eval_macro_call(macro_call, w, body_recursion_level), )?; Cow::Owned(Value::String(val)) } - ExprVal::Test(ref test) => Cow::Owned(Value::Bool(self.eval_test(test)?)), - ExprVal::Logic(_) => Cow::Owned(Value::Bool(self.eval_as_bool(expr)?)), - ExprVal::Math(_) => match self.eval_as_number(&expr.val) { + ExprVal::Test(ref test) => { + Cow::Owned(Value::Bool(self.eval_test(test, body_recursion_level)?)) + } + ExprVal::Logic(_) => { + Cow::Owned(Value::Bool(self.eval_as_bool(expr, body_recursion_level)?)) + } + ExprVal::Math(_) => match self.eval_as_number(&expr.val, body_recursion_level) { Ok(Some(n)) => Cow::Owned(Value::Number(n)), Ok(None) => Cow::Owned(Value::String("NaN".to_owned())), Err(e) => return Err(Error::msg(e)), @@ -432,14 +631,31 @@ impl<'a> Processor<'a> { if filter.name == "safe" || filter.name == "default" { continue; } - res = self.eval_filter(&res, filter, &mut needs_escape)?; + res = self.eval_filter(&res, filter, &mut needs_escape, body_recursion_level)?; } - // Lastly, we need to check if the expression is negated, thus turning it into a bool + // We need to check if the expression is negated, thus turning it into a bool if expr.negated { return Ok(Cow::Owned(Value::Bool(!res.is_truthy()))); } + // Check for bitnot + if expr.bitnot { + match *res { + Value::Number(ref n) => { + if let Some(n) = n.as_i64() { + return Ok(Cow::Owned(Value::Number(Number::from(!n)))); + } + } + _ => { + return Err(Error::msg(format!( + "Tried to apply `bitnot` to a non-number value: {:?}", + res + ))); + } + } + } + // Checks if it's a string and we need to escape it (if the last filter is `safe` we don't) if self.should_escape && needs_escape && res.is_string() && !expr.is_marked_safe() { res = Cow::Owned( @@ -450,30 +666,242 @@ impl<'a> Processor<'a> { Ok(res) } + #[cfg(feature = "async")] + #[async_recursion::async_recursion] + async fn eval_expression_async( + &mut self, + expr: &'a Expr, + body_recursion_level: usize, + ) -> Result> { + let mut needs_escape = false; + + let mut res = match expr.val { + ExprVal::Array(ref arr) => { + if arr.len() > crate::constraints::EXPRESSION_MAX_ARRAY_LENGTH { + return Err(Error::msg(format!( + "Max number of elements in an array literal is {}, {:?}", + crate::constraints::EXPRESSION_MAX_ARRAY_LENGTH, + expr.val + ))); + } + + let mut values = vec![]; + for v in arr { + values.push( + self.eval_expression_async(v, body_recursion_level).await?.into_owned(), + ); + } + Cow::Owned(Value::Array(values)) + } + ExprVal::In(ref in_cond) => { + Cow::Owned(Value::Bool(self.eval_in_condition(in_cond, body_recursion_level)?)) + } + ExprVal::String(ref val) => { + needs_escape = true; + Cow::Owned(Value::String(val.to_string())) + } + ExprVal::StringConcat(ref str_concat) => { + let mut res = String::new(); + for s in &str_concat.values { + match *s { + ExprVal::String(ref v) => res.push_str(v), + ExprVal::Int(ref v) => res.push_str(&format!("{}", v)), + ExprVal::Float(ref v) => res.push_str(&format!("{}", v)), + ExprVal::Ident(ref i) => match *self.lookup_ident(i)? { + Value::String(ref v) => res.push_str(v), + Value::Number(ref v) => res.push_str(&v.to_string()), + _ => return Err(Error::msg(format!( + "Tried to concat a value that is not a string or a number from ident {}", + i + ))), + }, + ExprVal::FunctionCall(ref fn_call) => match *self.eval_tera_fn_call_async(fn_call, &mut needs_escape, body_recursion_level).await? { + Value::String(ref v) => res.push_str(v), + Value::Number(ref v) => res.push_str(&v.to_string()), + _ => return Err(Error::msg(format!( + "Tried to concat a value that is not a string or a number from function call {}", + fn_call.name + ))), + }, + _ => return Err(Error::msg(format!("Unimplemented expression found in line {:?} [{:?}]", s, expr.val))), + }; + } + + Cow::Owned(Value::String(res)) + } + ExprVal::Int(val) => Cow::Owned(Value::Number(val.into())), + ExprVal::Float(val) => Cow::Owned(Value::Number(Number::from_f64(val).unwrap())), + ExprVal::Bool(val) => Cow::Owned(Value::Bool(val)), + ExprVal::Ident(ref ident) => { + needs_escape = ident != MAGICAL_DUMP_VAR; + // Negated idents are special cased as `not undefined_ident` should not + // error but instead be falsy values + match self.lookup_ident(ident) { + Ok(val) => { + if val.is_null() && expr.has_default_filter() { + self.get_default_value(expr, body_recursion_level)? + } else { + val + } + } + Err(e) => { + if expr.has_default_filter() { + self.get_default_value(expr, body_recursion_level)? + } else { + if !expr.negated { + return Err(e); + } + // A negative undefined ident is !false so truthy + return Ok(Cow::Owned(Value::Bool(true))); + } + } + } + } + ExprVal::FunctionCall(ref fn_call) => { + self.eval_tera_fn_call_async(fn_call, &mut needs_escape, body_recursion_level) + .await? + } + ExprVal::MacroCall(ref macro_call) => { + // Render to string doesnt support async yet so just do it ourselves + /* + pub(crate) fn render_to_string(context: C, render: F) -> Result + where + C: FnOnce() -> String, + F: FnOnce(&mut Vec) -> Result<(), E>, + Error: From, + { + let mut buffer = Vec::new(); + render(&mut buffer).map_err(Error::from)?; + buffer_to_string(context, buffer) + } + */ + + let mut buffer = Vec::new(); + self.eval_macro_call_async(macro_call, &mut buffer, body_recursion_level).await?; + let body = + crate::utils::buffer_to_string(|| format!("macro {}", macro_call.name), buffer) + .map_err(Error::from)?; + + Cow::Owned(Value::String(body)) + } + ExprVal::Test(ref test) => { + Cow::Owned(Value::Bool(self.eval_test(test, body_recursion_level)?)) + } + ExprVal::Logic(_) => { + Cow::Owned(Value::Bool(self.eval_as_bool(expr, body_recursion_level)?)) + } + ExprVal::Math(_) => match self.eval_as_number(&expr.val, body_recursion_level) { + Ok(Some(n)) => Cow::Owned(Value::Number(n)), + Ok(None) => Cow::Owned(Value::String("NaN".to_owned())), + Err(e) => return Err(Error::msg(e)), + }, + }; + + for filter in &expr.filters { + if filter.name == "safe" || filter.name == "default" { + continue; + } + res = self.eval_filter(&res, filter, &mut needs_escape, body_recursion_level)?; + } + + // We need to check if the expression is negated, thus turning it into a bool + if expr.negated { + return Ok(Cow::Owned(Value::Bool(!res.is_truthy()))); + } + + // Check for bitnot + if expr.bitnot { + match *res { + Value::Number(ref n) => { + if let Some(n) = n.as_i64() { + return Ok(Cow::Owned(Value::Number(Number::from(!n)))); + } + } + _ => { + return Err(Error::msg(format!( + "Tried to apply `bitnot` to a non-number value: {:?}", + res + ))); + } + } + } + + // Checks if it's a string and we need to escape it (if the last filter is `safe` we don't) + if self.should_escape && needs_escape && res.is_string() && !expr.is_marked_safe() { + res = Cow::Owned( + to_value(self.tera.get_escape_fn()(res.as_str().unwrap())).map_err(Error::json)?, + ); + } + + Ok(res) + } + + /// Render an expression and never escape its result + fn safe_eval_expression( + &mut self, + expr: &'a Expr, + body_recursion_level: usize, + ) -> Result> { + let should_escape = self.should_escape; + self.should_escape = false; + let res = self.eval_expression(expr, body_recursion_level); + self.should_escape = should_escape; + res + } + /// Render an expression and never escape its result - fn safe_eval_expression(&mut self, expr: &'a Expr) -> Result> { + /// + /// This is the async version of `safe_eval_expression` + #[cfg(feature = "async")] + async fn safe_eval_expression_async( + &mut self, + expr: &'a Expr, + body_recursion_level: usize, + ) -> Result> { let should_escape = self.should_escape; self.should_escape = false; - let res = self.eval_expression(expr); + let res = self.eval_expression_async(expr, body_recursion_level).await; self.should_escape = should_escape; res } /// Evaluate a set tag and add the value to the right context - fn eval_set(&mut self, set: &'a Set) -> Result<()> { - let assigned_value = self.safe_eval_expression(&set.value)?; - self.call_stack.add_assignment(&set.key[..], set.global, assigned_value); + fn eval_set(&mut self, set: &'a Set, body_recursion_level: usize) -> Result<()> { + let assigned_value = self.safe_eval_expression(&set.value, body_recursion_level)?; + self.call_stack.add_assignment(&set.key[..], set.global, assigned_value)?; + Ok(()) + } + + #[cfg(feature = "async")] + /// Evaluate a set tag and add the value to the right context + /// + /// This is the async version of `eval_set` + async fn eval_set_async(&mut self, set: &'a Set, body_recursion_level: usize) -> Result<()> { + let assigned_value = + self.safe_eval_expression_async(&set.value, body_recursion_level).await?; + self.call_stack.add_assignment(&set.key[..], set.global, assigned_value)?; Ok(()) } - fn eval_test(&mut self, test: &'a Test) -> Result { + /// Evaluate a delete tag and remove the value from the right context + /// + /// Unlike set, there is no async mode for delete as delete just removes a value from the context + fn eval_delete(&mut self, delete: &'a Delete) -> Result> { + self.call_stack.delete_assignment(&delete.key[..], delete.global) + } + + fn eval_test(&mut self, test: &'a Test, body_recursion_level: usize) -> Result { let tester_fn = self.tera.get_tester(&test.name)?; let err_wrap = |e| Error::call_test(&test.name, e); let mut tester_args = vec![]; for arg in &test.args { - tester_args - .push(self.safe_eval_expression(arg).map_err(err_wrap)?.clone().into_owned()); + tester_args.push( + self.safe_eval_expression(arg, body_recursion_level) + .map_err(err_wrap)? + .clone() + .into_owned(), + ); } let found = self.lookup_ident(&test.ident).map(|found| found.clone().into_owned()).ok(); @@ -490,24 +918,114 @@ impl<'a> Processor<'a> { &mut self, function_call: &'a FunctionCall, needs_escape: &mut bool, + body_recursion_level: usize, ) -> Result> { let tera_fn = self.tera.get_function(&function_call.name)?; *needs_escape = !tera_fn.is_safe(); - let err_wrap = |e| Error::call_function(&function_call.name, e); + let err_wrap = |e| Error::call_function(&function_call.name, e); + + let mut args = HashMap::with_capacity(function_call.args.len()); + for (arg_name, expr) in &function_call.args { + args.insert( + arg_name.to_string(), + self.safe_eval_expression(expr, body_recursion_level) + .map_err(err_wrap)? + .clone() + .into_owned(), + ); + } + + Ok(Cow::Owned(tera_fn.call(&args).map_err(err_wrap)?)) + } + + #[cfg(feature = "async")] + async fn eval_tera_fn_call_async( + &mut self, + function_call: &'a FunctionCall, + needs_escape: &mut bool, + body_recursion_level: usize, + ) -> Result> { + let tera_fn = self.tera.get_function(&function_call.name)?; + *needs_escape = !tera_fn.is_safe(); + + let err_wrap = |e| Error::call_function(&function_call.name, e); + + let mut args = HashMap::with_capacity(function_call.args.len()); + for (arg_name, expr) in &function_call.args { + args.insert( + arg_name.to_string(), + self.safe_eval_expression_async(expr, body_recursion_level) + .await + .map_err(err_wrap)? + .clone() + .into_owned(), + ); + } + + Ok(Cow::Owned(tera_fn.call(&args).map_err(err_wrap)?)) + } + + fn eval_macro_call( + &mut self, + macro_call: &'a MacroCall, + write: &mut impl Write, + body_recursion_level: usize, + ) -> Result<()> { + let active_template_name = if let Some(block) = self.blocks.last() { + block.1 + } else if self.template.name != self.template_root.name { + &self.template_root.name + } else { + &self.call_stack.active_template().name + }; + + let (macro_template_name, macro_definition) = self.macros.lookup_macro( + active_template_name, + ¯o_call.namespace[..], + ¯o_call.name[..], + )?; + + let mut frame_context = FrameContext::with_capacity(macro_definition.args.len()); + + // First the default arguments + for (arg_name, default_value) in ¯o_definition.args { + let value = match macro_call.args.get(arg_name) { + Some(val) => self.safe_eval_expression(val, body_recursion_level)?, + None => match *default_value { + Some(ref val) => self.safe_eval_expression(val, body_recursion_level)?, + None => { + return Err(Error::msg(format!( + "Macro `{}` is missing the argument `{}`", + macro_call.name, arg_name + ))); + } + }, + }; + frame_context.insert(arg_name, value); + } + + self.call_stack.push_macro_frame( + ¯o_call.namespace, + ¯o_call.name, + frame_context, + self.tera.get_template(macro_template_name)?, + ); + + self.render_body(¯o_definition.body, write, body_recursion_level)?; - let mut args = HashMap::with_capacity(function_call.args.len()); - for (arg_name, expr) in &function_call.args { - args.insert( - arg_name.to_string(), - self.safe_eval_expression(expr).map_err(err_wrap)?.clone().into_owned(), - ); - } + self.call_stack.pop(); - Ok(Cow::Owned(tera_fn.call(&args).map_err(err_wrap)?)) + Ok(()) } - fn eval_macro_call(&mut self, macro_call: &'a MacroCall, write: &mut impl Write) -> Result<()> { + #[cfg(feature = "async")] + async fn eval_macro_call_async( + &mut self, + macro_call: &'a MacroCall, + write: &mut (impl Write + Send + Sync), + body_recursion_level: usize, + ) -> Result<()> { let active_template_name = if let Some(block) = self.blocks.last() { block.1 } else if self.template.name != self.template_root.name { @@ -527,9 +1045,11 @@ impl<'a> Processor<'a> { // First the default arguments for (arg_name, default_value) in ¯o_definition.args { let value = match macro_call.args.get(arg_name) { - Some(val) => self.safe_eval_expression(val)?, + Some(val) => self.safe_eval_expression_async(val, body_recursion_level).await?, None => match *default_value { - Some(ref val) => self.safe_eval_expression(val)?, + Some(ref val) => { + self.safe_eval_expression_async(val, body_recursion_level).await? + } None => { return Err(Error::msg(format!( "Macro `{}` is missing the argument `{}`", @@ -548,7 +1068,7 @@ impl<'a> Processor<'a> { self.tera.get_template(macro_template_name)?, ); - self.render_body(¯o_definition.body, write)?; + self.render_body_async(¯o_definition.body, write, body_recursion_level).await?; self.call_stack.pop(); @@ -560,6 +1080,7 @@ impl<'a> Processor<'a> { value: &Val<'a>, fn_call: &'a FunctionCall, needs_escape: &mut bool, + body_recursion_level: usize, ) -> Result> { let filter_fn = self.tera.get_filter(&fn_call.name)?; *needs_escape = !filter_fn.is_safe(); @@ -570,25 +1091,34 @@ impl<'a> Processor<'a> { for (arg_name, expr) in &fn_call.args { args.insert( arg_name.to_string(), - self.safe_eval_expression(expr).map_err(err_wrap)?.clone().into_owned(), + self.safe_eval_expression(expr, body_recursion_level) + .map_err(err_wrap)? + .clone() + .into_owned(), ); } Ok(Cow::Owned(filter_fn.filter(value, &args).map_err(err_wrap)?)) } - fn eval_as_bool(&mut self, bool_expr: &'a Expr) -> Result { + fn eval_as_bool(&mut self, bool_expr: &'a Expr, body_recursion_level: usize) -> Result { let res = match bool_expr.val { ExprVal::Logic(LogicExpr { ref lhs, ref rhs, ref operator }) => { match *operator { - LogicOperator::Or => self.eval_as_bool(lhs)? || self.eval_as_bool(rhs)?, - LogicOperator::And => self.eval_as_bool(lhs)? && self.eval_as_bool(rhs)?, + LogicOperator::Or => { + self.eval_as_bool(lhs, body_recursion_level)? + || self.eval_as_bool(rhs, body_recursion_level)? + } + LogicOperator::And => { + self.eval_as_bool(lhs, body_recursion_level)? + && self.eval_as_bool(rhs, body_recursion_level)? + } LogicOperator::Gt | LogicOperator::Gte | LogicOperator::Lt | LogicOperator::Lte => { - let l = self.eval_expr_as_number(lhs)?; - let r = self.eval_expr_as_number(rhs)?; + let l = self.eval_expr_as_number(lhs, body_recursion_level)?; + let r = self.eval_expr_as_number(rhs, body_recursion_level)?; let (ll, rr) = match (l, r) { (Some(nl), Some(nr)) => (nl, nr), _ => return Err(Error::msg("Comparison to NaN")), @@ -599,12 +1129,17 @@ impl<'a> Processor<'a> { LogicOperator::Gt => ll.as_f64().unwrap() > rr.as_f64().unwrap(), LogicOperator::Lte => ll.as_f64().unwrap() <= rr.as_f64().unwrap(), LogicOperator::Lt => ll.as_f64().unwrap() < rr.as_f64().unwrap(), - _ => unreachable!(), + _ => { + return Err(Error::msg(format!( + "Unimplemented operator for eval_as_bool: {:?} [Gte/Gt/Lte/Lt only]", + operator + ))) + } } } LogicOperator::Eq | LogicOperator::NotEq => { - let mut lhs_val = self.eval_expression(lhs)?; - let mut rhs_val = self.eval_expression(rhs)?; + let mut lhs_val = self.eval_expression(lhs, body_recursion_level)?; + let mut rhs_val = self.eval_expression(rhs, body_recursion_level)?; // Monomorphize number vals. if lhs_val.is_number() || rhs_val.is_number() { @@ -624,33 +1159,44 @@ impl<'a> Processor<'a> { match *operator { LogicOperator::Eq => *lhs_val == *rhs_val, LogicOperator::NotEq => *lhs_val != *rhs_val, - _ => unreachable!(), + _ => { + return Err(Error::msg(format!( + "Unimplemented operator for eval_as_bool: {:?} [Eq/NotEq only]", + operator + ))) + } } } } } ExprVal::Ident(_) => { let mut res = self - .eval_expression(bool_expr) + .eval_expression(bool_expr, body_recursion_level) .unwrap_or(Cow::Owned(Value::Bool(false))) .is_truthy(); if bool_expr.negated { res = !res; } + + if bool_expr.bitnot { + return Err(Error::msg( + "Bitwise not (two's complement) operator `bitnot` can only be used on numbers in logic expressions", + )); + } res } ExprVal::Math(_) | ExprVal::Int(_) | ExprVal::Float(_) => { - match self.eval_as_number(&bool_expr.val)? { + match self.eval_as_number(&bool_expr.val, body_recursion_level)? { Some(n) => n.as_f64().unwrap() != 0.0, None => false, } } - ExprVal::In(ref in_cond) => self.eval_in_condition(in_cond)?, - ExprVal::Test(ref test) => self.eval_test(test)?, + ExprVal::In(ref in_cond) => self.eval_in_condition(in_cond, body_recursion_level)?, + ExprVal::Test(ref test) => self.eval_test(test, body_recursion_level)?, ExprVal::Bool(val) => val, ExprVal::String(ref string) => !string.is_empty(), ExprVal::FunctionCall(ref fn_call) => { - let v = self.eval_tera_fn_call(fn_call, &mut false)?; + let v = self.eval_tera_fn_call(fn_call, &mut false, body_recursion_level)?; match v.as_bool() { Some(val) => val, None => { @@ -662,41 +1208,60 @@ impl<'a> Processor<'a> { } } ExprVal::StringConcat(_) => { - let res = self.eval_expression(bool_expr)?; + let res = self.eval_expression(bool_expr, body_recursion_level)?; !res.as_str().unwrap().is_empty() } ExprVal::MacroCall(ref macro_call) => { let mut buf = Vec::new(); - self.eval_macro_call(macro_call, &mut buf)?; + self.eval_macro_call(macro_call, &mut buf, body_recursion_level)?; !buf.is_empty() } - _ => unreachable!("unimplemented logic operation for {:?}", bool_expr), + _ => { + return Err(Error::msg(format!( + "Unimplemented logic operation for {:?}", + bool_expr + ))) + } }; if bool_expr.negated { return Ok(!res); } + if bool_expr.bitnot { + return Err(Error::msg( + "Bitwise not (two's complement) operator `bitnot` can only be used on numbers in logic expressions", + )); + } + Ok(res) } /// In some cases, we will have filters in lhs/rhs of a math expression /// `eval_as_number` only works on ExprVal rather than Expr - fn eval_expr_as_number(&mut self, expr: &'a Expr) -> Result> { + fn eval_expr_as_number( + &mut self, + expr: &'a Expr, + body_recursion_level: usize, + ) -> Result> { if !expr.filters.is_empty() { - match *self.eval_expression(expr)? { + match *self.eval_expression(expr, body_recursion_level)? { Value::Number(ref s) => Ok(Some(s.clone())), _ => { Err(Error::msg("Tried to do math with an expression not resulting in a number")) } } } else { - self.eval_as_number(&expr.val) + self.eval_as_number(&expr.val, body_recursion_level) } } /// Return the value of an expression as a number - fn eval_as_number(&mut self, expr: &'a ExprVal) -> Result> { + fn eval_as_number( + &mut self, + expr: &'a ExprVal, + body_recursion_level: usize, + ) -> Result> { let result = match *expr { ExprVal::Ident(ref ident) => { let v = &*self.lookup_ident(ident)?; @@ -716,8 +1281,10 @@ impl<'a> Processor<'a> { ExprVal::Int(val) => Some(Number::from(val)), ExprVal::Float(val) => Some(Number::from_f64(val).unwrap()), ExprVal::Math(MathExpr { ref lhs, ref rhs, ref operator }) => { - let (l, r) = match (self.eval_expr_as_number(lhs)?, self.eval_expr_as_number(rhs)?) - { + let (l, r) = match ( + self.eval_expr_as_number(lhs, body_recursion_level)?, + self.eval_expr_as_number(rhs, body_recursion_level)?, + ) { (Some(l), Some(r)) => (l, r), _ => return Ok(None), }; @@ -752,21 +1319,71 @@ impl<'a> Processor<'a> { }; Some(Number::from(res)) } else { - let ll = l.as_f64().unwrap(); - let rr = r.as_f64().unwrap(); + let ll = l.as_f64().ok_or(Error::msg(format!( + "Tried to multiply a number with an unsupported type: {:?}", + l + )))?; + let rr = r.as_f64().ok_or(Error::msg(format!( + "Tried to multiply a number with an unsupported type: {:?}", + r + )))?; + Number::from_f64(ll * rr) } } MathOperator::Div => { - let ll = l.as_f64().unwrap(); - let rr = r.as_f64().unwrap(); - let res = ll / rr; - if res.is_nan() { - None - } else if res.round() == res && res.is_finite() { - Some(Number::from(res as i64)) + if l.is_i64() && r.is_i64() { + let ll = l.as_i64().unwrap(); + let rr = r.as_i64().unwrap(); + + match ll.checked_div(rr) { + Some(s) => Some(Number::from(s)), + None => { + return Err(Error::msg(format!( + "{} / {} results in an out of bounds i64 or division by zero", + ll, rr + ))); + } + } + } else if l.is_u64() && r.is_u64() { + let ll = l.as_u64().unwrap(); + let rr = r.as_u64().unwrap(); + + match ll.checked_div(rr) { + Some(s) => Some(Number::from(s)), + None => { + return Err(Error::msg(format!( + "{} / {} results in an out of bounds u64 or division by zero", + ll, rr + ))); + } + } } else { - Number::from_f64(res) + let ll = l.as_f64().ok_or(Error::msg(format!( + "Tried to divide a number with an unsupported type: {:?}", + l + )))?; + let rr = r.as_f64().ok_or(Error::msg(format!( + "Tried to divide a number with an unsupported type: {:?}", + r + )))?; + + if rr == 0.0 { + return Err(Error::msg(format!( + "Tried to divide by zero: {:?}/{:?}", + lhs, rhs + ))); + } + + let res = ll / rr; + + if res.is_nan() { + None + } else if res.round() == res && res.is_finite() { + Some(Number::from(res as i64)) + } else { + Number::from_f64(res) + } } } MathOperator::Add => { @@ -797,8 +1414,12 @@ impl<'a> Processor<'a> { }; Some(Number::from(res)) } else { - let ll = l.as_f64().unwrap(); - let rr = r.as_f64().unwrap(); + let ll = l.as_f64().ok_or(Error::msg( + "The `+` operator can only be used on numbers in math expressions", + ))?; + let rr = r.as_f64().ok_or(Error::msg( + "The `+` operator can only be used on numbers in math expressions", + ))?; Some(Number::from_f64(ll + rr).unwrap()) } } @@ -857,15 +1478,159 @@ impl<'a> Processor<'a> { } Some(Number::from(ll % rr)) } else { - let ll = l.as_f64().unwrap(); - let rr = r.as_f64().unwrap(); + let ll = l.as_f64().ok_or(Error::msg( + "The `%` operator can only be used on numbers in math expressions", + ))?; + let rr = r.as_f64().ok_or(Error::msg( + "The `%` operator can only be used on numbers in math expressions", + ))?; Number::from_f64(ll % rr) } } + MathOperator::Power => { + if l.is_i64() && r.is_i64() { + let ll = l.as_i64().unwrap(); + let rr = r.as_i64().unwrap(); + if rr < 0 { + return Err(Error::msg( + "The `**` operator can only be used with a positive number as the right operand", + )); + } + + let rr = rr.try_into().map_err(|_| { + Error::msg("The `**` operator can only be used with a positive number that fits in a u32 as the right operand") + })?; + + let res = ll.checked_pow(rr).ok_or(Error::msg(format!( + "{} ** {} results in an out of bounds i64", + ll, rr + )))?; + + Some(Number::from(res)) + } else if l.is_u64() && r.is_u64() { + let ll = l.as_u64().unwrap(); + let rr = r.as_u64().unwrap(); + + let rr = rr.try_into().map_err(|_| { + Error::msg("The `**` operator can only be used with a positive number that fits in a u32 as the right operand") + })?; + + let res = ll.checked_pow(rr).ok_or(Error::msg(format!( + "{} ** {} results in an out of bounds i64", + ll, rr + )))?; + + Some(Number::from(res)) + } else { + let ll = l.as_f64().ok_or(Error::msg( + "The `**` operator can only be used on numbers in math expressions", + ))?; + let rr = r.as_f64().ok_or(Error::msg( + "The `**` operator can only be used on numbers in math expressions", + ))?; + + Number::from_f64(ll.powf(rr)) + } + } + MathOperator::BitOr => { + if l.is_i64() && r.is_i64() { + let ll = l.as_i64().unwrap(); + let rr = r.as_i64().unwrap(); + Some(Number::from(ll | rr)) + } else if l.is_u64() && r.is_u64() { + let ll = l.as_u64().unwrap(); + let rr = r.as_u64().unwrap(); + Some(Number::from(ll | rr)) + } else { + return Err(Error::msg( + "The `|` operator can only be used on numbers in math expressions that can be cast to integers", + )); + } + } + MathOperator::BitXor => { + if l.is_i64() && r.is_i64() { + let ll = l.as_i64().unwrap(); + let rr = r.as_i64().unwrap(); + Some(Number::from(ll ^ rr)) + } else if l.is_u64() && r.is_u64() { + let ll = l.as_u64().unwrap(); + let rr = r.as_u64().unwrap(); + Some(Number::from(ll ^ rr)) + } else { + return Err(Error::msg( + "The `^` operator can only be used on numbers in math expressions that can be cast to integers", + )); + } + } + MathOperator::BitAnd => { + if l.is_i64() && r.is_i64() { + let ll = l.as_i64().unwrap(); + let rr = r.as_i64().unwrap(); + Some(Number::from(ll & rr)) + } else if l.is_u64() && r.is_u64() { + let ll = l.as_u64().unwrap(); + let rr = r.as_u64().unwrap(); + Some(Number::from(ll & rr)) + } else { + return Err(Error::msg( + "The `&` operator can only be used on numbers in math expressions that can be cast to integers", + )); + } + } + MathOperator::BitLeftShift => { + if l.is_i64() && r.is_i64() { + let ll = l.as_i64().unwrap(); + let rr = r.as_i64().unwrap(); + if rr < 0 { + return Err(Error::msg( + "The `<<` operator can only be used with a positive number as the right operand", + )); + } + + let rr = rr.try_into().map_err(|_| { + Error::msg("The `>>` operator can only be used with a positive number that fits in a u32 as the right operand") + })?; + + Some(Number::from(ll.rotate_left(rr))) // To avoid overflows, we actually rotate left instead of shifting + } else if l.is_u64() && r.is_u64() { + let ll = l.as_u64().unwrap(); + let rr = r.as_u64().unwrap(); + Some(Number::from(ll << rr)) + } else { + return Err(Error::msg( + "The `<<` operator can only be used on numbers in math expressions that can be cast to integers", + )); + } + } + MathOperator::BitRightShift => { + if l.is_i64() && r.is_i64() { + let ll = l.as_i64().unwrap(); + let rr = r.as_i64().unwrap(); + if rr < 0 { + return Err(Error::msg( + "The `>>` operator can only be used with a positive number as the right operand", + )); + } + + let rr = rr.try_into().map_err(|_| { + Error::msg("The `>>` operator can only be used with a positive number that fits in a u32 as the right operand") + })?; + + Some(Number::from(ll.rotate_right(rr))) // To avoid overflows, we actually rotate right instead of shifting + } else if l.is_u64() && r.is_u64() { + let ll = l.as_u64().unwrap(); + let rr = r.as_u64().unwrap(); + Some(Number::from(ll >> rr)) + } else { + return Err(Error::msg( + "The `>>` operator can only be used on numbers in math expressions that can be cast to integers", + )); + } + } } } ExprVal::FunctionCall(ref fn_call) => { - let v = self.eval_tera_fn_call(fn_call, &mut false)?; + let v = self.eval_tera_fn_call(fn_call, &mut false, body_recursion_level)?; if v.is_i64() { Some(Number::from(v.as_i64().unwrap())) } else if v.is_u64() { @@ -894,7 +1659,7 @@ impl<'a> Processor<'a> { ExprVal::Test(ref test) => { return Err(Error::msg(format!("Tried to do math with a test: {}", test.name))); } - _ => unreachable!("unimplemented math expression for {:?}", expr), + _ => return Err(Error::msg(format!("unimplemented math expression for {:?}", expr))), }; Ok(result) @@ -903,7 +1668,49 @@ impl<'a> Processor<'a> { /// Only called while rendering a block. /// This will look up the block we are currently rendering and its level and try to render /// the block at level + n, where would be the next template in the hierarchy the block is present - fn do_super(&mut self, write: &mut impl Write) -> Result<()> { + fn do_super(&mut self, write: &mut impl Write, body_recursion_level: usize) -> Result<()> { + let &(block_name, _, level) = self.blocks.last().unwrap(); + let mut next_level = level + 1; + + while next_level <= self.template.parents.len() { + let blocks_definitions = &self + .tera + .get_template(&self.template.parents[next_level - 1]) + .unwrap() + .blocks_definitions; + + if let Some(block_def) = blocks_definitions.get(block_name) { + let (ref tpl_name, Block { ref body, .. }) = block_def[0]; + self.blocks.push((block_name, tpl_name, next_level)); + + self.render_body(body, write, body_recursion_level)?; + self.blocks.pop(); + + // Can't go any higher for that block anymore? + if next_level >= self.template.parents.len() { + // then remove it from the stack, we're done with it + self.blocks.pop(); + } + return Ok(()); + } else { + next_level += 1; + } + } + + Err(Error::msg("Tried to use super() in the top level block")) + } + + #[cfg(feature = "async")] + /// Only called while rendering a block. + /// This will look up the block we are currently rendering and its level and try to render + /// the block at level + n, where would be the next template in the hierarchy the block is present + /// + /// This is the async version of (`do_super`)[Self::do_super] + async fn do_super_async( + &mut self, + write: &mut (impl Write + Send + Sync), + body_recursion_level: usize, + ) -> Result<()> { let &(block_name, _, level) = self.blocks.last().unwrap(); let mut next_level = level + 1; @@ -918,7 +1725,7 @@ impl<'a> Processor<'a> { let (ref tpl_name, Block { ref body, .. }) = block_def[0]; self.blocks.push((block_name, tpl_name, next_level)); - self.render_body(body, write)?; + self.render_body_async(body, write, body_recursion_level).await?; self.blocks.pop(); // Can't go any higher for that block anymore? @@ -942,49 +1749,180 @@ impl<'a> Processor<'a> { // Unwraps are safe since we are dealing with things that are already Value return Ok(Cow::Owned( to_value( - to_string_pretty(&self.call_stack.current_context_cloned().take()).unwrap(), + to_string_pretty(&self.call_stack.current_context_cloned()?.take()).unwrap(), ) .unwrap(), )); } + if key == MAGICAL_DUMP_VAR_RAW { + return Ok(Cow::Owned(self.call_stack.current_context_cloned()?)); + } + process_path(key, &self.call_stack) } /// Process the given node, appending the string result to the buffer /// if it is possible - fn render_node(&mut self, node: &'a Node, write: &mut impl Write) -> Result<()> { + fn render_node( + &mut self, + node: &'a Node, + write: &mut impl Write, + body_recursion_level: usize, // Must be tracked to avoid infinite recursion + ) -> Result<()> { match *node { // Comments are ignored when rendering Node::Comment(_, _) => (), Node::Text(ref s) | Node::Raw(_, ref s, _) => write!(write, "{}", s)?, - Node::VariableBlock(_, ref expr) => self.eval_expression(expr)?.render(write)?, - Node::Set(_, ref set) => self.eval_set(set)?, + Node::VariableBlock(_, ref expr) => { + self.eval_expression(expr, body_recursion_level)?.render(write)? + } + Node::Set(_, ref set) => self.eval_set(set, body_recursion_level)?, + Node::Delete(_, ref del) => { + self.eval_delete(del)?; // TODO: What should happen to the existing value? + } Node::FilterSection(_, FilterSection { ref filter, ref body }, _) => { let body = render_to_string( || format!("filter {}", filter.name), - |w| self.render_body(body, w), + |w| self.render_body(body, w, body_recursion_level), )?; // the safe filter doesn't actually exist if filter.name == "safe" { write!(write, "{}", body)?; } else { - self.eval_filter(&Cow::Owned(Value::String(body)), filter, &mut false)? - .render(write)?; + self.eval_filter( + &Cow::Owned(Value::String(body)), + filter, + &mut false, + body_recursion_level, + )? + .render(write)?; + } + } + // Macros have been imported at the beginning + Node::ImportMacro(_, _, _) => (), + Node::If(ref if_node, _) => { + self.render_if_node(if_node, write, body_recursion_level)? + } + Node::Forloop(_, ref forloop, _) => { + self.render_for_loop(forloop, write, body_recursion_level)? + } + Node::Break(_) => { + self.call_stack.break_for_loop()?; + } + Node::Continue(_) => { + self.call_stack.continue_for_loop()?; + } + Node::Block(_, ref block, _) => { + self.render_block(block, 0, body_recursion_level, write)? + } + Node::Super => self.do_super(write, body_recursion_level)?, + Node::Include(_, ref tpl_names, ignore_missing) => { + let mut found = false; + for tpl_name in tpl_names { + let template = self.tera.get_template(tpl_name); + if template.is_err() { + continue; + } + let template = template.unwrap(); + self.macros.add_macros_from_template(self.tera, template)?; + self.call_stack.push_include_frame(tpl_name, template); + self.render_body(&template.ast, write, body_recursion_level)?; + self.call_stack.pop(); + found = true; + break; + } + if !found && !ignore_missing { + return Err(Error::template_not_found( + ["[", &tpl_names.join(", "), "]"].join(""), + )); + } + } + Node::Extends(_, ref name) => { + return Err(Error::msg(format!( + "Inheritance in included templates is currently not supported: extended `{}`", + name + ))); + } + // Macro definitions are ignored when rendering + Node::MacroDefinition(_, _, _) => (), + }; + + Ok(()) + } + + #[cfg(feature = "async")] + /// Process the given node asynchronously, appending the string result to the buffer + /// if it is possible + async fn render_node_async( + &mut self, + node: &'a Node, + write: &mut (impl Write + Send + Sync), + body_recursion_level: usize, // Must be tracked to avoid infinite recursion + ) -> Result<()> { + match *node { + // Comments are ignored when rendering + Node::Comment(_, _) => (), + Node::Text(ref s) | Node::Raw(_, ref s, _) => write!(write, "{}", s)?, + Node::VariableBlock(_, ref expr) => { + self.eval_expression_async(expr, body_recursion_level).await?.render(write)? + } + Node::Set(_, ref set) => self.eval_set_async(set, body_recursion_level).await?, + Node::Delete(_, ref del) => { + self.eval_delete(del)?; // TODO: What should happen to the existing value? + } + Node::FilterSection(_, FilterSection { ref filter, ref body }, _) => { + // Render to string doesnt support async yet so just do it ourselves + /* + pub(crate) fn render_to_string(context: C, render: F) -> Result + where + C: FnOnce() -> String, + F: FnOnce(&mut Vec) -> Result<(), E>, + Error: From, + { + let mut buffer = Vec::new(); + render(&mut buffer).map_err(Error::from)?; + buffer_to_string(context, buffer) + } + */ + + let mut buffer = Vec::new(); + self.render_body_async(body, &mut buffer, body_recursion_level).await?; + let body = + crate::utils::buffer_to_string(|| format!("filter {}", filter.name), buffer) + .map_err(Error::from)?; + + // the safe filter doesn't actually exist + if filter.name == "safe" { + write!(write, "{}", body)?; + } else { + self.eval_filter( + &Cow::Owned(Value::String(body)), + filter, + &mut false, + body_recursion_level, + )? + .render(write)?; } } // Macros have been imported at the beginning Node::ImportMacro(_, _, _) => (), - Node::If(ref if_node, _) => self.render_if_node(if_node, write)?, - Node::Forloop(_, ref forloop, _) => self.render_for_loop(forloop, write)?, + Node::If(ref if_node, _) => { + self.render_if_node_async(if_node, write, body_recursion_level).await? + } + Node::Forloop(_, ref forloop, _) => { + self.render_for_loop_async(forloop, write, body_recursion_level).await? + } Node::Break(_) => { self.call_stack.break_for_loop()?; } Node::Continue(_) => { self.call_stack.continue_for_loop()?; } - Node::Block(_, ref block, _) => self.render_block(block, 0, write)?, - Node::Super => self.do_super(write)?, + Node::Block(_, ref block, _) => { + self.render_block_async(block, 0, body_recursion_level, write).await? + } + Node::Super => self.do_super_async(write, body_recursion_level).await?, Node::Include(_, ref tpl_names, ignore_missing) => { let mut found = false; for tpl_name in tpl_names { @@ -995,7 +1933,7 @@ impl<'a> Processor<'a> { let template = template.unwrap(); self.macros.add_macros_from_template(self.tera, template)?; self.call_stack.push_include_frame(tpl_name, template); - self.render_body(&template.ast, write)?; + self.render_body_async(&template.ast, write, body_recursion_level).await?; self.call_stack.pop(); found = true; break; @@ -1036,8 +1974,7 @@ impl<'a> Processor<'a> { // which template are we in? if let Some(&(name, _template, ref level)) = self.blocks.last() { - let block_def = - self.template.blocks_definitions.get(&name.to_string()).and_then(|b| b.get(*level)); + let block_def = self.template.blocks_definitions.get(name).and_then(|b| b.get(*level)); if let Some((tpl_name, _)) = block_def { if tpl_name != &self.template.name { @@ -1057,10 +1994,22 @@ impl<'a> Processor<'a> { /// Entry point for the rendering pub fn render(&mut self, write: &mut impl Write) -> Result<()> { for node in &self.template_root.ast { - self.render_node(node, write) + self.render_node(node, write, 0) .map_err(|e| Error::chain(self.get_error_location(), e))?; } Ok(()) } + + /// Async version of [`render`](Self::render) + #[cfg(feature = "async")] + pub async fn render_async(&mut self, write: &mut (impl Write + Send + Sync)) -> Result<()> { + for node in &self.template_root.ast { + self.render_node_async(node, write, 0) + .await + .map_err(|e: Error| Error::chain(self.get_error_location(), e))?; + } + + Ok(()) + } } diff --git a/src/renderer/stack_frame.rs b/src/renderer/stack_frame.rs index 59a2a0aa..24f42b63 100644 --- a/src/renderer/stack_frame.rs +++ b/src/renderer/stack_frame.rs @@ -1,3 +1,4 @@ +use crate::errors::{Error, Result}; use std::borrow::Cow; use std::collections::HashMap; @@ -51,6 +52,8 @@ pub struct StackFrame<'a> { pub for_loop: Option>, /// Macro namespace if MacroFrame pub macro_namespace: Option<&'a str>, + /// Size of the frame + pub size: usize, } impl<'a> StackFrame<'a> { @@ -62,6 +65,7 @@ impl<'a> StackFrame<'a> { active_template: tpl, for_loop: None, macro_namespace: None, + size: 0, } } @@ -73,6 +77,7 @@ impl<'a> StackFrame<'a> { active_template: tpl, for_loop: Some(for_loop), macro_namespace: None, + size: 0, } } @@ -89,6 +94,7 @@ impl<'a> StackFrame<'a> { active_template: tpl, for_loop: None, macro_namespace: Some(macro_namespace), + size: 0, } } @@ -100,6 +106,7 @@ impl<'a> StackFrame<'a> { active_template: tpl, for_loop: None, macro_namespace: None, + size: 0, } } @@ -177,8 +184,38 @@ impl<'a> StackFrame<'a> { } /// Insert a value in the context - pub fn insert(&mut self, key: &'a str, value: Val<'a>) { + pub fn insert(&mut self, key: &'a str, value: Val<'a>) -> Result<()> { + if self.context.len() >= crate::constraints::STACK_FRAME_MAX_ENTRIES { + return Err(Error::msg("Stack frame context is full")); + } + + if self.size >= crate::constraints::STACK_FRAME_MAX_SIZE { + return Err(Error::msg("Stack frame context is too big")); + } + self.context.insert(key, value); + + // Get size of self.context + self.size = self.context.iter().fold(0, |acc, (k, v)| { + acc + k.len() + + match v { + Cow::Borrowed(v) => v.to_string().len(), + Cow::Owned(v) => v.to_string().len(), + } + }); + + // Check one more time now with the new size + if self.size >= crate::constraints::STACK_FRAME_MAX_SIZE { + // Remove element + self.context.remove(key); + return Err(Error::msg("Stack frame context is too big")); + } + + Ok(()) + } + + pub fn remove(&mut self, key: &str) -> Result> { + self.context.remove(key).ok_or_else(|| Error::msg(format!("Key {} not found", key))) } /// Context is cleared on each loop diff --git a/src/renderer/tests/basic.rs b/src/renderer/tests/basic.rs index ca621be7..84e3af95 100644 --- a/src/renderer/tests/basic.rs +++ b/src/renderer/tests/basic.rs @@ -56,7 +56,6 @@ fn render_variable_block_lit_expr() { ("{{ (2 + 1) * 2 }}", "6"), ("{{ 2 * 4 % 8 }}", "0"), ("{{ 2.8 * 2 | round }}", "6"), - ("{{ 1 / 0 }}", "NaN"), ("{{ true and 10 }}", "true"), ("{{ true and not 10 }}", "false"), ("{{ not true }}", "false"), @@ -72,14 +71,14 @@ fn render_variable_block_lit_expr() { #[test] fn render_variable_block_ident() { let mut context = Context::new(); - context.insert("name", &"john"); - context.insert("malicious", &""); - context.insert("a", &2); - context.insert("b", &3); - context.insert("numbers", &vec![1, 2, 3]); - context.insert("tuple_list", &vec![(1, 2, 3), (1, 2, 3)]); - context.insert("review", &Review::new()); - context.insert("with_newline", &"Animal Alphabets\nB is for Bee-Eater"); + context.insert("name", &"john").unwrap(); + context.insert("malicious", &"").unwrap(); + context.insert("a", &2).unwrap(); + context.insert("b", &3).unwrap(); + context.insert("numbers", &vec![1, 2, 3]).unwrap(); + context.insert("tuple_list", &vec![(1, 2, 3), (1, 2, 3)]).unwrap(); + context.insert("review", &Review::new()).unwrap(); + context.insert("with_newline", &"Animal Alphabets\nB is for Bee-Eater").unwrap(); let inputs = vec![ ("{{ name }}", "john"), @@ -144,18 +143,18 @@ fn render_variable_block_ident() { #[test] fn render_variable_block_logic_expr() { let mut context = Context::new(); - context.insert("name", &"john"); - context.insert("malicious", &""); - context.insert("a", &2); - context.insert("b", &3); - context.insert("numbers", &vec![1, 2, 3]); - context.insert("tuple_list", &vec![(1, 2, 3), (1, 2, 3)]); + context.insert("name", &"john").unwrap(); + context.insert("malicious", &"").unwrap(); + context.insert("a", &2).unwrap(); + context.insert("b", &3).unwrap(); + context.insert("numbers", &vec![1, 2, 3]).unwrap(); + context.insert("tuple_list", &vec![(1, 2, 3), (1, 2, 3)]).unwrap(); let mut hashmap = HashMap::new(); hashmap.insert("a", 1); hashmap.insert("b", 10); hashmap.insert("john", 100); - context.insert("object", &hashmap); - context.insert("urls", &vec!["https://test"]); + context.insert("object", &hashmap).unwrap(); + context.insert("urls", &vec!["https://test"]).unwrap(); let inputs = vec![ ("{{ (1.9 + a) | round > 10 }}", "false"), @@ -199,8 +198,8 @@ fn render_variable_block_logic_expr() { #[test] fn render_variable_block_autoescaping_disabled() { let mut context = Context::new(); - context.insert("name", &"john"); - context.insert("malicious", &""); + context.insert("name", &"john").unwrap(); + context.insert("malicious", &"").unwrap(); let inputs = vec![ ("{{ name }}", "john"), @@ -245,7 +244,7 @@ fn escaping_happens_at_the_end() { for (input, expected) in inputs { let mut context = Context::new(); - context.insert("url", "https://www.example.org/apples-&-oranges/"); + context.insert("url", "https://www.example.org/apples-&-oranges/").unwrap(); assert_eq!(render_template(input, &context).unwrap(), expected); } } @@ -253,8 +252,8 @@ fn escaping_happens_at_the_end() { #[test] fn filter_args_are_not_escaped() { let mut context = Context::new(); - context.insert("my_var", &"hey"); - context.insert("to", &"&"); + context.insert("my_var", &"hey").unwrap(); + context.insert("to", &"&").unwrap(); let input = r#"{{ my_var | replace(from="h", to=to) }}"#; assert_eq!(render_template(input, &context).unwrap(), "&ey"); @@ -331,10 +330,10 @@ fn render_raw_tag() { #[test] fn add_set_values_in_context() { let mut context = Context::new(); - context.insert("my_var", &"hey"); - context.insert("malicious", &""); - context.insert("admin", &true); - context.insert("num", &1); + context.insert("my_var", &"hey").unwrap(); + context.insert("malicious", &"").unwrap(); + context.insert("admin", &true).unwrap(); + context.insert("num", &1).unwrap(); let inputs = vec![ ("{% set i = 1 %}{{ i }}", "1"), @@ -349,7 +348,6 @@ fn add_set_values_in_context() { ("{% set i = range(end=3) %}{{ i }}", "[0, 1, 2]"), ("{% set i = admin or true %}{{ i }}", "true"), ("{% set i = admin and num > 0 %}{{ i }}", "true"), - ("{% set i = 0 / 0 %}{{ i }}", "NaN"), ("{% set i = [1,2] %}{{ i }}", "[1, 2]"), ]; @@ -383,15 +381,15 @@ fn render_filter_section() { #[test] fn render_tests() { let mut context = Context::new(); - context.insert("is_true", &true); - context.insert("is_false", &false); - context.insert("age", &18); - context.insert("name", &"john"); + context.insert("is_true", &true).unwrap(); + context.insert("is_false", &false).unwrap(); + context.insert("age", &18).unwrap(); + context.insert("name", &"john").unwrap(); let mut map = HashMap::new(); map.insert(0, 1); - context.insert("map", &map); - context.insert("numbers", &vec![1, 2, 3]); - context.insert::, _>("maybe", &None); + context.insert("map", &map).unwrap(); + context.insert("numbers", &vec![1, 2, 3]).unwrap(); + context.insert::, _>("maybe", &None).unwrap(); let inputs = vec![ ("{% if is_true is defined %}Admin{% endif %}", "Admin"), @@ -420,12 +418,12 @@ fn render_tests() { #[test] fn render_if_elif_else() { let mut context = Context::new(); - context.insert("is_true", &true); - context.insert("is_false", &false); - context.insert("age", &18); - context.insert("name", &"john"); - context.insert("empty_string", &""); - context.insert("numbers", &vec![1, 2, 3]); + context.insert("is_true", &true).unwrap(); + context.insert("is_false", &false).unwrap(); + context.insert("age", &18).unwrap(); + context.insert("name", &"john").unwrap(); + context.insert("empty_string", &"").unwrap(); + context.insert("numbers", &vec![1, 2, 3]).unwrap(); let inputs = vec![ ("{% if is_true %}Admin{% endif %}", "Admin"), @@ -452,8 +450,6 @@ fn render_if_elif_else() { ("{% if not undefined %}a{% endif %}", "a"), ("{% if not is_false and is_true %}a{% endif %}", "a"), ("{% if not is_false or numbers | length > 0 %}a{% endif %}", "a"), - // doesn't panic with NaN results - ("{% if 0 / 0 %}a{% endif %}", ""), // if and else ("{% if is_true %}Admin{% else %}User{% endif %}", "Admin"), ("{% if is_false %}Admin{% else %}User{% endif %}", "User"), @@ -504,12 +500,12 @@ fn render_for() { map.insert("name", "bob"); map.insert("age", "18"); - context.insert("data", &vec![1, 2, 3]); - context.insert("notes", &vec![1, 2, 3]); - context.insert("vectors", &vec![vec![0, 3, 6], vec![1, 4, 7]]); - context.insert("vectors_some_empty", &vec![vec![0, 3, 6], vec![], vec![1, 4, 7]]); - context.insert("map", &map); - context.insert("truthy", &2); + context.insert("data", &vec![1, 2, 3]).unwrap(); + context.insert("notes", &vec![1, 2, 3]).unwrap(); + context.insert("vectors", &vec![vec![0, 3, 6], vec![1, 4, 7]]).unwrap(); + context.insert("vectors_some_empty", &vec![vec![0, 3, 6], vec![], vec![1, 4, 7]]).unwrap(); + context.insert("map", &map).unwrap(); + context.insert("truthy", &2).unwrap(); let inputs = vec![ ("{% for i in data %}{{i}}{% endfor %}", "123"), @@ -591,7 +587,7 @@ fn render_for() { #[test] fn render_magic_variable_isnt_escaped() { let mut context = Context::new(); - context.insert("html", &""); + context.insert("html", &"").unwrap(); let result = render_template("{{ __tera_context }}", &context); @@ -608,7 +604,7 @@ fn render_magic_variable_isnt_escaped() { #[test] fn ok_many_variable_blocks() { let mut context = Context::new(); - context.insert("username", &"bob"); + context.insert("username", &"bob").unwrap(); let mut tpl = String::new(); for _ in 0..200 { @@ -624,8 +620,8 @@ fn ok_many_variable_blocks() { #[test] fn can_set_variable_in_global_context_in_forloop() { let mut context = Context::new(); - context.insert("tags", &vec![1, 2, 3]); - context.insert("default", &"default"); + context.insert("tags", &vec![1, 2, 3]).unwrap(); + context.insert("default", &"default").unwrap(); let result = render_template( r#" @@ -644,8 +640,8 @@ fn can_set_variable_in_global_context_in_forloop() { fn default_filter_works() { let mut context = Context::new(); let i: Option = None; - context.insert("existing", "hello"); - context.insert("null", &i); + context.insert("existing", "hello").unwrap(); + context.insert("null", &i).unwrap(); let inputs = vec![ (r#"{{ existing | default(value="hey") }}"#, "hello"), @@ -673,7 +669,7 @@ fn filter_filter_works() { } let mut context = Context::new(); - context.insert("authors", &vec![Author { id: 1 }, Author { id: 2 }, Author { id: 3 }]); + context.insert("authors", &vec![Author { id: 1 }, Author { id: 2 }, Author { id: 3 }]).unwrap(); let inputs = vec![(r#"{{ authors | filter(attribute="id", value=1) | first | get(key="id") }}"#, "1")]; @@ -688,8 +684,8 @@ fn filter_filter_works() { fn filter_on_array_literal_works() { let mut context = Context::new(); let i: Option = None; - context.insert("existing", "hello"); - context.insert("null", &i); + context.insert("existing", "hello").unwrap(); + context.insert("null", &i).unwrap(); let inputs = vec![ (r#"{{ [1, 2, 3] | length }}"#, "3"), @@ -706,10 +702,10 @@ fn filter_on_array_literal_works() { #[test] fn can_do_string_concat() { let mut context = Context::new(); - context.insert("a_string", "hello"); - context.insert("another_string", "xXx"); - context.insert("an_int", &1); - context.insert("a_float", &3.18); + context.insert("a_string", "hello").unwrap(); + context.insert("another_string", "xXx").unwrap(); + context.insert("an_int", &1).unwrap(); + context.insert("a_float", &3.18).unwrap(); let inputs = vec![ (r#"{{ "hello" ~ " world" }}"#, "hello world"), @@ -735,7 +731,7 @@ fn can_do_string_concat() { #[test] fn can_fail_rendering_from_template() { let mut context = Context::new(); - context.insert("title", "hello"); + context.insert("title", "hello").unwrap(); let res = render_template( r#"{{ throw(message="Error: " ~ title ~ " did not include a summary") }}"#, @@ -764,7 +760,7 @@ fn does_render_owned_for_loop_with_objects() { {"id": 8}, {"id": 9, "year": null}, ]); - context.insert("something", &data); + context.insert("something", &data).unwrap(); let tpl = r#"{% for year, things in something | group_by(attribute="year") %}{{year}},{% endfor %}"#; @@ -786,7 +782,7 @@ fn does_render_owned_for_loop_with_objects_string_keys() { {"id": 8}, {"id": 9, "year": null}, ]); - context.insert("something", &data); + context.insert("something", &data).unwrap(); let tpl = r#"{% for group, things in something | group_by(attribute="group") %}{{group}},{% endfor %}"#; let expected = "a,b,c,"; @@ -796,9 +792,9 @@ fn does_render_owned_for_loop_with_objects_string_keys() { #[test] fn render_magic_variable_gets_all_contexts() { let mut context = Context::new(); - context.insert("html", &""); - context.insert("num", &1); - context.insert("i", &10); + context.insert("html", &"").unwrap(); + context.insert("num", &1).unwrap(); + context.insert("i", &10).unwrap(); let result = render_template( "{% set some_val = 1 %}{% for i in range(start=0, end=1) %}{% set for_val = i %}{{ __tera_context }}{% endfor %}", @@ -821,9 +817,9 @@ fn render_magic_variable_gets_all_contexts() { #[test] fn render_magic_variable_macro_doesnt_leak() { let mut context = Context::new(); - context.insert("html", &""); - context.insert("num", &1); - context.insert("i", &10); + context.insert("html", &"").unwrap(); + context.insert("num", &1).unwrap(); + context.insert("i", &10).unwrap(); let mut tera = Tera::default(); tera.add_raw_templates(vec![ @@ -934,7 +930,7 @@ fn split_on_context_value() { let mut tera = Tera::default(); tera.add_raw_template("split.html", r#"{{ body | split(pat="\n") }}"#).unwrap(); let mut context = Context::new(); - context.insert("body", "multi\nple\nlines"); + context.insert("body", "multi\nple\nlines").unwrap(); let res = tera.render("split.html", &context); assert_eq!(res.unwrap(), "[multi, ple, lines]"); } @@ -990,3 +986,41 @@ fn safe_function_works() { let res = tera.render("test.html", &Context::new()); assert_eq!(res.unwrap(), "
Hello
"); } + +#[test] +fn test_pemdas() { + let mut tera = Tera::default(); + let ctx = Context::new(); + let r = tera.render_str("{{ (1 + 2) * (3 + 4) }}", &ctx); + + assert_eq!(r.unwrap(), "21"); + + let r = tera.render_str("{{ 2 * (1 + 2) }}", &ctx); + + assert_eq!(r.unwrap(), "6"); + + let r = tera.render_str("{{ 2 * (1 << 2) }}", &ctx); + + assert_eq!(r.unwrap(), "8"); + + let r = tera.render_str("{{ 2 * (1 >> 0) }}", &ctx); + + assert_eq!(r.unwrap(), "2"); +} + +#[test] +fn test_zero_div_zero() { + let mut tera = Tera::default(); + let ctx = Context::new(); + let r = tera.render_str("{{ 1 / 0 }}", &ctx); + + let err = format!("{:?}", r.err().unwrap()); + assert!(err.contains("division by zero")); +} + +#[test] +fn delete_variables() { + let mut tera = Tera::default(); + let result = tera.render_str("{% set a = '1' %}{% delete a %}", &Context::new()).unwrap(); + assert_eq!(result, "".to_owned()); +} diff --git a/src/renderer/tests/errors.rs b/src/renderer/tests/errors.rs index 1a0a6703..f7bfc3fa 100644 --- a/src/renderer/tests/errors.rs +++ b/src/renderer/tests/errors.rs @@ -104,7 +104,7 @@ fn error_out_of_range_index() { let mut tera = Tera::default(); tera.add_raw_templates(vec![("tpl", "{{ arr[10] }}")]).unwrap(); let mut context = Context::new(); - context.insert("arr", &[1, 2, 3]); + context.insert("arr", &[1, 2, 3]).unwrap(); let result = tera.render("tpl", &Context::new()); @@ -119,7 +119,7 @@ fn error_unknown_index_variable() { let mut tera = Tera::default(); tera.add_raw_templates(vec![("tpl", "{{ arr[a] }}")]).unwrap(); let mut context = Context::new(); - context.insert("arr", &[1, 2, 3]); + context.insert("arr", &[1, 2, 3]).unwrap(); let result = tera.render("tpl", &context); @@ -135,8 +135,8 @@ fn error_invalid_type_index_variable() { tera.add_raw_templates(vec![("tpl", "{{ arr[a] }}")]).unwrap(); let mut context = Context::new(); - context.insert("arr", &[1, 2, 3]); - context.insert("a", &true); + context.insert("arr", &[1, 2, 3]).unwrap(); + context.insert("a", &true).unwrap(); let result = tera.render("tpl", &context); @@ -168,7 +168,7 @@ fn error_when_using_variable_set_in_included_templates_outside() { ]) .unwrap(); let mut context = Context::new(); - context.insert("a", &10); + context.insert("a", &10).unwrap(); let result = tera.render("base", &context); assert_eq!( @@ -184,7 +184,7 @@ fn right_variable_name_is_needed_in_for_loop() { let mut data = HashMap::new(); data.insert("content", "hello"); let mut context = Context::new(); - context.insert("comments", &vec![data]); + context.insert("comments", &vec![data]).unwrap(); let mut tera = Tera::default(); tera.add_raw_template( "tpl", @@ -229,7 +229,7 @@ fn error_string_concat_math_logic() { let mut tera = Tera::default(); tera.add_raw_templates(vec![("tpl", "{{ 'ho' ~ name < 10 }}")]).unwrap(); let mut context = Context::new(); - context.insert("name", &"john"); + context.insert("name", &"john").unwrap(); let result = tera.render("tpl", &context); diff --git a/src/renderer/tests/macros.rs b/src/renderer/tests/macros.rs index 7a86e667..1eb99c33 100644 --- a/src/renderer/tests/macros.rs +++ b/src/renderer/tests/macros.rs @@ -36,7 +36,7 @@ fn render_macros_defined_in_template() { #[test] fn render_macros_expression_arg() { let mut context = Context::new(); - context.insert("pages", &vec![1, 2, 3, 4, 5]); + context.insert("pages", &vec![1, 2, 3, 4, 5]).unwrap(); let mut tera = Tera::default(); tera.add_raw_templates(vec![ ("macros", "{% macro hello(val)%}{{val}}{% endmacro hello %}"), @@ -104,7 +104,7 @@ fn macro_param_arent_escaped() { ]) .unwrap(); let mut context = Context::new(); - context.insert("my_var", &"&"); + context.insert("my_var", &"&").unwrap(); let result = tera.render("hello.html", &context); assert_eq!(result.unwrap(), "&".to_string()); @@ -177,7 +177,7 @@ fn recursive_macro_with_loops() { numbers: vec![1, 2, 3], }; let mut context = Context::new(); - context.insert("objects", &vec![child]); + context.insert("objects", &vec![child]).unwrap(); let mut tera = Tera::default(); tera.add_raw_templates(vec![ diff --git a/src/renderer/tests/square_brackets.rs b/src/renderer/tests/square_brackets.rs index eb1e6577..8c4cff1e 100644 --- a/src/renderer/tests/square_brackets.rs +++ b/src/renderer/tests/square_brackets.rs @@ -14,12 +14,14 @@ struct Test { #[test] fn var_access_by_square_brackets() { let mut context = Context::new(); - context.insert( - "var", - &Test { a: "hi".into(), b: "i_am_actually_b".into(), c: vec!["fred".into()] }, - ); - context.insert("zero", &0); - context.insert("a", "b"); + context + .insert( + "var", + &Test { a: "hi".into(), b: "i_am_actually_b".into(), c: vec!["fred".into()] }, + ) + .unwrap(); + context.insert("zero", &0).unwrap(); + context.insert("a", "b").unwrap(); let mut map = HashMap::new(); map.insert("true", "yes"); @@ -28,9 +30,9 @@ fn var_access_by_square_brackets() { map.insert("with/slash", "works"); let mut deep_map = HashMap::new(); deep_map.insert("inner_map", &map); - context.insert("map", &map); - context.insert("deep_map", &deep_map); - context.insert("bool_vec", &vec!["true", "false"]); + context.insert("map", &map).unwrap(); + context.insert("deep_map", &deep_map).unwrap(); + context.insert("bool_vec", &vec!["true", "false"]).unwrap(); let inputs = vec![ ("{{var.a}}", "hi"), @@ -54,7 +56,7 @@ fn var_access_by_square_brackets() { #[test] fn var_access_by_square_brackets_errors() { let mut context = Context::new(); - context.insert("var", &Test { a: "hi".into(), b: "there".into(), c: vec![] }); + context.insert("var", &Test { a: "hi".into(), b: "there".into(), c: vec![] }).unwrap(); let t = Tera::one_off("{{var[csd]}}", &context, true); assert!(t.is_err(), "Access of csd should be impossible"); } @@ -98,10 +100,10 @@ fn var_access_by_loop_index_with_set() { #[test] fn can_get_value_if_key_contains_period() { let mut context = Context::new(); - context.insert("name", "Mt. Robson Provincial Park"); + context.insert("name", "Mt. Robson Provincial Park").unwrap(); let mut map = HashMap::new(); map.insert("Mt. Robson Provincial Park".to_string(), "hello".to_string()); - context.insert("tag_info", &map); + context.insert("tag_info", &map).unwrap(); let res = Tera::one_off(r#"{{ tag_info[name] }}"#, &context, true); assert!(res.is_ok()); diff --git a/src/renderer/tests/whitespace.rs b/src/renderer/tests/whitespace.rs index 92ab6244..7f154efc 100644 --- a/src/renderer/tests/whitespace.rs +++ b/src/renderer/tests/whitespace.rs @@ -4,7 +4,7 @@ use crate::tera::Tera; #[test] fn can_remove_whitespace_basic() { let mut context = Context::new(); - context.insert("numbers", &vec![1, 2, 3]); + context.insert("numbers", &vec![1, 2, 3]).unwrap(); let inputs = vec![ (" {%- for n in numbers %}{{n}}{% endfor -%} ", "123"), @@ -39,7 +39,7 @@ fn can_remove_whitespace_basic() { #[test] fn can_remove_whitespace_include() { let mut context = Context::new(); - context.insert("numbers", &vec![1, 2, 3]); + context.insert("numbers", &vec![1, 2, 3]).unwrap(); let inputs = vec![ (r#"Hi {%- include "include" -%} "#, "HiIncluded"), @@ -57,7 +57,7 @@ fn can_remove_whitespace_include() { #[test] fn can_remove_whitespace_macros() { let mut context = Context::new(); - context.insert("numbers", &vec![1, 2, 3]); + context.insert("numbers", &vec![1, 2, 3]).unwrap(); let inputs = vec![ (r#" {%- import "macros" as macros -%} {{macros::hey()}}"#, "Hey!"), @@ -79,7 +79,7 @@ fn can_remove_whitespace_macros() { #[test] fn can_remove_whitespace_inheritance() { let mut context = Context::new(); - context.insert("numbers", &vec![1, 2, 3]); + context.insert("numbers", &vec![1, 2, 3]).unwrap(); let inputs = vec![ (r#"{%- extends "base" -%} {% block content %}{{super()}}{% endblock %}"#, " Hey! "), @@ -102,7 +102,7 @@ fn can_remove_whitespace_inheritance() { #[test] fn works_with_filter_section() { let mut context = Context::new(); - context.insert("d", "d"); + context.insert("d", "d").unwrap(); let input = r#"{% filter upper %} {{ "c" }} d{% endfilter %}"#; let res = Tera::one_off(input, &context, true).unwrap(); assert_eq!(res, " C D"); @@ -111,7 +111,7 @@ fn works_with_filter_section() { #[test] fn make_sure_not_to_delete_whitespaces() { let mut context = Context::new(); - context.insert("d", "d"); + context.insert("d", "d").unwrap(); let input = r#"{% raw %} yaml_test: {% endraw %}"#; let res = Tera::one_off(input, &context, true).unwrap(); assert_eq!(res, " yaml_test: "); diff --git a/src/tera.rs b/src/tera.rs index 0c694f5c..51348288 100644 --- a/src/tera.rs +++ b/src/tera.rs @@ -392,6 +392,15 @@ impl Tera { renderer.render() } + /// Async version of [`render()`](Self::render). Note that the await points for ``render_async`` are + /// in between each ast parse + #[cfg(feature = "async")] + pub async fn render_async(&self, template_name: &str, context: &Context) -> Result { + let template = self.get_template(template_name)?; + let renderer = Renderer::new(template, self, context); + renderer.render_async().await + } + /// Renders a Tera template given a [`Context`] to something that implements [`Write`]. /// /// The only difference from [`render()`](Self::render) is that this version doesn't convert @@ -726,6 +735,7 @@ impl Tera { self.register_filter("as_str", common::as_str); self.register_filter("get", object::get); + self.register_filter("merge", object::merge); } fn register_tera_testers(&mut self) { @@ -751,7 +761,6 @@ impl Tera { self.register_function("throw", functions::throw); #[cfg(feature = "builtins")] self.register_function("get_random", functions::get_random); - self.register_function("get_env", functions::get_env); } /// Select which suffix(es) to automatically do HTML escaping on. @@ -1039,7 +1048,7 @@ mod tests { #[test] fn test_can_autoescape_one_off_template() { let mut context = Context::new(); - context.insert("greeting", &"

"); + context.insert("greeting", &"

").unwrap(); let result = Tera::one_off("{{ greeting }} world", &context, true).unwrap(); assert_eq!(result, "<p> world"); @@ -1048,7 +1057,7 @@ mod tests { #[test] fn test_can_disable_autoescape_one_off_template() { let mut context = Context::new(); - context.insert("greeting", &"

"); + context.insert("greeting", &"

").unwrap(); let result = Tera::one_off("{{ greeting }} world", &context, false).unwrap(); assert_eq!(result, "

world"); @@ -1076,7 +1085,7 @@ mod tests { tera.autoescape_on(vec!["foo"]); tera.set_escape_fn(escape_c_string); let mut context = Context::new(); - context.insert("content", &"Hello\n\'world\"!"); + context.insert("content", &"Hello\n\'world\"!").unwrap(); let result = tera.render("foo", &context).unwrap(); assert_eq!(result, r#""Hello\n\'world\"!""#); } @@ -1090,7 +1099,7 @@ mod tests { tera.set_escape_fn(no_escape); tera.reset_escape_fn(); let mut context = Context::new(); - context.insert("content", &"Hello\n\'world\"!"); + context.insert("content", &"Hello\n\'world\"!").unwrap(); let result = tera.render("foo", &context).unwrap(); assert_eq!(result, "Hello\n'world"!"); } @@ -1134,7 +1143,7 @@ mod tests { map.insert("https://example.com", "success"); let mut tera_context = Context::new(); - tera_context.insert("map", &map); + tera_context.insert("map", &map).unwrap(); my_tera.render("dots", &tera_context).unwrap(); my_tera.render("urls", &tera_context).unwrap(); diff --git a/tests/render_fails.rs b/tests/render_fails.rs index 0fd0e594..b22039a0 100644 --- a/tests/render_fails.rs +++ b/tests/render_fails.rs @@ -11,12 +11,12 @@ use crate::common::{Product, Review}; fn render_tpl(tpl_name: &str) -> Result { let tera = Tera::new("tests/render-failures/**/*").unwrap(); let mut context = Context::new(); - context.insert("product", &Product::new()); - context.insert("username", &"bob"); - context.insert("friend_reviewed", &true); - context.insert("number_reviews", &2); - context.insert("show_more", &true); - context.insert("reviews", &vec![Review::new(), Review::new()]); + context.insert("product", &Product::new()).unwrap(); + context.insert("username", &"bob").unwrap(); + context.insert("friend_reviewed", &true).unwrap(); + context.insert("number_reviews", &2).unwrap(); + context.insert("show_more", &true).unwrap(); + context.insert("reviews", &vec![Review::new(), Review::new()]).unwrap(); tera.render(tpl_name, &context) }