use std::{borrow::Cow, error::Error as _};
use error_stack::{Report, ResultExt};
use serde::Serialize;
use thiserror::Error;
use super::{services::EmailError, EmailBuilder};
pub struct EmailContent {
pub html: String,
pub text: String,
}
pub trait EmailTemplate {
fn subject(&self) -> String;
fn render(&self, renderer: &tera::Tera) -> Result<EmailContent, TeraError>;
fn tags(&self) -> Vec<String> {
vec![]
}
fn into_email(
&self,
renderer: &tera::Tera,
to: String,
) -> Result<EmailBuilder, Report<EmailError>> {
let EmailContent { html, text } = self
.render(renderer)
.change_context(EmailError::Rendering)?;
let builder = super::EmailBuilder::new(to, self.subject())
.html(html)
.text(text)
.tags(self.tags());
Ok(builder)
}
}
#[derive(Error, Debug)]
#[error("{0}{}", .0.source().map(|e| format!("\n{e}")).unwrap_or_default())]
pub struct TeraError(#[from] tera::Error);
pub fn render_template_pair(
tera: &tera::Tera,
data: &impl Serialize,
html_path: &str,
text_path: &str,
) -> Result<EmailContent, TeraError> {
let context = tera::Context::from_serialize(data)?;
render_template_pair_with_context(tera, &context, html_path, text_path)
}
pub fn render_template_pair_with_context(
tera: &tera::Tera,
context: &tera::Context,
html_path: &str,
text_path: &str,
) -> Result<EmailContent, TeraError> {
let html = tera.render(html_path, &context)?;
let text = tera.render(text_path, &context)?;
Ok(EmailContent { html, text })
}
pub fn create_templates(
templates: impl Iterator<Item = (Cow<'static, str>, rust_embed::EmbeddedFile)>,
) -> tera::Tera {
let templates = templates
.map(|(name, data)| {
let data = match data.data {
Cow::Borrowed(b) => Cow::Borrowed(std::str::from_utf8(b).unwrap()),
Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
};
(name, data)
})
.collect::<Vec<_>>();
let mut tera = tera::Tera::default();
tera.add_raw_templates(templates).unwrap();
tera
}