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, locale::Localization}; #[derive(Clone)] pub struct TemplateEngine { pub tera: Arc, } #[derive(Debug)] pub enum TemplateError { SerializationError { reason: Box }, RenderError { name: String, reason: Box }, } impl TemplateEngine { pub fn new(template_directory: &Path, localization: Localization) -> Self { let template_glob = template_directory.join("**").join("*"); match Tera::new(template_glob.to_str().expect("valid glob path string")) { Ok(mut tera) => { tera.register_function("tr", localization); TemplateEngine { tera: Arc::new(tera) } }, Err(e) => { println!("Loading templates failed: {}", e); exit(1) } } } pub fn render(&self, name: &str, context: S) -> Result { 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 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(), } } }