rocket_dyn_templates/
metadata.rs

1use std::fmt;
2use std::borrow::Cow;
3
4use rocket::{Request, Rocket, Ignite, Sentinel};
5use rocket::http::{Status, ContentType};
6use rocket::request::{self, FromRequest};
7use rocket::serde::Serialize;
8
9use crate::{Template, context::ContextManager};
10
11/// Request guard for dynamically querying template metadata.
12///
13/// # Usage
14///
15/// The `Metadata` type implements Rocket's [`FromRequest`] trait, so it can be
16/// used as a request guard in any request handler.
17///
18/// ```rust
19/// # #[macro_use] extern crate rocket;
20/// # #[macro_use] extern crate rocket_dyn_templates;
21/// use rocket_dyn_templates::{Template, Metadata, context};
22///
23/// #[get("/")]
24/// fn homepage(metadata: Metadata) -> Template {
25///     // Conditionally render a template if it's available.
26///     # let context = ();
27///     if metadata.contains_template("some-template") {
28///         Template::render("some-template", &context)
29///     } else {
30///         Template::render("fallback", &context)
31///     }
32/// }
33///
34/// fn main() {
35///     rocket::build()
36///         .attach(Template::fairing())
37///         // ...
38///     # ;
39/// }
40/// ```
41pub struct Metadata<'a>(&'a ContextManager);
42
43impl Metadata<'_> {
44    /// Returns `true` if the template with the given `name` is currently
45    /// loaded.  Otherwise, returns `false`.
46    ///
47    /// # Example
48    ///
49    /// ```rust
50    /// # #[macro_use] extern crate rocket;
51    /// # extern crate rocket_dyn_templates;
52    /// #
53    /// use rocket_dyn_templates::Metadata;
54    ///
55    /// #[get("/")]
56    /// fn handler(metadata: Metadata) {
57    ///     // Returns `true` if the template with name `"name"` was loaded.
58    ///     let loaded = metadata.contains_template("name");
59    /// }
60    /// ```
61    pub fn contains_template(&self, name: &str) -> bool {
62        self.0.context().templates.contains_key(name)
63    }
64
65    /// Returns `true` if template reloading is enabled.
66    ///
67    /// # Example
68    ///
69    /// ```rust
70    /// # #[macro_use] extern crate rocket;
71    /// # extern crate rocket_dyn_templates;
72    /// #
73    /// use rocket_dyn_templates::Metadata;
74    ///
75    /// #[get("/")]
76    /// fn handler(metadata: Metadata) {
77    ///     // Returns `true` if template reloading is enabled.
78    ///     let reloading = metadata.reloading();
79    /// }
80    /// ```
81    pub fn reloading(&self) -> bool {
82        self.0.is_reloading()
83    }
84
85    /// Directly render the template named `name` with the context `context`
86    /// into a `String`. Also returns the template's detected `ContentType`. See
87    /// [`Template::render()`] for more details on rendering.
88    ///
89    /// # Examples
90    ///
91    /// ```rust
92    /// # #[macro_use] extern crate rocket;
93    /// use rocket::http::ContentType;
94    /// use rocket_dyn_templates::{Metadata, Template, context};
95    ///
96    /// #[get("/")]
97    /// fn send_email(metadata: Metadata) -> Option<()> {
98    ///     let (mime, string) = metadata.render("email", context! {
99    ///         field: "Hello, world!"
100    ///     })?;
101    ///
102    ///     # /*
103    ///     send_email(mime, string).await?;
104    ///     # */
105    ///     Some(())
106    /// }
107    ///
108    /// #[get("/")]
109    /// fn raw_render(metadata: Metadata) -> Option<(ContentType, String)> {
110    ///     metadata.render("index", context! { field: "Hello, world!" })
111    /// }
112    ///
113    /// // Prefer the following, however, which is nearly identical but pithier:
114    ///
115    /// #[get("/")]
116    /// fn render() -> Template {
117    ///     Template::render("index", context! { field: "Hello, world!" })
118    /// }
119    /// ```
120    pub fn render<S, C>(&self, name: S, context: C) -> Option<(ContentType, String)>
121        where S: Into<Cow<'static, str>>, C: Serialize
122    {
123        Template::render(name.into(), context).finalize(&self.0.context()).ok()
124    }
125}
126
127impl fmt::Debug for Metadata<'_> {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        f.debug_map()
130            .entries(&self.0.context().templates)
131            .finish()
132    }
133}
134
135impl Sentinel for Metadata<'_> {
136    fn abort(rocket: &Rocket<Ignite>) -> bool {
137        if rocket.state::<ContextManager>().is_none() {
138            error!(
139                "uninitialized template context: missing `Template::fairing()`.\n\
140                To use templates, you must attach `Template::fairing()`."
141            );
142
143            return true;
144        }
145
146        false
147    }
148}
149
150/// Retrieves the template metadata. If a template fairing hasn't been attached,
151/// an error is printed and an empty `Err` with status `InternalServerError`
152/// (`500`) is returned.
153#[rocket::async_trait]
154impl<'r> FromRequest<'r> for Metadata<'r> {
155    type Error = ();
156
157    async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, ()> {
158        request.rocket().state::<ContextManager>()
159            .map(|cm| request::Outcome::Success(Metadata(cm)))
160            .unwrap_or_else(|| {
161                error!(
162                    "uninitialized template context: missing `Template::fairing()`.\n\
163                    To use templates, you must attach `Template::fairing()`."
164                );
165
166                request::Outcome::Error((Status::InternalServerError, ()))
167            })
168    }
169}