extern crate failure; extern crate serde; extern crate futures; extern crate mail_core; extern crate mail_headers; extern crate vec1; extern crate toml; extern crate maybe_owned; #[cfg(feature="handlebars")] extern crate handlebars as hbs; #[cfg(all(feature="handlebars", not(feature="handlebars-bindings")))] compile_error!("use feature `handlebars-bindings` instead of opt-dep-auto-feature `handlebars`"); use std::{ fs, collections::HashMap, fmt::Debug, path::{Path, PathBuf}, ops::Deref }; use serde::{ Serialize, Deserialize }; use failure::Error; use futures::{ Future, Poll, Async, try_ready, future::{self, Join, Either} }; use vec1::Vec1; use maybe_owned::MaybeOwned; use mail_core::{ Resource, Data, Metadata, Context, ResourceContainerLoadingFuture, compose::{MailParts, BodyPart}, Mail }; use mail_headers::{ HeaderKind, Header, header_components::MediaType, headers }; mod base_dir; mod path_rebase; mod additional_cid; pub mod serde_impl; pub mod error; #[cfg(feature="handlebars")] pub mod handlebars; pub use self::base_dir::*; pub use self::path_rebase::*; pub use self::additional_cid::*; /// Trait used to bind/implement template engines. pub trait TemplateEngine: Sized { type Id: Debug; type LazyBodyTemplate: PathRebaseable + Debug + Send + Serialize + for<'a> Deserialize<'a>; fn load_body_template(&mut self, tmpl: Self::LazyBodyTemplate) -> Result, Error>; fn load_subject_template(&mut self, template_string: String) -> Result; } /// Additional trait a template engine needs to implement for the types it can process as input. /// /// This could for example be implemented in a wild card impl for the template engine for /// any data `D` which implements `Serialize`. pub trait TemplateEngineCanHandleData: TemplateEngine { fn render<'r, 'a>( &'r self, id: &'r Self::Id, data: &'r D, additional_cids: AdditionalCIds<'r> ) -> Result; } /// Load a template as described in a toml file. /// /// This will set the default of the base_dir to the /// dir the template file loaded is in. pub fn load_toml_template_from_path( engine: TE, path: PathBuf, ctx: &C ) -> impl Future, Error=Error> where TE: TemplateEngine + 'static, C: Context { let ctx2 = ctx.clone(); ctx.offload_fn(move || { let content = fs::read_to_string(&path)?; let base: serde_impl::TemplateBase = toml::from_str(&content)?; let base_dir = path.parent().unwrap_or_else(||Path::new(".")); let base_dir = CwdBaseDir::from_path(base_dir)?; Ok((base, base_dir)) }).and_then(move |(base, base_dir)| base.load(engine, base_dir, &ctx2)) } /// Load a template as described in a toml string; pub fn load_toml_template_from_str( engine: TE, content: &str, ctx: &C ) -> impl Future, Error=Error> where TE: TemplateEngine, C: Context { let base: serde_impl::TemplateBase = match toml::from_str(content) { Ok(base) => base, Err(err) => { return Either::B(future::err(Error::from(err))); } }; let base_dir = match CwdBaseDir::from_path(Path::new(".")) { Ok(base_dir) => base_dir, Err(err) => { return Either::B(future::err(Error::from(err))) } }; Either::A(base.load(engine, base_dir, ctx)) } /// A Mail template. #[derive(Debug)] pub struct Template { template_name: String, base_dir: CwdBaseDir, subject: Subject, /// This can only be in the loaded form _iff_ this is coupled /// with a template engine instance, as using it with the wrong /// template engine will lead to potential bugs and panics. bodies: Vec1>, //TODO: make sure embeddings: HashMap, attachments: Vec, engine: TE, } impl Template where TE: TemplateEngine { pub fn inline_embeddings(&self) -> &HashMap { &self.embeddings } pub fn attachments(&self) -> &[Resource] { &self.attachments } pub fn engine(&self) -> &TE { &self.engine } pub fn bodies(&self) -> &[BodyTemplate] { &self.bodies } pub fn subject_template_id(&self) -> &TE::Id { &self.subject.template_id } } /// Represents one of potentially many alternate bodies in a template. #[derive(Debug)] pub struct BodyTemplate { pub template_id: TE::Id, pub media_type: MediaType, pub inline_embeddings: HashMap } impl BodyTemplate where TE: TemplateEngine { pub fn template_id(&self) -> &TE::Id { &self.template_id } pub fn media_type(&self) -> &MediaType { &self.media_type } pub fn inline_embeddings(&self) -> &HashMap { &self.inline_embeddings } } /// Represents a template used for generating the subject of a mail. #[derive(Debug)] pub struct Subject { template_id: TE::Id } impl Subject where TE: TemplateEngine { pub fn template_id(&self) -> &TE::Id { &self.template_id } } /// Automatically provides the `prepare_to_render` method for all `Templates` /// /// This trait is implemented for all `Templates`/`D`(data) combinations where /// the templates template engine can handle the given data (impl. `TemplateEngineCanHandleData`) /// /// This trait should not be implemented by hand. pub trait TemplateExt where TE: TemplateEngine + TemplateEngineCanHandleData { fn render_to_mail_parts<'r>(&self, data: LoadedTemplateData<'r, D>, ctx: &impl Context) -> Result<(MailParts, Header), Error>; fn render<'r>(&self, data: LoadedTemplateData<'r, D>, ctx: &impl Context) -> Result { let (parts, subject) = self.render_to_mail_parts(data, ctx)?; let mut mail = parts.compose(); mail.insert_header(subject); Ok(mail) } } impl TemplateExt for Template where TE: TemplateEngine + TemplateEngineCanHandleData { fn render_to_mail_parts<'r>(&self, data: LoadedTemplateData<'r, D>, ctx: &impl Context) -> Result<(MailParts, Header), Error> { let TemplateData { data, inline_embeddings, mut attachments } = data.into(); let subject = self.engine().render( self.subject_template_id(), &data, AdditionalCIds::new(&[]) )?; let subject = headers::Subject::auto_body(subject)?; //TODO use Vec1 try_map instead of loop let mut bodies = Vec::new(); for body in self.bodies().iter() { let raw = self.engine().render( body.template_id(), &data, AdditionalCIds::new(&[ &inline_embeddings, body.inline_embeddings(), self.inline_embeddings() ]) )?; let data = Data::new( raw.into_bytes(), Metadata { file_meta: Default::default(), media_type: body.media_type().clone(), content_id: ctx.generate_content_id() } ); let inline_embeddings = body.inline_embeddings() .values() .cloned() .collect(); bodies.push(BodyPart { resource: Resource::Data(data), inline_embeddings, attachments: Vec::new() }); } attachments.extend(self.attachments().iter().cloned()); let mut inline_embeddings_vec = Vec::new(); for (key, val) in self.inline_embeddings() { if !inline_embeddings.contains_key(key) { inline_embeddings_vec.push(val.clone()) } } inline_embeddings_vec.extend(inline_embeddings.into_iter().map(|(_,v)|v)); let parts = MailParts { //UNWRAP_SAFE (complexly mapping a Vec1 is safe) alternative_bodies: Vec1::try_from_vec(bodies).unwrap(), inline_embeddings: inline_embeddings_vec, attachments }; Ok((parts, subject)) } } pub struct TemplateData<'a, D: 'a> { pub data: MaybeOwned<'a, D>, pub attachments: Vec, pub inline_embeddings: HashMap } impl<'a, D> TemplateData<'a, D> { pub fn load(self, ctx: &impl Context) -> DataLoadingFuture<'a, D> { let TemplateData { data, attachments, inline_embeddings } = self; let loading_fut = Resource::load_container(inline_embeddings, ctx) .join(Resource::load_container(attachments, ctx)); DataLoadingFuture { payload: Some(data), loading_fut } } } impl From for TemplateData<'static, D> { fn from(data: D) -> Self { TemplateData { data: data.into(), attachments: Default::default(), inline_embeddings: Default::default() } } } impl<'a, D> From<&'a D> for TemplateData<'a, D> { fn from(data: &'a D) -> Self { TemplateData { data: data.into(), attachments: Default::default(), inline_embeddings: Default::default() } } } pub struct LoadedTemplateData<'a, D: 'a>(TemplateData<'a, D>); impl<'a, D> From<&'a D> for LoadedTemplateData<'a, D> { fn from(data: &'a D) -> Self { LoadedTemplateData(TemplateData::from(data)) } } impl From for LoadedTemplateData<'static, D> { fn from(data: D) -> Self { LoadedTemplateData(TemplateData::from(data)) } } impl<'a, D> Deref for LoadedTemplateData<'a, D> { type Target = TemplateData<'a, D>; fn deref(&self) -> &Self::Target { &self.0 } } impl<'a, D> Into> for LoadedTemplateData<'a, D> { fn into(self) -> TemplateData<'a, D> { let LoadedTemplateData(data) = self; data } } /// Future returned when preparing a template for rendering. pub struct DataLoadingFuture<'a, D: 'a> { payload: Option>, loading_fut: Join< ResourceContainerLoadingFuture>, ResourceContainerLoadingFuture> > } impl<'a, D> Future for DataLoadingFuture<'a, D> { type Item = LoadedTemplateData<'a, D>; type Error = Error; fn poll(&mut self) -> Poll { let ( inline_embeddings, attachments ) = try_ready!(self.loading_fut.poll()); //UNWRAP_SAFE only non if polled after resolved let data = self.payload.take().unwrap(); let inner = TemplateData { data, inline_embeddings, attachments }; Ok(Async::Ready(LoadedTemplateData(inner))) } }