tower_web/view/
handlebars.rs

1use response::{Serializer, SerializerContext, ContentType};
2
3use bytes::Bytes;
4use handlebars::Handlebars as Registry;
5use http::header::HeaderValue;
6use http::status::StatusCode;
7use serde::Serialize;
8
9use std::env;
10use std::path::{Path, MAIN_SEPARATOR};
11use std::sync::Arc;
12
13/// Serialize response values using Handlebars templates
14///
15/// This serializer is able to render handlebar templates using structs with
16/// `#[derive(Response)]` and a template name, set with the `#[web(template =
17/// "<template name>")]` annotation.
18#[derive(Clone, Debug)]
19pub struct Handlebars {
20    registry: Arc<Registry>,
21    html: HeaderValue,
22}
23
24const TEXT_HTML: &str = "text/html";
25
26impl Handlebars {
27    /// Create a new handlebars serializer.
28    ///
29    /// The serializer renders handlebar templates using the response value to
30    /// populate template variables. The response value must have
31    /// `#[derive(Response)]` and a template name specified using the
32    /// `#[web(template = "<template name>")]`.
33    ///
34    /// Templates are loaded from one of the following locations, checked in order:
35    ///
36    /// 1. A `templates` directory under the `TOWER_WEB_TEMPLATE_DIR` environment variable
37    /// 2. A `templates` directory under the crate root (`CARGO_MANIFEST_DIR` environment variable)
38    /// 3. A `templates` directory under the current working directory.
39    ///
40    /// Templates have the `.hbs` file extension.
41    ///
42    /// For more control over how templates are loaded, use
43    /// [`new_with_registry`](struct.Handlebars.html#method.new_with_registry).
44    pub fn new() -> Handlebars {
45        let mut registry = Registry::new();
46
47        let mut registered = false;
48
49        // 1. $TOWER_WEB_TEMPLATE_DIR/templates
50        if let Ok(value) = env::var("TOWER_WEB_TEMPLATE_DIR") {
51            let base_dir = Path::new(&value);
52
53            if !base_dir.exists() {
54                panic!("TOWER_WEB_TEMPLATE_DIR was set but {:?} does not exist", base_dir);
55            }
56
57            let template_dir = base_dir.join("templates");
58
59            if !template_dir.exists() {
60                panic!("TOWER_WEB_TEMPLATE_DIR was set but the template directory {:?} does not exist", template_dir);
61            }
62            registry.register_templates_directory(".hbs", template_dir).unwrap();
63            registered = true;
64        }
65        if !registered {
66            // 2. A 'templates' folder under the crate root
67            if let Ok(value) = env::var("CARGO_MANIFEST_DIR") {
68                let dir = Path::new(&value).join("templates");
69
70                if dir.exists() {
71                    registry.register_templates_directory(".hbs", dir).unwrap();
72                    registered = true;
73                }
74            }
75        }
76        if !registered {
77            // 3. A 'templates' folder under the current working directory
78            let dir = Path::new("templates");
79            if dir.exists() {
80                registry.register_templates_directory(".hbs", dir).unwrap();
81                registered = true;
82            }
83        }
84
85        if !registered {
86            let pwd = Path::new(&env::current_dir().unwrap()).join("templates");
87            panic!("A templates directory was not found. Registering Handlebars failed. Checked at $TOWER_WEB_TEMPLATE_DIR{}templates, $CARGO_MANIFEST_DIR{}templates (crate root), and {:?} (under the current working directory).", MAIN_SEPARATOR, MAIN_SEPARATOR, pwd);
88        }
89
90        Handlebars::new_with_registry(registry)
91    }
92
93    /// Create a new handlebars serializer.
94    ///
95    /// Similar to `new`, but uses the provided registry. This allows
96    /// customizing how templates are rendered.
97    pub fn new_with_registry(registry: Registry) -> Handlebars {
98        Handlebars {
99            registry: Arc::new(registry),
100            html: HeaderValue::from_static(TEXT_HTML),
101        }
102    }
103}
104
105impl Serializer for Handlebars {
106    type Format = ();
107
108    fn lookup(&self, name: &str) -> Option<ContentType<Self::Format>> {
109        match name {
110            "html" | TEXT_HTML => {
111                Some(ContentType::new(self.html.clone(), ()))
112            }
113            _ => None,
114        }
115    }
116
117    fn serialize<T>(&self, value: &T, _: &Self::Format, context: &SerializerContext)
118        -> Result<Bytes, ::Error>
119    where
120        T: Serialize
121    {
122        if let Some(template) = context.template() {
123            match self.registry.render(template, value) {
124                Ok(rendered) => {
125                    return Ok(rendered.into());
126                }
127                Err(err) => {
128                    error!("error rendering template; err={:?}", err);
129                    return Err(::Error::from(StatusCode::INTERNAL_SERVER_ERROR))
130                }
131            }
132        }
133
134        // TODO: Use a convention to pick a template name if none is
135        // specified. Probably "<module>/<handler>.hbs"
136        error!("no template specified; {}::{}::{}",
137               context.resource_mod().unwrap_or("???"),
138               context.resource_name().unwrap_or("???"),
139               context.handler_name().unwrap_or("???"));
140        Err(::error::Error::from(StatusCode::INTERNAL_SERVER_ERROR))
141    }
142}
143
144impl ::util::Sealed for Handlebars {}