use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Rendered {
pub subject: String,
pub html: String,
pub text: String,
}
#[derive(Debug, Clone, Error)]
pub enum TemplateError {
#[error("template {id:?} is not registered (locale {locale:?})")]
UnknownTemplate { id: String, locale: String },
#[error("template {id:?} failed to render: {reason}")]
RenderFailed { id: String, reason: String },
}
pub trait Template: Send + Sync {
fn render(&self, data: &Value, locale: &str) -> Result<Rendered, TemplateError>;
}
#[derive(Clone, Default)]
pub struct TemplateRegistry {
templates: HashMap<String, Arc<dyn Template>>,
}
impl TemplateRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, id: impl Into<String>, template: Box<dyn Template>) {
self.templates.insert(id.into(), Arc::from(template));
}
pub fn register_all(
&mut self,
templates: impl IntoIterator<Item = (String, Box<dyn Template>)>,
) {
for (id, template) in templates {
self.register(id, template);
}
}
pub fn render(&self, id: &str, data: &Value, locale: &str) -> Result<Rendered, TemplateError> {
let localized = format!("{id}@{locale}");
if let Some(template) = self.templates.get(&localized) {
return template.render(data, locale);
}
match self.templates.get(id) {
Some(template) => template.render(data, locale),
None => Err(TemplateError::UnknownTemplate {
id: id.to_string(),
locale: locale.to_string(),
}),
}
}
pub fn contains(&self, id: &str, locale: &str) -> bool {
self.templates.contains_key(&format!("{id}@{locale}")) || self.templates.contains_key(id)
}
}
impl std::fmt::Debug for TemplateRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut ids: Vec<&String> = self.templates.keys().collect();
ids.sort();
f.debug_struct("TemplateRegistry")
.field("ids", &ids)
.finish()
}
}