From 2f98299272a2a196a3d053918ebd542e03abfc70 Mon Sep 17 00:00:00 2001 From: Jeb Rosen Date: Wed, 4 Nov 2020 02:39:40 -0800 Subject: [PATCH] Allow fallible callback in 'Template::try_custom()'. Closes #1064. --- contrib/lib/src/templates/fairing.rs | 40 +++++++++++++------ contrib/lib/src/templates/mod.rs | 58 +++++++++++++++++++++------- contrib/lib/tests/templates.rs | 17 ++++++++ 3 files changed, 91 insertions(+), 24 deletions(-) diff --git a/contrib/lib/src/templates/fairing.rs b/contrib/lib/src/templates/fairing.rs index 6d5bbaac..1da06c42 100644 --- a/contrib/lib/src/templates/fairing.rs +++ b/contrib/lib/src/templates/fairing.rs @@ -1,3 +1,5 @@ +use std::error::Error; + use crate::templates::{DEFAULT_TEMPLATE_DIR, Context, Engines}; use rocket::Rocket; @@ -5,6 +7,8 @@ use rocket::fairing::{Fairing, Info, Kind}; pub(crate) use self::context::ContextManager; +type Callback = Box Result<(), Box>+ Send + Sync + 'static>; + #[cfg(not(debug_assertions))] mod context { use std::ops::Deref; @@ -37,7 +41,7 @@ mod context { 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 /// 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 /// reinitialized from disk and the user's customization callback is run /// again. - pub fn reload_if_needed(&self, custom_callback: F) { + pub fn reload_if_needed(&self, callback: &Callback) { self.watcher.as_ref().map(|w| { let rx_lock = w.lock().expect("receive queue lock"); let mut changed = false; @@ -100,11 +104,17 @@ mod context { info_!("Change detected: reloading templates."); let mut ctxt = self.context_mut(); if let Some(mut new_ctxt) = Context::initialize(ctxt.root.clone()) { - custom_callback(&mut new_ctxt.engines); - *ctxt = new_ctxt; + match callback(&mut new_ctxt.engines) { + Ok(()) => *ctxt = new_ctxt, + Err(e) => { + warn_!("The template customization callback returned an error:"); + warn_!("{}", e); + warn_!("The existing templates will remain active."); + } + } } else { 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 /// functionality specific to individual template engines. In debug mode, /// this callback might be run multiple times as templates are reloaded. - pub custom_callback: Box, + pub callback: Callback, } #[rocket::async_trait] @@ -161,11 +171,19 @@ impl Fairing for TemplateFairing { use crate::templates::Engines; info!("{}{}", Paint::emoji("📐 "), Paint::magenta("Templating:")); - info_!("directory: {}", Paint::white(root)); - info_!("engines: {:?}", Paint::white(Engines::ENABLED_EXTENSIONS)); - (self.custom_callback)(&mut ctxt.engines); - Ok(rocket.manage(ContextManager::new(ctxt))) + match (self.callback)(&mut ctxt.engines) { + 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), } @@ -176,6 +194,6 @@ impl Fairing for TemplateFairing { let cm = req.managed_state::() .expect("Template ContextManager registered in on_attach"); - cm.reload_if_needed(&*self.custom_callback); + cm.reload_if_needed(&self.callback); } } diff --git a/contrib/lib/src/templates/mod.rs b/contrib/lib/src/templates/mod.rs index 5459644d..16f681e7 100644 --- a/contrib/lib/src/templates/mod.rs +++ b/contrib/lib/src/templates/mod.rs @@ -93,11 +93,11 @@ //! ## Template Fairing //! //! Template discovery is actualized by the template fairing, which itself is -//! created via [`Template::fairing()`] or [`Template::custom()`], the latter of -//! which allows for customizations to the templating engine. In order for _any_ -//! templates to be rendered, the template fairing _must_ be -//! [attached](rocket::Rocket::attach()) to the running Rocket instance. Failure -//! to do so will result in a run-time error. +//! created via [`Template::fairing()`], [`Template::custom()`], or +//! [`Template::try_custom()`], the latter two allowing customizations to +//! enabled templating engines. In order for _any_ templates to be rendered, the +//! template fairing _must_ be [attached](rocket::Rocket::attach()) to the +//! 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 //! 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. //! //! [`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")] mod tera_templates; @@ -139,6 +135,7 @@ use serde_json::{Value, to_value}; use std::borrow::Cow; use std::path::PathBuf; +use std::error::Error; use rocket::Rocket; use rocket::request::Request; @@ -254,9 +251,12 @@ impl Template { /// Returns a fairing that initializes and maintains templating state. /// /// 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. /// + /// This method does not allow the function `f` to fail. If `f` is fallible, + /// use [`Template::try_custom()`] instead. + /// /// # Example /// /// ```rust @@ -275,10 +275,42 @@ impl Template { /// # ; /// } /// ``` - pub fn custom(f: F) -> impl Fairing - where F: Fn(&mut Engines) + Send + Sync + 'static + pub fn custom(f: F) -> impl Fairing + 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: F) -> impl Fairing + where F: Fn(&mut Engines) -> Result<(), Box> + { + TemplateFairing { callback: Box::new(f) } } /// Render the template named `name` with the context `context`. The diff --git a/contrib/lib/tests/templates.rs b/contrib/lib/tests/templates.rs index a584de39..ba41f8f6 100644 --- a/contrib/lib/tests/templates.rs +++ b/contrib/lib/tests/templates.rs @@ -32,6 +32,23 @@ mod templates_tests { .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")] mod tera_tests { use super::*;