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}