mail_template/
handlebars.rs

1use hbs;
2use serde::Serialize;
3use failure::Error;
4
5
6use super::{
7    TemplateEngine,
8    TemplateEngineCanHandleData,
9    BodyTemplate,
10    AdditionalCIds,
11    serde_impl
12};
13
14//TODO[FEAT] add custom engine config section to loading
15// e.g. something like:
16// ```
17// [engine]
18// load_partial = "../partials/baseh.html"
19// ```
20//
21// Just specific to each engine.
22
23
24pub struct Handlebars {
25    inner: hbs::Handlebars,
26    name_counter: usize
27}
28
29impl Handlebars {
30
31    pub fn new() -> Self {
32        Handlebars {
33            inner: hbs::Handlebars::new(),
34            name_counter: 0
35        }
36    }
37
38    pub fn inner(&self) -> &hbs::Handlebars {
39        &self.inner
40    }
41
42    /// Provides mutable access to the underling handlebars instance.
43    ///
44    /// This can be used to e.g. add partials (in the future the template
45    /// file will have a custom config section but currently it doesn't).
46    pub fn inner_mut(&mut self) -> &mut hbs::Handlebars {
47        &mut self.inner
48    }
49
50    fn next_body_template_name(&mut self) -> String {
51        let name = format!("body_{}", self.name_counter);
52        self.name_counter += 1;
53        name
54    }
55}
56impl TemplateEngine for Handlebars {
57    type Id = String;
58
59    type LazyBodyTemplate = serde_impl::StandardLazyBodyTemplate;
60
61    fn load_body_template(&mut self, tmpl: Self::LazyBodyTemplate)
62        -> Result<BodyTemplate<Self>, Error>
63    {
64        let serde_impl::StandardLazyBodyTemplate {
65            path, embeddings, media_type
66        } = tmpl;
67
68        let name = self.next_body_template_name();
69        self.inner.register_template_file(&name, &path)?;
70
71        const ERR_BAD_MEDIA_TYPE_DETECTION: &str =
72            "handlebars requires html/txt file extension or media type given in template spec";
73
74        let media_type =
75            if let Some(media_type) = media_type {
76                media_type
77            } else if let Some(extension) = path.extension().and_then(|osstr| osstr.to_str()) {
78                match extension {
79                    "html" => "text/html; charset=utf-8".parse().unwrap(),
80                    "txt" => "text/plain; charset=utf-8".parse().unwrap(),
81                    _ => { return Err(failure::err_msg(ERR_BAD_MEDIA_TYPE_DETECTION)); }
82                }
83            } else {
84                return Err(failure::err_msg(ERR_BAD_MEDIA_TYPE_DETECTION));
85            };
86
87        Ok(BodyTemplate {
88            template_id: name,
89            media_type,
90            inline_embeddings: embeddings,
91        })
92    }
93
94    fn load_subject_template(&mut self, template_string: String)
95        -> Result<Self::Id, Error>
96    {
97        let id = "subject".to_owned();
98        self.inner.register_template_string(&id, template_string)?;
99        Ok(id)
100    }
101}
102
103/// Additional trait a template engine needs to implement for the types it can process as input.
104///
105/// This could for example be implemented in a wild card impl for the template engine for
106/// any data `D` which implements `Serialize`.
107impl<D> TemplateEngineCanHandleData<D> for Handlebars
108    where D: Serialize
109{
110    fn render<'r, 'a>(
111        &'r self,
112        id: &'r Self::Id,
113        data: &'r D,
114        additional_cids: AdditionalCIds<'r>
115    ) -> Result<String, Error> {
116        Ok(self.inner.render(id, &SerHelper { data, cids: additional_cids })?)
117    }
118}
119
120#[derive(Serialize)]
121struct SerHelper<'r, D: 'r> {
122    data: &'r D,
123    cids: AdditionalCIds<'r>
124}