Allow fallible callback in 'Template::try_custom()'.

Closes #1064.
This commit is contained in:
Jeb Rosen 2020-11-04 02:39:40 -08:00 committed by Sergio Benitez
parent 007c4b093f
commit 2f98299272
3 changed files with 91 additions and 24 deletions

View File

@ -1,3 +1,5 @@
use std::error::Error;
use crate::templates::{DEFAULT_TEMPLATE_DIR, Context, Engines}; use crate::templates::{DEFAULT_TEMPLATE_DIR, Context, Engines};
use rocket::Rocket; use rocket::Rocket;
@ -5,6 +7,8 @@ use rocket::fairing::{Fairing, Info, Kind};
pub(crate) use self::context::ContextManager; pub(crate) use self::context::ContextManager;
type Callback = Box<dyn Fn(&mut Engines) -> Result<(), Box<dyn Error>>+ Send + Sync + 'static>;
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
mod context { mod context {
use std::ops::Deref; use std::ops::Deref;
@ -37,7 +41,7 @@ mod context {
use notify::{raw_watcher, RawEvent, RecommendedWatcher, RecursiveMode, Watcher}; use notify::{raw_watcher, RawEvent, RecommendedWatcher, RecursiveMode, Watcher};
use crate::templates::{Context, Engines}; use super::{Callback, Context};
/// Wraps a Context. With `cfg(debug_assertions)` active, this structure /// Wraps a Context. With `cfg(debug_assertions)` active, this structure
/// additionally provides a method to reload the context at runtime. /// additionally provides a method to reload the context at runtime.
@ -88,7 +92,7 @@ mod context {
/// have been changes since the last reload, all templates are /// have been changes since the last reload, all templates are
/// reinitialized from disk and the user's customization callback is run /// reinitialized from disk and the user's customization callback is run
/// again. /// again.
pub fn reload_if_needed<F: Fn(&mut Engines)>(&self, custom_callback: F) { pub fn reload_if_needed(&self, callback: &Callback) {
self.watcher.as_ref().map(|w| { self.watcher.as_ref().map(|w| {
let rx_lock = w.lock().expect("receive queue lock"); let rx_lock = w.lock().expect("receive queue lock");
let mut changed = false; let mut changed = false;
@ -100,11 +104,17 @@ mod context {
info_!("Change detected: reloading templates."); info_!("Change detected: reloading templates.");
let mut ctxt = self.context_mut(); let mut ctxt = self.context_mut();
if let Some(mut new_ctxt) = Context::initialize(ctxt.root.clone()) { if let Some(mut new_ctxt) = Context::initialize(ctxt.root.clone()) {
custom_callback(&mut new_ctxt.engines); match callback(&mut new_ctxt.engines) {
*ctxt = new_ctxt; Ok(()) => *ctxt = new_ctxt,
Err(e) => {
warn_!("The template customization callback returned an error:");
warn_!("{}", e);
warn_!("The existing templates will remain active.");
}
}
} else { } else {
warn_!("An error occurred while reloading templates."); warn_!("An error occurred while reloading templates.");
warn_!("The previous templates will remain active."); warn_!("The existing templates will remain active.");
}; };
} }
}); });
@ -120,7 +130,7 @@ pub struct TemplateFairing {
/// The user-provided customization callback, allowing the use of /// The user-provided customization callback, allowing the use of
/// functionality specific to individual template engines. In debug mode, /// functionality specific to individual template engines. In debug mode,
/// this callback might be run multiple times as templates are reloaded. /// this callback might be run multiple times as templates are reloaded.
pub custom_callback: Box<dyn Fn(&mut Engines) + Send + Sync + 'static>, pub callback: Callback,
} }
#[rocket::async_trait] #[rocket::async_trait]
@ -161,11 +171,19 @@ impl Fairing for TemplateFairing {
use crate::templates::Engines; use crate::templates::Engines;
info!("{}{}", Paint::emoji("📐 "), Paint::magenta("Templating:")); info!("{}{}", Paint::emoji("📐 "), Paint::magenta("Templating:"));
info_!("directory: {}", Paint::white(root));
info_!("engines: {:?}", Paint::white(Engines::ENABLED_EXTENSIONS));
(self.custom_callback)(&mut ctxt.engines); match (self.callback)(&mut ctxt.engines) {
Ok(rocket.manage(ContextManager::new(ctxt))) Ok(()) => {
info_!("directory: {}", Paint::white(root));
info_!("engines: {:?}", Paint::white(Engines::ENABLED_EXTENSIONS));
Ok(rocket.manage(ContextManager::new(ctxt)))
}
Err(e) => {
error_!("The template customization callback returned an error:");
error_!("{}", e);
Err(rocket)
}
}
} }
None => Err(rocket), None => Err(rocket),
} }
@ -176,6 +194,6 @@ impl Fairing for TemplateFairing {
let cm = req.managed_state::<ContextManager>() let cm = req.managed_state::<ContextManager>()
.expect("Template ContextManager registered in on_attach"); .expect("Template ContextManager registered in on_attach");
cm.reload_if_needed(&*self.custom_callback); cm.reload_if_needed(&self.callback);
} }
} }

