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}