handlebars/decorators/
mod.rs

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<'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<'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<'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}