1use crate::context::Context;
2use crate::error::RenderError;
3use crate::registry::Registry;
4use crate::render::{Decorator, RenderContext};
5
6pub use self::inline::INLINE_DECORATOR;
7
8pub type DecoratorResult = Result<(), RenderError>;
9
10/// Decorator Definition
11///
12/// Implement this trait to define your own decorators. Currently decorator
13/// shares same definition with helper.
14///
15/// In handlebars, it is recommended to use decorator to change context data and update helper
16/// definition.
17/// ## Updating context data
18///
19/// In decorator, you can change some context data you are about to render.
20///
21/// ```
22/// use handlebars::*;
23///
24/// fn update_data<'reg: 'rc, 'rc>(_: &Decorator, _: &Handlebars, ctx: &Context, rc: &mut RenderContext)
25/// -> Result<(), RenderError> {
26/// // modify json object
27/// let mut new_ctx = ctx.clone();
28/// {
29/// let mut data = new_ctx.data_mut();
30/// if let Some(ref mut m) = data.as_object_mut() {
31/// m.insert("hello".to_string(), to_json("world"));
32/// }
33/// }
34/// rc.set_context(new_ctx);
35/// Ok(())
36/// }
37///
38/// ```
39///
40/// ## Define local helper
41///
42/// You can override behavior of a helper from position of decorator to the end of template.
43///
44/// ```
45/// use handlebars::*;
46///
47/// fn override_helper(_: &Decorator, _: &Handlebars, _: &Context, rc: &mut RenderContext)
48/// -> Result<(), RenderError> {
49/// let new_helper = |h: &Helper, _: &Handlebars, _: &Context, rc: &mut RenderContext, out: &mut dyn Output|
50/// -> Result<(), RenderError> {
51/// // your helper logic
52/// Ok(())
53/// };
54/// rc.register_local_helper("distance", Box::new(new_helper));
55/// Ok(())
56/// }
57/// ```
58///
59pub trait DecoratorDef {
60 fn call<'reg: 'rc, 'rc>(
61 &'reg self,
62 d: &Decorator<'reg, 'rc>,
63 r: &'reg Registry<'reg>,
64 ctx: &'rc Context,
65 rc: &mut RenderContext<'reg, 'rc>,
66 ) -> DecoratorResult;
67}
68
69/// Implement DecoratorDef for bare function so we can use function as decorator
70impl<
71 F: for<'reg, 'rc> Fn(
72 &Decorator<'reg, 'rc>,
73 &'reg Registry<'reg>,
74 &'rc Context,
75 &mut RenderContext<'reg, 'rc>,
76 ) -> DecoratorResult,
77 > DecoratorDef for F
78{
79 fn call<'reg: 'rc, 'rc>(
80 &'reg self,
81 d: &Decorator<'reg, 'rc>,
82 reg: &'reg Registry<'reg>,
83 ctx: &'rc Context,
84 rc: &mut RenderContext<'reg, 'rc>,
85 ) -> DecoratorResult {
86 (*self)(d, reg, ctx, rc)
87 }
88}
89
90mod inline;
91
92#[cfg(test)]
93mod test {
94 use crate::context::Context;
95 use crate::error::RenderError;
96 use crate::json::value::{as_string, to_json};
97 use crate::output::Output;
98 use crate::registry::Registry;
99 use crate::render::{Decorator, Helper, RenderContext};
100
101 #[test]
102 fn test_register_decorator() {
103 let mut handlebars = Registry::new();
104 handlebars
105 .register_template_string("t0", "{{*foo}}".to_string())
106 .unwrap();
107
108 let data = json!({
109 "hello": "world"
110 });
111
112 assert!(handlebars.render("t0", &data).is_err());
113
114 handlebars.register_decorator(
115 "foo",
116 Box::new(
117 |_: &Decorator<'_, '_>,
118 _: &Registry<'_>,
119 _: &Context,
120 _: &mut RenderContext<'_, '_>|
121 -> Result<(), RenderError> { Ok(()) },
122 ),
123 );
124 assert_eq!(handlebars.render("t0", &data).ok().unwrap(), "".to_string());
125 }
126
127 // updating context data disabled for now
128 #[test]
129 fn test_update_data_with_decorator() {
130 let mut handlebars = Registry::new();
131 handlebars
132 .register_template_string("t0", "{{hello}}{{*foo}}{{hello}}".to_string())
133 .unwrap();
134
135 let data = json!({
136 "hello": "world"
137 });
138
139 handlebars.register_decorator(
140 "foo",
141 Box::new(
142 |_: &Decorator<'_, '_>,
143 _: &Registry<'_>,
144 ctx: &Context,
145 rc: &mut RenderContext<'_, '_>|
146 -> Result<(), RenderError> {
147 // modify json object
148 let mut new_ctx = ctx.clone();
149 {
150 let data = new_ctx.data_mut();
151 if let Some(ref mut m) = data.as_object_mut().as_mut() {
152 m.insert("hello".to_string(), to_json("war"));
153 }
154 }
155 rc.set_context(new_ctx);
156 Ok(())
157 },
158 ),
159 );
160
161 assert_eq!(
162 handlebars.render("t0", &data).ok().unwrap(),
163 "worldwar".to_string()
164 );
165
166 let data2 = 0;
167 handlebars.register_decorator(
168 "bar",
169 Box::new(
170 |d: &Decorator<'_, '_>,
171 _: &Registry<'_>,
172 _: &Context,
173 rc: &mut RenderContext<'_, '_>|
174 -> Result<(), RenderError> {
175 // modify value
176 let v = d
177 .param(0)
178 .and_then(|v| Context::wraps(v.value()).ok())
179 .unwrap_or(Context::null());
180 rc.set_context(v);
181 Ok(())
182 },
183 ),
184 );
185 handlebars
186 .register_template_string("t1", "{{this}}{{*bar 1}}{{this}}".to_string())
187 .unwrap();
188 assert_eq!(
189 handlebars.render("t1", &data2).ok().unwrap(),
190 "01".to_string()
191 );
192
193 handlebars
194 .register_template_string(
195 "t2",
196 "{{this}}{{*bar \"string_literal\"}}{{this}}".to_string(),
197 )
198 .unwrap();
199 assert_eq!(
200 handlebars.render("t2", &data2).ok().unwrap(),
201 "0string_literal".to_string()
202 );
203
204 handlebars
205 .register_template_string("t3", "{{this}}{{*bar}}{{this}}".to_string())
206 .unwrap();
207 assert_eq!(
208 handlebars.render("t3", &data2).ok().unwrap(),
209 "0".to_string()
210 );
211 }
212
213 #[test]
214 fn test_local_helper_with_decorator() {
215 let mut handlebars = Registry::new();
216 handlebars
217 .register_template_string(
218 "t0",
219 "{{distance 4.5}},{{*foo \"miles\"}}{{distance 10.1}},{{*bar}}{{distance 3.4}}"
220 .to_string(),
221 )
222 .unwrap();
223
224 handlebars.register_helper(
225 "distance",
226 Box::new(
227 |h: &Helper<'_, '_>,
228 _: &Registry<'_>,
229 _: &Context,
230 _: &mut RenderContext<'_, '_>,
231 out: &mut dyn Output|
232 -> Result<(), RenderError> {
233 write!(
234 out,
235 "{}m",
236 h.param(0)
237 .as_ref()
238 .map(|v| v.value())
239 .unwrap_or(&to_json(0))
240 )?;
241 Ok(())
242 },
243 ),
244 );
245 handlebars.register_decorator(
246 "foo",
247 Box::new(
248 |d: &Decorator<'_, '_>,
249 _: &Registry<'_>,
250 _: &Context,
251 rc: &mut RenderContext<'_, '_>|
252 -> Result<(), RenderError> {
253 let new_unit = d
254 .param(0)
255 .as_ref()
256 .and_then(|v| as_string(v.value()))
257 .unwrap_or("")
258 .to_owned();
259 let new_helper = move |h: &Helper<'_, '_>,
260 _: &Registry<'_>,
261 _: &Context,
262 _: &mut RenderContext<'_, '_>,
263 out: &mut dyn Output|
264 -> Result<(), RenderError> {
265 write!(
266 out,
267 "{}{}",
268 h.param(0)
269 .as_ref()
270 .map(|v| v.value())
271 .unwrap_or(&to_json(0)),
272 new_unit
273 )?;
274 Ok(())
275 };
276
277 rc.register_local_helper("distance", Box::new(new_helper));
278 Ok(())
279 },
280 ),
281 );
282 handlebars.register_decorator(
283 "bar",
284 Box::new(
285 |_: &Decorator<'_, '_>,
286 _: &Registry<'_>,
287 _: &Context,
288 rc: &mut RenderContext<'_, '_>|
289 -> Result<(), RenderError> {
290 rc.unregister_local_helper("distance");
291 Ok(())
292 },
293 ),
294 );
295 assert_eq!(
296 handlebars.render("t0", &0).ok().unwrap(),
297 "4.5m,10.1miles,3.4m".to_owned()
298 );
299 }
300}
301