View File

@ -93,11 +93,11 @@
//! ## Template Fairing //! ## Template Fairing
//! //!
//! Template discovery is actualized by the template fairing, which itself is //! Template discovery is actualized by the template fairing, which itself is
//! created via [`Template::fairing()`] or [`Template::custom()`], the latter of //! created via [`Template::fairing()`], [`Template::custom()`], or
//! which allows for customizations to the templating engine. In order for _any_ //! [`Template::try_custom()`], the latter two allowing customizations to
//! templates to be rendered, the template fairing _must_ be //! enabled templating engines. In order for _any_ templates to be rendered, the
//! [attached](rocket::Rocket::attach()) to the running Rocket instance. Failure //! template fairing _must_ be [attached](rocket::Rocket::attach()) to the
//! to do so will result in a run-time error. //! running Rocket instance. Failure to do so will result in a run-time error.
//! //!
//! 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
@ -110,10 +110,6 @@
//! 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 //! [`Serialize`]: serde::Serialize
//! [`Template`]: crate::templates::Template
//! [`Template::fairing()`]: crate::templates::Template::fairing()
//! [`Template::custom()`]: crate::templates::Template::custom()
//! [`Template::render()`]: crate::templates::Template::render()
#[cfg(feature = "tera_templates")] pub extern crate tera; #[cfg(feature = "tera_templates")] pub extern crate tera;
#[cfg(feature = "tera_templates")] mod tera_templates; #[cfg(feature = "tera_templates")] mod tera_templates;
@ -139,6 +135,7 @@ 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; use rocket::Rocket;
use rocket::request::Request; use rocket::request::Request;
@ -254,9 +251,12 @@ impl Template {
/// Returns a fairing that initializes and maintains templating state. /// Returns a fairing that initializes and maintains templating state.
/// ///
/// Unlike [`Template::fairing()`], this method allows you to configure /// Unlike [`Template::fairing()`], this method allows you to configure
/// templating engines via the parameter `f`. Note that only the enabled /// templating engines via the function `f`. Note that only the enabled
/// templating engines will be accessible from the `Engines` type. /// templating engines will be accessible from the `Engines` type.
/// ///
/// This method does not allow the function `f` to fail. If `f` is fallible,
/// use [`Template::try_custom()`] instead.
///
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
@ -275,10 +275,42 @@ impl Template {
/// # ; /// # ;
/// } /// }
/// ``` /// ```
pub fn custom<F>(f: F) -> impl Fairing pub fn custom<F: Send + Sync + 'static>(f: F) -> impl Fairing
where F: Fn(&mut Engines) + Send + Sync + 'static where F: Fn(&mut Engines)
{ {
TemplateFairing { custom_callback: Box::new(f) } Self::try_custom(move |engines| { f(engines); Ok(()) })
}
/// Returns a fairing that initializes and maintains templating state.
///
/// This variant of [`Template::custom()`] allows a fallible `f`. If `f`
/// returns an error during initialization, it will cancel the launch. If
/// `f` returns an error during template reloading (in debug mode), then the
/// newly-reloaded templates are discarded.
///
/// # Example
///
/// ```rust
/// extern crate rocket;
/// extern crate rocket_contrib;
///
/// use rocket_contrib::templates::Template;
///
/// fn main() {
/// rocket::ignite()
/// // ...
/// .attach(Template::try_custom(|engines| {
/// // engines.handlebars.register_helper ...
/// Ok(())
/// }))
/// // ...
/// # ;
/// }
/// ```
pub fn try_custom<F: Send + Sync + 'static>(f: F) -> impl Fairing
where F: Fn(&mut Engines) -> Result<(), Box<dyn Error>>
{
TemplateFairing { callback: Box::new(f) }
} }
/// Render the template named `name` with the context `context`. The /// Render the template named `name` with the context `context`. The

View File

@ -32,6 +32,23 @@ mod templates_tests {
.mount("/", routes![template_check, is_reloading]) .mount("/", routes![template_check, is_reloading])
} }
#[test]
fn test_callback_error() {
use rocket::{local::blocking::Client, error::ErrorKind::FailedFairings};
let rocket = rocket::ignite().attach(Template::try_custom(|_| {
Err("error reloading templates!".into())
}));
match Client::untracked(rocket) {
Err(e) => match e.kind() {
FailedFairings(failures) => assert_eq!(failures[0], "Templates"),
_ => panic!("Wrong kind of launch error"),
}
_ => panic!("Wrong kind of error"),
}
}
#[cfg(feature = "tera_templates")] #[cfg(feature = "tera_templates")]
mod tera_tests { mod tera_tests {
use super::*; use super::*;