Skip to main content

tide_tera/
lib.rs

1//! # Tide-Tera integration This crate exposes [an extension
2//! trait](TideTeraExt) that adds two methods to [`tera::Tera`]:
3//! [`render_response`](TideTeraExt::render_response) and
4//! [`render_body`](TideTeraExt::render_body). It also adds a
5//! convenience [`context`] macro for creating ad-hoc tera
6//! [`Context`](tera::Context)s.
7
8use std::path::PathBuf;
9use tera::{Context, Tera};
10use tide::{http::Mime, Body, Response, Result};
11
12/// This extension trait adds two methods to [`tera::Tera`]:
13/// [`render_response`](TideTeraExt::render_response) and
14/// [`render_body`](TideTeraExt::render_body)
15pub trait TideTeraExt {
16    /// `render_body` returns a fully-rendered [`tide::Body`] with mime
17    /// type set based on the template name file extension using the
18    /// logic at [`tide::http::Mime::from_extension`]. This will
19    /// return an `Err` variant if the render was unsuccessful.
20    ///
21    /// ```rust
22    /// use tide_tera::prelude::*;
23    /// let tera = tera::Tera::new("tests/templates/**/*").unwrap();
24    /// let response = tera
25    ///     .render_response("good_template.html", &context! { "name" => "tide" })
26    ///     .unwrap();
27    /// assert_eq!(response.content_type(), Some(tide::http::mime::HTML));
28    ///```
29    fn render_response(&self, template_name: &str, context: &Context) -> Result;
30    /// `render_response` returns a tide Response with a body rendered
31    /// with [`render_body`](TideTeraExt::render_body). This will
32    /// return an `Err` variant if the render was unsuccessful.
33    ///
34    /// ```rust
35    /// use tide_tera::prelude::*;
36    /// let tera = tera::Tera::new("tests/templates/**/*").unwrap();
37    /// let body = tera
38    ///     .render_body("good_template.html", &context! { "name" => "tide" })
39    ///     .unwrap();
40    /// assert_eq!(body.mime(), &tide::http::mime::HTML);
41    ///```
42    fn render_body(&self, template_name: &str, context: &Context) -> Result<Body>;
43}
44
45impl TideTeraExt for Tera {
46    fn render_body(&self, template_name: &str, context: &Context) -> Result<Body> {
47        let string = self.render(template_name, context)?;
48        let mut body = Body::from_string(string);
49
50        let path = PathBuf::from(template_name);
51        if let Some(extension) = path.extension() {
52            if let Some(mime) = Mime::from_extension(extension.to_string_lossy()) {
53                body.set_mime(mime)
54            }
55        }
56
57        Ok(body)
58    }
59
60    fn render_response(&self, template_name: &str, context: &tera::Context) -> Result {
61        let mut response = Response::new(200);
62        response.set_body(self.render_body(template_name, context)?);
63        Ok(response)
64    }
65}
66
67/// this macro simplifies creation of ad-hoc [`tera::Context`]s.
68/// ```rust
69/// # use tide_tera::context;
70/// let mut context = tera::Context::new();
71/// context.insert("template-engine", "tera");
72/// assert_eq!(context, context! { "template-engine" => "tera" });
73/// ```
74#[macro_export]
75macro_rules! context {
76    ($($key:expr => $value:expr,)+) => { context!($($key => $value),+) };
77    ($($key:expr => $value:expr),*) => {
78        {
79            let mut _context = ::tera::Context::new();
80            $(
81                _context.insert($key, &$value);
82            )*
83            _context
84        }
85     };
86}
87
88pub mod prelude {
89    //! exposes [`context`] and [`TideTeraExt].
90    pub use super::{context, TideTeraExt};
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96    use async_std::prelude::*;
97
98    #[test]
99    fn context() {
100        let context = context! {
101            "key" => "value"
102        };
103
104        assert_eq!(context.into_json()["key"], "value");
105
106        let context = context! { "key1" => "value1", "key2" => "value2" };
107        assert_eq!(context.into_json()["key2"], "value2");
108    }
109
110    #[async_std::test]
111    async fn test_body() {
112        let tera = Tera::new("tests/templates/**/*").unwrap();
113        let mut body = tera
114            .render_body("good_template.html", &context! { "name" => "tide" })
115            .unwrap();
116
117        assert_eq!(body.mime(), &tide::http::mime::HTML);
118
119        let mut body_string = String::new();
120        body.read_to_string(&mut body_string).await.unwrap();
121        assert_eq!(body_string, "hello tide!\n");
122    }
123
124    #[async_std::test]
125    async fn response() {
126        let tera = Tera::new("tests/templates/**/*").unwrap();
127        let mut response = tera
128            .render_response("good_template.html", &context! { "name" => "tide" })
129            .unwrap();
130
131        assert_eq!(response.content_type(), Some(tide::http::mime::HTML));
132
133        let http_response: &mut tide::http::Response = response.as_mut();
134        let body_string = http_response.body_string().await.unwrap();
135        assert_eq!(body_string, "hello tide!\n");
136    }
137
138    #[test]
139    fn unknown_content_type() {
140        let tera = Tera::new("tests/templates/**/*").unwrap();
141        let body = tera
142            .render_body("unknown_extension.tide", &context! { "name" => "tide" })
143            .unwrap();
144
145        assert_eq!(body.mime(), &tide::http::mime::PLAIN);
146    }
147
148    #[test]
149    fn no_extension() {
150        let tera = Tera::new("tests/templates/**/*").unwrap();
151        let body = tera
152            .render_body("no_extension", &context! { "name" => "tide" })
153            .unwrap();
154
155        assert_eq!(body.mime(), &tide::http::mime::PLAIN);
156    }
157
158    #[test]
159    fn bad_template() {
160        let tera = Tera::new("tests/templates/**/*").unwrap();
161        let result = tera.render_body("good_template.html", &context! { "framework" => "tide" });
162
163        assert!(result.is_err());
164    }
165}