1 //! This module implements the bytecode interpreter that actually renders the templates. 2 3 use compiler::TemplateCompiler; 4 use error::Error::*; 5 use error::*; 6 use instruction::{Instruction, PathSlice, PathStep}; 7 use serde_json::Value; 8 use std::collections::HashMap; 9 use std::fmt::Write; 10 use std::slice; 11 use ValueFormatter; 12 13 /// Enum defining the different kinds of records on the context stack. 14 enum 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. 35 struct RenderContext<'render, 'template> { 36 original_text: &'template str, 37 context_stack: Vec<ContextElement<'render, 'template>>, 38 } 39 impl<'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) lookup(&self, path: PathSlice) -> Result<&'render Value>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) lookup_in(&self, path: PathSlice, object: &'render Value) -> Result<&'render Value>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. lookup_index(&self) -> Result<(usize, usize)>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 lookup_root(&self) -> Result<&'render Value>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. 113 pub(crate) struct Template<'template> { 114 original_text: &'template str, 115 instructions: Vec<Instruction<'template>>, 116 template_len: usize, 117 } 118 impl<'template> Template<'template> { 119 /// Create a Template from the given template string. compile(text: &'template str) -> Result<Template>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). render( &self, context: &Value, template_registry: &HashMap<&str, Template>, formatter_registry: &HashMap<&str, Box<ValueFormatter>>, default_formatter: &ValueFormatter, ) -> Result<String>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. render_into( &self, context: &Value, template_registry: &HashMap<&str, Template>, formatter_registry: &HashMap<&str, Box<ValueFormatter>>, default_formatter: &ValueFormatter, output: &mut String, ) -> Result<()>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 value_is_truthy(&self, value: &Value, path: PathSlice) -> Result<bool>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)] 347 mod test { 348 use super::*; 349 use compiler::TemplateCompiler; 350 compile(text: &'static str) -> Template<'static>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 context() -> Value375 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 other_templates() -> HashMap<&'static str, Template<'static>>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 format(value: &Value, output: &mut String) -> Result<()>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 formatters() -> HashMap<&'static str, Box<ValueFormatter>>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 default_formatter() -> &'static ValueFormatter407 pub fn default_formatter() -> &'static ValueFormatter { 408 &::format 409 } 410 411 #[test] test_literal()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] test_value()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] test_path()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] test_if_taken()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] test_if_untaken()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] test_if_else_taken()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] test_if_else_untaken()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] test_ifnot_taken()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] test_ifnot_untaken()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] test_ifnot_else_taken()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] test_ifnot_else_untaken()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] test_nested_ifs()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] test_with()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] test_for_loop()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] test_for_loop_index()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] test_for_loop_first()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] test_for_loop_last()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] test_whitespace_stripping_value()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] test_call()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] test_formatter()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] test_unknown()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] test_escaping()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] test_unescaped()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] test_root_print()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] test_root_branch()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] test_root_iterate()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] test_number_truthiness_zero()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] test_number_truthiness_one()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] test_indexed_paths()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] test_indexed_paths_fall_back_to_string_lookup()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