handlebars/helpers/
helper_each.rs

1use serde_json::value::Value as Json;
2
3use super::block_util::create_block;
4use crate::block::{BlockContext, BlockParams};
5use crate::context::Context;
6use crate::error::RenderError;
7use crate::helpers::{HelperDef, HelperResult};
8use crate::json::value::to_json;
9use crate::output::Output;
10use crate::registry::Registry;
11use crate::render::{Helper, RenderContext, Renderable};
12use crate::util::copy_on_push_vec;
13use crate::RenderErrorReason;
14
15fn update_block_context(
16    block: &mut BlockContext<'_>,
17    base_path: Option<&Vec<String>>,
18    relative_path: String,
19    is_first: bool,
20    value: &Json,
21) {
22    if let Some(p) = base_path {
23        if is_first {
24            *block.base_path_mut() = copy_on_push_vec(p, relative_path);
25        } else if let Some(ptr) = block.base_path_mut().last_mut() {
26            *ptr = relative_path;
27        }
28    } else {
29        block.set_base_value(value.clone());
30    }
31}
32
33fn set_block_param<'rc>(
34    block: &mut BlockContext<'rc>,
35    h: &Helper<'rc>,
36    base_path: Option<&Vec<String>>,
37    k: &Json,
38    v: &Json,
39) -> Result<(), RenderError> {
40    if let Some(bp_val) = h.block_param() {
41        let mut params = BlockParams::new();
42        if base_path.is_some() {
43            params.add_path(bp_val, Vec::with_capacity(0))?;
44        } else {
45            params.add_value(bp_val, v.clone())?;
46        }
47
48        block.set_block_params(params);
49    } else if let Some((bp_val, bp_key)) = h.block_param_pair() {
50        let mut params = BlockParams::new();
51        if base_path.is_some() {
52            params.add_path(bp_val, Vec::with_capacity(0))?;
53        } else {
54            params.add_value(bp_val, v.clone())?;
55        }
56        params.add_value(bp_key, k.clone())?;
57
58        block.set_block_params(params);
59    }
60
61    Ok(())
62}
63
64#[derive(Clone, Copy)]
65pub struct EachHelper;
66
67impl HelperDef for EachHelper {
68    fn call<'reg: 'rc, 'rc>(
69        &self,
70        h: &Helper<'rc>,
71        r: &'reg Registry<'reg>,
72        ctx: &'rc Context,
73        rc: &mut RenderContext<'reg, 'rc>,
74        out: &mut dyn Output,
75    ) -> HelperResult {
76        let value = h
77            .param(0)
78            .ok_or(RenderErrorReason::ParamNotFoundForIndex("each", 0))?;
79
80        let template = h.template();
81
82        match template {
83            Some(t) => match *value.value() {
84                Json::Array(ref list)
85                    if !list.is_empty() || (list.is_empty() && h.inverse().is_none()) =>
86                {
87                    let block_context = create_block(value);
88                    rc.push_block(block_context);
89
90                    let len = list.len();
91
92                    let array_path = value.context_path();
93
94                    for (i, v) in list.iter().enumerate().take(len) {
95                        if let Some(ref mut block) = rc.block_mut() {
96                            let is_first = i == 0usize;
97                            let is_last = i == len - 1;
98
99                            let index = to_json(i);
100                            block.set_local_var("first", to_json(is_first));
101                            block.set_local_var("last", to_json(is_last));
102                            block.set_local_var("index", index.clone());
103
104                            update_block_context(block, array_path, i.to_string(), is_first, v);
105                            set_block_param(block, h, array_path, &index, v)?;
106                        }
107
108                        t.render(r, ctx, rc, out)?;
109                    }
110
111                    rc.pop_block();
112                    Ok(())
113                }
114                Json::Object(ref obj)
115                    if !obj.is_empty() || (obj.is_empty() && h.inverse().is_none()) =>
116                {
117                    let block_context = create_block(value);
118                    rc.push_block(block_context);
119
120                    let len = obj.len();
121
122                    let obj_path = value.context_path();
123
124                    for (i, (k, v)) in obj.iter().enumerate() {
125                        if let Some(ref mut block) = rc.block_mut() {
126                            let is_first = i == 0usize;
127                            let is_last = i == len - 1;
128
129                            let key = to_json(k);
130                            block.set_local_var("first", to_json(is_first));
131                            block.set_local_var("last", to_json(is_last));
132                            block.set_local_var("key", key.clone());
133                            block.set_local_var("index", to_json(i));
134
135                            update_block_context(block, obj_path, k.to_string(), is_first, v);
136                            set_block_param(block, h, obj_path, &key, v)?;
137                        }
138
139                        t.render(r, ctx, rc, out)?;
140                    }
141
142                    rc.pop_block();
143                    Ok(())
144                }
145                _ => {
146                    if let Some(else_template) = h.inverse() {
147                        else_template.render(r, ctx, rc, out)
148                    } else if r.strict_mode() {
149                        Err(RenderError::strict_error(value.relative_path()))
150                    } else {
151                        Ok(())
152                    }
153                }
154            },
155            None => Ok(()),
156        }
157    }
158}
159
160pub static EACH_HELPER: EachHelper = EachHelper;
161
162#[cfg(test)]
163mod test {
164    use crate::registry::Registry;
165    use serde_json::value::Value as Json;
166    use std::collections::BTreeMap;
167    use std::str::FromStr;
168
169    #[test]
170    fn test_empty_each() {
171        let mut hbs = Registry::new();
172        hbs.set_strict_mode(true);
173
174        let data = json!({
175            "a": [ ],
176        });
177
178        let template = "{{#each a}}each{{/each}}";
179        assert_eq!(hbs.render_template(template, &data).unwrap(), "");
180    }
181
182    #[test]
183    fn test_each() {
184        let mut handlebars = Registry::new();
185        assert!(handlebars
186            .register_template_string(
187                "t0",
188                "{{#each this}}{{@first}}|{{@last}}|{{@index}}:{{this}}|{{/each}}",
189            )
190            .is_ok());
191        assert!(handlebars
192            .register_template_string(
193                "t1",
194                "{{#each this}}{{@first}}|{{@last}}|{{@key}}:{{this}}|{{@index}}|{{/each}}",
195            )
196            .is_ok());
197
198        let r0 = handlebars.render("t0", &vec![1u16, 2u16, 3u16]);
199        assert_eq!(
200            r0.ok().unwrap(),
201            "true|false|0:1|false|false|1:2|false|true|2:3|".to_string()
202        );
203
204        let mut m: BTreeMap<String, u16> = BTreeMap::new();
205        m.insert("ftp".to_string(), 21);
206        m.insert("gopher".to_string(), 70);
207        m.insert("http".to_string(), 80);
208        let r1 = handlebars.render("t1", &m);
209        assert_eq!(
210            r1.ok().unwrap(),
211            "true|false|ftp:21|0|false|false|gopher:70|1|false|true|http:80|2|".to_string()
212        );
213    }
214
215    #[test]
216    fn test_each_with_parent() {
217        let json_str = r#"{"a":{"a":99,"c":[{"d":100},{"d":200}]}}"#;
218
219        let data = Json::from_str(json_str).unwrap();
220        // println!("data: {}", data);
221        let mut handlebars = Registry::new();
222
223        // previously, to access the parent in an each block,
224        // a user would need to specify ../../b, as the path
225        // that is computed includes the array index: ./a.c.[0]
226        assert!(handlebars
227            .register_template_string("t0", "{{#each a.c}} d={{d}} b={{../a.a}} {{/each}}")
228            .is_ok());
229
230        let r1 = handlebars.render("t0", &data);
231        assert_eq!(r1.ok().unwrap(), " d=100 b=99  d=200 b=99 ".to_string());
232    }
233
234    #[test]
235    fn test_nested_each_with_parent() {
236        let json_str = r#"{"a": [{"b": [{"d": 100}], "c": 200}]}"#;
237
238        let data = Json::from_str(json_str).unwrap();
239        let mut handlebars = Registry::new();
240        assert!(handlebars
241            .register_template_string(
242                "t0",
243                "{{#each a}}{{#each b}}{{d}}:{{../c}}{{/each}}{{/each}}",
244            )
245            .is_ok());
246
247        let r1 = handlebars.render("t0", &data);
248        assert_eq!(r1.ok().unwrap(), "100:200".to_string());
249    }
250
251    #[test]
252    fn test_nested_each() {
253        let json_str = r#"{"a": [{"b": true}], "b": [[1, 2, 3],[4, 5]]}"#;
254
255        let data = Json::from_str(json_str).unwrap();
256
257        let mut handlebars = Registry::new();
258        assert!(handlebars
259            .register_template_string(
260                "t0",
261                "{{#each b}}{{#if ../a}}{{#each this}}{{this}}{{/each}}{{/if}}{{/each}}",
262            )
263            .is_ok());
264
265        let r1 = handlebars.render("t0", &data);
266        assert_eq!(r1.ok().unwrap(), "12345".to_string());
267    }
268
269    #[test]
270    fn test_nested_array() {
271        let mut handlebars = Registry::new();
272        assert!(handlebars
273            .register_template_string("t0", "{{#each this.[0]}}{{this}}{{/each}}")
274            .is_ok());
275
276        let r0 = handlebars.render("t0", &(vec![vec![1, 2, 3]]));
277
278        assert_eq!(r0.ok().unwrap(), "123".to_string());
279    }
280
281    #[test]
282    fn test_empty_key() {
283        let mut handlebars = Registry::new();
284        assert!(handlebars
285            .register_template_string("t0", "{{#each this}}{{@key}}-{{value}}\n{{/each}}")
286            .is_ok());
287
288        let r0 = handlebars
289            .render(
290                "t0",
291                &json!({
292                    "foo": {
293                        "value": "bar"
294                    },
295                    "": {
296                        "value": "baz"
297                    }
298                }),
299            )
300            .unwrap();
301
302        let mut r0_sp: Vec<_> = r0.split('\n').collect();
303        r0_sp.sort_unstable();
304
305        assert_eq!(r0_sp, vec!["", "-baz", "foo-bar"]);
306    }
307
308    #[test]
309    fn test_each_else() {
310        let mut handlebars = Registry::new();
311        assert!(handlebars
312            .register_template_string("t0", "{{#each a}}1{{else}}empty{{/each}}")
313            .is_ok());
314        let m1 = json!({
315            "a": []
316        });
317        let r0 = handlebars.render("t0", &m1).unwrap();
318        assert_eq!(r0, "empty");
319
320        let m2 = json!({
321            "b": []
322        });
323        let r1 = handlebars.render("t0", &m2).unwrap();
324        assert_eq!(r1, "empty");
325    }
326
327    #[test]
328    fn test_block_param() {
329        let mut handlebars = Registry::new();
330        assert!(handlebars
331            .register_template_string("t0", "{{#each a as |i|}}{{i}}{{/each}}")
332            .is_ok());
333        let m1 = json!({
334            "a": [1,2,3,4,5]
335        });
336        let r0 = handlebars.render("t0", &m1).unwrap();
337        assert_eq!(r0, "12345");
338    }
339
340    #[test]
341    fn test_each_object_block_param() {
342        let mut handlebars = Registry::new();
343        let template = "{{#each this as |v k|}}\
344                        {{#with k as |inner_k|}}{{inner_k}}{{/with}}:{{v}}|\
345                        {{/each}}";
346        assert!(handlebars.register_template_string("t0", template).is_ok());
347
348        let m = json!({
349            "ftp": 21,
350            "http": 80
351        });
352        let r0 = handlebars.render("t0", &m);
353        assert_eq!(r0.ok().unwrap(), "ftp:21|http:80|".to_string());
354    }
355
356    #[test]
357    fn test_each_object_block_param2() {
358        let mut handlebars = Registry::new();
359        let template = "{{#each this as |v k|}}\
360                        {{#with v as |inner_v|}}{{k}}:{{inner_v}}{{/with}}|\
361                        {{/each}}";
362
363        assert!(handlebars.register_template_string("t0", template).is_ok());
364
365        let m = json!({
366            "ftp": 21,
367            "http": 80
368        });
369        let r0 = handlebars.render("t0", &m);
370        assert_eq!(r0.ok().unwrap(), "ftp:21|http:80|".to_string());
371    }
372
373    fn test_nested_each_with_path_ups() {
374        let mut handlebars = Registry::new();
375        assert!(handlebars
376            .register_template_string(
377                "t0",
378                "{{#each a.b}}{{#each c}}{{../../d}}{{/each}}{{/each}}",
379            )
380            .is_ok());
381
382        let data = json!({
383            "a": {
384                "b": [{"c": [1]}]
385            },
386            "d": 1
387        });
388
389        let r0 = handlebars.render("t0", &data);
390        assert_eq!(r0.ok().unwrap(), "1".to_string());
391    }
392
393    #[test]
394    fn test_nested_each_with_path_up_this() {
395        let mut handlebars = Registry::new();
396        let template = "{{#each variant}}{{#each ../typearg}}\
397                        {{#if @first}}template<{{/if}}{{this}}{{#if @last}}>{{else}},{{/if}}\
398                        {{/each}}{{/each}}";
399        assert!(handlebars.register_template_string("t0", template).is_ok());
400        let data = json!({
401            "typearg": ["T"],
402            "variant": ["1", "2"]
403        });
404        let r0 = handlebars.render("t0", &data);
405        assert_eq!(r0.ok().unwrap(), "template<T>template<T>".to_string());
406    }
407
408    #[test]
409    fn test_key_iteration_with_unicode() {
410        let mut handlebars = Registry::new();
411        assert!(handlebars
412            .register_template_string("t0", "{{#each this}}{{@key}}: {{this}}\n{{/each}}")
413            .is_ok());
414        let data = json!({
415            "normal": 1,
416            "你好": 2,
417            "#special key": 3,
418            "😂": 4,
419            "me.dot.key": 5
420        });
421        let r0 = handlebars.render("t0", &data).ok().unwrap();
422        assert!(r0.contains("normal: 1"));
423        assert!(r0.contains("你好: 2"));
424        assert!(r0.contains("#special key: 3"));
425        assert!(r0.contains("😂: 4"));
426        assert!(r0.contains("me.dot.key: 5"));
427    }
428
429    #[test]
430    fn test_base_path_after_each() {
431        let mut handlebars = Registry::new();
432        assert!(handlebars
433            .register_template_string("t0", "{{#each a}}{{this}}{{/each}} {{b}}")
434            .is_ok());
435        let data = json!({
436            "a": [1, 2, 3, 4],
437            "b": "good",
438        });
439
440        let r0 = handlebars.render("t0", &data).ok().unwrap();
441
442        assert_eq!("1234 good", r0);
443    }
444
445    #[test]
446    fn test_else_context() {
447        let reg = Registry::new();
448        let template = "{{#each list}}A{{else}}{{foo}}{{/each}}";
449        let input = json!({"list": [], "foo": "bar"});
450        let rendered = reg.render_template(template, &input).unwrap();
451        assert_eq!("bar", rendered);
452    }
453
454    #[test]
455    fn test_block_context_leak() {
456        let reg = Registry::new();
457        let template = "{{#each list}}{{#each inner}}{{this}}{{/each}}{{foo}}{{/each}}";
458        let input = json!({"list": [{"inner": [], "foo": 1}, {"inner": [], "foo": 2}]});
459        let rendered = reg.render_template(template, &input).unwrap();
460        assert_eq!("12", rendered);
461    }
462
463    #[test]
464    fn test_derived_array_as_block_params() {
465        handlebars_helper!(range: |x: u64| (0..x).collect::<Vec<u64>>());
466        let mut reg = Registry::new();
467        reg.register_helper("range", Box::new(range));
468        let template = "{{#each (range 3) as |i|}}{{i}}{{/each}}";
469        let input = json!(0);
470        let rendered = reg.render_template(template, &input).unwrap();
471        assert_eq!("012", rendered);
472    }
473
474    #[test]
475    fn test_derived_object_as_block_params() {
476        handlebars_helper!(point: |x: u64, y: u64| json!({"x":x, "y":y}));
477        let mut reg = Registry::new();
478        reg.register_helper("point", Box::new(point));
479        let template = "{{#each (point 0 1) as |i|}}{{i}}{{/each}}";
480        let input = json!(0);
481        let rendered = reg.render_template(template, &input).unwrap();
482        assert_eq!("01", rendered);
483    }
484
485    #[test]
486    fn test_derived_array_without_block_param() {
487        handlebars_helper!(range: |x: u64| (0..x).collect::<Vec<u64>>());
488        let mut reg = Registry::new();
489        reg.register_helper("range", Box::new(range));
490        let template = "{{#each (range 3)}}{{this}}{{/each}}";
491        let input = json!(0);
492        let rendered = reg.render_template(template, &input).unwrap();
493        assert_eq!("012", rendered);
494    }
495
496    #[test]
497    fn test_derived_object_without_block_params() {
498        handlebars_helper!(point: |x: u64, y: u64| json!({"x":x, "y":y}));
499        let mut reg = Registry::new();
500        reg.register_helper("point", Box::new(point));
501        let template = "{{#each (point 0 1)}}{{this}}{{/each}}";
502        let input = json!(0);
503        let rendered = reg.render_template(template, &input).unwrap();
504        assert_eq!("01", rendered);
505    }
506
507    #[test]
508    fn test_non_iterable() {
509        let reg = Registry::new();
510        let template = "{{#each this}}each block{{else}}else block{{/each}}";
511        let input = json!("strings aren't iterable");
512        let rendered = reg.render_template(template, &input).unwrap();
513        assert_eq!("else block", rendered);
514    }
515
516    #[test]
517    fn test_render_array_without_trailig_commas() {
518        let reg = Registry::new();
519        let template = "Array: {{array}}";
520        let input = json!({"array": [1, 2, 3]});
521        let rendered = reg.render_template(template, &input);
522
523        assert_eq!("Array: [1, 2, 3]", rendered.unwrap());
524    }
525
526    #[test]
527    fn test_recursion() {
528        let mut reg = Registry::new();
529        assert!(reg
530            .register_template_string(
531                "walk",
532                "(\
533                    {{#each this}}\
534                        {{#if @key}}{{@key}}{{else}}{{@index}}{{/if}}: \
535                        {{this}} \
536                        {{> walk this}}, \
537                    {{/each}}\
538                )",
539            )
540            .is_ok());
541
542        let input = json!({
543            "array": [42, {"wow": "cool"}, [[]]],
544            "object": { "a": { "b": "c", "d": ["e"] } },
545            "string": "hi"
546        });
547        let expected_output = "(\
548            array: [42, [object], [[]]] (\
549                0: 42 (), \
550                1: [object] (wow: cool (), ), \
551                2: [[]] (0: [] (), ), \
552            ), \
553            object: [object] (\
554                a: [object] (\
555                    b: c (), \
556                    d: [e] (0: e (), ), \
557                ), \
558            ), \
559            string: hi (), \
560        )";
561
562        let rendered = reg.render("walk", &input).unwrap();
563        assert_eq!(expected_output, rendered);
564    }
565
566    #[test]
567    fn test_strict_each() {
568        let mut reg = Registry::new();
569
570        assert!(reg
571            .render_template("{{#each data}}{{/each}}", &json!({}))
572            .is_ok());
573        assert!(reg
574            .render_template("{{#each data}}{{/each}}", &json!({"data": 24}))
575            .is_ok());
576
577        reg.set_strict_mode(true);
578
579        assert!(reg
580            .render_template("{{#each data}}{{/each}}", &json!({}))
581            .is_err());
582        assert!(reg
583            .render_template("{{#each data}}{{/each}}", &json!({"data": 24}))
584            .is_err());
585        assert!(reg
586            .render_template("{{#each data}}{{else}}food{{/each}}", &json!({}))
587            .is_ok());
588        assert!(reg
589            .render_template("{{#each data}}{{else}}food{{/each}}", &json!({"data": 24}))
590            .is_ok());
591    }
592
593    #[test]
594    fn newline_stripping_for_each() {
595        let reg = Registry::new();
596
597        let tpl = r"<ul>
598  {{#each a}}
599    {{!-- comment --}}
600    <li>{{this}}</li>
601  {{/each}}
602</ul>";
603        assert_eq!(
604            r"<ul>
605    <li>0</li>
606    <li>1</li>
607</ul>",
608            reg.render_template(tpl, &json!({"a": [0, 1]})).unwrap()
609        );
610    }
611}