Use Figment's 'Value' in contrib templating.

Previously, 'serde_json::Value' was used to store the serialized
template context. This value does not represent all of serde's data
model. This means we may fail to serialize a valid Rust value into it,
for instance, 128-bit integers. This is reduced with Figment's 'Value',
which supports the majority if not all of the serde data model.

At present, all supported templating engines use 'serde_json::Value', so
in practice, this commit has no effect but to reduce local dependencies
and provide better error messages for bad contexts.
This commit is contained in:
Sergio Benitez 2021-06-18 22:53:28 -07:00
parent a875da1666
commit 0b2fcb9f4b
6 changed files with 52 additions and 39 deletions

View File

@ -16,8 +16,6 @@ tera = ["tera_"]
handlebars = ["handlebars_"] handlebars = ["handlebars_"]
[dependencies] [dependencies]
serde = "1.0"
serde_json = "1.0.26"
glob = "0.3" glob = "0.3"
notify = "4.0.6" notify = "4.0.6"
normpath = "0.3" normpath = "0.3"

View File

@ -1,7 +1,7 @@
use std::path::Path; use std::path::Path;
use std::collections::HashMap; use std::collections::HashMap;
use serde::Serialize; use rocket::serde::Serialize;
use crate::TemplateInfo; use crate::TemplateInfo;

View File

@ -1,6 +1,6 @@
use std::path::Path; use std::path::Path;
use serde::Serialize; use rocket::serde::Serialize;
use crate::engine::Engine; use crate::engine::Engine;
pub use crate::handlebars::Handlebars; pub use crate::handlebars::Handlebars;
@ -29,12 +29,8 @@ impl Engine for Handlebars<'static> {
return None; return None;
} }
match Handlebars::render(self, name, &context) { Handlebars::render(self, name, &context)
Ok(string) => Some(string), .map_err(|e| error_!("Handlebars: {}", e))
Err(e) => { .ok()
error_!("Error rendering Handlebars template '{}': {}", name, e);
None
}
}
} }
} }

View File

@ -118,8 +118,8 @@
//! //!
//! Templates are rendered with the `render` method. The method takes in the //! Templates are rendered with the `render` method. The method takes in the
//! name of a template and a context to render the template with. The context //! name of a template and a context to render the template with. The context
//! can be any type that implements [`Serialize`] from [`serde`] and would //! can be any type that implements [`Serialize`] and would serialize to an
//! serialize to an `Object` value. //! `Object` value.
//! //!
//! ## Automatic Reloading //! ## Automatic Reloading
//! //!
@ -127,8 +127,6 @@
//! will be automatically reloaded from disk if any changes have been made to //! will be automatically reloaded from disk if any changes have been made to
//! the templates directory since the previous request. In release builds, //! the templates directory since the previous request. In release builds,
//! template reloading is disabled to improve performance and cannot be enabled. //! template reloading is disabled to improve performance and cannot be enabled.
//!
//! [`Serialize`]: serde::Serialize
#![doc(html_root_url = "https://api.rocket.rs/v0.5-rc/rocket_dyn_templates")] #![doc(html_root_url = "https://api.rocket.rs/v0.5-rc/rocket_dyn_templates")]
#![doc(html_favicon_url = "https://rocket.rs/images/favicon.ico")] #![doc(html_favicon_url = "https://rocket.rs/images/favicon.ico")]
@ -166,18 +164,16 @@ pub use self::metadata::Metadata;
use self::fairing::TemplateFairing; use self::fairing::TemplateFairing;
use self::context::{Context, ContextManager}; use self::context::{Context, ContextManager};
use serde::Serialize;
use serde_json::{Value, to_value};
use std::borrow::Cow; use std::borrow::Cow;
use std::path::PathBuf; use std::path::PathBuf;
use std::error::Error;
use rocket::{Rocket, Orbit, Ignite, Sentinel}; use rocket::{Rocket, Orbit, Ignite, Sentinel};
use rocket::request::Request; use rocket::request::Request;
use rocket::fairing::Fairing; use rocket::fairing::Fairing;
use rocket::response::{self, Responder}; use rocket::response::{self, Responder};
use rocket::http::{ContentType, Status}; use rocket::http::{ContentType, Status};
use rocket::figment::{value::Value, error::Error};
use rocket::serde::Serialize;
const DEFAULT_TEMPLATE_DIR: &str = "templates"; const DEFAULT_TEMPLATE_DIR: &str = "templates";
@ -191,7 +187,7 @@ const DEFAULT_TEMPLATE_DIR: &str = "templates";
#[derive(Debug)] #[derive(Debug)]
pub struct Template { pub struct Template {
name: Cow<'static, str>, name: Cow<'static, str>,
value: Option<Value> value: Result<Value, Error>
} }
#[derive(Debug)] #[derive(Debug)]
@ -299,7 +295,7 @@ impl Template {
/// } /// }
/// ``` /// ```
pub fn try_custom<F: Send + Sync + 'static>(f: F) -> impl Fairing pub fn try_custom<F: Send + Sync + 'static>(f: F) -> impl Fairing
where F: Fn(&mut Engines) -> Result<(), Box<dyn Error>> where F: Fn(&mut Engines) -> Result<(), Box<dyn std::error::Error>>
{ {
TemplateFairing { callback: Box::new(f) } TemplateFairing { callback: Box::new(f) }
} }
@ -325,7 +321,7 @@ impl Template {
pub fn render<S, C>(name: S, context: C) -> Template pub fn render<S, C>(name: S, context: C) -> Template
where S: Into<Cow<'static, str>>, C: Serialize where S: Into<Cow<'static, str>>, C: Serialize
{ {
Template { name: name.into(), value: to_value(context).ok() } Template { name: name.into(), value: Value::serialize(context) }
} }
/// Render the template named `name` with the context `context` into a /// Render the template named `name` with the context `context` into a
@ -386,13 +382,13 @@ impl Template {
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();
error_!("Template '{}' does not exist.", name); error_!("Template '{}' does not exist.", name);
info_!("Known templates: {}", ts.join(", ")); info_!("Known templates: {}.", ts.join(", "));
info_!("Searched in {:?}.", ctxt.root); info_!("Searched in {:?}.", ctxt.root);
Status::InternalServerError Status::InternalServerError
})?; })?;
let value = self.value.ok_or_else(|| { let value = self.value.map_err(|e| {
error_!("The provided template context failed to serialize."); error_!("Template context failed to serialize: {}.", e);
Status::InternalServerError Status::InternalServerError
})?; })?;

