diff options
author | Philipp Korber <philippkorber@gmail.com> | 2018-05-30 13:58:03 +0200 |
---|---|---|
committer | Philipp Korber <philippkorber@gmail.com> | 2018-05-30 14:07:40 +0200 |
commit | 40a6c11c4a4860af62f584e4b314f389b7894fb3 (patch) | |
tree | 7d4e0b70a7634f7c3894f3cdedff937eab423485 | |
parent | b3ef57906728442dbe117570a65ce403552ee401 (diff) |
chore(api)*: simplified api
[won't compile tests, see below]
- use InspectEmbeddedResources + #[derive(..)] instead of
strange side channel during serialize for setting content
ids
- provide a compose methods on `MailSendData` instead of the
strange CompositionBase trait + structs
- use Embedded/EmbeddedWithCId instead of Attachment, Embedding,
EmbeddingWithCId,...
- remove unused crates
- rename mod `tempalate` => `template_enigne`
- changes are not yet applied to render_template_eninge and
tera bindings
-rw-r--r-- | Cargo.toml | 4 | ||||
-rw-r--r-- | src/builder_extension.rs | 16 | ||||
-rw-r--r-- | src/compositor/_impl.rs | 138 | ||||
-rw-r--r-- | src/compositor/builder.rs | 198 | ||||
-rw-r--r-- | src/compositor/composition_base_impl.rs | 82 | ||||
-rw-r--r-- | src/compositor/impl_compose.rs | 160 | ||||
-rw-r--r-- | src/compositor/mail_send_data.rs | 455 | ||||
-rw-r--r-- | src/compositor/mod.rs | 307 | ||||
-rw-r--r-- | src/lib.rs | 80 | ||||
-rw-r--r-- | src/resource.rs | 315 | ||||
-rw-r--r-- | src/resource/impl_inspect.rs | 199 | ||||
-rw-r--r-- | src/resource/mod.rs | 160 | ||||
-rw-r--r-- | src/template.rs | 104 | ||||
-rw-r--r-- | src/template_engine.rs | 139 | ||||
-rw-r--r-- | src/utils.rs | 10 |
15 files changed, 1152 insertions, 1215 deletions
@@ -15,14 +15,12 @@ repository = "https://github.com/1aim/mail-template" mail-types = { git="https://github.com/1aim/mail-types" } mail-common = { git="https://github.com/1aim/mail-common" } mail-headers = { git="https://github.com/1aim/mail-headers" } +mail-derive = { git="https://github.com/1aim/mail-derive" } failure = "0.1.1" futures = "0.1.14" log = "0.3.8" rand = "0.3.15" -scoped-tls = "0.1.0" -serde = "1.0.10" -serde_derive = "1.0.10" chrono = "0.4.0" vec1 = { git="https://github.com/1aim/vec1" } soft-ascii-string = "0.2.0" diff --git a/src/builder_extension.rs b/src/builder_extension.rs index 29e5343..9d5c7a4 100644 --- a/src/builder_extension.rs +++ b/src/builder_extension.rs @@ -7,8 +7,8 @@ use headers::components::{Disposition, MediaType}; use mail::{Resource, Mail, Builder}; use mail::error::OtherBuilderErrorKind; -use ::template::BodyPart; -use ::resource::{EmbeddingWithCId, Attachment}; +use ::template_engine::BodyPart; +use ::resource::EmbeddedWithCId; use ::error::{ExtendedBuilderError, ExtendedBuilderErrorKind}; @@ -31,7 +31,7 @@ pub trait BuilderExt { fn create_with_attachments<HM>( body: Mail, - attachments: Vec<Attachment>, + attachments: Vec<EmbeddedWithCId>, headers: HM ) -> Result<Mail, ExtendedBuilderError> where HM: Into<Option<HeaderMap>>; @@ -48,7 +48,7 @@ pub trait BuilderExt { headers: HM ) -> Result<Mail, ExtendedBuilderError> where HM: Into<Option<HeaderMap>>, - EMB: Iterator<Item=EmbeddingWithCId> + ExactSizeIterator; + EMB: Iterator<Item=EmbeddedWithCId> + ExactSizeIterator; fn create_alternate_bodies_with_embeddings<HM, EMB>( bodies: Vec1<BodyPart>, @@ -56,7 +56,7 @@ pub trait BuilderExt { header: HM ) -> Result<Mail, ExtendedBuilderError> where HM: Into<Option<HeaderMap>>, - EMB: Iterator<Item=EmbeddingWithCId> + ExactSizeIterator; + EMB: Iterator<Item=EmbeddedWithCId> + ExactSizeIterator; } @@ -96,7 +96,7 @@ impl BuilderExt for Builder { headers: HM ) -> Result<Mail, ExtendedBuilderError> where HM: Into<Option<HeaderMap>>, - EMB: Iterator<Item=EmbeddingWithCId> + ExactSizeIterator + EMB: Iterator<Item=EmbeddedWithCId> + ExactSizeIterator { match embeddings.len() { 0 => { @@ -149,7 +149,7 @@ impl BuilderExt for Builder { headers: HM ) -> Result<Mail, ExtendedBuilderError> where HM: Into<Option<HeaderMap>>, - EMB: Iterator<Item=EmbeddingWithCId> + ExactSizeIterator + EMB: Iterator<Item=EmbeddedWithCId> + ExactSizeIterator { if embeddings.len() == 0 { @@ -179,7 +179,7 @@ impl BuilderExt for Builder { fn create_with_attachments<HM>( body: Mail, - attachments: Vec<Attachment>, + attachments: Vec<EmbeddedWithCId>, headers: HM ) -> Result<Mail, ExtendedBuilderError> where HM: Into<Option<HeaderMap>> diff --git a/src/compositor/_impl.rs b/src/compositor/_impl.rs deleted file mode 100644 index 2608196..0000000 --- a/src/compositor/_impl.rs +++ /dev/null @@ -1,138 +0,0 @@ -use std::borrow::Cow; - -use serde::Serialize; -use vec1::Vec1; - -use headers::{ - HeaderMap, HeaderTryFrom, - _From, _To, Subject, Sender -}; -use headers::components::Unstructured; -use mail::{Mail, Builder}; - -use ::utils::SerializeOnly; -use ::resource::{ - EmbeddingWithCId, Attachment, - with_resource_sidechanel -}; -use ::builder_extension::BuilderExt; -use ::template::{ - BodyPart, TemplateEngine, MailParts -}; -use ::error::CompositionError; - -use super::mail_send_data::MailSendData; -use super::CompositionBase; - -pub(crate) trait InnerCompositionBaseExt: CompositionBase { - - fn _compose_mail<D>( - &self, - send_data: MailSendData< - <Self::TemplateEngine as TemplateEngine<Self::Context>>::TemplateId, D> - ) -> Result< - Mail, - CompositionError<<Self::TemplateEngine as TemplateEngine<Self::Context>>::Error> - > - where D: Serialize - { - //compose display name => create Address with display name; - let (core_headers, data, template_id) = self.process_mail_send_data(send_data)?; - - let MailParts { alternative_bodies, shared_embeddings, attachments } - = self.use_template_engine(&*template_id, data)?; - - let mail = self.build_mail(alternative_bodies, shared_embeddings.into_iter(), - attachments, core_headers)?; - - Ok(mail) - } - - fn process_mail_send_data<'a, D>( - &self, - send_data: - MailSendData<'a, <Self::TemplateEngine as TemplateEngine<Self::Context>>::TemplateId, D> - ) -> Result<( - HeaderMap, - D, - Cow<'a, <Self::TemplateEngine as TemplateEngine<Self::Context>>::TemplateId> - ), CompositionError<<Self::TemplateEngine as TemplateEngine<Self::Context>>::Error>> - where D: Serialize - { - let (sender, from_mailboxes, to_mailboxes, subject, template_id, data) - = send_data.destruct(); - - // The subject header field - let subject = Unstructured::try_from( subject )?; - - // creating the header map - let mut core_headers: HeaderMap = headers! { - //NOTE: if we support multiple mailboxes in _From we have to - // ensure Sender is used _iff_ there is more than one from - _From: from_mailboxes, - _To: to_mailboxes, - Subject: subject - }?; - - // add sender header if needed - if let Some(sender) = sender { - core_headers.insert(Sender, sender)?; - } - - Ok((core_headers, data, template_id)) - } - - fn use_template_engine<D>( - &self, - template_id: &<Self::TemplateEngine as TemplateEngine<Self::Context>>::TemplateId, - //TODO change to &D? - data: D - ) -> Result<MailParts, CompositionError<<Self::TemplateEngine as TemplateEngine<Self::Context>>::Error>> - where D: Serialize - { - let ( mut mail_parts, embeddings, attachments ) = - with_resource_sidechanel(self.context(), || -> Result<_, CompositionError<<Self::TemplateEngine as TemplateEngine<Self::Context>>::Error>> { - // we just want to make sure that the template engine does - // really serialize the data, so we make it so that it can - // only do so (if we pass in the data directly it could use - // TypeID+Transmute or TraitObject+downcast to undo the generic - // type erasure and then create the template in some other way - // but this would break the whole Embedding/Attachment extraction ) - let sdata = SerializeOnly::new(data); - self.template_engine() - .use_templates(self.context(), template_id, &sdata) - .map_err(|err| CompositionError::Template(err)) - })?; - - mail_parts.attachments.extend(attachments); - mail_parts.shared_embeddings.extend(embeddings); - Ok(mail_parts) - } - - - - /// uses the results of preprocessing data and templates, as well as a list of - /// mail headers like `_From`,`To`, etc. to create a new mail - fn build_mail<EMB>(&self, - bodies: Vec1<BodyPart>, - embeddings: EMB, - attachments: Vec<Attachment>, - core_headers: HeaderMap - ) -> Result<Mail, CompositionError<<Self::TemplateEngine as TemplateEngine<Self::Context>>::Error>> - where EMB: Iterator<Item=EmbeddingWithCId> + ExactSizeIterator - { - let mail = match attachments.len() { - 0 => Builder::create_alternate_bodies_with_embeddings( - bodies, embeddings, Some(core_headers))?, - _n => Builder::create_with_attachments( - Builder::create_alternate_bodies_with_embeddings(bodies, embeddings, None)?, - attachments, - Some(core_headers) - )? - }; - Ok(mail) - } -} - - -impl<COT: ?Sized> InnerCompositionBaseExt for COT where COT: CompositionBase {} diff --git a/src/compositor/builder.rs b/src/compositor/builder.rs new file mode 100644 index 0000000..91a03f9 --- /dev/null +++ b/src/compositor/builder.rs @@ -0,0 +1,198 @@ +use std::borrow::{ToOwned, Cow}; +use std::fmt::{self, Debug}; + +use vec1::Vec1; + +use headers::components::{ + Mailbox, MailboxList, +}; + +use ::resource::InspectEmbeddedResources; +use ::error::{MailSendDataError, MailSendDataErrorKind, WithSource, WithSourceExt}; + +use super::MailSendData; + +/// Builder to create `MailSendData` +pub struct MailSendDataBuilder<'a, TId: ?Sized + 'a, D> + where TId: ToOwned + Debug, TId::Owned: Debug, D: InspectEmbeddedResources + Debug +{ + sender: Option<Mailbox>, + from: Vec<Mailbox>, + to: Vec<Mailbox>, + subject: Option<String>, + template_id: Option<Cow<'a, TId>>, + data: Option<D> +} + + + + + +impl<'a, TId: ?Sized + 'a, D> Debug for MailSendDataBuilder<'a, TId, D> + where TId: ToOwned + Debug, TId::Owned: Debug, D: InspectEmbeddedResources + Debug +{ + fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result { + fter.debug_struct("MailSendData") + .field("sender", &self.sender) + .field("from", &self.from) + .field("to", &self.to) + .field("subject", &self.subject) + .field("template_id", &self.template_id) + .field("data", &self.data) + .finish() + } +} + +// Sadly I can not used derive(Default) (it want's a bound on TId) +// if the deriviate create is stable, I could use them for that +impl<'a, TId: ?Sized + 'a, D> Default for MailSendDataBuilder<'a, TId, D> + where TId: ToOwned + Debug, TId::Owned: Debug, D: InspectEmbeddedResources + Debug +{ + fn default() -> Self { + Self::new() + } +} + +impl<'a, TId: ?Sized + 'a, D> MailSendDataBuilder<'a, TId, D> + where TId: ToOwned + Debug, TId::Owned: Debug, D: InspectEmbeddedResources + Debug +{ + pub fn new() -> Self { + MailSendDataBuilder { + sender: None, + from: Vec::new(), + to: Vec::new(), + subject: None, + template_id: None, + data: None + } + } + + /// adds a Mailbox to the list of from addresses + pub fn add_from(&mut self, mb: Mailbox) -> &mut Self { + self.from.push(mb); + self + } + + /// add a Mailbox to the list of to addresses + pub fn add_to(&mut self, mb: Mailbox) -> &mut Self { + self.to.push(mb); + self + } + + /// set the sender to the given mailbox and inserts it into the front of the from Mailboxes + /// + /// If a sender was set before it will be override, _but it still will be in the + /// from MailboxList_. + pub fn sender(&mut self, mb: Mailbox) -> &mut Self { + self.sender = Some(mb.clone()); + self.from.insert(0, mb); + self + } + + /// sets the subject as a string + /// + /// If a subject was set previously it will be overwritten. + pub fn subject<I>(&mut self, sbj: I) -> &mut Self + where I: Into<String> + { + self.subject = Some(sbj.into()); + self + } + + /// sets the template_id (borrowed form) + /// + /// If a template_id was set previously it will be overwritten. + pub fn template(&mut self, tid: &'a TId) -> &mut Self { + self.template_id = Some(Cow::Borrowed(tid)); + self + } + + /// sets the template_id (owned form) + /// + /// If a template_id was set previously it will be overwritten. + pub fn owned_template(&mut self, tid: <TId as ToOwned>::Owned) -> &mut Self { + self.template_id = Some(Cow::Owned(tid)); + self + } + + /// sets the template_id (cow form) + /// + /// If a template_id was set previously it will be overwritten. + pub fn cow_template(&mut self, tid: Cow<'a, TId>) -> &mut Self { + self.template_id = Some(tid); + self + } + + + /// sets the data + /// + /// If data was set previously it will be overwritten. + pub fn data(&mut self, data: D) -> &mut Self { + self.data = Some(data); + self + } + + //TODO provide custom error + /// create `MailSendData` from this builder if possible. + /// + /// If there is only one mailbox in from no sender needs + /// to be set. + /// + /// # Error + /// + /// Cases in which an error is returned: + /// + /// - no data, template_id, from or to was set + /// - more than one from was set, but no sender was set + pub fn build(self) + -> Result<MailSendData<'a, TId, D>, WithSource<MailSendDataError, Self>> + { + match self.check_fields_are_set() { + Ok(_) => {}, + Err(err) => return Err(err.with_source(self)) + } + + if self.from.len() > 1 && self.sender.is_none() { + return Err(MailSendDataError + ::from(MailSendDataErrorKind::MultiFromButNoSender) + .with_source(self)); + } + + + //UNWRAP_SAFE..: we already checked that there is data + let from = Vec1::from_vec(self.from).unwrap(); + let to = Vec1::from_vec(self.to).unwrap(); + let subject = self.subject.unwrap(); + let template_id = self.template_id.unwrap(); + let data = self.data.unwrap(); + + Ok(MailSendData { + sender: self.sender, + from: MailboxList(from), + to: MailboxList(to), + subject, + template_id, + data + }) + } + + fn check_fields_are_set(&self) -> Result<(), MailSendDataError> { + use self::MailSendDataErrorKind::*; + let kind = + if self.from.is_empty() { + MissingFrom + } else if self.to.is_empty() { + MissingTo + } else if self.subject.is_none() { + MissingSubject + } else if self.template_id.is_none() { + MissingTemplateId + } else if self.data.is_none() { + MissingTemplateData + } else { + return Ok(()); + }; + + Err(MailSendDataError::from(kind)) + } +} diff --git a/src/compositor/composition_base_impl.rs b/src/compositor/composition_base_impl.rs deleted file mode 100644 index 7755744..0000000 --- a/src/compositor/composition_base_impl.rs +++ /dev/null @@ -1,82 +0,0 @@ -use std::sync::Arc; - -use template::TemplateEngine; -use mail::Context; - -use super::CompositionBase; - -pub struct SimpleCompositionBase<CTX, TE> { - template_engine: TE, - context: CTX -} - -impl<CTX, TE> SimpleCompositionBase<CTX, TE> - where TE: TemplateEngine<CTX>, CTX: Context -{ - pub fn new(context: CTX, template_engine: TE) -> Self { - SimpleCompositionBase { template_engine, context } - } - - - pub fn context_mut(&mut self) -> &mut CTX { - &mut self.context - } - - pub fn template_engine_mut(&mut self) -> &TE { - &mut self.template_engine - } -} - - -impl<CTX, TE> CompositionBase for SimpleCompositionBase<CTX, TE> - where TE: TemplateEngine<CTX>, CTX: Context -{ - type Context = CTX; - type TemplateEngine = TE; - - fn template_engine(&self) -> &Self::TemplateEngine { - &self.template_engine - } - fn context(&self) -> &Self::Context { - &self.context - } -} - -pub struct SharedCompositionBase<CTX, TE> { - template_engine: Arc<TE>, - context: CTX -} - -impl<TE, CTX> SharedCompositionBase<CTX, TE> - where TE: TemplateEngine<CTX>, CTX: Context -{ - pub fn new(context: CTX, template_engine: TE) -> Self { - SharedCompositionBase { - template_engine: Arc::new(template_engine), - context - } - } -} - -impl<TE, CTX> CompositionBase for SharedCompositionBase<CTX, TE> - where TE: TemplateEngine<CTX>, CTX: Context -{ - type TemplateEngine = TE; - type Context = CTX; - - fn template_engine(&self) -> &Self::TemplateEngine { &self.template_engine } - fn context(&self) -> &Self::Context { &self.context } -} - - -impl<'a, TE: 'a, CTX: 'a> CompositionBase for (&'a CTX, &'a TE) - where TE: TemplateEngine<CTX>, CTX: Context -{ - type Context = CTX; - type TemplateEngine = TE; - - fn template_engine(&self) -> &Self::TemplateEngine { self.1 } - fn context(&self) -> &Self::Context { self.0 } -} - - diff --git a/src/compositor/impl_compose.rs b/src/compositor/impl_compose.rs new file mode 100644 index 0000000..fc6663d --- /dev/null +++ b/src/compositor/impl_compose.rs @@ -0,0 +1,160 @@ +use std::borrow::Cow; +use std::marker::PhantomData; +use vec1::Vec1; + +use headers::{ + HeaderMap, HeaderTryFrom, + _From, _To, Subject, Sender +}; +use headers::components::Unstructured; +use mail::{Mail, Builder, Context}; + +use ::resource::{ + Embedded, EmbeddedWithCId, + InspectEmbeddedResources, Disposition +}; +use ::builder_extension::BuilderExt; +use ::template_engine::{ + BodyPart, TemplateEngine, MailParts +}; +use ::error::CompositionError; + +use super::MailSendData; + +pub(crate) fn compose_mail<'a, C, E, D>( + ctx: &C, + engine: &E, + send_data: MailSendData<'a, E::TemplateId, D> +) -> Result<Mail, CompositionError<E::Error>> + where C: Context, E: TemplateEngine<C, D>, D: InspectEmbeddedResources +{ + (Ctx { ctx, engine, _p: PhantomData }).compose_mail(send_data) +} + +struct Ctx<'a, 'b, C: 'a, E: 'b, D> + where C: Context, E: TemplateEngine<C, D>, D: InspectEmbeddedResources +{ + ctx: &'a C, + engine: &'b E, + _p: PhantomData<D> +} + +impl<'a, 'b, C: 'a, E: 'b, D> Copy for Ctx<'a, 'b, C, E, D> + where C: Context, E: TemplateEngine<C, D>, D: InspectEmbeddedResources +{} + +impl<'a, 'b, C: 'a, E: 'b, D> Clone for Ctx<'a, 'b, C, E, D> + where C: Context, E: TemplateEngine<C, D>, D: InspectEmbeddedResources +{ + fn clone(&self) -> Self { + Ctx { ctx: self.ctx, engine: self.engine, _p: self._p } + } +} + +impl<'a, 'b, C, E, D> Ctx<'a, 'b, C, E, D> + where C: Context, E: TemplateEngine<C, D>, D: InspectEmbeddedResources +{ + + fn compose_mail(self, send_data: MailSendData<E::TemplateId, D>) + -> Result<Mail, CompositionError<E::Error>> + { + //compose display name => create Address with display name; + let (core_headers, data, template_id) = self.process_mail_send_data(send_data)?; + + let MailParts { alternative_bodies, shared_embeddings, attachments } + = self.use_template_engine(&*template_id, data)?; + + let mail = self.build_mail(alternative_bodies, shared_embeddings.into_iter(), + attachments, core_headers)?; + + Ok(mail) + } + + fn process_mail_send_data<'n>( + self, + send_data: + MailSendData<'n, E::TemplateId, D> + ) -> Result<( + HeaderMap, + D, + Cow<'n, E::TemplateId> + ), CompositionError<E::Error>> + where D: InspectEmbeddedResources + { + let (sender, from_mailboxes, to_mailboxes, subject, template_id, data) + = send_data.destruct(); + + // The subject header field + let subject = Unstructured::try_from( subject )?; + + // creating the header map + let mut core_headers: HeaderMap = headers! { + //NOTE: if we support multiple mailboxes in _From we have to + // ensure Sender is used _iff_ there is more than one from + _From: from_mailboxes, + _To: to_mailboxes, + Subject: subject + }?; + + // add sender header if needed + if let Some(sender) = sender { + core_headers.insert(Sender, sender)?; + } + + Ok((core_headers, data, template_id)) + } + + fn use_template_engine( + self, + template_id: &E::TemplateId, + //TODO change to &D? + data: D + ) -> Result<MailParts, CompositionError<E::Error>> + where D: InspectEmbeddedResources + { + let mut data = data; + let mut embeddings = Vec::new(); + let mut attachments = Vec::new(); + + data.inspect_resources_mut(&mut |embedded: &mut Embedded| { + let embedded_wcid = embedded.assure_content_id_and_copy(self.ctx); + match embedded_wcid.disposition() { + Disposition::Inline => embeddings.push(embedded_wcid), + Disposition::Attachment => attachments.push(embedded_wcid) + } + }); + + let mut mail_parts = self.engine + .use_template(template_id, &data, self.ctx) + .map_err(|err| CompositionError::Template(err))?; + + mail_parts.attachments.extend(attachments); + mail_parts.shared_embeddings.extend(embeddings); + Ok(mail_parts) + } + + + + /// uses the results of preprocessing data and templates, as well as a list of + /// mail headers like `_From`,`To`, etc. to create a new mail + fn build_mail<EMB>( + self, + bodies: Vec1<BodyPart>, + embeddings: EMB, + attachments: Vec<EmbeddedWithCId>, + core_headers: HeaderMap + ) -> Result<Mail, CompositionError<E::Error>> + where EMB: Iterator<Item=EmbeddedWithCId> + ExactSizeIterator + { + let mail = match attachments.len() { + 0 => Builder::create_alternate_bodies_with_embeddings( + bodies, embeddings, Some(core_headers))?, + _n => Builder::create_with_attachments( + Builder::create_alternate_bodies_with_embeddings(bodies, embeddings, None)?, + attachments, + Some(core_headers) + )? + }; + Ok(mail) + } +} diff --git a/src/compositor/mail_send_data.rs b/src/compositor/mail_send_data.rs deleted file mode 100644 index 6168513..0000000 --- a/src/compositor/mail_send_data.rs +++ /dev/null @@ -1,455 +0,0 @@ -use std::borrow::{ToOwned, Cow}; -use std::sync::Arc; -use std::ops::Deref; -use std::fmt::{self, Debug}; - -use serde::Serialize; -use vec1::Vec1; - -use headers::HeaderTryFrom; -use headers::error::ComponentCreationError; -use headers::components::{ - Mailbox, MailboxList, - Phrase, Email -}; - -use ::error::{MailSendDataError, MailSendDataErrorKind, WithSource, WithSourceExt}; - -/// A type containing all per-Mail specific information -/// -/// The contained information is: -/// -/// - sender (if any) -/// - from (1+ Mailboxes) -/// - to (1+ Mailboxes) -/// - subject (a String) -/// - template (a template id, or more concrete `Cow<'a, TId>`, often a cow string) -/// - data (the data for the template) -/// -/// To create a `MailSendData` instance use the `MailSendDataBuilder`. -/// -/// All information in a `MailSendData` instance can be accesses for reading, -/// but some constraints are set on modifying it so that following constraints -/// are uphold: -/// -/// 1. if there is more than one Mailbox in from then there is a sender -/// 2. there has to be at last one Mailbox in from -/// 3. there has to be at last one Mailbox in to -/// -/// # Example (Construction) -/// -/// ``` -/// # extern crate mail_common as common; -/// # extern crate mail_headers as headers; -/// # extern crate mail_types as mail; -/// # extern crate mail_template as compose; -/// # use std::collections::HashMap; -/// # use headers::HeaderTryFrom; -/// # use headers::components::{Mailbox, Email}; -/// # use compose::MailSendDataBuilder; -/// # -/// # fn main() { -/// # -/// # let me = Email::try_from("me@thisisme.mememe").unwrap().into(); -/// # let an_additional_from = Email::try_from("notme@thisisntme.notmenotmenotme").unwrap().into(); -/// # let some_one_else = Email::try_from("other@person.that_is").unwrap().into(); -/// # let test_data = HashMap::<&'static str, String>::new(); -/// let mut builder = MailSendDataBuilder::new(); -/// builder -/// .sender(me) -/// .add_from(an_additional_from) -/// .add_to(some_one_else) -/// .subject("Una test") -/// .template("template_a1_b") -/// .data(test_data); -/// -/// // build() consumes the builder so we can not chain -/// // it with the the other calls to the builder -/// let mail_send_data = builder.build().unwrap(); -/// # } -/// ``` -/// -pub struct MailSendData<'a, TId: ?Sized + 'a, D> - where TId: ToOwned, D: Serialize -{ - sender: Option<Mailbox>, - from: MailboxList, - to: MailboxList, - subject: String, - template_id: Cow<'a, TId>, - data: D -} - -impl<'a, TId: ?Sized + 'a, D> Debug for MailSendData<'a, TId, D> - where TId: ToOwned + Debug, <TId as ToOwned>::Owned: Debug, D: Serialize + Debug -{ - fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result { - fter.debug_struct("MailSendData") - .field("sender", &self.sender) - .field("from", &self.from) - .field("to", &self.to) - .field("subject", &self.subject) - .field("template_id", &self.template_id) - .field("data", &self.data) - .finish() - } -} - - -impl<'a, TId: ?Sized + 'a, D> MailSendData<'a, TId, D> - where TId: ToOwned, D: Serialize -{ - /// create a simple MailSendData with a sing From and a single To Mailbox - pub fn simple_new<I>( - from: Mailbox, to: Mailbox, - subject: I, - template_id: Cow<'a, TId>, data: D - ) -> Self - where I: Into<String> - { - MailSendData { - sender: None, - from: MailboxList(vec1![from]), - to: MailboxList(vec1![to]), - subject: subject.into(), - template_id, data - } - } - - /// returns a reference to a explicity set sender or else the first (and only) from mailbox - pub fn sender(&self) -> &Mailbox { - self.sender.as_ref().unwrap_or_else(|| self.from.first()) - } - - - pub fn _from(&self) -> &MailboxList { - &self.from - } - - /// Allows mutating from Mailboxes - /// - /// this does only expose a &mut Slice of Mailboxes, instead of a &mut MailboxList - /// to make sure that no from mailbox can be added as sender might be empty - pub fn _from_mut(&mut self) -> &mut [Mailbox] { - &mut self.from - } - - //TODO add set_sender method - //TODO add try_add_from method failing if sender is None - //TODO maybe add a try_set_from(MailboxList) too - - pub fn _to(&self) -> &MailboxList { - &self.to - } - - pub fn _to_mut(&mut self) -> &mut MailboxList { - &mut self.to - } - - pub fn subject(&self) -> &str { - &self.subject - } - - pub fn subject_mut(&mut self) -> &mut String { - &mut self.subject - } - - pub fn template(&self) -> &TId { - &self.template_id - } - - pub fn template_mut(&mut self) -> &mut Cow<'a, TId> { - &mut self.template_id - } - - pub fn data(&self) -> &D { - &self.data - } - - pub fn data_mut(&mut self) -> &mut D { - &mut self.data - } - - pub(crate) fn destruct(self) -> - (Option<Mailbox>, MailboxList, MailboxList, String, Cow<'a, TId>, D) - { - //use let destruction to make it more refactoring resistend - let MailSendData { sender, from, to, subject, template_id, data } = self; - (sender, from, to, subject, template_id, data) - } - - pub fn auto_gen_display_names<NC>(&mut self, name_composer: NC) -> Result<(), ComponentCreationError> - where NC: NameComposer<D> - { - let data = &mut self.data; - { - let mut from_auto_gen = |email: &Email| { - match name_composer.compose_from_name(email, data)? { - Some(name) => Ok(Some(Phrase::try_from(name)?)), - None => Ok(None), - } - }; - - if let Some(sender) = self.sender.as_mut() { - sender.auto_gen_name(&mut from_auto_gen)?; - } - - for elem in self.from.iter_mut() { - elem.auto_gen_name(&mut from_auto_gen)?; - } - } - - for elem in self.to.iter_mut() { - elem.auto_gen_name(|email| { - match name_composer.compose_to_name(email, data)? { - Some(name) => Ok(Some(Phrase::try_from(name)?)), - None => Ok(None), - } - })?; - } - - Ok(()) - } -} - -pub trait NameComposer<D> { - /// generates a display name used in a From header based on email address and mails data - /// - /// The data is passed in as a `&mut` ref so that the generated name can - /// also be made available to the template engine, e.g. for generating - /// greetings. The data should _not_ be changed in any other way. - /// - /// The composer can decide to not generate a display name if, e.g. there - /// is not enough information to doe so. - /// - /// # Error - /// - /// A error can be returned if generated the name failed, e.g. because - /// a query to a database failed with an connection error. A error should - /// _not_ be returned if there is "just" not enough data to create a display - /// name, in which `Ok(None)` should be returned indicating that there is - /// no display name. - fn compose_from_name( &self, email: &Email, data: &mut D ) -> Result<Option<String>, ComponentCreationError>; - - /// generates a display name used in a To header based on email address and mails data |