rocket_dyn_templates/engine/
mod.rs

1use std::path::Path;
2use std::collections::HashMap;
3
4use rocket::serde::Serialize;
5
6use crate::template::TemplateInfo;
7
8#[cfg(feature = "tera")]
9mod tera;
10#[cfg(feature = "tera")]
11use ::tera::Tera;
12
13#[cfg(feature = "handlebars")]
14mod handlebars;
15#[cfg(feature = "handlebars")]
16use ::handlebars::Handlebars;
17
18#[cfg(feature = "minijinja")]
19mod minijinja;
20#[cfg(feature = "minijinja")]
21use ::minijinja::Environment;
22
23pub(crate) trait Engine: Send + Sync + Sized + 'static {
24    const EXT: &'static str;
25
26    fn init<'a>(templates: impl Iterator<Item = (&'a str, &'a Path)>) -> Option<Self>;
27    fn render<C: Serialize>(&self, name: &str, context: C) -> Option<String>;
28}
29
30/// A structure exposing access to templating engines.
31///
32/// Calling methods on the exposed template engine types may require importing
33/// types from the respective templating engine library. These types should be
34/// imported from the reexported crate at the root of `rocket_dyn_templates` to
35/// avoid version mismatches. For instance, when registering a Tera filter, the
36/// [`tera::Value`] and [`tera::Result`] types are required. Import them from
37/// `rocket_dyn_templates::tera`. The example below illustrates this:
38///
39/// ```rust
40/// # #[cfg(feature = "tera")] {
41/// use std::collections::HashMap;
42///
43/// use rocket_dyn_templates::{Template, Engines};
44/// use rocket_dyn_templates::tera::{self, Value};
45///
46/// fn my_filter(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
47///     # /*
48///     ...
49///     # */ unimplemented!();
50/// }
51///
52/// fn main() {
53///     rocket::build()
54///         // ...
55///         .attach(Template::custom(|engines: &mut Engines| {
56///             engines.tera.register_filter("my_filter", my_filter);
57///         }))
58///         // ...
59///         # ;
60/// }
61/// # }
62/// ```
63///
64/// [`tera::Value`]: crate::tera::Value
65/// [`tera::Result`]: crate::tera::Result
66pub struct Engines {
67    /// A `Tera` templating engine.
68    ///
69    /// This field is only available when the `tera` feature is enabled. When
70    /// calling methods on the `Tera` instance, ensure you use types imported
71    /// from `rocket_dyn_templates::tera` to avoid version mismatches.
72    #[cfg(feature = "tera")]
73    pub tera: Tera,
74
75    /// The Handlebars templating engine.
76    ///
77    /// This field is only available when the `handlebars` feature is enabled.
78    /// When calling methods on the `Handlebars` instance, ensure you use types
79    /// imported from `rocket_dyn_templates::handlebars` to avoid version
80    /// mismatches.
81    #[cfg(feature = "handlebars")]
82    pub handlebars: Handlebars<'static>,
83
84    /// The minijinja templating engine.
85    ///
86    /// This field is only available when the `minijinja` feature is enabled.
87    /// When calling methods on the [`Environment`] instance, ensure you use
88    /// types imported from `rocket_dyn_templates::minijinja` to avoid version
89    /// mismatches.
90    #[cfg(feature = "minijinja")]
91    pub minijinja: Environment<'static>,
92}
93
94impl Engines {
95    pub(crate) const ENABLED_EXTENSIONS: &'static [&'static str] = &[
96        #[cfg(feature = "tera")] Tera::EXT,
97        #[cfg(feature = "handlebars")] Handlebars::EXT,
98        #[cfg(feature = "minijinja")] Environment::EXT,
99    ];
100
101    pub(crate) fn init(templates: &HashMap<String, TemplateInfo>) -> Option<Engines> {
102        fn inner<E: Engine>(templates: &HashMap<String, TemplateInfo>) -> Option<E> {
103            let named_templates = templates.iter()
104                .filter(|&(_, i)| i.engine_ext == E::EXT)
105                .filter_map(|(k, i)| Some((k.as_str(), i.path.as_ref()?)))
106                .map(|(k, p)| (k, p.as_path()));
107
108            E::init(named_templates)
109        }
110
111        Some(Engines {
112            #[cfg(feature = "tera")]
113            tera: match inner::<Tera>(templates) {
114                Some(tera) => tera,
115                None => return None
116            },
117            #[cfg(feature = "handlebars")]
118            handlebars: match inner::<Handlebars<'static>>(templates) {
119                Some(hb) => hb,
120                None => return None
121            },
122            #[cfg(feature = "minijinja")]
123            minijinja: match inner::<Environment<'static>>(templates) {
124                Some(hb) => hb,
125                None => return None
126            },
127        })
128    }
129
130    pub(crate) fn render<C: Serialize>(
131        &self,
132        name: &str,
133        info: &TemplateInfo,
134        context: C,
135    ) -> Option<String> {
136        #[cfg(feature = "tera")] {
137            if info.engine_ext == Tera::EXT {
138                return Engine::render(&self.tera, name, context);
139            }
140        }
141
142        #[cfg(feature = "handlebars")] {
143            if info.engine_ext == Handlebars::EXT {
144                return Engine::render(&self.handlebars, name, context);
145            }
146        }
147
148        #[cfg(feature = "minijinja")] {
149            if info.engine_ext == Environment::EXT {
150                return Engine::render(&self.minijinja, name, context);
151            }
152        }
153
154        None
155    }
156
157    /// Returns iterator over template (name, engine_extension).
158    pub(crate) fn templates(&self) -> impl Iterator<Item = (&str, &'static str)> {
159        #[cfg(feature = "tera")]
160        let tera = self.tera.get_template_names().map(|name| (name, Tera::EXT));
161
162        #[cfg(feature = "handlebars")]
163        let handlebars = self.handlebars.get_templates().keys()
164                .map(|name| (name.as_str(), Handlebars::EXT));
165
166        #[cfg(feature = "minijinja")]
167        let minijinja = self.minijinja.templates()
168            .map(|(name, _)| (name, Environment::EXT));
169
170        #[cfg(not(feature = "tera"))] let tera = std::iter::empty();
171        #[cfg(not(feature = "handlebars"))] let handlebars = std::iter::empty();
172        #[cfg(not(feature = "minijinja"))] let minijinja = std::iter::empty();
173
174        tera.chain(handlebars).chain(minijinja)
175    }
176}