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}}")
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(), String::new());
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}}")
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}}")
187            .unwrap();
188        assert_eq!(
189            handlebars.render("t1", &data2).ok().unwrap(),
190            "01".to_string()
191        );
192
193        handlebars
194            .register_template_string("t2", "{{this}}{{*bar \"string_literal\"}}{{this}}")
195            .unwrap();
196        assert_eq!(
197            handlebars.render("t2", &data2).ok().unwrap(),
198            "0string_literal".to_string()
199        );
200
201        handlebars
202            .register_template_string("t3", "{{this}}{{*bar}}{{this}}")
203            .unwrap();
204        assert_eq!(
205            handlebars.render("t3", &data2).ok().unwrap(),
206            "0".to_string()
207        );
208    }
209
210    #[test]
211    fn test_local_helper_with_decorator() {
212        let mut handlebars = Registry::new();
213        handlebars
214            .register_template_string(
215                "t0",
216                "{{distance 4.5}},{{*foo \"miles\"}}{{distance 10.1}},{{*bar}}{{distance 3.4}}",
217            )
218            .unwrap();
219
220        handlebars.register_helper(
221            "distance",
222            Box::new(
223                |h: &Helper<'_>,
224                 _: &Registry<'_>,
225                 _: &Context,
226                 _: &mut RenderContext<'_, '_>,
227                 out: &mut dyn Output|
228                 -> Result<(), RenderError> {
229                    write!(
230                        out,
231                        "{}m",
232                        h.param(0).as_ref().map_or(&to_json(0), |v| v.value())
233                    )?;
234                    Ok(())
235                },
236            ),
237        );
238        handlebars.register_decorator(
239            "foo",
240            Box::new(
241                |d: &Decorator<'_>,
242                 _: &Registry<'_>,
243                 _: &Context,
244                 rc: &mut RenderContext<'_, '_>|
245                 -> Result<(), RenderError> {
246                    let new_unit = d
247                        .param(0)
248                        .as_ref()
249                        .and_then(|v| as_string(v.value()))
250                        .unwrap_or("")
251                        .to_owned();
252                    let new_helper = move |h: &Helper<'_>,
253                                           _: &Registry<'_>,
254                                           _: &Context,
255                                           _: &mut RenderContext<'_, '_>,
256                                           out: &mut dyn Output|
257                          -> Result<(), RenderError> {
258                        write!(
259                            out,
260                            "{}{}",
261                            h.param(0).as_ref().map_or(&to_json(0), |v| v.value()),
262                            new_unit
263                        )?;
264                        Ok(())
265                    };
266
267                    rc.register_local_helper("distance", Box::new(new_helper));
268                    Ok(())
269                },
270            ),
271        );
272        handlebars.register_decorator(
273            "bar",
274            Box::new(
275                |_: &Decorator<'_>,
276                 _: &Registry<'_>,
277                 _: &Context,
278                 rc: &mut RenderContext<'_, '_>|
279                 -> Result<(), RenderError> {
280                    rc.unregister_local_helper("distance");
281                    Ok(())
282                },
283            ),
284        );
285        assert_eq!(
286            handlebars.render("t0", &0).ok().unwrap(),
287            "4.5m,10.1miles,3.4m".to_owned()
288        );
289    }
290}