tinytemplate_async/
template.rs

1//! This module implements the bytecode interpreter that actually renders the templates.
2
3use compiler::TemplateCompiler;
4use error::Error::*;
5use error::*;
6use instruction::{Instruction, PathSlice, PathStep};
7use serde_json::Value;
8use std::collections::HashMap;
9use std::fmt::Write;
10use std::slice;
11use std::sync::Arc;
12use ValueFormatter;
13
14/// Enum defining the different kinds of records on the context stack.
15enum ContextElement<'render> {
16    /// Object contexts shadow everything below them on the stack, because every name is looked up
17    /// in this object.
18    Object(&'render Value),
19    /// Named contexts shadow only one name. Any path that starts with that name is looked up in
20    /// this object, and all others are passed on down the stack.
21    Named(String, &'render Value),
22    /// Iteration contexts shadow one name with the current value of the iteration. They also
23    /// store the iteration state. The two usizes are the index of the current value and the length
24    /// of the array that we're iterating over.
25    Iteration(
26        String,
27        &'render Value,
28        usize,
29        usize,
30        slice::Iter<'render, Value>,
31    ),
32}
33
34/// Helper struct which mostly exists so that I have somewhere to put functions that access the
35/// rendering context stack.
36struct RenderContext<'render> {
37    original_text: String,
38    context_stack: Vec<ContextElement<'render>>,
39}
40impl<'render> RenderContext<'render> {
41    /// Look up the given path in the context stack and return the value (if found) or an error (if
42    /// not)
43    fn lookup(&self, path: PathSlice) -> Result<&'render Value> {
44        for stack_layer in self.context_stack.iter().rev() {
45            match stack_layer {
46                ContextElement::Object(obj) => return self.lookup_in(path, obj),
47                ContextElement::Named(name, obj) => {
48                    if *name == &*path[0] {
49                        return self.lookup_in(&path[1..], obj);
50                    }
51                }
52                ContextElement::Iteration(name, obj, _, _, _) => {
53                    if *name == &*path[0] {
54                        return self.lookup_in(&path[1..], obj);
55                    }
56                }
57            }
58        }
59        panic!("Attempted to do a lookup with an empty context stack. That shouldn't be possible.")
60    }
61
62    /// Look up a path within a given value object and return the resulting value (if found) or
63    /// an error (if not)
64    fn lookup_in(&self, path: PathSlice, object: &'render Value) -> Result<&'render Value> {
65        let mut current = object;
66        for step in path.iter() {
67            if let PathStep::Index(_, n) = step {
68                if let Some(next) = current.get(n) {
69                    current = next;
70                    continue;
71                }
72            }
73
74            let step: &str = &*step;
75
76            match current.get(step) {
77                Some(next) => current = next,
78                None => {
79                    return Err(lookup_error(
80                        self.original_text.as_str(),
81                        step,
82                        path,
83                        current,
84                    ))
85                }
86            }
87        }
88        Ok(current)
89    }
90
91    /// Look up the index and length values for the top iteration context on the stack.
92    fn lookup_index(&self) -> Result<(usize, usize)> {
93        for stack_layer in self.context_stack.iter().rev() {
94            match stack_layer {
95                ContextElement::Iteration(_, _, index, length, _) => return Ok((*index, *length)),
96                _ => continue,
97            }
98        }
99        Err(GenericError {
100            msg: "Used @index outside of a foreach block.".to_string(),
101        })
102    }
103
104    /// Look up the root context object
105    fn lookup_root(&self) -> Result<&'render Value> {
106        match self.context_stack.get(0) {
107            Some(ContextElement::Object(obj)) => Ok(obj),
108            Some(_) => {
109                panic!("Expected Object value at root of context stack, but was something else.")
110            }
111            None => panic!(
112                "Attempted to do a lookup with an empty context stack. That shouldn't be possible."
113            ),
114        }
115    }
116}
117
118/// Structure representing a parsed template. It holds the bytecode program for rendering the
119/// template as well as the length of the original template string, which is used as a guess to
120/// pre-size the output string buffer.
121pub(crate) struct Template {
122    original_text: String,
123    instructions: Vec<Instruction>,
124    template_len: usize,
125}
126impl Template {
127    /// Create a Template from the given template string.
128    pub fn compile(text: String) -> Result<Template> {
129        Ok(Template {
130            original_text: text.clone(),
131            template_len: text.len(),
132            instructions: TemplateCompiler::new(text).compile()?,
133        })
134    }
135
136    /// Render this template into a string and return it (or any error if one is encountered).
137    pub fn render(
138        &self,
139        context: &Value,
140        template_registry: &HashMap<String, Template>,
141        formatter_registry: &HashMap<String, Box<ValueFormatter>>,
142        default_formatter: Arc<ValueFormatter>,
143    ) -> Result<String> {
144        // The length of the original template seems like a reasonable guess at the length of the
145        // output.
146        let mut output = String::with_capacity(self.template_len);
147        self.render_into(
148            context,
149            template_registry,
150            formatter_registry,
151            default_formatter,
152            &mut output,
153        )?;
154        Ok(output)
155    }
156
157    /// Render this template into a given string. Used for calling other templates.
158    pub fn render_into(
159        &self,
160        context: &Value,
161        template_registry: &HashMap<String, Template>,
162        formatter_registry: &HashMap<String, Box<ValueFormatter>>,
163        default_formatter: Arc<ValueFormatter>,
164        output: &mut String,
165    ) -> Result<()> {
166        let mut program_counter = 0;
167        let mut render_context = RenderContext {
168            original_text: self.original_text.clone(),
169            context_stack: vec![ContextElement::Object(context)],
170        };
171
172        while program_counter < self.instructions.len() {
173            match &self.instructions[program_counter] {
174                Instruction::Literal(text) => {
175                    output.push_str(text);
176                    program_counter += 1;
177                }
178                Instruction::Value(path) => {
179                    let first = path.first().unwrap();
180                    if first.starts_with('@') {
181                        // Currently we just hard-code the special @-keywords and have special
182                        // lookup functions to use them because there are lifetime complexities with
183                        // looking up values that don't live for as long as the given context object.
184                        let first: &str = &*first;
185                        match first {
186                            "@index" => {
187                                write!(output, "{}", render_context.lookup_index()?.0).unwrap()
188                            }
189                            "@first" => {
190                                write!(output, "{}", render_context.lookup_index()?.0 == 0).unwrap()
191                            }
192                            "@last" => {
193                                let (index, length) = render_context.lookup_index()?;
194                                write!(output, "{}", index == length - 1).unwrap()
195                            }
196                            "@root" => {
197                                let value_to_render = render_context.lookup_root()?;
198                                default_formatter(value_to_render, output)?;
199                            }
200                            _ => panic!(), // This should have been caught by the parser.
201                        }
202                    } else {
203                        let value_to_render = render_context.lookup(path)?;
204                        default_formatter(value_to_render, output)?;
205                    }
206                    program_counter += 1;
207                }
208                Instruction::FormattedValue(path, name) => {
209                    // The @ keywords aren't supported for formatted values. Should they be?
210                    let value_to_render = render_context.lookup(path)?;
211                    match formatter_registry.get(name) {
212                        Some(formatter) => {
213                            let formatter_result = formatter(value_to_render, output);
214                            if let Err(err) = formatter_result {
215                                return Err(called_formatter_error(
216                                    self.original_text.as_str(),
217                                    name,
218                                    err,
219                                ));
220                            }
221                        }
222                        None => return Err(unknown_formatter(self.original_text.as_str(), name)),
223                    }
224                    program_counter += 1;
225                }
226                Instruction::Branch(path, negate, target) => {
227                    let first = path.first().unwrap();
228                    let mut truthy = if first.starts_with('@') {
229                        let first: &str = &*first;
230                        match &*first {
231                            "@index" => render_context.lookup_index()?.0 != 0,
232                            "@first" => render_context.lookup_index()?.0 == 0,
233                            "@last" => {
234                                let (index, length) = render_context.lookup_index()?;
235                                index == (length - 1)
236                            }
237                            "@root" => self.value_is_truthy(render_context.lookup_root()?, path)?,
238                            other => panic!("Unknown keyword {}", other), // This should have been caught by the parser.
239                        }
240                    } else {
241                        let value_to_render = render_context.lookup(path)?;
242                        self.value_is_truthy(value_to_render, path)?
243                    };
244                    if *negate {
245                        truthy = !truthy;
246                    }
247
248                    if truthy {
249                        program_counter = *target;
250                    } else {
251                        program_counter += 1;
252                    }
253                }
254                Instruction::PushNamedContext(path, name) => {
255                    let context_value = render_context.lookup(path)?;
256                    render_context
257                        .context_stack
258                        .push(ContextElement::Named(name.clone(), context_value));
259                    program_counter += 1;
260                }
261                Instruction::PushIterationContext(path, name) => {
262                    // We push a context with an invalid index and no value and then wait for the
263                    // following Iterate instruction to set the index and value properly.
264                    let first = path.first().unwrap();
265                    let context_value = match first {
266                        PathStep::Name(x) if x.as_str() == "@root" => {
267                            render_context.lookup_root()?
268                        }
269                        PathStep::Name(other) if other.starts_with('@') => {
270                            return Err(not_iterable_error(self.original_text.as_str(), path))
271                        }
272                        _ => render_context.lookup(path)?,
273                    };
274                    match context_value {
275                        Value::Array(ref arr) => {
276                            render_context.context_stack.push(ContextElement::Iteration(
277                                name.clone(),
278                                &Value::Null,
279                                ::std::usize::MAX,
280                                arr.len(),
281                                arr.iter(),
282                            ))
283                        }
284                        _ => return Err(not_iterable_error(self.original_text.as_str(), path)),
285                    };
286                    program_counter += 1;
287                }
288                Instruction::PopContext => {
289                    render_context.context_stack.pop();
290                    program_counter += 1;
291                }
292                Instruction::Goto(target) => {
293                    program_counter = *target;
294                }
295                Instruction::Iterate(target) => {
296                    match render_context.context_stack.last_mut() {
297                        Some(ContextElement::Iteration(_, val, index, _, iter)) => {
298                            match iter.next() {
299                                Some(new_val) => {
300                                    *val = new_val;
301                                    // On the first iteration, this will be usize::MAX so it will
302                                    // wrap around to zero.
303                                    *index = index.wrapping_add(1);
304                                    program_counter += 1;
305                                }
306                                None => {
307                                    program_counter = *target;
308                                }
309                            }
310                        }
311                        _ => panic!("Malformed program."),
312                    };
313                }
314                Instruction::Call(template_name, path) => {
315                    let context_value = render_context.lookup(path)?;
316                    match template_registry.get(template_name) {
317                        Some(templ) => {
318                            let called_templ_result = templ.render_into(
319                                context_value,
320                                template_registry,
321                                formatter_registry,
322                                default_formatter.clone(),
323                                output,
324                            );
325                            if let Err(err) = called_templ_result {
326                                return Err(called_template_error(
327                                    self.original_text.as_str(),
328                                    template_name,
329                                    err,
330                                ));
331                            }
332                        }
333                        None => {
334                            return Err(unknown_template(
335                                self.original_text.as_str(),
336                                template_name,
337                            ))
338                        }
339                    }
340                    program_counter += 1;
341                }
342            }
343        }
344        Ok(())
345    }
346
347    fn value_is_truthy(&self, value: &Value, path: PathSlice) -> Result<bool> {
348        let truthy = match value {
349            Value::Null => false,
350            Value::Bool(b) => *b,
351            Value::Number(n) => match n.as_f64() {
352                Some(float) => float != 0.0,
353                None => {
354                    return Err(truthiness_error(self.original_text.as_str(), path));
355                }
356            },
357            Value::String(s) => !s.is_empty(),
358            Value::Array(arr) => !arr.is_empty(),
359            Value::Object(_) => true,
360        };
361        Ok(truthy)
362    }
363}
364
365#[cfg(test)]
366mod test {
367    use super::*;
368    use compiler::TemplateCompiler;
369
370    fn compile(text: &'static str) -> Template {
371        Template {
372            original_text: text.to_string(),
373            template_len: text.len(),
374            instructions: TemplateCompiler::new(text.to_string()).compile().unwrap(),
375        }
376    }
377
378    #[derive(Serialize)]
379    struct NestedContext {
380        value: usize,
381    }
382
383    #[derive(Serialize)]
384    struct TestContext {
385        number: usize,
386        string: &'static str,
387        boolean: bool,
388        null: Option<usize>,
389        array: Vec<usize>,
390        nested: NestedContext,
391        escapes: &'static str,
392    }
393
394    fn context() -> Value {
395        let ctx = TestContext {
396            number: 5,
397            string: "test",
398            boolean: true,
399            null: None,
400            array: vec![1, 2, 3],
401            nested: NestedContext { value: 10 },
402            escapes: "1:< 2:> 3:& 4:' 5:\"",
403        };
404        ::serde_json::to_value(&ctx).unwrap()
405    }
406
407    fn other_templates() -> HashMap<String, Template> {
408        let mut map = HashMap::new();
409        map.insert("my_macro".to_string(), compile("{value}"));
410        map
411    }
412
413    fn format(value: &Value, output: &mut String) -> Result<()> {
414        output.push_str("{");
415        ::format(value, output)?;
416        output.push_str("}");
417        Ok(())
418    }
419
420    fn formatters() -> HashMap<String, Box<ValueFormatter>> {
421        let mut map = HashMap::<String, Box<ValueFormatter>>::new();
422        map.insert("my_formatter".to_string(), Box::new(format));
423        map
424    }
425
426    pub fn default_formatter() -> &'static ValueFormatter {
427        &::format
428    }
429
430    #[test]
431    fn test_literal() {
432        let template = compile("Hello!");
433        let context = context();
434        let template_registry = other_templates();
435        let formatter_registry = formatters();
436        let string = template
437            .render(
438                &context,
439                &template_registry,
440                &formatter_registry,
441                Arc::from(default_formatter()),
442            )
443            .unwrap();
444        assert_eq!("Hello!", &string);
445    }
446
447    #[test]
448    fn test_value() {
449        let template = compile("{ number }");
450        let context = context();
451        let template_registry = other_templates();
452        let formatter_registry = formatters();
453        let string = template
454            .render(
455                &context,
456                &template_registry,
457                &formatter_registry,
458                Arc::from(default_formatter()),
459            )
460            .unwrap();
461        assert_eq!("5", &string);
462    }
463
464    #[test]
465    fn test_path() {
466        let template = compile("The number of the day is { nested.value }.");
467        let context = context();
468        let template_registry = other_templates();
469        let formatter_registry = formatters();
470        let string = template
471            .render(
472                &context,
473                &template_registry,
474                &formatter_registry,
475                Arc::from(default_formatter()),
476            )
477            .unwrap();
478        assert_eq!("The number of the day is 10.", &string);
479    }
480
481    #[test]
482    fn test_if_taken() {
483        let template = compile("{{ if boolean }}Hello!{{ endif }}");
484        let context = context();
485        let template_registry = other_templates();
486        let formatter_registry = formatters();
487        let string = template
488            .render(
489                &context,
490                &template_registry,
491                &formatter_registry,
492                Arc::from(default_formatter()),
493            )
494            .unwrap();
495        assert_eq!("Hello!", &string);
496    }
497
498    #[test]
499    fn test_if_untaken() {
500        let template = compile("{{ if null }}Hello!{{ endif }}");
501        let context = context();
502        let template_registry = other_templates();
503        let formatter_registry = formatters();
504        let string = template
505            .render(
506                &context,
507                &template_registry,
508                &formatter_registry,
509                Arc::from(default_formatter()),
510            )
511            .unwrap();
512        assert_eq!("", &string);
513    }
514
515    #[test]
516    fn test_if_else_taken() {
517        let template = compile("{{ if boolean }}Hello!{{ else }}Goodbye!{{ endif }}");
518        let context = context();
519        let template_registry = other_templates();
520        let formatter_registry = formatters();
521        let string = template
522            .render(
523                &context,
524                &template_registry,
525                &formatter_registry,
526                Arc::from(default_formatter()),
527            )
528            .unwrap();
529        assert_eq!("Hello!", &string);
530    }
531
532    #[test]
533    fn test_if_else_untaken() {
534        let template = compile("{{ if null }}Hello!{{ else }}Goodbye!{{ endif }}");
535        let context = context();
536        let template_registry = other_templates();
537        let formatter_registry = formatters();
538        let string = template
539            .render(
540                &context,
541                &template_registry,
542                &formatter_registry,
543                Arc::from(default_formatter()),
544            )
545            .unwrap();
546        assert_eq!("Goodbye!", &string);
547    }
548
549    #[test]
550    fn test_ifnot_taken() {
551        let template = compile("{{ if not boolean }}Hello!{{ endif }}");
552        let context = context();
553        let template_registry = other_templates();
554        let formatter_registry = formatters();
555        let string = template
556            .render(
557                &context,
558                &template_registry,
559                &formatter_registry,
560                Arc::from(default_formatter()),
561            )
562            .unwrap();
563        assert_eq!("", &string);
564    }
565
566    #[test]
567    fn test_ifnot_untaken() {
568        let template = compile("{{ if not null }}Hello!{{ endif }}");
569        let context = context();
570        let template_registry = other_templates();
571        let formatter_registry = formatters();
572        let string = template
573            .render(
574                &context,
575                &template_registry,
576                &formatter_registry,
577                Arc::from(default_formatter()),
578            )
579            .unwrap();
580        assert_eq!("Hello!", &string);
581    }
582
583    #[test]
584    fn test_ifnot_else_taken() {
585        let template = compile("{{ if not boolean }}Hello!{{ else }}Goodbye!{{ endif }}");
586        let context = context();
587        let template_registry = other_templates();
588        let formatter_registry = formatters();
589        let string = template
590            .render(
591                &context,
592                &template_registry,
593                &formatter_registry,
594                Arc::from(default_formatter()),
595            )
596            .unwrap();
597        assert_eq!("Goodbye!", &string);
598    }
599
600    #[test]
601    fn test_ifnot_else_untaken() {
602        let template = compile("{{ if not null }}Hello!{{ else }}Goodbye!{{ endif }}");
603        let context = context();
604        let template_registry = other_templates();
605        let formatter_registry = formatters();
606        let string = template
607            .render(
608                &context,
609                &template_registry,
610                &formatter_registry,
611                Arc::from(default_formatter()),
612            )
613            .unwrap();
614        assert_eq!("Hello!", &string);
615    }
616
617    #[test]
618    fn test_nested_ifs() {
619        let template = compile(
620            "{{ if boolean }}Hi, {{ if null }}there!{{ else }}Hello!{{ endif }}{{ endif }}",
621        );
622        let context = context();
623        let template_registry = other_templates();
624        let formatter_registry = formatters();
625        let string = template
626            .render(
627                &context,
628                &template_registry,
629                &formatter_registry,
630                Arc::from(default_formatter()),
631            )
632            .unwrap();
633        assert_eq!("Hi, Hello!", &string);
634    }
635
636    #[test]
637    fn test_with() {
638        let template = compile("{{ with nested as n }}{ n.value } { number }{{endwith}}");
639        let context = context();
640        let template_registry = other_templates();
641        let formatter_registry = formatters();
642        let string = template
643            .render(
644                &context,
645                &template_registry,
646                &formatter_registry,
647                Arc::from(default_formatter()),
648            )
649            .unwrap();
650        assert_eq!("10 5", &string);
651    }
652
653    #[test]
654    fn test_for_loop() {
655        let template = compile("{{ for a in array }}{ a }{{ endfor }}");
656        let context = context();
657        let template_registry = other_templates();
658        let formatter_registry = formatters();
659        let string = template
660            .render(
661                &context,
662                &template_registry,
663                &formatter_registry,
664                Arc::from(default_formatter()),
665            )
666            .unwrap();
667        assert_eq!("123", &string);
668    }
669
670    #[test]
671    fn test_for_loop_index() {
672        let template = compile("{{ for a in array }}{ @index }{{ endfor }}");
673        let context = context();
674        let template_registry = other_templates();
675        let formatter_registry = formatters();
676        let string = template
677            .render(
678                &context,
679                &template_registry,
680                &formatter_registry,
681                Arc::from(default_formatter()),
682            )
683            .unwrap();
684        assert_eq!("012", &string);
685    }
686
687    #[test]
688    fn test_for_loop_first() {
689        let template =
690            compile("{{ for a in array }}{{if @first }}{ @index }{{ endif }}{{ endfor }}");
691        let context = context();
692        let template_registry = other_templates();
693        let formatter_registry = formatters();
694        let string = template
695            .render(
696                &context,
697                &template_registry,
698                &formatter_registry,
699                Arc::from(default_formatter()),
700            )
701            .unwrap();
702        assert_eq!("0", &string);
703    }
704
705    #[test]
706    fn test_for_loop_last() {
707        let template =
708            compile("{{ for a in array }}{{ if @last}}{ @index }{{ endif }}{{ endfor }}");
709        let context = context();
710        let template_registry = other_templates();
711        let formatter_registry = formatters();
712        let string = template
713            .render(
714                &context,
715                &template_registry,
716                &formatter_registry,
717                Arc::from(default_formatter()),
718            )
719            .unwrap();
720        assert_eq!("2", &string);
721    }
722
723    #[test]
724    fn test_whitespace_stripping_value() {
725        let template = compile("1  \n\t   {- number -}  \n   1");
726        let context = context();
727        let template_registry = other_templates();
728        let formatter_registry = formatters();
729        let string = template
730            .render(
731                &context,
732                &template_registry,
733                &formatter_registry,
734                Arc::from(default_formatter()),
735            )
736            .unwrap();
737        assert_eq!("151", &string);
738    }
739
740    #[test]
741    fn test_call() {
742        let template = compile("{{ call my_macro with nested }}");
743        let context = context();
744        let template_registry = other_templates();
745        let formatter_registry = formatters();
746        let string = template
747            .render(
748                &context,
749                &template_registry,
750                &formatter_registry,
751                Arc::from(default_formatter()),
752            )
753            .unwrap();
754        assert_eq!("10", &string);
755    }
756
757    #[test]
758    fn test_formatter() {
759        let template = compile("{ nested.value | my_formatter }");
760        let context = context();
761        let template_registry = other_templates();
762        let formatter_registry = formatters();
763        let string = template
764            .render(
765                &context,
766                &template_registry,
767                &formatter_registry,
768                Arc::from(default_formatter()),
769            )
770            .unwrap();
771        assert_eq!("{10}", &string);
772    }
773
774    #[test]
775    fn test_unknown() {
776        let template = compile("{ foobar }");
777        let context = context();
778        let template_registry = other_templates();
779        let formatter_registry = formatters();
780        template
781            .render(
782                &context,
783                &template_registry,
784                &formatter_registry,
785                Arc::from(default_formatter()),
786            )
787            .unwrap_err();
788    }
789
790    #[test]
791    fn test_escaping() {
792        let template = compile("{ escapes }");
793        let context = context();
794        let template_registry = other_templates();
795        let formatter_registry = formatters();
796        let string = template
797            .render(
798                &context,
799                &template_registry,
800                &formatter_registry,
801                Arc::from(default_formatter()),
802            )
803            .unwrap();
804        assert_eq!("1:&lt; 2:&gt; 3:&amp; 4:&#39; 5:&quot;", &string);
805    }
806
807    #[test]
808    fn test_unescaped() {
809        let template = compile("{ escapes | unescaped }");
810        let context = context();
811        let template_registry = other_templates();
812        let mut formatter_registry = formatters();
813        formatter_registry.insert("unescaped".to_string(), Box::new(::format_unescaped));
814        let string = template
815            .render(
816                &context,
817                &template_registry,
818                &formatter_registry,
819                Arc::from(default_formatter()),
820            )
821            .unwrap();
822        assert_eq!("1:< 2:> 3:& 4:' 5:\"", &string);
823    }
824
825    #[test]
826    fn test_root_print() {
827        let template = compile("{ @root }");
828        let context = "Hello World!";
829        let context = ::serde_json::to_value(&context).unwrap();
830        let template_registry = other_templates();
831        let formatter_registry = formatters();
832        let string = template
833            .render(
834                &context,
835                &template_registry,
836                &formatter_registry,
837                Arc::from(default_formatter()),
838            )
839            .unwrap();
840        assert_eq!("Hello World!", &string);
841    }
842
843    #[test]
844    fn test_root_branch() {
845        let template = compile("{{ if @root }}Hello World!{{ endif }}");
846        let context = true;
847        let context = ::serde_json::to_value(&context).unwrap();
848        let template_registry = other_templates();
849        let formatter_registry = formatters();
850        let string = template
851            .render(
852                &context,
853                &template_registry,
854                &formatter_registry,
855                Arc::from(default_formatter()),
856            )
857            .unwrap();
858        assert_eq!("Hello World!", &string);
859    }
860
861    #[test]
862    fn test_root_iterate() {
863        let template = compile("{{ for a in @root }}{ a }{{ endfor }}");
864        let context = vec!["foo", "bar"];
865        let context = ::serde_json::to_value(&context).unwrap();
866        let template_registry = other_templates();
867        let formatter_registry = formatters();
868        let string = template
869            .render(
870                &context,
871                &template_registry,
872                &formatter_registry,
873                Arc::from(default_formatter()),
874            )
875            .unwrap();
876        assert_eq!("foobar", &string);
877    }
878
879    #[test]
880    fn test_number_truthiness_zero() {
881        let template = compile("{{ if @root }}truthy{{else}}not truthy{{ endif }}");
882        let context = 0;
883        let context = ::serde_json::to_value(&context).unwrap();
884        let template_registry = other_templates();
885        let formatter_registry = formatters();
886        let string = template
887            .render(
888                &context,
889                &template_registry,
890                &formatter_registry,
891                Arc::from(default_formatter()),
892            )
893            .unwrap();
894        assert_eq!("not truthy", &string);
895    }
896
897    #[test]
898    fn test_number_truthiness_one() {
899        let template = compile("{{ if @root }}truthy{{else}}not truthy{{ endif }}");
900        let context = 1;
901        let context = ::serde_json::to_value(&context).unwrap();
902        let template_registry = other_templates();
903        let formatter_registry = formatters();
904        let string = template
905            .render(
906                &context,
907                &template_registry,
908                &formatter_registry,
909                Arc::from(default_formatter()),
910            )
911            .unwrap();
912        assert_eq!("truthy", &string);
913    }
914
915    #[test]
916    fn test_indexed_paths() {
917        #[derive(Serialize)]
918        struct Context {
919            foo: (usize, usize),
920        }
921
922        let template = compile("{ foo.1 }{ foo.0 }");
923        let context = Context { foo: (123, 456) };
924        let context = ::serde_json::to_value(&context).unwrap();
925        let template_registry = other_templates();
926        let formatter_registry = formatters();
927        let string = template
928            .render(
929                &context,
930                &template_registry,
931                &formatter_registry,
932                Arc::from(default_formatter()),
933            )
934            .unwrap();
935        assert_eq!("456123", &string);
936    }
937
938    #[test]
939    fn test_indexed_paths_fall_back_to_string_lookup() {
940        #[derive(Serialize)]
941        struct Context {
942            foo: HashMap<&'static str, usize>,
943        }
944
945        let template = compile("{ foo.1 }{ foo.0 }");
946        let mut foo = HashMap::new();
947        foo.insert("0", 123);
948        foo.insert("1", 456);
949        let context = Context { foo };
950        let context = ::serde_json::to_value(&context).unwrap();
951        let template_registry = other_templates();
952        let formatter_registry = formatters();
953        let string = template
954            .render(
955                &context,
956                &template_registry,
957                &formatter_registry,
958                Arc::from(default_formatter()),
959            )
960            .unwrap();
961        assert_eq!("456123", &string);
962    }
963    #[test]
964    fn test_escaping_blocks() {
965        #[derive(Serialize)]
966        struct Context {
967            foo: HashMap<&'static str, usize>,
968        }
969
970        let template = compile("{ foo.1 }{ foo.0 }\\{foo.0}\\{\n}");
971        let mut foo = HashMap::new();
972        foo.insert("0", 123);
973        foo.insert("1", 456);
974        let context = Context { foo };
975        let context = ::serde_json::to_value(&context).unwrap();
976        let template_registry = other_templates();
977        let formatter_registry = formatters();
978        let string = template
979            .render(
980                &context,
981                &template_registry,
982                &formatter_registry,
983                Arc::from(default_formatter()),
984            )
985            .unwrap();
986        assert_eq!("456123{foo.0}{\n}", &string);
987    }
988}