1use std::collections::BTreeMap;
2
3use serde::Serialize;
4
5use crate::{
6 ast::{Node, NodeList},
7 error::Error,
8 parser::TemplateParser,
9 value::{Value, to_value},
10 vm::Renderer,
11};
12
13const MAX_EXTEND_DEPTH: usize = 128;
14
15#[derive(Clone, Debug)]
20pub enum EscapeMode {
21 Html,
22 Text,
23}
24
25pub(crate) struct ParsedTemplate {
26 pub(crate) nodes: NodeList,
27}
28
29pub struct Engine {
34 pub(crate) mode: EscapeMode,
35 pub(crate) templates: BTreeMap<String, ParsedTemplate>,
36}
37
38impl Engine {
39 pub fn new(mode: EscapeMode) -> Self {
41 Self {
42 mode,
43 templates: BTreeMap::new(),
44 }
45 }
46
47 pub fn add_template(&mut self, name: &str, source: &str) -> Result<(), Error> {
53 if self.templates.contains_key(name) {
54 return Err(Error::parse(format!("template `{name}` already exists")));
55 }
56 let mut parser = TemplateParser::new(source);
57 let nodes = parser.parse()?;
58
59 self.templates.insert(
60 name.to_string(),
61 ParsedTemplate {
62 nodes,
63 },
64 );
65 Ok(())
66 }
67
68 pub fn render<S: Serialize>(&self, name: &str, variables: S) -> Result<String, Error> {
76 let template = self
77 .templates
78 .get(name)
79 .ok_or_else(|| Error::undefined_template(name))?;
80
81 let variables_value = to_value(&variables).map_err(|e| Error::parse(e.0))?;
82
83 let mut output = String::new();
84
85 self.render_with_extends(&template.nodes, &variables_value, &mut output, None, 0)?;
86
87 Ok(output)
88 }
89
90 fn render_with_extends(
91 &self,
92 nodes: &NodeList,
93 variables: &Value,
94 output: &mut String,
95 parent_blocks: Option<&BTreeMap<String, Vec<NodeList>>>,
96 extend_depth: usize,
97 ) -> Result<(), Error> {
98 if extend_depth >= MAX_EXTEND_DEPTH {
99 return Err(Error::render(format!("extend depth limit ({MAX_EXTEND_DEPTH}) exceeded")));
100 }
101 let extends_name = nodes.iter().find_map(|n| {
103 if let Node::Extends(name) = n {
104 Some(name.clone())
105 } else {
106 None
107 }
108 });
109
110 if let Some(parent_name) = extends_name {
111 let mut blocks: BTreeMap<String, NodeList> = BTreeMap::new();
113 for node in nodes {
114 if let Node::Block(block) = node {
115 blocks.insert(block.name.clone(), block.body.clone());
116 }
117 }
118
119 let parent = self
121 .templates
122 .get(parent_name.as_str())
123 .ok_or_else(|| Error::undefined_template(&parent_name))?;
124
125 let mut merged_chain: BTreeMap<String, Vec<NodeList>> = if let Some(pb) = parent_blocks {
129 pb.clone()
130 } else {
131 BTreeMap::new()
132 };
133 for (name, body) in &blocks {
134 match merged_chain.get_mut(name) {
135 Some(chain) => chain.push(body.clone()),
136 None => {
137 merged_chain.insert(name.clone(), vec![body.clone()]);
138 }
139 }
140 }
141
142 self.render_with_extends(&parent.nodes, variables, output, Some(&merged_chain), extend_depth + 1)?;
144 } else {
145 let mut renderer = Renderer::new(self, output, variables.clone());
147
148 if let Some(blocks) = parent_blocks {
149 for (name, chain) in blocks {
150 let mut full_chain = chain.clone();
151 if let Some(current_block) = nodes.iter().find_map(|n| {
152 if let Node::Block(b) = n {
153 if b.name == *name { Some(b.body.clone()) } else { None }
154 } else {
155 None
156 }
157 }) {
158 full_chain.push(current_block);
159 }
160 renderer.block_overrides.insert(name.clone(), full_chain);
161 }
162 }
163
164 renderer.render_nodes(nodes)?;
165 }
166
167 Ok(())
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use serde::Serialize;
174
175 use super::*;
176
177 #[test]
178 fn test_simple_template() {
179 let mut engine = Engine::new(EscapeMode::Text);
180 engine.add_template("hello", "Hello, {{ name }}!").unwrap();
181 let result = engine.render("hello", serde_json::json!({"name": "World"})).unwrap();
182 assert_eq!(result, "Hello, World!");
183 }
184
185 #[test]
186 fn test_html_escape() {
187 let mut engine = Engine::new(EscapeMode::Html);
188 engine.add_template("t", "<p>{{ content }}</p>").unwrap();
189 let result = engine
190 .render("t", serde_json::json!({"content": "<script>alert('xss')</script>"}))
191 .unwrap();
192 assert_eq!(result, "<p><script>alert('xss')</script></p>");
193 }
194
195 #[test]
196 fn test_text_no_escape() {
197 let mut engine = Engine::new(EscapeMode::Text);
198 engine.add_template("t", "{{ content }}").unwrap();
199 let result = engine
200 .render("t", serde_json::json!({"content": "<script>alert('xss')</script>"}))
201 .unwrap();
202 assert_eq!(result, "<script>alert('xss')</script>");
203 }
204
205 #[test]
206 fn test_if_true() {
207 let mut engine = Engine::new(EscapeMode::Text);
208 engine.add_template("t", "{% if show %}visible{% endif %}").unwrap();
209 let result = engine.render("t", serde_json::json!({"show": true})).unwrap();
210 assert_eq!(result, "visible");
211 }
212
213 #[test]
214 fn test_if_false() {
215 let mut engine = Engine::new(EscapeMode::Text);
216 engine.add_template("t", "{% if show %}visible{% endif %}").unwrap();
217 let result = engine.render("t", serde_json::json!({"show": false})).unwrap();
218 assert_eq!(result, "");
219 }
220
221 #[test]
222 fn test_if_else() {
223 let mut engine = Engine::new(EscapeMode::Text);
224 engine
225 .add_template("t", "{% if show %}yes{% else %}no{% endif %}")
226 .unwrap();
227 let result = engine.render("t", serde_json::json!({"show": false})).unwrap();
228 assert_eq!(result, "no");
229 }
230
231 #[test]
232 fn test_if_elif_else() {
233 let mut engine = Engine::new(EscapeMode::Text);
234 engine
235 .add_template("t", "{% if x == 1 %}one{% elif x == 2 %}two{% else %}other{% endif %}")
236 .unwrap();
237 assert_eq!(engine.render("t", serde_json::json!({"x": 1})).unwrap(), "one");
238 assert_eq!(engine.render("t", serde_json::json!({"x": 2})).unwrap(), "two");
239 assert_eq!(engine.render("t", serde_json::json!({"x": 3})).unwrap(), "other");
240 }
241
242 #[test]
243 fn test_for_loop() {
244 let mut engine = Engine::new(EscapeMode::Text);
245 engine
246 .add_template("t", "{% for item in items %}{{ item }},{% endfor %}")
247 .unwrap();
248 let result = engine
249 .render("t", serde_json::json!({"items": ["a", "b", "c"]}))
250 .unwrap();
251 assert_eq!(result, "a,b,c,");
252 }
253
254 #[test]
255 fn test_dotted_access() {
256 let mut engine = Engine::new(EscapeMode::Text);
257 engine.add_template("t", "{{ user.name }}").unwrap();
258 let result = engine
259 .render("t", serde_json::json!({"user": {"name": "Alice"}}))
260 .unwrap();
261 assert_eq!(result, "Alice");
262 }
263
264 #[test]
265 fn test_filter_upper() {
266 let mut engine = Engine::new(EscapeMode::Text);
267 engine.add_template("t", "{{ name | upper }}").unwrap();
268 let result = engine.render("t", serde_json::json!({"name": "hello"})).unwrap();
269 assert_eq!(result, "HELLO");
270 }
271
272 #[test]
273 fn test_filter_chain() {
274 let mut engine = Engine::new(EscapeMode::Text);
275 engine.add_template("t", "{{ name | upper | reverse }}").unwrap();
276 let result = engine.render("t", serde_json::json!({"name": "abc"})).unwrap();
277 assert_eq!(result, "CBA");
278 }
279
280 #[test]
281 fn test_set_variable() {
282 let mut engine = Engine::new(EscapeMode::Text);
283 engine.add_template("t", "{% set x = 42 %}{{ x }}").unwrap();
284 let result = engine.render("t", serde_json::json!({})).unwrap();
285 assert_eq!(result, "42");
286 }
287
288 #[test]
289 fn test_raw_block() {
290 let mut engine = Engine::new(EscapeMode::Text);
291 engine
292 .add_template("t", "before{% raw %}{{ not processed }}{% endraw %}after")
293 .unwrap();
294 let result = engine.render("t", serde_json::json!({})).unwrap();
295 assert_eq!(result, "before{{ not processed }}after");
296 }
297
298 #[test]
299 fn test_include() {
300 let mut engine = Engine::new(EscapeMode::Text);
301 engine.add_template("header", "Header").unwrap();
302 engine.add_template("page", "{% include \"header\" %}Body").unwrap();
303 let result = engine.render("page", serde_json::json!({})).unwrap();
304 assert_eq!(result, "HeaderBody");
305 }
306
307 #[test]
308 fn test_comparisons() {
309 let mut engine = Engine::new(EscapeMode::Text);
310 engine.add_template("t", "{% if x == 1 %}eq{% endif %}").unwrap();
311 assert_eq!(engine.render("t", serde_json::json!({"x": 1})).unwrap(), "eq");
312 assert_eq!(engine.render("t", serde_json::json!({"x": 2})).unwrap(), "");
313 }
314
315 #[test]
316 fn test_not_operator() {
317 let mut engine = Engine::new(EscapeMode::Text);
318 engine.add_template("t", "{% if not x %}empty{% endif %}").unwrap();
319 let result = engine.render("t", serde_json::json!({"x": false})).unwrap();
320 assert_eq!(result, "empty");
321 }
322
323 #[test]
324 fn test_in_operator() {
325 let mut engine = Engine::new(EscapeMode::Text);
326 engine
327 .add_template("t", "{% if \"a\" in items %}found{% endif %}")
328 .unwrap();
329 let result = engine
330 .render("t", serde_json::json!({"items": ["a", "b", "c"]}))
331 .unwrap();
332 assert_eq!(result, "found");
333 }
334
335 #[test]
336 fn test_nested_if_for() {
337 let mut engine = Engine::new(EscapeMode::Text);
338 engine
339 .add_template(
340 "t",
341 "{% for item in items %}{% if item.active %}{{ item.name }}{% endif %}{% endfor %}",
342 )
343 .unwrap();
344 let result = engine
345 .render(
346 "t",
347 serde_json::json!({
348 "items": [
349 {"name": "a", "active": true},
350 {"name": "b", "active": false},
351 {"name": "c", "active": true},
352 ]
353 }),
354 )
355 .unwrap();
356 assert_eq!(result, "ac");
357 }
358
359 #[test]
360 fn test_extends_and_blocks() {
361 let mut engine = Engine::new(EscapeMode::Text);
362 engine
363 .add_template("base", "before{% block content %}default{% endblock %}after")
364 .unwrap();
365 engine
366 .add_template("child", "{% extends \"base\" %}{% block content %}child content{% endblock %}")
367 .unwrap();
368 let result = engine.render("child", serde_json::json!({})).unwrap();
369 assert_eq!(result, "beforechild contentafter");
370 }
371
372 #[test]
373 fn test_super_in_block() {
374 let mut engine = Engine::new(EscapeMode::Text);
375 engine
376 .add_template("base", "{% block content %}parent{% endblock %}")
377 .unwrap();
378 engine
379 .add_template(
380 "child",
381 "{% extends \"base\" %}{% block content %}{{ super() }} + child{% endblock %}",
382 )
383 .unwrap();
384 let result = engine.render("child", serde_json::json!({})).unwrap();
385 assert_eq!(result, "parent + child");
386 }
387
388 #[test]
389 fn test_comment_is_ignored() {
390 let mut engine = Engine::new(EscapeMode::Text);
391 engine.add_template("t", "before{# comment #}after").unwrap();
392 let result = engine.render("t", serde_json::json!({})).unwrap();
393 assert_eq!(result, "beforeafter");
394 }
395
396 #[test]
397 fn test_empty_variable() {
398 let mut engine = Engine::new(EscapeMode::Text);
399 engine.add_template("t", "{{ missing }}").unwrap();
400 let result = engine.render("t", serde_json::json!({})).unwrap();
401 assert_eq!(result, "");
402 }
403
404 #[test]
405 fn test_struct_variables() {
406 #[derive(Serialize)]
407 struct User {
408 name: String,
409 age: i32,
410 }
411
412 let mut engine = Engine::new(EscapeMode::Text);
413 engine.add_template("t", "{{ name }} is {{ age }}").unwrap();
414 let user = User {
415 name: "Bob".into(),
416 age: 30,
417 };
418 let result = engine.render("t", user).unwrap();
419 assert_eq!(result, "Bob is 30");
420 }
421
422 #[test]
423 fn test_default_filter() {
424 let mut engine = Engine::new(EscapeMode::Text);
425 engine.add_template("t", "{{ name | default(\"unknown\") }}").unwrap();
426
427 let result = engine.render("t", serde_json::json!({"name": "Alice"})).unwrap();
429 assert_eq!(result, "Alice");
430
431 let result = engine.render("t", serde_json::json!({})).unwrap();
433 assert_eq!(result, "unknown");
434 }
435
436 #[test]
437 fn test_length_filter() {
438 let mut engine = Engine::new(EscapeMode::Text);
439 engine.add_template("t", "{{ items | length }}").unwrap();
440 let result = engine.render("t", serde_json::json!({"items": [1, 2, 3]})).unwrap();
441 assert_eq!(result, "3");
442 }
443
444 #[test]
445 fn test_arithmetic() {
446 let mut engine = Engine::new(EscapeMode::Text);
447 engine.add_template("t", "{{ 1 + 2 * 3 }}").unwrap();
448 let result = engine.render("t", serde_json::json!({})).unwrap();
449 assert_eq!(result, "7");
450 }
451
452 #[test]
453 fn test_float_arithmetic() {
454 let mut engine = Engine::new(EscapeMode::Text);
455 engine.add_template("t", "{{ 3.5 + 2.5 }}").unwrap();
456 let result = engine.render("t", serde_json::json!({})).unwrap();
457 assert_eq!(result, "6");
458 }
459
460 #[test]
461 fn test_float_division() {
462 let mut engine = Engine::new(EscapeMode::Text);
463 engine.add_template("t", "{{ 10.0 / 3 }}").unwrap();
464 let result = engine.render("t", serde_json::json!({})).unwrap();
465 assert!(
466 result == "3.3333333333333335" || result == "3.333333333333333",
467 "unexpected float division result: {result}"
468 );
469 }
470
471 #[test]
472 fn test_super_in_html_mode() {
473 let mut engine = Engine::new(EscapeMode::Html);
474 engine
475 .add_template("base", "{% block content %}<b>parent</b>{% endblock %}")
476 .unwrap();
477 engine
478 .add_template(
479 "child",
480 "{% extends \"base\" %}{% block content %}{{ super() }}<i>child</i>{% endblock %}",
481 )
482 .unwrap();
483 let result = engine.render("child", serde_json::json!({})).unwrap();
484 assert_eq!(result, "<b>parent</b><i>child</i>");
485 }
486
487 #[test]
488 fn test_multi_level_extends() {
489 let mut engine = Engine::new(EscapeMode::Text);
490 engine
491 .add_template("base", "{% block content %}base{% endblock %}")
492 .unwrap();
493 engine
494 .add_template(
495 "child",
496 "{% extends \"base\" %}{% block content %}child {{ super() }}{% endblock %}",
497 )
498 .unwrap();
499 engine
500 .add_template(
501 "grandchild",
502 "{% extends \"child\" %}{% block content %}grandchild {{ super() }}{% endblock %}",
503 )
504 .unwrap();
505 let result = engine.render("grandchild", serde_json::json!({})).unwrap();
506 assert_eq!(result, "grandchild child base");
507 }
508
509 #[test]
510 fn test_safe_filter_html_mode() {
511 let mut engine = Engine::new(EscapeMode::Html);
512 engine.add_template("t", "{{ content | safe }}").unwrap();
513 let result = engine
514 .render("t", serde_json::json!({"content": "<b>bold</b>"}))
515 .unwrap();
516 assert_eq!(result, "<b>bold</b>");
517 }
518
519 #[test]
520 fn test_escape_filter_html_mode() {
521 let mut engine = Engine::new(EscapeMode::Html);
522 engine
523 .add_template("t", "{{ content }} and {{ content | escape }}")
524 .unwrap();
525 let result = engine.render("t", serde_json::json!({"content": "<br>"})).unwrap();
526 assert_eq!(result, "<br> and <br>");
528 }
529
530 #[test]
531 fn test_for_loop_over_string() {
532 let mut engine = Engine::new(EscapeMode::Text);
533 engine
534 .add_template("t", "{% for c in s %}{{ c }}|{% endfor %}")
535 .unwrap();
536 let result = engine.render("t", serde_json::json!({"s": "ab"})).unwrap();
537 assert_eq!(result, "a|b|");
538 }
539
540 #[test]
541 fn test_index_access() {
542 let mut engine = Engine::new(EscapeMode::Text);
543 engine.add_template("t", "{{ items[0] }},{{ items[1] }}").unwrap();
544 let result = engine.render("t", serde_json::json!({"items": ["a", "b"]})).unwrap();
545 assert_eq!(result, "a,b");
546 }
547
548 #[test]
549 fn test_in_operator_string() {
550 let mut engine = Engine::new(EscapeMode::Text);
551 engine
552 .add_template("t", "{% if \"world\" in \"hello world\" %}found{% endif %}")
553 .unwrap();
554 let result = engine.render("t", serde_json::json!({})).unwrap();
555 assert_eq!(result, "found");
556 }
557
558 #[test]
559 fn test_missing_endif_errors() {
560 let mut engine = Engine::new(EscapeMode::Text);
561 let result = engine.add_template("t", "{% if true %}hello");
562 assert!(result.is_err());
563 }
564
565 #[test]
566 fn test_length_filter_map() {
567 let mut engine = Engine::new(EscapeMode::Text);
568 engine.add_template("t", "{{ obj | length }}").unwrap();
569 let result = engine
570 .render("t", serde_json::json!({"obj": {"a": 1, "b": 2}}))
571 .unwrap();
572 assert_eq!(result, "2");
573 }
574
575 #[test]
576 fn test_undefined_template_error() {
577 let engine = Engine::new(EscapeMode::Text);
578 let result = engine.render("nonexistent", serde_json::json!({}));
579 assert!(result.is_err());
580 }
581
582 #[test]
583 fn test_division_by_zero() {
584 let mut engine = Engine::new(EscapeMode::Text);
585 engine.add_template("t", "{{ 1 / 0 }}").unwrap();
586 let result = engine.render("t", serde_json::json!({}));
587 assert!(result.is_err());
588 }
589
590 #[test]
591 fn test_modulo_by_zero() {
592 let mut engine = Engine::new(EscapeMode::Text);
593 engine.add_template("t", "{{ 10 % 0 }}").unwrap();
594 let result = engine.render("t", serde_json::json!({}));
595 assert!(result.is_err());
596 }
597
598 #[test]
599 fn test_circular_include() {
600 let mut engine = Engine::new(EscapeMode::Text);
601 engine.add_template("a", "{% include \"b\" %}").unwrap();
602 engine.add_template("b", "{% include \"a\" %}").unwrap();
603 let result = engine.render("a", serde_json::json!({}));
604 assert!(result.is_err());
605 }
606
607 #[test]
608 fn test_circular_extends() {
609 let mut engine = Engine::new(EscapeMode::Text);
610 engine
611 .add_template("a", "{% extends \"b\" %}{% block x %}a{% endblock %}")
612 .unwrap();
613 engine
614 .add_template("b", "{% extends \"a\" %}{% block x %}b{% endblock %}")
615 .unwrap();
616 let result = engine.render("a", serde_json::json!({}));
617 assert!(result.is_err());
618 }
619
620 #[test]
621 fn test_unknown_function() {
622 let mut engine = Engine::new(EscapeMode::Text);
623 engine.add_template("t", "{{ foobar() }}").unwrap();
624 let result = engine.render("t", serde_json::json!({}));
625 assert!(result.is_err());
626 }
627
628 #[test]
629 fn test_range_with_float() {
630 let mut engine = Engine::new(EscapeMode::Text);
631 engine
632 .add_template("t", "{% for i in range(5.5) %}{{ i }}{% endfor %}")
633 .unwrap();
634 let result = engine.render("t", serde_json::json!({}));
635 assert!(result.is_err());
636 }
637
638 #[test]
639 fn test_trailing_tokens_in_expr() {
640 let mut engine = Engine::new(EscapeMode::Text);
641 let result = engine.add_template("t", "{{ true false }}");
642 assert!(result.is_err());
643 }
644
645 #[test]
646 fn test_trailing_tokens_after_filter() {
647 let mut engine = Engine::new(EscapeMode::Text);
648 let result = engine.add_template("t", "{{ name | upper + 1 }}");
649 assert!(result.is_err());
650 }
651
652 #[test]
653 fn test_add_template_overwrite_error() {
654 let mut engine = Engine::new(EscapeMode::Text);
655 engine.add_template("t", "hello").unwrap();
656 let result = engine.add_template("t", "world");
657 assert!(result.is_err());
658 }
659
660 #[test]
661 fn test_raw_block_preserves_inner_tags() {
662 let mut engine = Engine::new(EscapeMode::Text);
663 engine
664 .add_template("t", "before{% raw %}{% inner %}{% endraw %}after")
665 .unwrap();
666 let result = engine.render("t", serde_json::json!({})).unwrap();
667 assert_eq!(result, "before{% inner %}after");
668 }
669
670 #[test]
671 fn test_raw_block_preserves_trailing_whitespace() {
672 let mut engine = Engine::new(EscapeMode::Text);
673 engine
674 .add_template("t", "before{% raw %}hello {% endraw %}after")
675 .unwrap();
676 let result = engine.render("t", serde_json::json!({})).unwrap();
677 assert_eq!(result, "beforehello after");
678 }
679
680 #[test]
681 fn test_range_with_integer() {
682 let mut engine = Engine::new(EscapeMode::Text);
683 engine
684 .add_template("t", "{% for i in range(3) %}{{ i }}{% endfor %}")
685 .unwrap();
686 let result = engine.render("t", serde_json::json!({})).unwrap();
687 assert_eq!(result, "012");
688 }
689
690 #[test]
691 fn test_length_filter_in_html_mode_on_safe() {
692 let mut engine = Engine::new(EscapeMode::Html);
693 engine.add_template("t", "{{ \"hello\" | escape | length }}").unwrap();
694 let result = engine.render("t", serde_json::json!({})).unwrap();
695 assert_eq!(result, "5");
697 }
698
699 #[test]
700 fn test_first_on_safe_string() {
701 let mut engine = Engine::new(EscapeMode::Text);
702 engine.add_template("t", "{{ \"abc\" | safe | first }}").unwrap();
703 let result = engine.render("t", serde_json::json!({})).unwrap();
704 assert_eq!(result, "a");
705 }
706
707 #[test]
708 fn test_last_on_safe_string() {
709 let mut engine = Engine::new(EscapeMode::Text);
710 engine.add_template("t", "{{ \"abc\" | safe | last }}").unwrap();
711 let result = engine.render("t", serde_json::json!({})).unwrap();
712 assert_eq!(result, "c");
713 }
714
715 #[test]
716 fn test_reverse_on_safe_string() {
717 let mut engine = Engine::new(EscapeMode::Text);
718 engine.add_template("t", "{{ \"abc\" | safe | reverse }}").unwrap();
719 let result = engine.render("t", serde_json::json!({})).unwrap();
720 assert_eq!(result, "cba");
721 }
722
723 #[test]
724 fn test_length_on_safe_string() {
725 let mut engine = Engine::new(EscapeMode::Text);
726 engine.add_template("t", "{{ \"hello\" | safe | length }}").unwrap();
727 let result = engine.render("t", serde_json::json!({})).unwrap();
728 assert_eq!(result, "5");
729 }
730
731 #[test]
732 fn test_short_circuit_and() {
733 let mut engine = Engine::new(EscapeMode::Text);
734 engine.add_template("t", "{% if false and 1/0 %}ok{% endif %}").unwrap();
735 let result = engine.render("t", serde_json::json!({})).unwrap();
736 assert_eq!(result, "");
737 }
738
739 #[test]
740 fn test_short_circuit_or() {
741 let mut engine = Engine::new(EscapeMode::Text);
742 engine.add_template("t", "{% if true or 1/0 %}ok{% endif %}").unwrap();
743 let result = engine.render("t", serde_json::json!({})).unwrap();
744 assert_eq!(result, "ok");
745 }
746
747 #[test]
748 fn test_range_no_args() {
749 let mut engine = Engine::new(EscapeMode::Text);
750 engine.add_template("t", "{{ range() }}").unwrap();
751 let result = engine.render("t", serde_json::json!({}));
752 assert!(result.is_err());
753 }
754
755 #[test]
756 fn test_range_extra_args() {
757 let mut engine = Engine::new(EscapeMode::Text);
758 engine
759 .add_template("t", "{% for i in range(1,2,3) %}{{ i }}{% endfor %}")
760 .unwrap();
761 let result = engine.render("t", serde_json::json!({}));
762 assert!(result.is_err());
763 }
764
765 #[test]
766 fn test_for_empty_string() {
767 let mut engine = Engine::new(EscapeMode::Text);
768 engine
769 .add_template("t", "{% for c in \"\" %}{{ c }}{% endfor %}")
770 .unwrap();
771 let result = engine.render("t", serde_json::json!({})).unwrap();
772 assert_eq!(result, "");
773 }
774
775 #[test]
776 fn test_for_empty_array() {
777 let mut engine = Engine::new(EscapeMode::Text);
778 engine
779 .add_template("t", "{% for i in items %}{{ i }}{% endfor %}")
780 .unwrap();
781 let result = engine.render("t", serde_json::json!({"items": []})).unwrap();
782 assert_eq!(result, "");
783 }
784
785 #[test]
786 fn test_for_over_number() {
787 let mut engine = Engine::new(EscapeMode::Text);
788 engine
789 .add_template("t", "{% for i in 42 %}{{ i }}{% endfor %}")
790 .unwrap();
791 let result = engine.render("t", serde_json::json!({})).unwrap();
792 assert_eq!(result, "");
793 }
794
795 #[test]
796 fn test_for_over_map() {
797 let mut engine = Engine::new(EscapeMode::Text);
798 engine.add_template("t", "{% for i in m %}{{ i }}{% endfor %}").unwrap();
799 let result = engine.render("t", serde_json::json!({"m": {"a": 1}})).unwrap();
800 assert_eq!(result, "");
801 }
802
803 #[test]
804 fn test_missing_include() {
805 let mut engine = Engine::new(EscapeMode::Text);
806 engine.add_template("t", "{% include \"missing\" %}").unwrap();
807 let result = engine.render("t", serde_json::json!({}));
808 assert!(result.is_err());
809 }
810
811 #[test]
812 fn test_missing_extends() {
813 let mut engine = Engine::new(EscapeMode::Text);
814 engine.add_template("t", "{% extends \"missing\" %}").unwrap();
815 let result = engine.render("t", serde_json::json!({}));
816 assert!(result.is_err());
817 }
818
819 #[test]
820 fn test_super_without_parent() {
821 let mut engine = Engine::new(EscapeMode::Text);
822 engine.add_template("t", "{{ super() }}").unwrap();
823 let result = engine.render("t", serde_json::json!({})).unwrap();
824 assert_eq!(result, "");
825 }
826
827 #[test]
828 fn test_set_overwrite() {
829 let mut engine = Engine::new(EscapeMode::Text);
830 engine
831 .add_template("t", "{% set x = 1 %}{% set x = 2 %}{{ x }}")
832 .unwrap();
833 let result = engine.render("t", serde_json::json!({})).unwrap();
834 assert_eq!(result, "2");
835 }
836
837 #[test]
838 fn test_nested_missing() {
839 let mut engine = Engine::new(EscapeMode::Text);
840 engine.add_template("t", "{{ a.b.c }}").unwrap();
841 let result = engine.render("t", serde_json::json!({})).unwrap();
842 assert_eq!(result, "");
843 }
844
845 #[test]
846 fn test_negative_index_runtime() {
847 let mut engine = Engine::new(EscapeMode::Text);
848 engine.add_template("t", "{{ items[i] }}").unwrap();
849 let result = engine.render("t", serde_json::json!({"items": [1, 2], "i": -1}));
850 assert!(result.is_err());
851 }
852
853 #[test]
854 fn test_float_index_runtime() {
855 let mut engine = Engine::new(EscapeMode::Text);
856 engine.add_template("t", "{{ items[i] }}").unwrap();
857 let result = engine.render("t", serde_json::json!({"items": [1, 2], "i": 1.5}));
858 assert!(result.is_err());
859 }
860
861 #[test]
862 fn test_empty_map_index() {
863 let mut engine = Engine::new(EscapeMode::Text);
864 engine.add_template("t", "{{ m.key }}").unwrap();
865 let result = engine.render("t", serde_json::json!({})).unwrap();
866 assert_eq!(result, "");
867 }
868
869 #[test]
870 fn test_float_div_by_zero_inf() {
871 let mut engine = Engine::new(EscapeMode::Text);
872 engine.add_template("t", "{{ 1.0 / 0.0 }}").unwrap();
873 let result = engine.render("t", serde_json::json!({})).unwrap();
874 assert_eq!(result, "inf");
875 }
876
877 #[test]
878 fn test_neg_float_div_by_zero_neg_inf() {
879 let mut engine = Engine::new(EscapeMode::Text);
880 engine.add_template("t", "{{ -1.0 / 0.0 }}").unwrap();
881 let result = engine.render("t", serde_json::json!({})).unwrap();
882 assert_eq!(result, "-inf");
883 }
884
885 #[test]
886 fn test_float_zero_div_by_zero_nan() {
887 let mut engine = Engine::new(EscapeMode::Text);
888 engine.add_template("t", "{{ 0.0 / 0.0 }}").unwrap();
889 let result = engine.render("t", serde_json::json!({})).unwrap();
890 assert_eq!(result, "NaN");
891 }
892
893 #[test]
894 fn test_elif_after_else_error() {
895 let mut engine = Engine::new(EscapeMode::Text);
896 let result = engine.add_template("t", "{% if a %}b{% else %}c{% elif d %}e{% endif %}");
897 assert!(result.is_err());
898 }
899
900 #[test]
901 fn test_multiple_else_error() {
902 let mut engine = Engine::new(EscapeMode::Text);
903 let result = engine.add_template("t", "{% if a %}b{% else %}c{% else %}d{% endif %}");
904 assert!(result.is_err());
905 }
906
907 #[test]
908 fn test_empty_if_condition_error() {
909 let mut engine = Engine::new(EscapeMode::Text);
910 let result = engine.add_template("t", "{% if %}a{% endif %}");
911 assert!(result.is_err());
912 }
913}