iron_tera/
lib.rs

1//! Please contact me on github and file any issues if you find some, I'm also open to PRs or other suggestions.
2//!
3//! Updated to Tera 0.11 / Serde 1.0 / Iron 0.5 !
4//! Serde 1.0 to_value returns a `Result`, this means you need to handle the possiblity of a serialization failure.
5//! If you just want to `unwrap()` there is an implementation of From<Value> for TemplateMode so `Template::new(path, value)`
6//! works also. This is the implementation that's on 'stable'.
7//!
8//! You can try the unstable crate feature which uses `TryFrom`/`TryInto` to make `Template::new` polymorphic over `Context` and `Value`.
9//!
10//! **update iron-tera-0.5.0**: If you build this crate with feature = "unstable" on the nightly compiler,
11//! I've included a `TryFrom` impl to improve API ergonomics.
12//!
13//! ## Examples
14//! Full examples ON GITHUB for both stable and unstable.
15//!
16//! ```ignore
17//!   [dependencies]
18//!   iron-tera = { version = "0.5.0" }  # optional:  features = [ "unstable" ]
19//! ```
20//!
21//! Using `iron-tera` stable.
22//!
23//! ```ignore
24//!     extern crate tera;
25//!     extern crate iron;
26//!     extern crate router;
27//!     #[macro_use] extern crate serde_json;
28//!     #[macro_use] extern crate serde_derive;
29//!     extern crate iron_tera;
30//!
31//! fn main() {
32//!     use iron::prelude::*;
33//!     use iron::status;
34//!     use router::Router;
35//!     use tera::Context;
36//!
37//!     use iron_tera::{Template, TeraEngine};
38//!
39//!     let mut router = Router::new();
40//!     router.get("/context", context_handler, "context");
41//!     router.get("/json", json_handler, "json");
42//!
43//!     let mut chain = Chain::new(router);
44//!     let teng = TeraEngine::new("src/examples/templates/**/*");
45//!     chain.link_after(teng);
46//!
47//!     Iron::new(chain).http("localhost:5000").unwrap();
48//!
49//!
50//!     fn context_handler(_: &mut Request) -> IronResult<Response> {
51//!         let mut resp = Response::new();
52//!
53//!         let mut context = Context::new();
54//!         context.add("username", &"Bob");
55//!         context.add("my_var", &"Thing"); // comment out to see alternate thing
56//!         context.add("numbers", &vec![1, 2, 3]);
57//!         context.add("bio", &"<script>alert('pwnd');</script>");
58//!
59//!         // can use Template::new(path, TemplateMode::from_context(context)) or TemplateMode::from(context) also
60//!         resp.set_mut(Template::new("users/profile.html", context))
61//!             .set_mut(status::Ok);
62//!         Ok(resp)
63//!     }
64//!     fn json_handler(_: &mut Request) -> IronResult<Response> {
65//!         let mut resp = Response::new();
66//!
67//!         let blob = json!({
68//!             "username": "John Doe",
69//!             "my_var": "Thing",
70//!             "numbers": [
71//!                 "1",
72//!                 "+44 2345678",
73//!                 "3"
74//!             ],
75//!             "bio": "<script>alert('pwnd');</script>"
76//!          });
77//!         // you can use blob.try_into() and handle the `Result` explicitly (not shown here)
78//!         // on the `unstable` feature of `iron-tera`
79//!         resp.set_mut(Template::new("users/profile.html", blob))
80//!             .set_mut(status::Ok);
81//!         Ok(resp)
82//!     }
83//! }
84//! ```
85//!
86//! Creating a template from a struct
87//!
88//! ```ignore
89//! // The following uses serde's Serialize
90//! #[derive(Serialize)]
91//! struct Product {
92//!     name: String,
93//!     value: i32,
94//! }
95//! // Rendering from a struct that implements Serialize
96//! fn produce_handler(_: &mut Request) -> IronResult<Response> {
97//!     let mut resp = Response::new();
98//!
99//!     // Using serialized values
100//!     let product = Product {
101//!         name: "Foo".into(),
102//!         value: 42,
103//!     };
104//!     // You can use TemplateMode::from()
105//!     resp.set_mut(Template::new("product.html", product))
106//!         .set_mut(status::Ok);
107//!     Ok(resp)
108//! }
109//! ```
110#![cfg_attr(feature = "unstable", feature(try_from))]
111#![allow(dead_code)]
112#[macro_use]
113extern crate serde_json;
114#[macro_use]
115extern crate tera;
116extern crate iron;
117extern crate plugin;
118extern crate serde;
119
120use iron::headers::ContentType;
121use iron::modifier::Modifier;
122use iron::prelude::*;
123use iron::{status, typemap, AfterMiddleware};
124
125use plugin::Plugin;
126
127use serde::ser::Serialize;
128use serde_json::{to_value, Value};
129
130use std::convert::{TryFrom, TryInto};
131use std::error::Error;
132
133use tera::{Context, Tera};
134
135/// There are 2 main ways to pass data to generate a template.
136#[derive(Clone, Debug)]
137pub enum TemplateMode {
138    /// TeraContext constructor takes a `Context`
139    TeraContext(Context),
140    /// Serialized constructor takes a `Value` from `serde_json`
141    Serialized(Value),
142}
143
144/// TemplateMode should only ever be created from these smart constructors,
145/// not with the enums type constructors.
146impl TemplateMode {
147    pub fn from_context(context: Context) -> TemplateMode {
148        TemplateMode::TeraContext(context)
149    }
150
151    pub fn from_serial<S: Serialize>(serializeable: S) -> Result<TemplateMode, TemplateError> {
152        Ok(TemplateMode::Serialized(to_value(serializeable)?))
153    }
154}
155#[cfg(not(feature = "unstable"))]
156impl From<Context> for TemplateMode {
157    fn from(context: Context) -> Self {
158        TemplateMode::from_context(context)
159    }
160}
161
162#[cfg(not(feature = "unstable"))]
163impl From<Value> for TemplateMode {
164    fn from(serializeable: Value) -> Self {
165        TemplateMode::from_serial(serializeable).unwrap()
166    }
167}
168#[derive(Debug)]
169pub enum TemplateError {
170    SerdeErr(serde_json::Error),
171    ContextErr(),
172}
173
174impl From<serde_json::Error> for TemplateError {
175    fn from(e: serde_json::Error) -> TemplateError {
176        TemplateError::SerdeErr(e)
177    }
178}
179impl ::std::fmt::Display for TemplateError {
180    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
181        match *self {
182            TemplateError::SerdeErr(ref e) => write!(f, "Serde Error: {}", e),
183            TemplateError::ContextErr() => write!(f, "Context Error"),
184        }
185    }
186}
187
188impl Error for TemplateError {
189    fn description(&self) -> &str {
190        match *self {
191            TemplateError::SerdeErr(ref e) => e.description(),
192            TemplateError::ContextErr() => "Context Error",
193        }
194    }
195    fn cause(&self) -> Option<&Error> {
196        None
197    }
198}
199
200#[cfg(feature = "unstable")]
201impl TryFrom<Value> for TemplateMode {
202    type Error = TemplateError;
203    fn try_from(serialize: Value) -> Result<Self, Self::Error> {
204        TemplateMode::from_serial(serialize)
205    }
206}
207
208#[cfg(feature = "unstable")]
209impl TryFrom<Context> for TemplateMode {
210    type Error = TemplateError;
211    fn try_from(serialize: Context) -> Result<Self, Self::Error> {
212        Ok(TemplateMode::from_context(serialize))
213    }
214}
215
216/// Our template holds a name (path to template) and a mode (constructed with `from_context` or `from_serial`)
217#[derive(Clone, Debug)]
218pub struct Template {
219    mode: TemplateMode,
220    name: String,
221}
222
223impl Template {
224    #[cfg(not(feature = "unstable"))]
225    pub fn new<T: Into<TemplateMode>, S: Into<String>>(name: S, mode: T) -> Template {
226        Template {
227            name: name.into(),
228            mode: mode.into(),
229        }
230    }
231    #[cfg(feature = "unstable")]
232    pub fn new<T, S>(name: S, mode: T) -> Result<Template, TemplateError>
233    where
234        TemplateError: From<<T as TryInto<TemplateMode>>::Error>,
235        S: Into<String>,
236        T: TryInto<TemplateMode>,
237    {
238        let mode = mode.try_into()?;
239        Ok(Template {
240            name: name.into(),
241            mode,
242        })
243    }
244}
245
246/// TeraEngine holds the Tera struct so that it can be used by many handlers without explicitly passing
247pub struct TeraEngine {
248    pub tera: Tera,
249}
250
251/// `compile_templates!` is used to parse the contents of a dir for all templates.
252impl TeraEngine {
253    /// Take a `String` and convert to a slice
254    pub fn new<S: AsRef<str>>(dir: S) -> TeraEngine {
255        TeraEngine {
256            tera: compile_templates!(dir.as_ref()),
257        }
258    }
259}
260
261impl typemap::Key for TeraEngine {
262    type Value = Template;
263}
264
265impl Modifier<Response> for Template {
266    fn modify(self, resp: &mut Response) {
267        resp.extensions.insert::<TeraEngine>(self);
268    }
269}
270
271/// The middleware implementation for TeraEngine
272impl AfterMiddleware for TeraEngine {
273    /// This is where all the magic happens. We extract `TeraEngine` from Iron's `Response`,
274    /// determine what `TemplateMode` we should render in, and pass the appropriate values to
275    /// tera's render methods.
276    fn after(&self, _: &mut Request, mut resp: Response) -> IronResult<Response> {
277        let wrapper = resp.extensions
278            .remove::<TeraEngine>()
279            .and_then(|t| match t.mode {
280                TemplateMode::TeraContext(ref context) => Some(self.tera.render(&t.name, context)),
281                TemplateMode::Serialized(ref value) => Some(self.tera.render(&t.name, value)),
282            });
283        match wrapper {
284            Some(result) => result
285                .map_err(|e| IronError::new(e, status::InternalServerError))
286                .and_then(|page| {
287                    if !resp.headers.has::<ContentType>() {
288                        resp.headers.set(ContentType::html());
289                    }
290                    resp.set_mut(page);
291                    Ok(resp)
292                }),
293            None => Ok(resp),
294        }
295    }
296
297    fn catch(&self, req: &mut Request, mut err: IronError) -> IronResult<Response> {
298        err.response = self.after(req, err.response)?;
299        Err(err)
300    }
301}
302
303impl Plugin<Response> for TeraEngine {
304    type Error = ();
305
306    fn eval(resp: &mut Response) -> Result<Template, ()> {
307        match resp.extensions.get::<TeraEngine>() {
308            Some(t) => Ok(t.clone()),
309            None => Err(()),
310        }
311    }
312}
313
314#[cfg(test)]
315mod tests {
316    use super::{Template, TemplateMode, TeraEngine};
317    use iron::prelude::*;
318    use tera::Context;
319
320    fn from_context_response() -> IronResult<Response> {
321        let resp = Response::new();
322        let mut context = Context::new();
323        context.add("greeting", &"hi!");
324        Ok(resp.set(Template::new("./test_template/users/foo.html", context)))
325    }
326
327    #[test]
328    fn test_from_context() {
329        let mut resp = from_context_response().ok().expect("response expected");
330        match resp.get::<TeraEngine>() {
331            Ok(h) => {
332                assert_eq!(h.name, "./test_template/users/foo.html".to_string());
333                if let TemplateMode::TeraContext(context) = h.mode {
334                    assert_eq!(
335                        context
336                            .as_json()
337                            .unwrap()
338                            .get("greeting")
339                            .unwrap()
340                            .as_str()
341                            .unwrap(),
342                        "hi!"
343                    );
344                } else {
345                    panic!("TeraContext expected");
346                }
347            }
348            _ => panic!("template expected"),
349        }
350    }
351}