summaryrefslogtreecommitdiffstats
path: root/template/src/handlebars.rs
blob: 370e649ba2ae268d8429349718b45549cb9a7e16 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use hbs;
use serde::Serialize;
use failure::Error;


use super::{
    TemplateEngine,
    TemplateEngineCanHandleData,
    BodyTemplate,
    AdditionalCIds,
    serde_impl
};

//TODO[FEAT] add custom engine config section to loading
// e.g. something like:
// ```
// [engine]
// load_partial = "../partials/baseh.html"
// ```
//
// Just specific to each engine.


pub struct Handlebars {
    inner: hbs::Handlebars,
    name_counter: usize
}

impl Handlebars {

    pub fn new() -> Self {
        Handlebars {
            inner: hbs::Handlebars::new(),
            name_counter: 0
        }
    }

    pub fn inner(&self) -> &hbs::Handlebars {
        &self.inner
    }

    /// Provides mutable access to the underling handlebars instance.
    ///
    /// This can be used to e.g. add partials (in the future the template
    /// file will have a custom config section but currently it doesn't).
    pub fn inner_mut(&mut self) -> &mut hbs::Handlebars {
        &mut self.inner
    }

    fn next_body_template_name(&mut self) -> String {
        let name = format!("body_{}", self.name_counter);
        self.name_counter += 1;
        name
    }
}
impl TemplateEngine for Handlebars {
    type Id = String;

    type LazyBodyTemplate = serde_impl::StandardLazyBodyTemplate;

    fn load_body_template(&mut self, tmpl: Self::LazyBodyTemplate)
        -> Result<BodyTemplate<Self>, Error>
    {
        let serde_impl::StandardLazyBodyTemplate {
            path, embeddings, media_type
        } = tmpl;

        let name = self.next_body_template_name();
        self.inner.register_template_file(&name, &path)?;

        const ERR_BAD_MEDIA_TYPE_DETECTION: &str =
            "handlebars requires html/txt file extension or media type given in template spec";

        let media_type =
            if let Some(media_type) = media_type {
                media_type
            } else if let Some(extension) = path.extension().and_then(|osstr| osstr.to_str()) {
                match extension {
                    "html" => "text/html; charset=utf-8".parse().unwrap(),
                    "txt" => "text/plain; charset=utf-8".parse().unwrap(),
                    _ => { return Err(failure::err_msg(ERR_BAD_MEDIA_TYPE_DETECTION)); }
                }
            } else {
                return Err(failure::err_msg(ERR_BAD_MEDIA_TYPE_DETECTION));
            };

        Ok(BodyTemplate {
            template_id: name,
            media_type,
            inline_embeddings: embeddings,
        })
    }

    fn load_subject_template(&mut self, template_string: String)
        -> Result<Self::Id, Error>
    {
        let id = "subject".to_owned();
        self.inner.register_template_string(&id, template_string)?;
        Ok(id)
    }
}

/// Additional trait a template engine needs to implement for the types it can process as input.
///
/// This could for example be implemented in a wild card impl for the template engine for
/// any data `D` which implements `Serialize`.
impl<D> TemplateEngineCanHandleData<D> for Handlebars
    where D: Serialize
{
    fn render<'r, 'a>(
        &'r self,
        id: &'r Self::Id,
        data: &'r D,
        additional_cids: AdditionalCIds<'r>
    ) -> Result<String, Error> {
        Ok(self.inner.render(id, &SerHelper { data, cids: additional_cids })?)
    }
}

#[derive(Serialize)]
struct SerHelper<'r, D: 'r> {
    data: &'r D,
    cids: AdditionalCIds<'r>
}