View File

@ -1,7 +1,7 @@
use std::path::Path; use std::path::Path;
use std::error::Error; use std::error::Error;
use serde::Serialize; use rocket::serde::Serialize;
use crate::engine::Engine; use crate::engine::Engine;
@ -42,16 +42,9 @@ impl Engine for Tera {
return None; return None;
}; };
let tera_ctx = match Context::from_serialize(context) { let tera_ctx = Context::from_serialize(context)
Ok(ctx) => ctx, .map_err(|e| error_!("Tera context error: {}.", e))
Err(_) => { .ok()?;
error_!(
"Error generating context when rendering Tera template '{}'.",
name
);
return None;
}
};
match Tera::render(self, name, &tera_ctx) { match Tera::render(self, name, &tera_ctx) {
Ok(string) => Some(string), Ok(string) => Some(string),

View File

@ -126,6 +126,21 @@ mod tera_tests {
assert_eq!(template, Some(ESCAPED_EXPECTED.into())); assert_eq!(template, Some(ESCAPED_EXPECTED.into()));
} }
// u128 is not supported. enable when it is.
// #[test]
// fn test_tera_u128() {
// const EXPECTED: &'static str
// = "\nh_start\ntitle: 123\nh_end\n\n\n1208925819614629174706176\n\nfoot\n";
//
// let client = Client::debug(rocket()).unwrap();
// let mut map = HashMap::new();
// map.insert("title", 123);
// map.insert("number", 1u128 << 80);
//
// let template = Template::show(client.rocket(), "tera/txt_test", &map);
// assert_eq!(template, Some(EXPECTED.into()));
// }
#[test] #[test]
fn test_template_metadata_with_tera() { fn test_template_metadata_with_tera() {
let client = Client::debug(rocket()).unwrap(); let client = Client::debug(rocket()).unwrap();
@ -151,11 +166,11 @@ mod handlebars_tests {
use rocket::http::Status; use rocket::http::Status;
use rocket::local::blocking::Client; use rocket::local::blocking::Client;
const EXPECTED: &'static str
= "Hello _test_!\n\n<main> &lt;script /&gt; hi </main>\nDone.\n\n";
#[test] #[test]
fn test_handlebars_templates() { fn test_handlebars_templates() {
const EXPECTED: &'static str
= "Hello _test_!\n\n<main> &lt;script /&gt; hi </main>\nDone.\n\n";
let client = Client::debug(rocket()).unwrap(); let client = Client::debug(rocket()).unwrap();
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert("title", "_test_"); map.insert("title", "_test_");
@ -166,6 +181,21 @@ mod handlebars_tests {
assert_eq!(template, Some(EXPECTED.into())); assert_eq!(template, Some(EXPECTED.into()));
} }
// u128 is not supported. enable when it is.
// #[test]
// fn test_handlebars_u128() {
// const EXPECTED: &'static str
// = "Hello 123!\n\n<main> 1208925819614629174706176 </main>\nDone.\n\n";
//
// let client = Client::debug(rocket()).unwrap();
// let mut map = HashMap::new();
// map.insert("title", 123);
// map.insert("number", 1u128 << 80);
//
// let template = Template::show(client.rocket(), "hbs/test", &map);
// assert_eq!(template, Some(EXPECTED.into()));
// }
#[test] #[test]
fn test_template_metadata_with_handlebars() { fn test_template_metadata_with_handlebars() {
let client = Client::debug(rocket()).unwrap(); let client = Client::debug(rocket()).unwrap();