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