handlebars/helpers/
helper_with.rs

1use super::block_util::create_block;
2use crate::block::BlockParams;
3use crate::context::Context;
4use crate::error::RenderError;
5use crate::helpers::{HelperDef, HelperResult};
6use crate::json::value::JsonTruthy;
7use crate::output::Output;
8use crate::registry::Registry;
9use crate::render::{Helper, RenderContext, Renderable};
10use crate::RenderErrorReason;
11
12#[derive(Clone, Copy)]
13pub struct WithHelper;
14
15impl HelperDef for WithHelper {
16    fn call<'reg: 'rc, 'rc>(
17        &self,
18        h: &Helper<'rc>,
19        r: &'reg Registry<'reg>,
20        ctx: &'rc Context,
21        rc: &mut RenderContext<'reg, 'rc>,
22        out: &mut dyn Output,
23    ) -> HelperResult {
24        let param = h
25            .param(0)
26            .ok_or(RenderErrorReason::ParamNotFoundForIndex("with", 0))?;
27
28        if param.value().is_truthy(false) {
29            let mut block = create_block(param);
30
31            if let Some(block_param) = h.block_param() {
32                let mut params = BlockParams::new();
33                if param.context_path().is_some() {
34                    params.add_path(block_param, Vec::with_capacity(0))?;
35                } else {
36                    params.add_value(block_param, param.value().clone())?;
37                }
38
39                block.set_block_params(params);
40            }
41
42            rc.push_block(block);
43
44            if let Some(t) = h.template() {
45                t.render(r, ctx, rc, out)?;
46            };
47
48            rc.pop_block();
49            Ok(())
50        } else if let Some(t) = h.inverse() {
51            t.render(r, ctx, rc, out)
52        } else if r.strict_mode() {
53            Err(RenderError::strict_error(param.relative_path()))
54        } else {
55            Ok(())
56        }
57    }
58}
59
60pub static WITH_HELPER: WithHelper = WithHelper;
61
62#[cfg(test)]
63mod test {
64    use crate::registry::Registry;
65
66    #[derive(Serialize)]
67    struct Address {
68        city: String,
69        country: String,
70    }
71
72    #[derive(Serialize)]
73    struct Person {
74        name: String,
75        age: i16,
76        addr: Address,
77        titles: Vec<String>,
78    }
79
80    #[test]
81    fn test_with() {
82        let addr = Address {
83            city: "Beijing".to_string(),
84            country: "China".to_string(),
85        };
86
87        let person = Person {
88            name: "Ning Sun".to_string(),
89            age: 27,
90            addr,
91            titles: vec!["programmer".to_string(), "cartographier".to_string()],
92        };
93
94        let mut handlebars = Registry::new();
95        assert!(handlebars
96            .register_template_string("t0", "{{#with addr}}{{city}}{{/with}}")
97            .is_ok());
98        assert!(handlebars
99            .register_template_string("t1", "{{#with notfound}}hello{{else}}world{{/with}}")
100            .is_ok());
101        assert!(handlebars
102            .register_template_string("t2", "{{#with addr/country}}{{this}}{{/with}}")
103            .is_ok());
104
105        let r0 = handlebars.render("t0", &person);
106        assert_eq!(r0.ok().unwrap(), "Beijing".to_string());
107
108        let r1 = handlebars.render("t1", &person);
109        assert_eq!(r1.ok().unwrap(), "world".to_string());
110
111        let r2 = handlebars.render("t2", &person);
112        assert_eq!(r2.ok().unwrap(), "China".to_string());
113    }
114
115    #[test]
116    fn test_with_block_param() {
117        let addr = Address {
118            city: "Beijing".to_string(),
119            country: "China".to_string(),
120        };
121
122        let person = Person {
123            name: "Ning Sun".to_string(),
124            age: 27,
125            addr,
126            titles: vec!["programmer".to_string(), "cartographier".to_string()],
127        };
128
129        let mut handlebars = Registry::new();
130        assert!(handlebars
131            .register_template_string("t0", "{{#with addr as |a|}}{{a.city}}{{/with}}")
132            .is_ok());
133        assert!(handlebars
134            .register_template_string("t1", "{{#with notfound as |c|}}hello{{else}}world{{/with}}")
135            .is_ok());
136        assert!(handlebars
137            .register_template_string("t2", "{{#with addr/country as |t|}}{{t}}{{/with}}")
138            .is_ok());
139
140        let r0 = handlebars.render("t0", &person);
141        assert_eq!(r0.ok().unwrap(), "Beijing".to_string());
142
143        let r1 = handlebars.render("t1", &person);
144        assert_eq!(r1.ok().unwrap(), "world".to_string());
145
146        let r2 = handlebars.render("t2", &person);
147        assert_eq!(r2.ok().unwrap(), "China".to_string());
148    }
149
150    #[test]
151    fn test_with_in_each() {
152        let addr = Address {
153            city: "Beijing".to_string(),
154            country: "China".to_string(),
155        };
156
157        let person = Person {
158            name: "Ning Sun".to_string(),
159            age: 27,
160            addr,
161            titles: vec!["programmer".to_string(), "cartographier".to_string()],
162        };
163
164        let addr2 = Address {
165            city: "Beijing".to_string(),
166            country: "China".to_string(),
167        };
168
169        let person2 = Person {
170            name: "Ning Sun".to_string(),
171            age: 27,
172            addr: addr2,
173            titles: vec!["programmer".to_string(), "cartographier".to_string()],
174        };
175
176        let people = vec![person, person2];
177
178        let mut handlebars = Registry::new();
179        assert!(handlebars
180            .register_template_string(
181                "t0",
182                "{{#each this}}{{#with addr}}{{city}}{{/with}}{{/each}}"
183            )
184            .is_ok());
185        assert!(handlebars
186            .register_template_string(
187                "t1",
188                "{{#each this}}{{#with addr}}{{../age}}{{/with}}{{/each}}"
189            )
190            .is_ok());
191        assert!(handlebars
192            .register_template_string(
193                "t2",
194                "{{#each this}}{{#with addr}}{{@../index}}{{/with}}{{/each}}"
195            )
196            .is_ok());
197
198        let r0 = handlebars.render("t0", &people);
199        assert_eq!(r0.ok().unwrap(), "BeijingBeijing".to_string());
200
201        let r1 = handlebars.render("t1", &people);
202        assert_eq!(r1.ok().unwrap(), "2727".to_string());
203
204        let r2 = handlebars.render("t2", &people);
205        assert_eq!(r2.ok().unwrap(), "01".to_string());
206    }
207
208    #[test]
209    fn test_path_up() {
210        let mut handlebars = Registry::new();
211        assert!(handlebars
212            .register_template_string("t0", "{{#with a}}{{#with b}}{{../../d}}{{/with}}{{/with}}")
213            .is_ok());
214        let data = json!({
215            "a": {
216                "b": [{"c": [1]}]
217            },
218            "d": 1
219        });
220
221        let r0 = handlebars.render("t0", &data);
222        assert_eq!(r0.ok().unwrap(), "1".to_string());
223    }
224
225    #[test]
226    fn test_else_context() {
227        let reg = Registry::new();
228        let template = "{{#with list}}A{{else}}{{foo}}{{/with}}";
229        let input = json!({"list": [], "foo": "bar"});
230        let rendered = reg.render_template(template, &input).unwrap();
231        assert_eq!("bar", rendered);
232    }
233
234    #[test]
235    fn test_derived_value() {
236        let hb = Registry::new();
237        let data = json!({"a": {"b": {"c": "d"}}});
238        let template = "{{#with (lookup a.b \"c\")}}{{this}}{{/with}}";
239        assert_eq!("d", hb.render_template(template, &data).unwrap());
240    }
241
242    #[test]
243    fn test_nested_derived_value() {
244        let hb = Registry::new();
245        let data = json!({"a": {"b": {"c": "d"}}});
246        let template = "{{#with (lookup a \"b\")}}{{#with this}}{{c}}{{/with}}{{/with}}";
247        assert_eq!("d", hb.render_template(template, &data).unwrap());
248    }
249
250    #[test]
251    fn test_strict_with() {
252        let mut hb = Registry::new();
253
254        assert_eq!(
255            hb.render_template("{{#with name}}yes{{/with}}", &json!({}))
256                .unwrap(),
257            ""
258        );
259        assert_eq!(
260            hb.render_template("{{#with name}}yes{{else}}no{{/with}}", &json!({}))
261                .unwrap(),
262            "no"
263        );
264
265        hb.set_strict_mode(true);
266
267        assert!(hb
268            .render_template("{{#with name}}yes{{/with}}", &json!({}))
269            .is_err());
270        assert_eq!(
271            hb.render_template("{{#with name}}yes{{else}}no{{/with}}", &json!({}))
272                .unwrap(),
273            "no"
274        );
275    }
276}