use std::path::Path; use std::process::exit; use std::sync::Arc; use axum::response::{Html, IntoResponse}; use serde::Serialize; use tera::{Tera, Context}; use crate::errors::Error; #[derive(Clone)] pub struct TemplateEngine { tera: Arc<Tera>, } pub enum TemplateError { SerializationError { reason: Box<dyn std::error::Error> }, RenderError { name: String, reason: Box<dyn std::error::Error> }, } impl TemplateEngine { pub fn new(template_directory: &Path) -> Self { let template_glob = template_directory.join("**").join("*"); match Tera::new(template_glob.to_str().expect("valid glob path string")) { Ok(tera) => TemplateEngine { tera: Arc::new(tera) }, Err(e) => { println!("Loading templates failed: {}", e); exit(1) } } } pub fn render<S: Serialize>(&self, name: &str, context: S) -> Result<String, TemplateError> { let context = Context::from_serialize(context).map_err(|e| { TemplateError::SerializationError { reason: Box::new(e) } })?; let content = self.tera.render(name, &context).map_err(|e| { TemplateError::RenderError { name: name.into(), reason: Box::new(e) } })?; Ok(content) } } pub struct Template<'n, S: Serialize> { pub name: &'n str, pub engine: TemplateEngine, pub context: S, } impl<'n, S: Serialize> Template<'n, S> { pub fn new(name: &'n str, engine: TemplateEngine, context: S) -> Self { Template { name, engine, context, } } } impl<S: Serialize> IntoResponse for Template<'_, S> { fn into_response(self) -> axum::response::Response { let res = self.engine.render(self.name, self.context); match res { Ok(content) => Html(content).into_response(), Err(err) => Error::from(err).into_response(), } } }