handlebars/
partial.rs

1use std::collections::{HashMap, VecDeque};
2
3use serde_json::Value as Json;
4
5use crate::block::BlockContext;
6use crate::context::{merge_json, Context};
7use crate::error::RenderError;
8use crate::output::Output;
9use crate::registry::Registry;
10use crate::render::{Decorator, Evaluable, RenderContext, Renderable};
11use crate::template::Template;
12use crate::{Path, RenderErrorReason};
13
14pub(crate) const PARTIAL_BLOCK: &str = "@partial-block";
15
16fn find_partial<'reg: 'rc, 'rc>(
17    rc: &RenderContext<'reg, 'rc>,
18    r: &'reg Registry<'reg>,
19    d: &Decorator<'rc>,
20    name: &str,
21) -> Result<Option<&'rc Template>, RenderError> {
22    if let Some(partial) = rc.get_partial(name) {
23        return Ok(Some(partial));
24    }
25
26    if let Some(t) = rc.get_dev_mode_template(name) {
27        return Ok(Some(t));
28    }
29
30    if let Some(t) = r.get_template(name) {
31        return Ok(Some(t));
32    }
33
34    if let Some(tpl) = d.template() {
35        return Ok(Some(tpl));
36    }
37
38    Ok(None)
39}
40
41pub fn expand_partial<'reg: 'rc, 'rc>(
42    d: &Decorator<'rc>,
43    r: &'reg Registry<'reg>,
44    ctx: &'rc Context,
45    rc: &mut RenderContext<'reg, 'rc>,
46    out: &mut dyn Output,
47) -> Result<(), RenderError> {
48    // try eval inline partials first
49    if let Some(t) = d.template() {
50        t.eval(r, ctx, rc)?;
51    }
52
53    let tname = d.name();
54
55    let current_template_before = rc.get_current_template_name();
56    let indent_before = rc.get_indent_string().cloned();
57
58    if rc.is_current_template(tname) {
59        return Err(RenderErrorReason::CannotIncludeSelf.into());
60    }
61
62    let partial = find_partial(rc, r, d, tname)?;
63
64    let Some(partial) = partial else {
65        return Err(RenderErrorReason::PartialNotFound(tname.to_owned()).into());
66    };
67
68    let is_partial_block = tname == PARTIAL_BLOCK;
69
70    // add partial block depth there are consecutive partial
71    // blocks in the stack.
72    if is_partial_block {
73        rc.inc_partial_block_depth();
74    } else {
75        // depth cannot be lower than 0, which is guaranted in the
76        // `dec_partial_block_depth` method
77        rc.dec_partial_block_depth();
78    }
79
80    // hash
81    let hash_ctx = d
82        .hash()
83        .iter()
84        .map(|(k, v)| (*k, v.value()))
85        .collect::<HashMap<&str, &Json>>();
86
87    let mut partial_include_block = BlockContext::new();
88    // evaluate context for partial
89    let merged_context = if let Some(p) = d.param(0) {
90        if let Some(relative_path) = p.relative_path() {
91            // path as parameter provided
92            merge_json(rc.evaluate(ctx, relative_path)?.as_json(), &hash_ctx)
93        } else {
94            // literal provided
95            merge_json(p.value(), &hash_ctx)
96        }
97    } else {
98        // use current path
99        merge_json(rc.evaluate2(ctx, &Path::current())?.as_json(), &hash_ctx)
100    };
101    partial_include_block.set_base_value(merged_context);
102
103    // replace and hold blocks from current render context
104    let current_blocks = rc.replace_blocks(VecDeque::with_capacity(1));
105    rc.push_block(partial_include_block);
106
107    // @partial-block
108    if let Some(pb) = d.template() {
109        rc.push_partial_block(pb);
110    }
111
112    // indent
113    rc.set_indent_string(d.indent().cloned());
114
115    let result = partial.render(r, ctx, rc, out);
116
117    // cleanup
118    let trailing_newline = rc.get_trailine_newline();
119
120    if d.template().is_some() {
121        rc.pop_partial_block();
122    }
123
124    let _ = rc.replace_blocks(current_blocks);
125    rc.set_trailing_newline(trailing_newline);
126    rc.set_current_template_name(current_template_before);
127    rc.set_indent_string(indent_before);
128
129    result
130}
131
132#[cfg(test)]
133mod test {
134    use crate::context::Context;
135    use crate::error::RenderError;
136    use crate::output::Output;
137    use crate::registry::Registry;
138    use crate::render::{Helper, RenderContext};
139
140    #[test]
141    fn test() {
142        let mut handlebars = Registry::new();
143        assert!(handlebars
144            .register_template_string("t0", "{{> t1}}")
145            .is_ok());
146        assert!(handlebars
147            .register_template_string("t1", "{{this}}")
148            .is_ok());
149        assert!(handlebars
150            .register_template_string("t2", "{{#> t99}}not there{{/t99}}")
151            .is_ok());
152        assert!(handlebars
153            .register_template_string("t3", "{{#*inline \"t31\"}}{{this}}{{/inline}}{{> t31}}")
154            .is_ok());
155        assert!(handlebars
156            .register_template_string(
157                "t4",
158                "{{#> t5}}{{#*inline \"nav\"}}navbar{{/inline}}{{/t5}}"
159            )
160            .is_ok());
161        assert!(handlebars
162            .register_template_string("t5", "include {{> nav}}")
163            .is_ok());
164        assert!(handlebars
165            .register_template_string("t6", "{{> t1 a}}")
166            .is_ok());
167        assert!(handlebars
168            .register_template_string(
169                "t7",
170                "{{#*inline \"t71\"}}{{a}}{{/inline}}{{> t71 a=\"world\"}}"
171            )
172            .is_ok());
173        assert!(handlebars.register_template_string("t8", "{{a}}").is_ok());
174        assert!(handlebars
175            .register_template_string("t9", "{{> t8 a=2}}")
176            .is_ok());
177
178        assert_eq!(handlebars.render("t0", &1).ok().unwrap(), "1".to_string());
179        assert_eq!(
180            handlebars.render("t2", &1).ok().unwrap(),
181            "not there".to_string()
182        );
183        assert_eq!(handlebars.render("t3", &1).ok().unwrap(), "1".to_string());
184        assert_eq!(
185            handlebars.render("t4", &1).ok().unwrap(),
186            "include navbar".to_string()
187        );
188        assert_eq!(
189            handlebars.render("t6", &json!({"a": "2"})).ok().unwrap(),
190            "2".to_string()
191        );
192        assert_eq!(
193            handlebars.render("t7", &1).ok().unwrap(),
194            "world".to_string()
195        );
196        assert_eq!(handlebars.render("t9", &1).ok().unwrap(), "2".to_string());
197    }
198
199    #[test]
200    fn test_include_partial_block() {
201        let t0 = "hello {{> @partial-block}}";
202        let t1 = "{{#> t0}}inner {{this}}{{/t0}}";
203
204        let mut handlebars = Registry::new();
205        assert!(handlebars.register_template_string("t0", t0).is_ok());
206        assert!(handlebars.register_template_string("t1", t1).is_ok());
207
208        let r0 = handlebars.render("t1", &true);
209        assert_eq!(r0.ok().unwrap(), "hello inner true".to_string());
210    }
211
212    #[test]
213    fn test_self_inclusion() {
214        let t0 = "hello {{> t1}} {{> t0}}";
215        let t1 = "some template";
216        let mut handlebars = Registry::new();
217        assert!(handlebars.register_template_string("t0", t0).is_ok());
218        assert!(handlebars.register_template_string("t1", t1).is_ok());
219
220        let r0 = handlebars.render("t0", &true);
221        assert!(r0.is_err());
222    }
223
224    #[test]
225    fn test_issue_143() {
226        let main_template = "one{{> two }}three{{> two }}";
227        let two_partial = "--- two ---";
228
229        let mut handlebars = Registry::new();
230        assert!(handlebars
231            .register_template_string("template", main_template)
232            .is_ok());
233        assert!(handlebars
234            .register_template_string("two", two_partial)
235            .is_ok());
236
237        let r0 = handlebars.render("template", &true);
238        assert_eq!(r0.ok().unwrap(), "one--- two ---three--- two ---");
239    }
240
241    #[test]
242    fn test_hash_context_outscope() {
243        let main_template = "In: {{> p a=2}} Out: {{a}}";
244        let p_partial = "{{a}}";
245
246        let mut handlebars = Registry::new();
247        assert!(handlebars
248            .register_template_string("template", main_template)
249            .is_ok());
250        assert!(handlebars.register_template_string("p", p_partial).is_ok());
251
252        let r0 = handlebars.render("template", &true);
253        assert_eq!(r0.ok().unwrap(), "In: 2 Out: ");
254    }
255
256    #[test]
257    fn test_partial_context_hash() {
258        let mut hbs = Registry::new();
259        hbs.register_template_string("one", "This is a test. {{> two name=\"fred\" }}")
260            .unwrap();
261        hbs.register_template_string("two", "Lets test {{name}}")
262            .unwrap();
263        assert_eq!(
264            "This is a test. Lets test fred",
265            hbs.render("one", &0).unwrap()
266        );
267    }
268
269    #[test]
270    fn teset_partial_context_with_both_hash_and_param() {
271        let mut hbs = Registry::new();
272        hbs.register_template_string("one", "This is a test. {{> two this name=\"fred\" }}")
273            .unwrap();
274        hbs.register_template_string("two", "Lets test {{name}} and {{root_name}}")
275            .unwrap();
276        assert_eq!(
277            "This is a test. Lets test fred and tom",
278            hbs.render("one", &json!({"root_name": "tom"})).unwrap()
279        );
280    }
281
282    #[test]
283    fn test_partial_subexpression_context_hash() {
284        let mut hbs = Registry::new();
285        hbs.register_template_string("one", "This is a test. {{> (x @root) name=\"fred\" }}")
286            .unwrap();
287        hbs.register_template_string("two", "Lets test {{name}}")
288            .unwrap();
289
290        hbs.register_helper(
291            "x",
292            Box::new(
293                |_: &Helper<'_>,
294                 _: &Registry<'_>,
295                 _: &Context,
296                 _: &mut RenderContext<'_, '_>,
297                 out: &mut dyn Output|
298                 -> Result<(), RenderError> {
299                    out.write("two")?;
300                    Ok(())
301                },
302            ),
303        );
304        assert_eq!(
305            "This is a test. Lets test fred",
306            hbs.render("one", &0).unwrap()
307        );
308    }
309
310    #[test]
311    fn test_nested_partial_scope() {
312        let t = "{{#*inline \"pp\"}}{{a}} {{b}}{{/inline}}{{#each c}}{{> pp a=2}}{{/each}}";
313        let data = json!({"c": [{"b": true}, {"b": false}]});
314
315        let mut handlebars = Registry::new();
316        assert!(handlebars.register_template_string("t", t).is_ok());
317        let r0 = handlebars.render("t", &data);
318        assert_eq!(r0.ok().unwrap(), "2 true2 false");
319    }
320
321    #[test]
322    fn test_nested_partial_block() {
323        let mut handlebars = Registry::new();
324        let template1 = "<outer>{{> @partial-block }}</outer>";
325        let template2 = "{{#> t1 }}<inner>{{> @partial-block }}</inner>{{/ t1 }}";
326        let template3 = "{{#> t2 }}Hello{{/ t2 }}";
327
328        handlebars
329            .register_template_string("t1", template1)
330            .unwrap();
331        handlebars
332            .register_template_string("t2", template2)
333            .unwrap();
334
335        let page = handlebars.render_template(template3, &json!({})).unwrap();
336        assert_eq!("<outer><inner>Hello</inner></outer>", page);
337    }
338
339    #[test]
340    fn test_up_to_partial_level() {
341        let outer = r#"{{>inner name="fruit:" vegetables=fruits}}"#;
342        let inner = "{{#each vegetables}}{{../name}} {{this}},{{/each}}";
343
344        let data = json!({ "fruits": ["carrot", "tomato"] });
345
346        let mut handlebars = Registry::new();
347        handlebars.register_template_string("outer", outer).unwrap();
348        handlebars.register_template_string("inner", inner).unwrap();
349
350        assert_eq!(
351            handlebars.render("outer", &data).unwrap(),
352            "fruit: carrot,fruit: tomato,"
353        );
354    }
355
356    #[test]
357    fn line_stripping_with_inline_and_partial() {
358        let tpl0 = r#"{{#*inline "foo"}}foo
359{{/inline}}
360{{> foo}}
361{{> foo}}
362{{> foo}}"#;
363        let tpl1 = r#"{{#*inline "foo"}}foo{{/inline}}
364{{> foo}}
365{{> foo}}
366{{> foo}}"#;
367
368        let hbs = Registry::new();
369        assert_eq!(
370            r"foo
371foo
372foo
373",
374            hbs.render_template(tpl0, &json!({})).unwrap()
375        );
376        assert_eq!(
377            r"
378foofoofoo",
379            hbs.render_template(tpl1, &json!({})).unwrap()
380        );
381    }
382
383    #[test]
384    fn test_partial_indent() {
385        let outer = r"                {{> inner inner_solo}}
386
387{{#each inners}}
388                {{> inner}}
389{{/each}}
390
391        {{#each inners}}
392        {{> inner}}
393        {{/each}}
394";
395        let inner = r"name: {{name}}
396";
397
398        let mut hbs = Registry::new();
399
400        hbs.register_template_string("inner", inner).unwrap();
401        hbs.register_template_string("outer", outer).unwrap();
402
403        let result = hbs
404            .render(
405                "outer",
406                &json!({
407                    "inner_solo": {"name": "inner_solo"},
408                    "inners": [
409                        {"name": "hello"},
410                        {"name": "there"}
411                    ]
412                }),
413            )
414            .unwrap();
415
416        assert_eq!(
417            result,
418            r"                name: inner_solo
419
420                name: hello
421                name: there
422
423        name: hello
424        name: there
425"
426        );
427    }
428    // Rule::partial_expression should not trim leading indent  by default
429
430    #[test]
431    fn test_partial_prevent_indent() {
432        let outer = r"                {{> inner inner_solo}}
433
434{{#each inners}}
435                {{> inner}}
436{{/each}}
437
438        {{#each inners}}
439        {{> inner}}
440        {{/each}}
441";
442        let inner = r"name: {{name}}
443";
444
445        let mut hbs = Registry::new();
446        hbs.set_prevent_indent(true);
447
448        hbs.register_template_string("inner", inner).unwrap();
449        hbs.register_template_string("outer", outer).unwrap();
450
451        let result = hbs
452            .render(
453                "outer",
454                &json!({
455                    "inner_solo": {"name": "inner_solo"},
456                    "inners": [
457                        {"name": "hello"},
458                        {"name": "there"}
459                    ]
460                }),
461            )
462            .unwrap();
463
464        assert_eq!(
465            result,
466            r"                name: inner_solo
467
468                name: hello
469                name: there
470
471        name: hello
472        name: there
473"
474        );
475    }
476
477    #[test]
478    fn test_nested_partials() {
479        let mut hb = Registry::new();
480        hb.register_template_string("partial", "{{> @partial-block}}")
481            .unwrap();
482        hb.register_template_string(
483            "index",
484            r"{{#>partial}}
485    Yo
486    {{#>partial}}
487    Yo 2
488    {{/partial}}
489{{/partial}}",
490        )
491        .unwrap();
492        assert_eq!(
493            r"    Yo
494    Yo 2
495",
496            hb.render("index", &()).unwrap()
497        );
498
499        hb.register_template_string("partial2", "{{> @partial-block}}")
500            .unwrap();
501        let r2 = hb
502            .render_template(
503                r"{{#> partial}}
504{{#> partial2}}
505:(
506{{/partial2}}
507{{/partial}}",
508                &(),
509            )
510            .unwrap();
511        assert_eq!(":(\n", r2);
512    }
513
514    #[test]
515    fn test_partial_context_issue_495() {
516        let mut hb = Registry::new();
517        hb.register_template_string(
518            "t1",
519            r#"{{~#*inline "displayName"~}}
520Template:{{name}}
521{{/inline}}
522{{#each data as |name|}}
523Name:{{name}}
524{{>displayName name="aaaa"}}
525{{/each}}"#,
526        )
527        .unwrap();
528
529        hb.register_template_string(
530            "t2",
531            r#"{{~#*inline "displayName"~}}
532Template:{{this}}
533{{/inline}}
534{{#each data as |name|}}
535Name:{{name}}
536{{>displayName}}
537{{/each}}"#,
538        )
539        .unwrap();
540
541        let data = json!({
542            "data": ["hudel", "test"]
543        });
544
545        assert_eq!(
546            r"Name:hudel
547Template:aaaa
548Name:test
549Template:aaaa
550",
551            hb.render("t1", &data).unwrap()
552        );
553        assert_eq!(
554            r"Name:hudel
555Template:hudel
556Name:test
557Template:test
558",
559            hb.render("t2", &data).unwrap()
560        );
561    }
562
563    #[test]
564    fn test_multiline_partial_indent() {
565        let mut hb = Registry::new();
566
567        hb.register_template_string(
568            "t1",
569            r#"{{#*inline "thepartial"}}
570  inner first line
571  inner second line
572{{/inline}}
573  {{> thepartial}}
574outer third line"#,
575        )
576        .unwrap();
577        assert_eq!(
578            r"    inner first line
579    inner second line
580outer third line",
581            hb.render("t1", &()).unwrap()
582        );
583
584        hb.register_template_string(
585            "t2",
586            r#"{{#*inline "thepartial"}}inner first line
587inner second line
588{{/inline}}
589  {{> thepartial}}
590outer third line"#,
591        )
592        .unwrap();
593        assert_eq!(
594            r"  inner first line
595  inner second line
596outer third line",
597            hb.render("t2", &()).unwrap()
598        );
599
600        hb.register_template_string(
601            "t3",
602            r#"{{#*inline "thepartial"}}{{a}}{{/inline}}
603  {{> thepartial}}
604outer third line"#,
605        )
606        .unwrap();
607        assert_eq!(
608            r"
609  inner first line
610  inner second lineouter third line",
611            hb.render("t3", &json!({"a": "inner first line\ninner second line"}))
612                .unwrap()
613        );
614
615        hb.register_template_string(
616            "t4",
617            r#"{{#*inline "thepartial"}}
618  inner first line
619  inner second line
620{{/inline}}
621  {{~> thepartial}}
622outer third line"#,
623        )
624        .unwrap();
625        assert_eq!(
626            r"  inner first line
627  inner second line
628outer third line",
629            hb.render("t4", &()).unwrap()
630        );
631
632        let mut hb2 = Registry::new();
633        hb2.set_prevent_indent(true);
634
635        hb2.register_template_string(
636            "t1",
637            r#"{{#*inline "thepartial"}}
638  inner first line
639  inner second line
640{{/inline}}
641  {{> thepartial}}
642outer third line"#,
643        )
644        .unwrap();
645        assert_eq!(
646            r"    inner first line
647  inner second line
648outer third line",
649            hb2.render("t1", &()).unwrap()
650        );
651    }
652
653    #[test]
654    fn test_indent_level_on_nested_partials() {
655        let nested_partial = "
656<div>
657    content
658</div>
659";
660        let partial = "
661<div>
662    {{>nested_partial}}
663</div>
664";
665
666        let partial_indented = "
667<div>
668    {{>partial}}
669</div>
670";
671
672        let result = "
673<div>
674    <div>
675        <div>
676            content
677        </div>
678    </div>
679</div>
680";
681
682        let mut hb = Registry::new();
683        hb.register_template_string("nested_partial", nested_partial.trim_start())
684            .unwrap();
685        hb.register_template_string("partial", partial.trim_start())
686            .unwrap();
687        hb.register_template_string("partial_indented", partial_indented.trim_start())
688            .unwrap();
689
690        let s = hb.render("partial_indented", &()).unwrap();
691
692        assert_eq!(&s, result.trim_start());
693    }
694
695    #[test]
696    fn test_issue_534() {
697        let t1 = "{{title}}";
698        let t2 = "{{#each modules}}{{> (lookup this \"module\") content name=0}}{{/each}}";
699
700        let data = json!({
701          "modules": [
702            {"module": "t1", "content": {"title": "foo"}},
703            {"module": "t1", "content": {"title": "bar"}},
704          ]
705        });
706
707        let mut hbs = Registry::new();
708        hbs.register_template_string("t1", t1).unwrap();
709        hbs.register_template_string("t2", t2).unwrap();
710
711        assert_eq!("foobar", hbs.render("t2", &data).unwrap());
712    }
713
714    #[test]
715    fn test_partial_not_found() {
716        let t1 = "{{> bar}}";
717        let hbs = Registry::new();
718        assert!(hbs.render_template(t1, &()).is_err());
719    }
720
721    #[test]
722    fn test_issue_643_this_context() {
723        let t1 = "{{this}}";
724        let t2 = "{{> t1 \"hello world\"}}";
725
726        let mut hbs = Registry::new();
727        hbs.register_template_string("t1", t1).unwrap();
728        hbs.register_template_string("t2", t2).unwrap();
729
730        assert_eq!("hello world", hbs.render("t2", &()).unwrap());
731
732        let t1 = "{{a}} {{[0]}} {{[1]}}";
733        let t2 = "{{> t1 \"hello world\" a=1}}";
734
735        let mut hbs = Registry::new();
736        hbs.register_template_string("t1", t1).unwrap();
737        hbs.register_template_string("t2", t2).unwrap();
738
739        assert_eq!("1 h e", hbs.render("t2", &()).unwrap());
740
741        let t1 = "{{#each this}}{{@key}}:{{this}},{{/each}}";
742        let t2 = "{{> t1 a=1}}";
743
744        let mut hbs = Registry::new();
745        hbs.register_template_string("t1", t1).unwrap();
746        hbs.register_template_string("t2", t2).unwrap();
747
748        assert_eq!("a:1,", hbs.render("t2", &()).unwrap());
749
750        let t1 = "{{#each this}}{{@key}}:{{this}},{{/each}}";
751        let t2 = "{{> t1 a=1}}";
752
753        let mut hbs = Registry::new();
754        hbs.register_template_string("t1", t1).unwrap();
755        hbs.register_template_string("t2", t2).unwrap();
756
757        assert_eq!("a:1,b:2,", hbs.render("t2", &json!({"b": 2})).unwrap());
758
759        let t1 = "{{#each this}}{{@key}}:{{this}},{{/each}}";
760        let t2 = "{{> t1 b a=1}}";
761
762        let mut hbs = Registry::new();
763        hbs.register_template_string("t1", t1).unwrap();
764        hbs.register_template_string("t2", t2).unwrap();
765
766        assert_eq!("a:1,", hbs.render("t2", &json!({"b": 2})).unwrap());
767    }
768}