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}