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
use std::collections::HashMap;

use templates::{TemplateInfo, serde::Serialize};

#[cfg(feature = "tera_templates")] use templates::tera::Tera;
#[cfg(feature = "handlebars_templates")] use templates::handlebars::Handlebars;

crate trait Engine: Send + Sync + 'static {
    const EXT: &'static str;

    fn init(templates: &[(&str, &TemplateInfo)]) -> Option<Self> where Self: Sized;
    fn render<C: Serialize>(&self, name: &str, context: C) -> Option<String>;
}

/// A structure exposing access to templating engines.
///
/// Calling methods on the exposed template engine types may require importing
/// types from the respective templating engine library. These types should be
/// imported from the reexported crate at the root of `rocket_contrib` to avoid
/// version mismatches. For instance, when registering a Tera filter, the
/// [`tera::Value`] and [`tera::Result`] types are required. Import them from
/// `rocket_contrib::templates::tera`. The example below illustrates this:
///
/// ```rust
/// # #[cfg(feature = "tera_templates")] {
/// use std::collections::HashMap;
///
/// use rocket_contrib::templates::{Template, Engines};
/// use rocket_contrib::templates::tera::{self, Value};
///
/// fn my_filter(value: Value, _: HashMap<String, Value>) -> tera::Result<Value> {
///     # /*
///     ...
///     # */ unimplemented!();
/// }
///
/// Template::custom(|engines: &mut Engines| {
///     engines.tera.register_filter("my_filter", my_filter);
/// });
/// # }
/// ```
///
/// [`tera::Value`]: ::templates::tera::Value
/// [`tera::Result`]: ::templates::tera::Result
pub struct Engines {
    /// A `Tera` templating engine. This field is only available when the
    /// `tera_templates` feature is enabled. When calling methods on the `Tera`
    /// instance, ensure you use types imported from
    /// `rocket_contrib::templates::tera` to avoid version mismatches.
    #[cfg(feature = "tera_templates")]
    pub tera: Tera,
    /// The Handlebars templating engine. This field is only available when the
    /// `handlebars_templates` feature is enabled. When calling methods on the
    /// `Tera` instance, ensure you use types imported from
    /// `rocket_contrib::templates::handlebars` to avoid version mismatches.
    #[cfg(feature = "handlebars_templates")]
    pub handlebars: Handlebars,
}

impl Engines {
    crate const ENABLED_EXTENSIONS: &'static [&'static str] = &[
        #[cfg(feature = "tera_templates")] Tera::EXT,
        #[cfg(feature = "handlebars_templates")] Handlebars::EXT,
    ];

    crate fn init(templates: &HashMap<String, TemplateInfo>) -> Option<Engines> {
        fn inner<E: Engine>(templates: &HashMap<String, TemplateInfo>) -> Option<E> {
            let named_templates = templates.iter()
                .filter(|&(_, i)| i.extension == E::EXT)
                .map(|(k, i)| (k.as_str(), i))
                .collect::<Vec<_>>();

            E::init(&*named_templates)
        }

        Some(Engines {
            #[cfg(feature = "tera_templates")]
            tera: match inner::<Tera>(templates) {
                Some(tera) => tera,
                None => return None
            },
            #[cfg(feature = "handlebars_templates")]
            handlebars: match inner::<Handlebars>(templates) {
                Some(hb) => hb,
                None => return None
            },
        })
    }

    crate fn render<C: Serialize>(
        &self,
        name: &str,
        info: &TemplateInfo,
        context: C
    ) -> Option<String> {
        #[cfg(feature = "tera_templates")]
        {
            if info.extension == Tera::EXT {
                return Engine::render(&self.tera, name, context);
            }
        }

        #[cfg(feature = "handlebars_templates")]
        {
            if info.extension == Handlebars::EXT {
                return Engine::render(&self.handlebars, name, context);
            }
        }

        None
    }
}