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:< 2:> 3:& 4:' 5:"", &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}
945