Skip to main content

handlebars_iron/
middleware.rs

1use std::sync::{RwLock, RwLockWriteGuard};
2
3use iron::headers::ContentType;
4use iron::modifier::Modifier;
5use iron::prelude::*;
6use iron::status;
7use iron::{typemap, AfterMiddleware};
8use plugin::Plugin as PluginFor;
9
10use handlebars::{to_json, Handlebars, TemplateRenderError};
11use serde::ser::Serialize as ToJson;
12use serde_json::value::Value as Json;
13
14use source::{Source, SourceError};
15
16#[derive(Clone)]
17pub struct Template {
18    name: Option<String>,
19    content: Option<String>,
20    value: Json,
21}
22
23impl Template {
24    /// render some template from pre-registered templates
25    pub fn new<T: ToJson>(name: &str, value: T) -> Template {
26        Template {
27            name: Some(name.to_string()),
28            value: to_json(&value),
29            content: None,
30        }
31    }
32
33    /// render some template with temporary template string
34    pub fn with<T: ToJson>(content: &str, value: T) -> Template {
35        Template {
36            name: None,
37            value: to_json(&value),
38            content: Some(content.to_string()),
39        }
40    }
41}
42
43/// The handlebars template engine
44pub struct HandlebarsEngine {
45    pub sources: Vec<Box<dyn Source + Send + Sync>>,
46    pub registry: RwLock<Box<Handlebars<'static>>>,
47}
48
49impl typemap::Key for HandlebarsEngine {
50    type Value = Template;
51}
52
53impl Modifier<Response> for Template {
54    fn modify(self, resp: &mut Response) {
55        resp.extensions.insert::<HandlebarsEngine>(self);
56    }
57}
58
59impl PluginFor<Response> for HandlebarsEngine {
60    type Error = ();
61
62    fn eval(resp: &mut Response) -> Result<Template, ()> {
63        match resp.extensions.get::<HandlebarsEngine>() {
64            Some(t) => Ok(t.clone()),
65            None => Err(()),
66        }
67    }
68}
69
70impl HandlebarsEngine {
71    /// create a handlebars template engine
72    pub fn new() -> HandlebarsEngine {
73        HandlebarsEngine {
74            sources: Vec::new(),
75            registry: RwLock::new(Box::new(Handlebars::new())),
76        }
77    }
78
79    /// create a handlebars template engine from existed handlebars registry
80    pub fn from(reg: Handlebars<'static>) -> HandlebarsEngine {
81        HandlebarsEngine {
82            sources: Vec::new(),
83            registry: RwLock::new(Box::new(reg)),
84        }
85    }
86
87    /// add a template source
88    pub fn add(&mut self, source: Box<dyn Source + Send + Sync>) {
89        self.sources.push(source);
90    }
91
92    /// load template from registered sources
93    pub fn reload(&self) -> Result<(), SourceError> {
94        let mut hbs = self.handlebars_mut();
95        hbs.clear_templates();
96        for s in self.sources.iter() {
97            s.load(&mut hbs)?
98        }
99        Ok(())
100    }
101
102    /// access internal handlebars registry, useful to register custom helpers
103    pub fn handlebars_mut(&self) -> RwLockWriteGuard<Box<Handlebars<'static>>> {
104        self.registry.write().unwrap()
105    }
106}
107
108impl AfterMiddleware for HandlebarsEngine {
109    fn after(&self, _: &mut Request, mut resp: Response) -> IronResult<Response> {
110        let page_wrapper = resp.extensions.remove::<HandlebarsEngine>().and_then(|h| {
111            let hbs = self.registry.read().unwrap();
112            if let Some(ref name) = h.name {
113                Some(
114                    hbs.render(name, &h.value)
115                        .map_err(TemplateRenderError::from),
116                )
117            } else if let Some(ref content) = h.content {
118                Some(hbs.render_template(content, &h.value))
119            } else {
120                None
121            }
122        });
123
124        match page_wrapper {
125            Some(page_result) => match page_result {
126                Ok(page) => {
127                    if !resp.headers.has::<ContentType>() {
128                        resp.headers.set(ContentType::html());
129                    }
130                    resp.set_mut(page);
131                    Ok(resp)
132                }
133                Err(e) => {
134                    info!("{}", e);
135                    Err(IronError::new(e, status::InternalServerError))
136                }
137            },
138            None => Ok(resp),
139        }
140    }
141
142    fn catch(&self, req: &mut Request, mut err: IronError) -> IronResult<Response> {
143        err.response = self.after(req, err.response)?;
144        Err(err)
145    }
146}
147
148#[cfg(test)]
149mod test {
150    use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError};
151    use iron::prelude::*;
152    use middleware::*;
153    use std::collections::BTreeMap;
154
155    fn hello_world() -> IronResult<Response> {
156        let resp = Response::new();
157
158        let mut data = BTreeMap::new();
159        data.insert("title".to_owned(), "Handlebars on Iron".to_owned());
160
161        Ok(resp.set(Template::new("index", data)))
162    }
163
164    fn hello_world2() -> IronResult<Response> {
165        let resp = Response::new();
166
167        let mut data = BTreeMap::new();
168        data.insert("title".to_owned(), "Handlebars on Iron".to_owned());
169
170        Ok(resp.set(Template::with("{{title}}", data)))
171    }
172
173    #[test]
174    fn test_resp_set() {
175        let mut resp = hello_world().ok().expect("response expected");
176
177        // use response plugin to retrieve a cloned template for testing
178        match resp.get::<HandlebarsEngine>() {
179            Ok(h) => {
180                assert_eq!(h.name.unwrap(), "index".to_string());
181                assert_eq!(
182                    h.value
183                        .as_object()
184                        .unwrap()
185                        .get(&"title".to_string())
186                        .unwrap()
187                        .as_str()
188                        .unwrap(),
189                    "Handlebars on Iron"
190                );
191            }
192            _ => panic!("template expected"),
193        }
194    }
195
196    #[test]
197    fn test_resp_set2() {
198        let mut resp = hello_world2().ok().expect("response expected");
199
200        // use response plugin to retrieve a cloned template for testing
201        match resp.get::<HandlebarsEngine>() {
202            Ok(h) => {
203                assert_eq!(h.content.unwrap(), "{{title}}".to_string());
204                assert_eq!(
205                    h.value
206                        .as_object()
207                        .unwrap()
208                        .get(&"title".to_string())
209                        .unwrap()
210                        .as_str()
211                        .unwrap(),
212                    "Handlebars on Iron"
213                );
214            }
215            _ => panic!("template expected"),
216        }
217    }
218
219    #[test]
220    fn test_register_helper() {
221        let hbs = HandlebarsEngine::new();
222        let mut reg = hbs.handlebars_mut();
223        reg.register_helper(
224            "ignore",
225            Box::new(
226                |_: &Helper,
227                 _: &Handlebars,
228                 _: &Context,
229                 _: &mut RenderContext,
230                 _: &mut dyn Output|
231                 -> Result<(), RenderError> { Ok(()) },
232            ),
233        );
234    }
235}