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