use std::collections::{VecDeque, HashMap};
use serde_json::map::Map;
use serde_json::to_string_pretty;
use serde_json::value::{Value, to_value, Number};
use context::{ValueRender, ValueNumber, ValueTruthy, get_json_pointer};
use template::Template;
use errors::{Result, ResultExt};
use parser::{Node, Operator};
use parser::Node::*;
use tera::Tera;
use utils::escape_html;
static MAGICAL_DUMP_VAR: &'static str = "__tera_context";
#[derive(PartialEq, Debug)]
enum ForLoopKind {
List,
KeyValue,
}
#[derive(Debug)]
struct ForLoop {
key_name: Option<String>,
value_name: String,
current: usize,
values: Vec<(Option<String>, Value)>,
kind: ForLoopKind,
}
impl ForLoop {
pub fn new_list(value_name: &str, values: Value) -> ForLoop {
let mut for_values = vec![];
for val in values.as_array().unwrap() {
for_values.push((None, val.clone()));
}
ForLoop {
key_name: None,
value_name: value_name.to_string(),
current: 0,
values: for_values,
kind: ForLoopKind::List,
}
}
pub fn new_key_value(key_name: String, value_name: &str, values: Value) -> ForLoop {
let mut for_values = vec![];
for (key, val) in values.as_object().unwrap() {
for_values.push((Some(key.clone()), val.clone()));
}
ForLoop {
key_name: Some(key_name),
value_name: value_name.to_string(),
current: 0,
values: for_values,
kind: ForLoopKind::KeyValue,
}
}
#[inline]
pub fn increment(&mut self) {
self.current += 1;
}
#[inline]
pub fn get_val(&self) -> Option<&Value> {
if let Some(v) = self.values.get(self.current) {
return Some(&v.1);
}
None
}
#[inline]
pub fn get_key(&self) -> String {
if let Some(v) = self.values.get(self.current) {
if let Some(ref k) = v.0 {
return k.clone();
}
}
unreachable!();
}
pub fn is_key(&self, name: &str) -> bool {
if self.kind == ForLoopKind::List {
return false;
}
if let Some(ref key_name) = self.key_name {
return key_name == name;
}
false
}
#[inline]
pub fn len(&self) -> usize {
self.values.len()
}
}
#[derive(Debug)]
pub struct Renderer<'a> {
template: &'a Template,
context: Value,
tera: &'a Tera,
for_loops: Vec<ForLoop>,
macros: Vec<HashMap<String, &'a HashMap<String, Node>>>,
macro_context: Vec<Value>,
macro_namespaces: Vec<String>,
should_escape: bool,
blocks: Vec<(String, usize)>,
}
impl<'a> Renderer<'a> {
pub fn new(tpl: &'a Template, tera: &'a Tera, context: Value) -> Renderer<'a> {
let should_escape = tera.autoescape_extensions.iter().any(|ext| {
if let Some(ref p) = tpl.path {
return p.ends_with(ext);
}
tpl.name.ends_with(ext)
});
Renderer {
template: tpl,
tera: tera,
context: context,
for_loops: vec![],
macros: vec![],
macro_context: vec![],
macro_namespaces: vec![],
should_escape: should_escape,
blocks: vec![],
}
}
fn lookup_variable(&self, key: &str) -> Result<Value> {
let context = match self.macro_context.last() {
Some(c) => c,
None => &self.context
};
if key == MAGICAL_DUMP_VAR {
return Ok(to_value(
to_string_pretty(context).expect("Couldn't serialize context for `__tera_context`")
)?);
}
#[inline]
fn find_variable(context: &Value, key: &str, tpl_name: &str) -> Result<Value> {
match context.pointer(&get_json_pointer(key)) {
Some(v) => Ok(v.clone()),
None => bail!("Field `{}` not found in context while rendering '{}'", key, tpl_name)
}
}
if self.for_loops.is_empty() {
return find_variable(context, key, &self.template.name);
}
for for_loop in self.for_loops.iter().rev() {
let (real_key, tail) = if let Some(tail_pos) = key.find('.') {
(&key[..tail_pos], &key[tail_pos+1..])
} else {
(key, "")
};
if real_key == for_loop.value_name {
let value = match for_loop.get_val() {
Some(f) => f,
None => { return Ok(to_value("").unwrap()); }
};
if tail.len() > 0 {
return find_variable(value, tail, &self.template.name)
.chain_err(|| format!("Variable lookup failed in forloop for `{}`", key));
} else {
return Ok(value.clone());
}
} else if real_key == "loop" {
match tail {
"index" => { return Ok(to_value(&(for_loop.current + 1))?); },
"index0" => { return Ok(to_value(&for_loop.current)?); },
"first" => { return Ok(to_value(&(for_loop.current == 0))?); },
"last" => { return Ok(to_value(&(for_loop.current == for_loop.len() - 1))?); },
_ => { bail!("Unknown loop subscript: {:?}", key); }
}
} else if for_loop.is_key(key) {
return Ok(to_value(&for_loop.get_key())?);
}
}
find_variable(context, key, &self.template.name)
}
fn eval_ident(&self, node: &Node) -> Result<Value> {
let (name, filters) = match *node {
Identifier { ref name, ref filters } => (name, filters),
_ => unreachable!(),
};
let mut value = self.lookup_variable(name)?;
let mut is_safe = false;
if let Some(ref _filters) = *filters {
for filter in _filters {
let (name, params) = match *filter {
Filter { ref name, ref params } => (name, params),
_ => unreachable!(),
};
if name == "safe" {
is_safe = true;
continue;
}
let filter_fn = self.tera.get_filter(name)?;
let mut all_args = HashMap::new();
for (arg_name, exp) in params {
all_args.insert(arg_name.to_string(), self.eval_expression(exp)?);
}
value = filter_fn(value, all_args)?;
}
}
if name != MAGICAL_DUMP_VAR && self.should_escape && !is_safe {
if let Value::String(s) = value {
value = to_value(escape_html(s.as_str()))?;
}
}
Ok(value)
}
fn eval_math(&self, node: &Node) -> Result<f64> {
match *node {
Identifier { ref name, .. } => {
self.eval_ident(node)?
.to_number()
.or_else(|_| Err(
format!(
"Variable `{}` was used in a math operation but is not a number", name
).into()
))
},
Int(s) => Ok(s as f64),
Float(s) => Ok(s),
Math { ref lhs, ref rhs, ref operator } => {
let l = self.eval_math(lhs)?;
let r = self.eval_math(rhs)?;
let result = match *operator {
Operator::Mul => l * r,
Operator::Div => l / r,
Operator::Add => l + r,
Operator::Sub => l - r,
_ => unreachable!()
};
Ok(result)
}
Text(ref s) => bail!("Tried to do math with a String: `{}`", s),
Bool(s) => bail!("Tried to do math with a boolean: `{}`", s),
_ => unreachable!()
}
}
fn eval_expression(&self, node: &Node) -> Result<Value> {
match node {
&Identifier { .. } => {
Ok(self.eval_ident(node)?)
},
&Logic { .. } => {
let value = self.eval_condition(node)?;
Ok(Value::Bool(value))
},
&Math { .. } => {
let result = self.eval_math(node)?;
Ok(Value::Number(Number::from_f64(result).unwrap()))
},
&Int(val) => {
Ok(Value::Number(val.into()))
},
&Float(val) => {
Ok(Value::Number(Number::from_f64(val).unwrap()))
},
&Bool(b) => {
Ok(Value::Bool(b))
},
&Text(ref t) => {
Ok(Value::String(t.to_string()))
},
_ => unreachable!()
}
}
fn eval_condition(&self, node: &Node) -> Result<bool> {
match node {
&Identifier { .. } => {
Ok(self.eval_ident(node).map(|v| v.is_truthy()).unwrap_or(false))
},
&Test { ref expression, ref name, ref params } => {
let tester = self.tera.get_tester(name)?;
let mut value_params = vec![];
for param in params {
value_params.push(self.eval_expression(param)?);
}
tester(self.eval_expression(expression).ok(), value_params)
},
&Logic { ref lhs, ref rhs, operator } => {
match operator {
Operator::Or => {
let result = self.eval_condition(lhs)? || self.eval_condition(rhs)?;
Ok(result)
},
Operator::And => {
let result = self.eval_condition(lhs)? && self.eval_condition(rhs)?;
Ok(result)
},
Operator::Gt | Operator::Gte | Operator::Lt | Operator::Lte => {
let l = self.eval_math(lhs)?;
let r = self.eval_math(rhs)?;
let result = match operator {
Operator::Gte => l >= r,
Operator::Gt => l > r,
Operator::Lte => l <= r,
Operator::Lt => l < r,
_ => unreachable!()
};
Ok(result)
},
Operator::Eq | Operator::NotEq => {
let mut lhs_val = self.eval_expression(lhs)?;
let mut rhs_val = self.eval_expression(rhs)?;
if lhs_val.is_number() || rhs_val.is_number() {
if !lhs_val.is_number() || !rhs_val.is_number() {
return Ok(false);
}
lhs_val = Value::Number(Number::from_f64(lhs_val.as_f64().unwrap()).unwrap());
rhs_val = Value::Number(Number::from_f64(rhs_val.as_f64().unwrap()).unwrap());
}
let result = match operator {
Operator::Eq => lhs_val == rhs_val,
Operator::NotEq => lhs_val != rhs_val,
_ => unreachable!()
};
Ok(result)
},
_ => unreachable!()
}
}
&Not(ref n) => {
Ok(self.eval_expression(n).map(|v| !v.is_truthy()).unwrap_or(true))
},
_ => unreachable!()
}
}
fn render_variable_block(&mut self, node: &Node) -> Result<String> {
match node {
&Identifier { .. } => Ok(self.eval_ident(node)?.render()),
&Math { .. } => Ok(self.eval_math(node)?.to_string()),
&Text(ref s) => Ok(s.to_string()),
_ => unreachable!()
}
}
fn render_if(&mut self, condition_nodes: &VecDeque<Node>, else_node: &Option<Box<Node>>) -> Result<String> {
let mut skip_else = false;
let mut output = String::new();
for node in condition_nodes {
match node {
&Conditional {ref condition, ref body } => {
if self.eval_condition(condition)? {
skip_else = true;
output.push_str(self.render_node(body)?.trim_left());
}
},
_ => unreachable!()
}
}
if !skip_else {
if let Some(ref e) = *else_node {
output.push_str(self.render_node(e)?.trim_left());
}
}
Ok(output.trim_right().to_string())
}
fn render_for(&mut self, key_name: &Option<String>, value_name: &str, container: &Node, body: &Node) -> Result<String> {
let container_name = match container {
&Node::Identifier {ref name, ..} => name,
_ => unreachable!()
};
let container_val = self.eval_ident(container)?;
if key_name.is_some() && !container_val.is_object() {
bail!("Tried to iterate using key value on variable `{}`, but it isn't an object/map", container_name);
} else if key_name.is_none() && !container_val.is_array() {
bail!("Tried to iterate on variable `{}`, but it isn't an array", container_name);
}
let for_loop = if container_val.is_array() {
ForLoop::new_list(value_name, container_val)
} else {
ForLoop::new_key_value(key_name.clone().expect("Failed to key name in loop"), value_name, container_val)
};
let length = for_loop.len();
self.for_loops.push(for_loop);
let mut output = String::new();
for _ in 0..length {
output.push_str(self.render_node(body)?.trim_left());
self.for_loops.last_mut().unwrap().increment();
}
self.for_loops.pop();
Ok(output.trim_right().to_string())
}
fn render_macro(&mut self, call_node: &Node) -> Result<String> {
let (namespace, macro_name, call_params) = match *call_node {
MacroCall { ref namespace, ref name, ref params } => (namespace, name, params),
_ => unreachable!("Got a node other than a MacroCall when rendering a macro"),
};
let active_namespace = match namespace.as_ref() {
"self" => {
self.macro_namespaces
.last()
.expect("Open an issue with a template sample please (mention `self namespace macro`)!")
.to_string()
},
_ => {
self.macro_namespaces.push(namespace.clone());
namespace.clone()
}
};
let macro_definition = self.macros
.last()
.and_then(|m| m.get(&active_namespace))
.and_then(|m| m.get(macro_name));
if let Some(&Macro { ref body, ref params, ..}) = macro_definition {
if params.len() != call_params.len() {
let params_seen = call_params.keys().cloned().collect::<Vec<String>>();
bail!("Macro `{}` got `{:?}` for args but was expecting `{:?}` (order does not matter)", macro_name, params, params_seen);
}
let mut context = Map::new();
for (param_name, exp) in call_params {
if !params.contains(param_name) {
let params_seen = call_params.keys().cloned().collect::<Vec<String>>();
bail!("Macro `{}` got `{:?}` for args but was expecting `{:?}` (order does not matter)", macro_name, params, params_seen);
}
context.insert(param_name.to_string(), self.eval_expression(exp)?);
}
self.macro_context.push(context.into());
let mut output = String::new();
for node in body.get_children() {
output.push_str(&self.render_node(node)?);
}
if namespace == &active_namespace {
self.macro_namespaces.pop();
}
self.macro_context.pop();
Ok(output.trim().to_string())
} else {
bail!("Macro `{}` was not found in the namespace `{}`", macro_name, active_namespace);
}
}
fn import_macros(&mut self, tpl_name: &str) -> Result<bool> {
let tpl = self.tera.get_template(tpl_name)?;
if tpl.imported_macro_files.is_empty() {
return Ok(false);
}
let mut map = HashMap::new();
for &(ref filename, ref namespace) in &tpl.imported_macro_files {
let macro_tpl = self.tera.get_template(filename)?;
map.insert(namespace.to_string(), ¯o_tpl.macros);
}
self.macros.push(map);
Ok(true)
}
pub fn render_node(&mut self, node: &Node) -> Result<String> {
match node {
&Include(ref p) => {
let ast = self.tera.get_template(p)?.ast.get_children();
let mut output = String::new();
for node in ast {
output.push_str(&self.render_node(node)?);
}
Ok(output.trim().to_string())
},
&ImportMacro {ref tpl_name, ref name} => {
let tpl = self.tera.get_template(tpl_name)?;
let mut map = if self.macros.is_empty() {
HashMap::new()
} else {
self.macros.pop().unwrap()
};
map.insert(name.to_string(), &tpl.macros);
self.macros.push(map);
Ok("".to_string())
},
&MacroCall {..} => self.render_macro(node),
&Text(ref s) => Ok(s.to_string()),
&Raw(ref s) => Ok(s.trim().to_string()),
&FilterSection {ref name, ref params, ref body} => {
let filter_fn = self.tera.get_filter(name)?;
let mut all_args = HashMap::new();
for (arg_name, exp) in params {
all_args.insert(arg_name.to_string(), self.eval_expression(exp)?);
}
let value = self.render_node(body)?;
match filter_fn(Value::String(value), all_args)? {
Value::String(s) => Ok(s),
val => Ok(val.render())
}
},
&VariableBlock(ref exp) => self.render_variable_block(exp),
&If {ref condition_nodes, ref else_node} => {
self.render_if(condition_nodes, else_node)
},
&List(ref body) => {
let mut output = String::new();
for n in body {
output.push_str(&self.render_node(n)?);
}
Ok(output)
},
&For {ref key, ref value, ref container, ref body} => {
self.render_for(key, value, container, body)
},
&Block {ref name, ref body} => {
match self.template.blocks_definitions.get(name) {
Some(b) => {
match &b[0] {
&(ref tpl_name, Block { ref body, ..}) => {
self.blocks.push((name.clone(), 0));
let has_macro = self.import_macros(tpl_name)?;
let res = self.render_node(body);
if has_macro {
self.macros.pop();
}
res
},
x => unreachable!("render_node Block {:?}", x)
}
},
None => {
self.render_node(body)
}
}
},
&Super => {
if let Some((name, level)) = self.blocks.pop() {
let new_level = level + 1;
match self.template.blocks_definitions.get(&name) {
Some(b) => {
match &b[new_level] {
&(ref tpl_name, Block { ref body, .. }) => {
self.blocks.push((name, new_level));
let has_macro = self.import_macros(tpl_name)?;
let res = self.render_node(body);
if has_macro {
self.macros.pop();
}
if new_level == b.len() - 1 {
self.blocks.pop();
}
res
},
x => unreachable!("render_node Block {:?}", x)
}
},
None => unreachable!("render_node -> didn't get block")
}
} else {
unreachable!("Super called outside of a block or in base template")
}
},
&Extends(_) | &Macro {..} => Ok("".to_string()),
x => unreachable!("render_node -> unexpected node: {:?}", x)
}
}
fn get_error_location(&self) -> String {
let mut error_location = format!("Failed to render '{}'", self.template.name);
if let Some(macro_namespace) = self.macro_namespaces.last() {
error_location += &format!(": error while rendering a macro from the `{}` namespace", macro_namespace);
}
if let Some(&(ref name, ref level)) = self.blocks.last() {
let block_def = self.template.blocks_definitions
.get(name)
.and_then(|b| b.get(*level));
if let Some(&(ref tpl_name, _)) = block_def {
if tpl_name != &self.template.name {
error_location += &format!(" (error happened in '{}').", tpl_name);
}
}
} else if let Some(parent) = self.template.parents.last() {
error_location += &format!(" (error happened in '{}').", parent);
}
error_location
}
pub fn render(&mut self) -> Result<String> {
let ast = if !self.template.parents.is_empty() {
let parent = self.tera.get_template(
self.template.parents.last().expect("Couldn't get first ancestor template")
).chain_err(|| format!("Failed to render '{}'", self.template.name))?;
parent.ast.get_children()
} else {
self.template.ast.get_children()
};
let mut output = String::new();
for node in ast {
output.push_str(
&self.render_node(node).chain_err(|| self.get_error_location())?
);
}
Ok(output)
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use context::Context;
use errors::Result;
use tera::Tera;
fn render_template(content: &str, context: Context) -> Result<String> {
let mut tera = Tera::default();
tera.add_raw_template("hello", content).unwrap();
tera.render("hello", &context)
}
#[test]
fn test_render_include() {
let mut tera = Tera::default();
tera.add_raw_template("world", "world").unwrap();
tera.add_raw_template("hello", "<h1>Hello {% include \"world\" %}</h1>").unwrap();
let result = tera.render("hello", &Context::new());
assert_eq!(result.unwrap(), "<h1>Hello world</h1>".to_owned());
}
#[test]
fn test_render_simple_string() {
let result = render_template("<h1>Hello world</h1>", Context::new());
assert_eq!(result.unwrap(), "<h1>Hello world</h1>".to_owned());
}
#[test]
fn test_render_math() {
let tests = vec![
("{{ 1 + 1 }}", "2".to_string()),
("{{ 1 + 1.1 }}", "2.1".to_string()),
("{{ 3 - 1 }}", "2".to_string()),
("{{ 3 - 1.1 }}", "1.9".to_string()),
("{{ 2 * 5 }}", "10".to_string()),
("{{ 10 / 5 }}", "2".to_string()),
("{{ 2.1 * 5 }}", "10.5".to_string()),
("{{ 2.1 * 5.05 }}", "10.605".to_string()),
("{{ 2 / 0.5 }}", "4".to_string()),
("{{ 2.1 / 0.5 }}", "4.2".to_string()),
];
for (input, expected) in tests {
assert_eq!(render_template(input, Context::new()).unwrap(), expected);
}
}
#[test]
fn test_render_basic_variable() {
let mut context = Context::new();
context.add("name", &"Vincent");
let result = render_template("My name is {{ name }}.", context);
assert_eq!(result.unwrap(), "My name is Vincent.".to_owned());
}
#[test]
fn test_render_math_with_variable() {
let mut context = Context::new();
context.add("vat_rate", &0.20);
let result = render_template("Vat: £{{ 100 * vat_rate }}.", context);
assert_eq!(result.unwrap(), "Vat: £20.".to_owned());
}
#[test]
fn test_render_comment() {
let result = render_template("<h1>Hello {# comment #} world</h1>", Context::new());
assert_eq!(result.unwrap(), "<h1>Hello world</h1>".to_owned());
}
#[test]
fn test_render_nested_comment() {
let result = render_template("<h1>Hello {# comment {# nested #} world</h1>", Context::new());
assert_eq!(result.unwrap(), "<h1>Hello world</h1>".to_owned());
}
#[test]
fn test_ignore_variable_in_comment() {
let mut context = Context::new();
context.add("name", &"Vincent");
let result = render_template("My name {# was {{ name }} #} is No One.", context);
assert_eq!(result.unwrap(), "My name is No One.".to_owned());
}
#[test]
fn test_render_if_simple() {
let mut context = Context::new();
context.add("is_admin", &true);
let result = render_template("{% if is_admin %}Admin{% endif %}", context);
assert_eq!(result.unwrap(), "Admin".to_owned());
}
#[test]
fn test_render_if_or_conditions() {
let mut context = Context::new();
context.add("is_adult", &false);
context.add("age", &18);
let result = render_template("{% if is_adult or age + 1 > 18 %}Adult{% endif %}", context);
assert_eq!(result.unwrap(), "Adult".to_owned());
}
#[test]
fn test_render_if_and_conditions_with_equality() {
let mut context = Context::new();
context.add("is_adult", &true);
context.add("age", &18);
let result = render_template("{% if is_adult and age == 18 %}Adult{% endif %}", context);
assert_eq!(result.unwrap(), "Adult".to_owned());
}
#[test]
fn test_render_basic_for() {
let mut context = Context::new();
context.add("data", &vec![1,2,3]);
let result = render_template("{% for i in data %}{{i}}{% endfor %}", context);
assert_eq!(result.unwrap(), "123".to_owned());
}
#[test]
fn test_render_key_value_for() {
let mut context = Context::new();
let mut map = BTreeMap::new();
map.insert("name", "bob");
map.insert("age", "18");
context.add("data", &map);
let result = render_template("{% for key, val in data %}{{key}}:{{val}} {% endfor %}", context);
assert_eq!(result.unwrap(), "age:18 name:bob".to_owned());
}
#[test]
fn test_render_loop_variables() {
let mut context = Context::new();
context.add("data", &vec![1,2,3]);
let result = render_template(
"{% for i in data %}{{loop.index}}{{loop.index0}}{{loop.first}}{{loop.last}}{% endfor %}",
context
);
assert_eq!(result.unwrap(), "10truefalse21falsefalse32falsetrue".to_owned());
}
#[test]
fn test_render_nested_loop_simple() {
let mut context = Context::new();
context.add("vectors", &vec![vec![0, 3, 6], vec![1, 4, 7]]);
let result = render_template(
"{% for vector in vectors %}{% for j in vector %}{{ j }}{% endfor %}{% endfor %}",
context
);
assert_eq!(result.unwrap(), "036147".to_owned());
}
#[test]
fn test_render_nested_loop_with_empty_vec() {
let mut context = Context::new();
context.add("vectors", &vec![vec![0, 3, 6], vec![], vec![1, 4, 7]]);
let result = render_template(
"{% for vector in vectors %}{% for j in vector %}{{ j }}{% endfor %}{% endfor %}",
context
);
assert_eq!(result.unwrap(), "036147".to_owned());
}
#[test]
fn test_render_filter() {
let mut context = Context::new();
context.add("greeting", &"hello");
let result = render_template(
"{{ greeting | upper }}",
context
);
assert_eq!(result.unwrap(), "HELLO".to_owned());
}
#[test]
fn test_render_filter_section() {
let context = Context::new();
let result = render_template(
"{% filter upper %}Hello{% endfilter %}",
context
);
assert_eq!(result.unwrap(), "HELLO".to_owned());
}
#[test]
fn test_render_string_in_variable_braces() {
let context = Context::new();
let result = render_template(r#"{{ "{{ hey }}" }}"#, context);
assert_eq!(result.unwrap(), "{{ hey }}".to_owned());
}
#[test]
fn test_render_index_array() {
let mut context = Context::new();
context.add("my_arr", &vec![1, 2, 3]);
context.add("my_arr2", &vec![(1,2,3), (1,2,3), (1,2,3)]);
let result = render_template(
"{{ my_arr.1 }}{{ my_arr2.1.1 }}",
context
);
assert_eq!(result.unwrap(), "22".to_owned());
}
#[test]
fn test_render_if_in_for() {
let mut context = Context::new();
context.add("sel", &2u32);
context.add("seq", &vec![1,2,3]);
let result = render_template(
"{% for val in seq %} {% if val == sel %} on {% else %} off {% endif %} {% endfor %}",
context
);
assert_eq!(result.unwrap(), "off on off".to_string());
}
#[test]
fn test_autoescape_html() {
let mut context = Context::new();
context.add("bad", &"<script>alert('pwnd');</script>");
let mut tera = Tera::default();
tera.add_raw_template("hello.html", "{{bad}}").unwrap();
let result = tera.render("hello.html", &context);
assert_eq!(result.unwrap(), "<script>alert('pwnd');</script>".to_string());
}
#[test]
fn test_no_autoescape_on_extensions_not_specified() {
let mut context = Context::new();
context.add("bad", &"<script>alert('pwnd');</script>");
let mut tera = Tera::default();
tera.add_raw_template("hello.sql", "{{bad}}").unwrap();
let result = tera.render("hello.sql", &context);
assert_eq!(result.unwrap(), "<script>alert('pwnd');</script>".to_string());
}
#[test]
fn test_no_autoescape_with_safe_filter() {
let mut context = Context::new();
context.add("bad", &"<script>alert('pwnd');</script>");
let mut tera = Tera::default();
tera.add_raw_template("hello.html", "{{ bad | safe }}").unwrap();
let result = tera.render("hello.html", &context);
assert_eq!(result.unwrap(), "<script>alert('pwnd');</script>".to_string());
}
#[test]
fn test_render_super_multiple_inheritance() {
let mut tera = Tera::default();
tera.add_raw_templates(vec![
("grandparent", "{% block hey %}hello{% endblock hey %} {% block ending %}sincerely{% endblock ending %}"),
("parent", "{% extends \"grandparent\" %}{% block hey %}hi and grandma says {{ super() }}{% endblock hey %}"),
("child", "{% extends \"parent\" %}{% block hey %}dad says {{ super() }}{% endblock hey %}{% block ending %}{{ super() }} with love{% endblock ending %}"),
]).unwrap();
let result = tera.render("child", &Context::new());
assert_eq!(result.unwrap(), "dad says hi and grandma says hello sincerely with love".to_string());
}
#[test]
fn test_render_super_multiple_inheritance_nested_block() {
let mut tera = Tera::default();
tera.add_raw_templates(vec![
("grandparent", "{% block hey %}hello{% endblock hey %}"),
("parent", "{% extends \"grandparent\" %}{% block hey %}hi and grandma says {{ super() }} {% block ending %}sincerely{% endblock ending %}{% endblock hey %}"),
("child", "{% extends \"parent\" %}{% block hey %}dad says {{ super() }}{% endblock hey %}{% block ending %}{{ super() }} with love{% endblock ending %}"),
]).unwrap();
let result = tera.render("child", &Context::new());
assert_eq!(result.unwrap(), "dad says hi and grandma says hello sincerely with love".to_string());
}
#[test]
fn test_render_macros() {
let mut tera = Tera::default();
tera.add_raw_templates(vec![
("macros", "{% macro hello()%}Hello{% endmacro hello %}"),
("tpl", "{% import \"macros\" as macros %}{% block hey %}{{macros::hello()}}{% endblock hey %}"),
]).unwrap();
let result = tera.render("tpl", &Context::new());
assert_eq!(result.unwrap(), "Hello".to_string());
}
#[test]
fn test_render_macros_in_child_templates_same_namespace() {
let mut tera = Tera::default();
tera.add_raw_templates(vec![
("grandparent", "{% block hey %}hello{% endblock hey %}"),
("macros", "{% macro hello()%}Hello{% endmacro hello %}"),
("macros2", "{% macro hi()%}Hi{% endmacro hi %}"),
("parent", "{% extends \"grandparent\" %}{% import \"macros\" as macros %}{% block hey %}{{macros::hello()}}{% endblock hey %}"),
("child", "{% extends \"parent\" %}{% import \"macros2\" as macros %}{% block hey %}{{super()}}/{{macros::hi()}}{% endblock hey %}"),
]).unwrap();
let result = tera.render("child", &Context::new());
assert_eq!(result.unwrap(), "Hello/Hi".to_string());
}
#[test]
fn test_render_macros_in_child_templates_different_namespace() {
let mut tera = Tera::default();
tera.add_raw_templates(vec![
("grandparent", "{% block hey %}hello{% endblock hey %}"),
("macros", "{% macro hello()%}Hello{% endmacro hello %}"),
("macros2", "{% macro hi()%}Hi{% endmacro hi %}"),
("parent", "{% extends \"grandparent\" %}{% import \"macros\" as macros %}{% block hey %}{{macros::hello()}}{% endblock hey %}"),
("child", "{% extends \"parent\" %}{% import \"macros2\" as macros2 %}{% block hey %}{{super()}}/{{macros2::hi()}}{% endblock hey %}"),
]).unwrap();
let result = tera.render("child", &Context::new());
assert_eq!(result.unwrap(), "Hello/Hi".to_string());
}
#[test]
fn test_render_macros_in_parent_template_with_inheritance() {
let mut tera = Tera::default();
tera.add_raw_templates(vec![
("macros", "{% macro hello()%}Hello{% endmacro hello %}"),
("grandparent", "{% import \"macros\" as macros %}{% block hey %}{{macros::hello()}}{% endblock hey %}"),
("child", "{% extends \"grandparent\" %}{% import \"macros\" as macros %}{% block hey %}{{super()}}/{{macros::hello()}}{% endblock hey %}"),
]).unwrap();
let result = tera.render("child", &Context::new());
assert_eq!(result.unwrap(), "Hello/Hello".to_string());
}
#[test]
fn test_render_not_condition_simple_value_exists() {
let mut context = Context::new();
context.add("logged_in", &false);
let mut tera = Tera::default();
tera.add_raw_template("hello.html", "{% if not logged_in %}Login{% endif %}").unwrap();
let result = tera.render("hello.html", &context);
assert_eq!(result.unwrap(), "Login".to_string());
}
#[test]
fn test_render_not_condition_simple_value_does_not_exist() {
let mut tera = Tera::default();
tera.add_raw_template("hello.html", "{% if not logged_in %}Login{% endif %}").unwrap();
let result = tera.render("hello.html", &Context::new());
assert_eq!(result.unwrap(), "Login".to_string());
}
#[test]
fn test_render_not_complex_condition_and() {
let mut context = Context::new();
context.add("logged_in", &false);
context.add("active", &true);
let mut tera = Tera::default();
tera.add_raw_template("hello.html", "{% if not logged_in and active %}Login{% endif %}").unwrap();
let result = tera.render("hello.html", &context);
assert_eq!(result.unwrap(), "Login".to_string());
}
#[test]
fn test_render_not_complex_condition_or() {
let mut context = Context::new();
context.add("number_users", &11);
context.add("active", &true);
let mut tera = Tera::default();
tera.add_raw_template("hello.html", "{% if not active or number_users > 10 %}Login{% endif %}").unwrap();
let result = tera.render("hello.html", &context);
assert_eq!(result.unwrap(), "Login".to_string());
}
#[test]
fn test_error_location_basic() {
let mut tera = Tera::default();
tera.add_raw_templates(vec![
("tpl", "{{ 1 + true }}"),
]).unwrap();
let result = tera.render("tpl", &Context::new());
assert_eq!(
result.unwrap_err().iter().nth(0).unwrap().description(),
"Failed to render \'tpl\'"
);
}
#[test]
fn test_error_location_inside_macro() {
let mut tera = Tera::default();
tera.add_raw_templates(vec![
("macros", "{% macro hello()%}{{ 1 + true }}{% endmacro hello %}"),
("tpl", "{% import \"macros\" as macros %}{{ macro::hello() }}"),
]).unwrap();
let result = tera.render("tpl", &Context::new());
assert_eq!(
result.unwrap_err().iter().nth(0).unwrap().description(),
"Failed to render \'tpl\': error while rendering a macro from the `macro` namespace"
);
}
#[test]
fn test_error_location_base_template() {
let mut tera = Tera::default();
tera.add_raw_templates(vec![
("parent", "Hello {{ greeting + 1}} {% block bob %}{% endblock bob %}"),
("child", "{% extends \"parent\" %}{% block bob %}Hey{% endblock bob %}"),
]).unwrap();
let result = tera.render("child", &Context::new());
assert_eq!(
result.unwrap_err().iter().nth(0).unwrap().description(),
"Failed to render \'child\' (error happened in 'parent')."
);
}
#[test]
fn test_error_location_in_parent_block() {
let mut tera = Tera::default();
tera.add_raw_templates(vec![
("parent", "Hello {{ greeting }} {% block bob %}{{ 1 + true }}{% endblock bob %}"),
("child", "{% extends \"parent\" %}{% block bob %}{{ super() }}Hey{% endblock bob %}"),
]).unwrap();
let result = tera.render("child", &Context::new());
assert_eq!(
result.unwrap_err().iter().nth(0).unwrap().description(),
"Failed to render \'child\' (error happened in 'parent')."
);
}
#[test]
fn test_error_location_in_parent_in_macro() {
let mut tera = Tera::default();
tera.add_raw_templates(vec![
("macros", "{% macro hello()%}{{ 1 + true }}{% endmacro hello %}"),
("parent", "{% import \"macros\" as macros %}{{ macro::hello() }}{% block bob %}{% endblock bob %}"),
("child", "{% extends \"parent\" %}{% block bob %}{{ super() }}Hey{% endblock bob %}"),
]).unwrap();
let result = tera.render("child", &Context::new());
assert_eq!(
result.unwrap_err().iter().nth(0).unwrap().description(),
"Failed to render \'child\': error while rendering a macro from the `macro` namespace (error happened in \'parent\')."
);
}
}