mirror of https://github.com/rwf2/Rocket.git
parent
4827948401
commit
9b3c83eb70
|
@ -115,10 +115,14 @@
|
||||||
//!
|
//!
|
||||||
//! ## Rendering
|
//! ## Rendering
|
||||||
//!
|
//!
|
||||||
//! Templates are rendered with the `render` method. The method takes in the
|
//! Templates are typically rendered indirectly via [`Template::render()`] which
|
||||||
//! name of a template and a context to render the template with. The context
|
//! returns a `Template` responder which renders the template at response time.
|
||||||
//! can be any type that implements [`Serialize`] and would serialize to an
|
//! To render a template directly into a `String`, use [`Metadata::render()`]
|
||||||
//! `Object` value. The [`context!`] macro can also be used to create inline
|
//! instead.
|
||||||
|
//!
|
||||||
|
//! Both methods take in a template name and context to use while rendering. The
|
||||||
|
//! context can be any [`Serialize`] type that serializes to an `Object` (a
|
||||||
|
//! dictionary) value. The [`context!`] macro may be used to create inline
|
||||||
//! `Serialize`-able context objects.
|
//! `Serialize`-able context objects.
|
||||||
//!
|
//!
|
||||||
//! ## Automatic Reloading
|
//! ## Automatic Reloading
|
||||||
|
@ -308,6 +312,8 @@ impl Template {
|
||||||
/// be of any type that implements `Serialize`, such as `HashMap` or a
|
/// be of any type that implements `Serialize`, such as `HashMap` or a
|
||||||
/// custom `struct`.
|
/// custom `struct`.
|
||||||
///
|
///
|
||||||
|
/// To render a template directly into a string, use [`Metadata::render()`].
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// Using the `context` macro:
|
/// Using the `context` macro:
|
||||||
|
@ -383,14 +389,14 @@ impl Template {
|
||||||
None
|
None
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Template::render(name, context).finalize(&ctxt).ok().map(|v| v.0)
|
Template::render(name, context).finalize(&ctxt).ok().map(|v| v.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Actually render this template given a template context. This method is
|
/// Actually render this template given a template context. This method is
|
||||||
/// called by the `Template` `Responder` implementation as well as
|
/// called by the `Template` `Responder` implementation as well as
|
||||||
/// `Template::show()`.
|
/// `Template::show()`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn finalize(self, ctxt: &Context) -> Result<(String, ContentType), Status> {
|
fn finalize(self, ctxt: &Context) -> Result<(ContentType, String), Status> {
|
||||||
let name = &*self.name;
|
let name = &*self.name;
|
||||||
let info = ctxt.templates.get(name).ok_or_else(|| {
|
let info = ctxt.templates.get(name).ok_or_else(|| {
|
||||||
let ts: Vec<_> = ctxt.templates.keys().map(|s| s.as_str()).collect();
|
let ts: Vec<_> = ctxt.templates.keys().map(|s| s.as_str()).collect();
|
||||||
|
@ -410,7 +416,7 @@ impl Template {
|
||||||
Status::InternalServerError
|
Status::InternalServerError
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok((string, info.data_type.clone()))
|
Ok((info.data_type.clone(), string))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,18 +425,16 @@ impl Template {
|
||||||
/// rendering fails, an `Err` of `Status::InternalServerError` is returned.
|
/// rendering fails, an `Err` of `Status::InternalServerError` is returned.
|
||||||
impl<'r> Responder<'r, 'static> for Template {
|
impl<'r> Responder<'r, 'static> for Template {
|
||||||
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
|
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
|
||||||
let (render, content_type) = {
|
let ctxt = req.rocket()
|
||||||
let ctxt = req.rocket().state::<ContextManager>().ok_or_else(|| {
|
.state::<ContextManager>()
|
||||||
|
.ok_or_else(|| {
|
||||||
error_!("Uninitialized template context: missing fairing.");
|
error_!("Uninitialized template context: missing fairing.");
|
||||||
info_!("To use templates, you must attach `Template::fairing()`.");
|
info_!("To use templates, you must attach `Template::fairing()`.");
|
||||||
info_!("See the `Template` documentation for more information.");
|
info_!("See the `Template` documentation for more information.");
|
||||||
Status::InternalServerError
|
Status::InternalServerError
|
||||||
})?.context();
|
})?;
|
||||||
|
|
||||||
self.finalize(&ctxt)?
|
self.finalize(&ctxt.context())?.respond_to(req)
|
||||||
};
|
|
||||||
|
|
||||||
(content_type, render).respond_to(req)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
use rocket::{Request, Rocket, Ignite, Sentinel};
|
use std::borrow::Cow;
|
||||||
use rocket::http::Status;
|
|
||||||
use rocket::request::{self, FromRequest};
|
|
||||||
|
|
||||||
use crate::context::ContextManager;
|
use rocket::{Request, Rocket, Ignite, Sentinel};
|
||||||
|
use rocket::http::{Status, ContentType};
|
||||||
|
use rocket::request::{self, FromRequest};
|
||||||
|
use rocket::serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{Template, context::ContextManager};
|
||||||
|
|
||||||
/// Request guard for dynamically querying template metadata.
|
/// Request guard for dynamically querying template metadata.
|
||||||
///
|
///
|
||||||
|
@ -77,6 +80,47 @@ impl Metadata<'_> {
|
||||||
pub fn reloading(&self) -> bool {
|
pub fn reloading(&self) -> bool {
|
||||||
self.0.is_reloading()
|
self.0.is_reloading()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Directly render the template named `name` with the context `context`
|
||||||
|
/// into a `String`. Also returns the template's detected `ContentType`. See
|
||||||
|
/// [`Template::render()`] for more details on rendering.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// use rocket::http::ContentType;
|
||||||
|
/// use rocket_dyn_templates::{Metadata, Template, context};
|
||||||
|
///
|
||||||
|
/// #[get("/")]
|
||||||
|
/// fn send_email(metadata: Metadata) -> Option<()> {
|
||||||
|
/// let (mime, string) = metadata.render("email", context! {
|
||||||
|
/// field: "Hello, world!"
|
||||||
|
/// })?;
|
||||||
|
///
|
||||||
|
/// # /*
|
||||||
|
/// send_email(mime, string).await?;
|
||||||
|
/// # */
|
||||||
|
/// Some(())
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[get("/")]
|
||||||
|
/// fn raw_render(metadata: Metadata) -> Option<(ContentType, String)> {
|
||||||
|
/// metadata.render("index", context! { field: "Hello, world!" })
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Prefer the following, however, which is nearly identical but pithier:
|
||||||
|
///
|
||||||
|
/// #[get("/")]
|
||||||
|
/// fn render() -> Template {
|
||||||
|
/// Template::render("index", context! { field: "Hello, world!" })
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn render<S, C>(&self, name: S, context: C) -> Option<(ContentType, String)>
|
||||||
|
where S: Into<Cow<'static, str>>, C: Serialize
|
||||||
|
{
|
||||||
|
Template::render(name.into(), context).finalize(&self.0.context()).ok()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sentinel for Metadata<'_> {
|
impl Sentinel for Metadata<'_> {
|
||||||
|
|
|
@ -10,10 +10,7 @@ use rocket_dyn_templates::{Template, Metadata, context};
|
||||||
|
|
||||||
#[get("/<engine>/<name>")]
|
#[get("/<engine>/<name>")]
|
||||||
fn template_check(md: Metadata<'_>, engine: &str, name: &str) -> Option<()> {
|
fn template_check(md: Metadata<'_>, engine: &str, name: &str) -> Option<()> {
|
||||||
match md.contains_template(&format!("{}/{}", engine, name)) {
|
md.contains_template(&format!("{}/{}", engine, name)).then(|| ())
|
||||||
true => Some(()),
|
|
||||||
false => None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/is_reloading")]
|
#[get("/is_reloading")]
|
||||||
|
@ -207,28 +204,37 @@ fn test_context_macro() {
|
||||||
mod tera_tests {
|
mod tera_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use rocket::http::Status;
|
use rocket::http::{ContentType, Status};
|
||||||
use rocket::local::blocking::Client;
|
use rocket::request::FromRequest;
|
||||||
|
|
||||||
const UNESCAPED_EXPECTED: &'static str
|
const UNESCAPED_EXPECTED: &'static str
|
||||||
= "\nh_start\ntitle: _test_\nh_end\n\n\n<script />\n\nfoot\n";
|
= "\nh_start\ntitle: _test_\nh_end\n\n\n<script />\n\nfoot\n";
|
||||||
const ESCAPED_EXPECTED: &'static str
|
const ESCAPED_EXPECTED: &'static str
|
||||||
= "\nh_start\ntitle: _test_\nh_end\n\n\n<script />\n\nfoot\n";
|
= "\nh_start\ntitle: _test_\nh_end\n\n\n<script />\n\nfoot\n";
|
||||||
|
|
||||||
#[test]
|
#[async_test]
|
||||||
fn test_tera_templates() {
|
async fn test_tera_templates() {
|
||||||
let client = Client::debug(rocket()).unwrap();
|
use rocket::local::asynchronous::Client;
|
||||||
|
|
||||||
|
let client = Client::debug(rocket()).await.unwrap();
|
||||||
|
let req = client.get("/");
|
||||||
|
let metadata = Metadata::from_request(&req).await.unwrap();
|
||||||
|
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert("title", "_test_");
|
map.insert("title", "_test_");
|
||||||
map.insert("content", "<script />");
|
map.insert("content", "<script />");
|
||||||
|
|
||||||
// Test with a txt file, which shouldn't escape.
|
// Test with a txt file, which shouldn't escape.
|
||||||
let template = Template::show(client.rocket(), "tera/txt_test", &map);
|
let template = Template::show(client.rocket(), "tera/txt_test", &map);
|
||||||
|
let md_rendered = metadata.render("tera/txt_test", &map);
|
||||||
assert_eq!(template, Some(UNESCAPED_EXPECTED.into()));
|
assert_eq!(template, Some(UNESCAPED_EXPECTED.into()));
|
||||||
|
assert_eq!(md_rendered, Some((ContentType::Text, UNESCAPED_EXPECTED.into())));
|
||||||
|
|
||||||
// Now with an HTML file, which should.
|
// Now with an HTML file, which should.
|
||||||
let template = Template::show(client.rocket(), "tera/html_test", &map);
|
let template = Template::show(client.rocket(), "tera/html_test", &map);
|
||||||
|
let md_rendered = metadata.render("tera/html_test", &map);
|
||||||
assert_eq!(template, Some(ESCAPED_EXPECTED.into()));
|
assert_eq!(template, Some(ESCAPED_EXPECTED.into()));
|
||||||
|
assert_eq!(md_rendered, Some((ContentType::HTML, ESCAPED_EXPECTED.into())));
|
||||||
}
|
}
|
||||||
|
|
||||||
// u128 is not supported. enable when it is.
|
// u128 is not supported. enable when it is.
|
||||||
|
@ -248,6 +254,8 @@ mod tera_tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_template_metadata_with_tera() {
|
fn test_template_metadata_with_tera() {
|
||||||
|
use rocket::local::blocking::Client;
|
||||||
|
|
||||||
let client = Client::debug(rocket()).unwrap();
|
let client = Client::debug(rocket()).unwrap();
|
||||||
|
|
||||||
let response = client.get("/tera/txt_test").dispatch();
|
let response = client.get("/tera/txt_test").dispatch();
|
||||||
|
@ -268,22 +276,29 @@ mod tera_tests {
|
||||||
mod handlebars_tests {
|
mod handlebars_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use rocket::http::Status;
|
use rocket::request::FromRequest;
|
||||||
use rocket::local::blocking::Client;
|
use rocket::http::{ContentType, Status};
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn test_handlebars_templates() {
|
||||||
|
use rocket::local::asynchronous::Client;
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_handlebars_templates() {
|
|
||||||
const EXPECTED: &'static str
|
const EXPECTED: &'static str
|
||||||
= "Hello _test_!\n<main> <script /> hi </main>\nDone.\n";
|
= "Hello _test_!\n<main> <script /> hi </main>\nDone.\n";
|
||||||
|
|
||||||
let client = Client::debug(rocket()).unwrap();
|
let client = Client::debug(rocket()).await.unwrap();
|
||||||
|
let req = client.get("/");
|
||||||
|
let metadata = Metadata::from_request(&req).await.unwrap();
|
||||||
|
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert("title", "_test_");
|
map.insert("title", "_test_");
|
||||||
map.insert("content", "<script /> hi");
|
map.insert("content", "<script /> hi");
|
||||||
|
|
||||||
// Test with a txt file, which shouldn't escape.
|
// Test with a txt file, which shouldn't escape.
|
||||||
let template = Template::show(client.rocket(), "hbs/test", &map);
|
let template = Template::show(client.rocket(), "hbs/test", &map);
|
||||||
|
let md_rendered = metadata.render("hbs/test", &map);
|
||||||
assert_eq!(template, Some(EXPECTED.into()));
|
assert_eq!(template, Some(EXPECTED.into()));
|
||||||
|
assert_eq!(md_rendered, Some((ContentType::HTML, EXPECTED.into())));
|
||||||
}
|
}
|
||||||
|
|
||||||
// u128 is not supported. enable when it is.
|
// u128 is not supported. enable when it is.
|
||||||
|
@ -303,6 +318,8 @@ mod handlebars_tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_template_metadata_with_handlebars() {
|
fn test_template_metadata_with_handlebars() {
|
||||||
|
use rocket::local::blocking::Client;
|
||||||
|
|
||||||
let client = Client::debug(rocket()).unwrap();
|
let client = Client::debug(rocket()).unwrap();
|
||||||
|
|
||||||
let response = client.get("/hbs/test").dispatch();
|
let response = client.get("/hbs/test").dispatch();
|
||||||
|
|
Loading…
Reference in New Issue