use std::collections::HashMap; use std::sync::Arc; use maud::Markup; use tokio::sync::RwLock; use tracing::debug; use crate::error::EmailError; pub type TemplateFn = Arc Markup + Send + Sync>; #[derive(Default)] pub struct TemplateRegistry { inner: RwLock>, } impl TemplateRegistry { pub fn new() -> Self { Self::default() } pub async fn register(&self, name: &str, f: F) where F: Fn(&serde_json::Value) -> Markup + Send + Sync + 'static, { let mut guard = self.inner.write().await; let replacing = guard.contains_key(name); guard.insert(name.to_string(), Arc::new(f)); debug!(template = %name, replacing, "template registered"); } pub async fn render(&self, name: &str, vars: &serde_json::Value) -> Result { let guard = self.inner.read().await; let Some(f) = guard.get(name) else { debug!(template = %name, "template not found"); return Err(EmailError::TemplateNotFound(name.to_string())); }; let markup = f(vars).into_string(); debug!( template = %name, bytes = markup.len(), vars_bytes = vars.to_string().len(), "template rendered" ); Ok(markup) } pub async fn contains(&self, name: &str) -> bool { let guard = self.inner.read().await; guard.contains_key(name) } pub async fn names(&self) -> Vec { let guard = self.inner.read().await; guard.keys().cloned().collect() } }