diff --git a/crates/cli/src/templates.rs b/crates/cli/src/templates.rs index 1b9f67d8..ab07bf32 100644 --- a/crates/cli/src/templates.rs +++ b/crates/cli/src/templates.rs @@ -59,7 +59,8 @@ impl TemplatesCommand { } SC::Check { path, skip_builtin } => { - Templates::load(Some(path), !skip_builtin)?; + let templates = Templates::load(Some(path), !skip_builtin)?; + templates.check_render()?; Ok(()) } diff --git a/crates/core/src/templates/context.rs b/crates/core/src/templates/context.rs index a9623f82..a8050e54 100644 --- a/crates/core/src/templates/context.rs +++ b/crates/core/src/templates/context.rs @@ -22,6 +22,7 @@ use crate::{errors::ErroredForm, filters::CsrfToken, storage::SessionInfo}; /// Helper trait to construct context wrappers pub trait TemplateContext { + /// Attach a user session to the template context fn with_session(self, current_session: SessionInfo) -> WithSession where Self: Sized, @@ -32,6 +33,7 @@ pub trait TemplateContext { } } + /// Attach an optional user session to the template context fn maybe_with_session(self, current_session: Option) -> WithOptionalSession where Self: Sized, @@ -42,6 +44,7 @@ pub trait TemplateContext { } } + /// Attach a CSRF token to the template context fn with_csrf(self, token: &CsrfToken) -> WithCsrf where Self: Sized, @@ -52,6 +55,10 @@ pub trait TemplateContext { } } + /// Generate sample values for this context type + /// + /// This is then used to check for template validity in unit tests and in + /// the CLI (`cargo run -- templates check`) fn sample() -> Vec where Self: Sized; diff --git a/crates/core/src/templates/macros.rs b/crates/core/src/templates/macros.rs index c39ec440..ea6ac0a2 100644 --- a/crates/core/src/templates/macros.rs +++ b/crates/core/src/templates/macros.rs @@ -32,10 +32,6 @@ macro_rules! register_templates { extra = { $( $extra_template:expr ),* }; )? - $( - generics = { $( $generic:ident = $sample:ty ),* }; - )? - $( // Match any attribute on the function, such as #[doc], #[allow(dead_code)], etc. $( #[ $attr:meta ] )* @@ -76,34 +72,31 @@ macro_rules! register_templates { .map_err(|source| TemplateError::Render { template: $template, source }) } )* + } - pub fn check_render(&self) -> Result<(), TemplateError> { - self.check_render_inner $( ::< $( $sample ),+ > )? () - } + /// Helps rendering each template with sample data + pub mod check { + use super::*; - fn check_render_inner - $( < $( $generic ),+ > )? - (&self) -> Result<(), TemplateError> - $( where - $( $generic : TemplateContext + Serialize, )+ - )? + $( + #[doc = concat!("Render the `", $template, "` template with sample contexts")] + pub fn $name + $(< $( $lt $( : $clt $(+ $dlt )* + TemplateContext )? ),+ >)? + (templates: &Templates) + -> anyhow::Result<()> { + let samples: Vec< $param > = TemplateContext::sample(); - { - $( - { - let samples: Vec< $param > = TemplateContext::sample(); - - let name = $template; - for sample in samples { - ::tracing::info!(name, "Rendering template"); - self. $name (&sample)?; - } + let name = $template; + for sample in samples { + let context = serde_json::to_value(&sample)?; + ::tracing::info!(name, %context, "Rendering template"); + templates. $name (&sample) + .with_context(|| format!("Failed to render template {:?} with context {}", name, context))?; } - )* - - Ok(()) - } + Ok(()) + } + )* } }; } diff --git a/crates/core/src/templates/mod.rs b/crates/core/src/templates/mod.rs index bf485b14..040cd318 100644 --- a/crates/core/src/templates/mod.rs +++ b/crates/core/src/templates/mod.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![deny(missing_docs)] + //! Templates rendering use std::{collections::HashSet, io::Cursor, path::Path, string::ToString, sync::Arc}; @@ -25,7 +27,9 @@ use tokio::{fs::OpenOptions, io::AsyncWriteExt}; use tracing::{debug, info, warn}; use warp::reject::Reject; +#[allow(missing_docs)] // TODO mod context; + #[macro_use] mod macros; @@ -38,14 +42,19 @@ pub use self::context::{ #[derive(Debug, Clone)] pub struct Templates(Arc); +/// There was an issue while loading the templates #[derive(Error, Debug)] pub enum TemplateLoadingError { + /// Some templates failed to compile #[error("could not load and compile some templates")] Compile(#[from] TeraError), + /// There are essential templates missing #[error("missing templates {missing:?}")] MissingTemplates { + /// List of missing templates missing: HashSet, + /// List of templates that were loaded loaded: HashSet, }, } @@ -146,18 +155,27 @@ impl Templates { } } +/// Failed to render a template #[derive(Error, Debug)] pub enum TemplateError { + /// Failed to prepare the context used by this template #[error("could not prepare context for template {template:?}")] Context { + /// The name of the template being rendered template: &'static str, + + /// The underlying error #[source] source: TeraError, }, + /// Failed to render the template #[error("could not render template {template:?}")] Render { + /// The name of the template being rendered template: &'static str, + + /// The underlying error #[source] source: TeraError, }, @@ -167,7 +185,6 @@ impl Reject for TemplateError {} register_templates! { extra = { "base.html" }; - generics = { T = EmptyContext }; /// Render the login page pub fn render_login(WithCsrf) { "login.html" } @@ -188,12 +205,26 @@ register_templates! { pub fn render_error(ErrorContext) { "error.html" } } +impl Templates { + /// Render all templates with the generated samples to check if they render + /// properly + pub fn check_render(&self) -> anyhow::Result<()> { + check::render_login(self)?; + check::render_register(self)?; + check::render_index(self)?; + check::render_reauth(self)?; + check::render_form_post::(self)?; + check::render_error(self)?; + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; #[test] - fn check_all_templates() { + fn check_builtin_templates() { let templates = Templates::load(None, true).unwrap(); templates.check_render().unwrap(); }