diff options
author | Philipp Korber <p.korber@1aim.com> | 2018-11-19 17:57:44 +0100 |
---|---|---|
committer | Philipp Korber <p.korber@1aim.com> | 2018-11-19 17:57:44 +0100 |
commit | bada4eb94b86f3aae99d4fcf2a504bd3dffe5f54 (patch) | |
tree | 4eb4eed39da42f897d6ed2fd36098317aeb849b6 | |
parent | ad91e471abde1a1806baa11b0ea01e6b155394a1 (diff) |
chore(new-api) scratched out new api
- The deps (mail-base/common/headers) changed and have some
of the functionality previous in this crate directly and
with a changed (improved) interface.
- The previous approach had some problems.
-rw-r--r-- | Cargo.toml | 26 | ||||
-rw-r--r-- | src/askama_engine/error.rs | 38 | ||||
-rw-r--r-- | src/askama_engine/mod.rs | 316 | ||||
-rw-r--r-- | src/builder_extension.rs | 223 | ||||
-rw-r--r-- | src/compositor/builder.rs | 198 | ||||
-rw-r--r-- | src/compositor/impl_compose.rs | 162 | ||||
-rw-r--r-- | src/compositor/mod.rs | 297 | ||||
-rw-r--r-- | src/error.rs | 270 | ||||
-rw-r--r-- | src/lib.rs | 472 | ||||
-rw-r--r-- | src/resource/impl_inspect.rs | 278 | ||||
-rw-r--r-- | src/resource/mod.rs | 421 | ||||
-rw-r--r-- | src/template_engine.rs | 199 |
12 files changed, 335 insertions, 2565 deletions
@@ -1,28 +1,31 @@ [package] name = "mail-template" version = "0.2.0" -description = "[internal/mail-api] provides a \"composition through template\" api for the Mail type from the mail-api crates" +description = "[mail] provides a way to create bind string template engines to produce mails" authors = ["Philipp Korber <p.korber@1aim.com>"] -keywords = ["mail-api", "internal"] +keywords = ["mail-api", "template"] categories = [] license = "MIT OR Apache-2.0" readme = "./README.md" documentation = "https://docs.rs/mail-template" repository = "https://github.com/1aim/mail-template" +[features] +default = [] +handlebars-bindings = [] [dependencies] -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" } +mail-core = { git="https://github.com/1aim/mail" } +mail-internals = { git="https://github.com/1aim/mail" } +mail-headers = { git="https://github.com/1aim/mail" } failure = "0.1.1" futures = "0.1.14" vec1 = "1.0" soft-ascii-string = "1.0" -serde = { version="1.0.64", optional=true } - +serde = { version="1", optional=true } +toml = "0.4.8" +galemu = "0.2.2" [dependencies.mime] @@ -30,13 +33,6 @@ git="https://github.com/1aim/mime" branch="parser_revamp" version="0.4.0" -[dependencies.askama] -version = "0.6.4" -optional = true [dev-dependencies] -[features] -default = [] -askama-engine = ["askama"] -serialize-to-content-id = ["serde"]
\ No newline at end of file diff --git a/src/askama_engine/error.rs b/src/askama_engine/error.rs deleted file mode 100644 index 8edffd9..0000000 --- a/src/askama_engine/error.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::fmt::{self, Debug, Display}; -use std::sync::Mutex; -use askama; - -/// a wrapper needed as askama is not Sync -#[derive(Fail)] -pub struct AskamaError { - inner: Mutex<askama::Error> -} - -impl AskamaError { - - pub fn inner(&self) -> &Mutex<askama::Error> { - &self.inner - } -} - -const POISON_MSG: &str = "<Debug/Display of crate::askama::Error paniced previously>"; -macro_rules! impl_fmt { - ($($trait:ident),*) => ($( - impl $trait for AskamaError { - fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result { - match self.inner.lock() { - Ok(inner_error) => $trait::fmt(&*inner_error, fter), - Err(_err) => write!(fter, "{}", POISON_MSG) - } - } - } - )*); -} - -impl_fmt!(Debug, Display); - -impl From<askama::Error> for AskamaError { - fn from(err: askama::Error) -> Self { - AskamaError { inner: Mutex::new(err) } - } -}
\ No newline at end of file diff --git a/src/askama_engine/mod.rs b/src/askama_engine/mod.rs deleted file mode 100644 index 2bd0ae5..0000000 --- a/src/askama_engine/mod.rs +++ /dev/null @@ -1,316 +0,0 @@ -use vec1::Vec1; - -use askama; -use headers::components::MediaType; -use mail::{Resource, Context}; - -use ::{TemplateEngine, MailParts, BodyPart, EmbeddedWithCId}; -mod error; -pub use self::error::*; - -pub trait AskamaMailTemplate: askama::Template { - - fn media_type(&self) -> MediaType; - - /// Implement this to have alternate bodies, e.g. a alternate text body for an html body - /// - /// A simple way to bind another template to an data type is by wrapping a reference of - /// the original type into it. - /// - /// # Example - /// - /// ```rust - /// # #[macro_use] - /// # extern crate askama; - /// # extern crate mail_template; - /// # extern crate mail_headers; - /// # mod mail { pub use mail_template::askama_engine as askama; pub use mail_headers::components::MediaType; } - /// use std::ops::Deref; - /// use std::borrow::Cow; - /// use askama::Template; - /// use mail::askama::AskamaMailTemplate; - /// use mail::MediaType; - /// - /// #[derive(Template)] - /// #[template(source = "<h2>Hy {{ name }}</h2>", ext="html")] - /// struct HtmlHy { - /// name: &'static str - /// } - /// - /// impl AskamaMailTemplate for HtmlHy { - /// fn media_type(&self) -> MediaType { - /// MediaType::parse("text/html; charset=utf-8").unwrap() - /// } - /// - /// fn alternate_template<'a>(&'a self) -> Option<Box<AskamaMailTemplate + 'a>> { - /// // theoretically we could circumvent the boxing by returning a &Trait - /// // but this would require transmuting `&HtmlHy` to `&TextHy` so we don't - /// // do this - /// Some(Box::new(TextHy(self))) - /// } - /// } - /// - /// #[derive(Template)] - /// #[template(source = "Hy {{ name }}, use html please", ext="txt")] - /// struct TextHy<'a>(&'a HtmlHy); - /// - /// /// we implement deref so that we can use the fields - /// /// of `HtmlHy` without indirection, e.g. use `name` - /// /// instead of `inner.name` - /// impl<'a> Deref for TextHy<'a> { - /// type Target = HtmlHy; - /// - /// fn deref(&self) -> &Self::Target { - /// self.0 - /// } - /// } - /// - /// impl<'a> AskamaMailTemplate for TextHy<'a> { - /// fn media_type(&self) -> MediaType { - /// MediaType::parse("text/plain; charset=utf-8").unwrap() - /// } - /// } - /// - /// fn main() { - /// let hy = HtmlHy { name: "Liz" }; - /// - /// let rendered = hy.render().unwrap(); - /// assert_eq!(rendered, "<h2>Hy Liz</h2>"); - /// let rendered = hy.alternate_template().unwrap().render().unwrap(); - /// assert_eq!(rendered, "Hy Liz, use html please"); - /// } - /// ``` - /// - fn alternate_template<'a>(&'a self) -> Option<Box<AskamaMailTemplate + 'a>> { - None - } - - fn attachments(&self) -> Vec<Resource> { - Vec::new() - } -} - - -pub struct AskamaTemplateEngine; - - -impl<C, D> TemplateEngine<C, D> for AskamaTemplateEngine - where C: Context, D: AskamaMailTemplate -{ - type TemplateId = (); - type Error = AskamaError; - - fn use_template(&self, _id: &(), data: &D, ctx: &C) -> Result<MailParts, Self::Error> { - let mut state = State::new(ctx); - state.render_bodies::<Self::Error>(data)?; - let (alternative_bodies, attachments) = state.destruct(); - - Ok(MailParts { - alternative_bodies, - attachments, - shared_embeddings: Vec::new(), - }) - } -} - -struct State<'a, C: 'a> { - ctx: &'a C, - bodies: Vec<BodyPart>, - attachments: Vec<EmbeddedWithCId> -} - - -impl<'a, C: 'a> State<'a, C> - where C: Context -{ - fn new(ctx: &'a C) -> Self { - State { - ctx, - bodies: Vec::new(), - attachments: Vec::new() - } - } - - fn render_bodies<E>( - &mut self, - template: &AskamaMailTemplate, - ) -> Result<(), E> - where E: From<askama::Error> - { - let string = template.render()?; - let media_type = template.media_type(); - let resource = Resource::sourceless(media_type, string); - self.bodies.push(BodyPart { - resource: resource, - embeddings: Vec::new() - }); - - for attachment in template.attachments() { - self.attachments.push(EmbeddedWithCId::attachment(attachment, self.ctx)); - } - - let sub = template.alternate_template(); - if let Some(alt) = sub { - self.render_bodies::<E>(&*alt)?; - } - Ok(()) - } - - /// # Panics - /// - /// if render_bodies was not called at last once successfully - fn destruct(self) -> (Vec1<BodyPart>, Vec<EmbeddedWithCId>) { - let State { bodies, attachments, ctx:_ } = self; - let bodies = Vec1::from_vec(bodies).expect("[BUG] should have at last one body"); - (bodies, attachments) - } -} - - -#[cfg(test)] -mod test { - use std::ops::Deref; - - use futures::Future; - use soft_ascii_string::SoftAsciiString; - use askama::Template; - - use mail::Context; - use mail::default_impl::simple_context; - use headers::components::Domain; - use headers::HeaderTryFrom; - - use ::{InspectEmbeddedResources, Embedded}; - use super::*; - //TODO test with alternate bodies and attachments - - #[derive(InspectEmbeddedResources)] - struct Person { - name: &'static str, - name_prefix: &'static str, - avatar: Embedded - } - - #[derive(Template, InspectEmbeddedResources)] - #[template( - source="<img src=\"cid:{{ avatar.content_id().unwrap().as_str() }}\"><h2>Dear {{name_prefix}} {{name}}</h2>", - ext="html")] - // #[askama_mail(media_type = "text/html; charset=utf-8")] - // #[askama_mail(alternate=TextGreeting)] - struct HtmlGreeting<'a> { - person: &'a mut Person - } - - impl<'a> Deref for HtmlGreeting<'a> { - type Target = Person; - - fn deref(&self) -> &Self::Target { - self.person - } - } - - impl<'a> AskamaMailTemplate for HtmlGreeting<'a> { - fn media_type(&self) -> MediaType { - MediaType::parse("text/html; charset=utf-8").unwrap() - } - - fn attachments(&self) -> Vec<Resource> { - vec![ Resource::sourceless_from_string("hy"), Resource::sourceless_from_string("ho") ] - } - - fn alternate_template<'e>(&'e self) -> Option<Box<AskamaMailTemplate + 'e>> { - Some(Box::new(TextGreeting::from(self))) - } - } - - - - #[derive(Template)] - #[template(source="Dear {{name_prefix}} {{name}}", ext="txt")] - // #[askama_mail(media_type = "text/plain; charset=utf-8")] - // #[askama_mail(wraps=HtmlGreeting)] - struct TextGreeting<'a> { - inner: &'a HtmlGreeting<'a> - } - - impl<'a> AskamaMailTemplate for TextGreeting<'a> { - fn media_type(&self) -> MediaType { - MediaType::parse("text/plain; charset=utf-8").unwrap() - } - - fn attachments(&self) -> Vec<Resource> { - vec![ Resource::sourceless_from_string("so") ] - } - } - - //auto-gen from wraps - impl<'a> Deref for TextGreeting<'a> { - type Target = HtmlGreeting<'a>; - - fn deref(&self) -> &Self::Target { - self.inner - } - } - - //auto-gen from wraps - impl<'a> From<&'a HtmlGreeting<'a>> for TextGreeting<'a> { - fn from(inner: &'a HtmlGreeting<'a>) -> Self { - TextGreeting { inner } - } - } - - fn ctx() -> impl Context { - let domain = Domain::try_from("bla.test").unwrap(); - let unique = SoftAsciiString::from_unchecked("dq-9c2e"); - simple_context::new(domain, unique).unwrap() - } - - #[test] - fn use_template_works_as_expected() { - let ctx = ctx(); - - let mut person = Person { - name: "Liz", - name_prefix: "Prof. Dr. Ex. Gd. Frk.", - avatar: Embedded::attachment(Resource::sourceless_from_string("should be an image")) - }; - - let mut data = HtmlGreeting { person: &mut person }; - // this is normally done by the compose mail parts - // this is also why HtmlGreeting takes a &mut Person - // instead of a &Person - let mut nr_visited = 0; - data.inspect_resources_mut(&mut |emb: &mut Embedded| { - emb.assure_content_id(&ctx); - nr_visited += 1; - }); - assert_eq!(nr_visited, 1); - - // this engine binds the template through the data, so no Id - let id = &(); - let res = AskamaTemplateEngine.use_template(id, &data, &ctx); - - let MailParts { - alternative_bodies, - attachments, - shared_embeddings - } = res.unwrap(); - - assert_eq!(alternative_bodies.len(), 2); - assert_eq!(attachments.len(), 3); - assert_eq!(shared_embeddings.len(), 0); - - let first = alternative_bodies.first(); - assert_eq!(first.embeddings.len(), 0); - let res = first.resource.create_loading_future(ctx.clone()).wait().unwrap(); - let stringed = String::from_utf8(res.access().as_slice().to_owned()).unwrap(); - assert!(stringed.starts_with("<img src=3D\"cid:dq-9c2e.")); - assert!(stringed.ends_with("@bla.test\"><h2>Dear Prof. Dr. Ex. G=\r\nd. Frk. Liz</h2>")); - - let last = alternative_bodies.last(); - assert_eq!(last.embeddings.len(), 0); - let res = last.resource.create_loading_future(ctx.clone()).wait().unwrap(); - let stringed = String::from_utf8(res.access().as_slice().to_owned()).unwrap(); - assert_eq!(stringed, "Dear Prof. Dr. Ex. Gd. Frk. Liz") - } -}
\ No newline at end of file diff --git a/src/builder_extension.rs b/src/builder_extension.rs deleted file mode 100644 index 9956ccd..0000000 --- a/src/builder_extension.rs +++ /dev/null @@ -1,223 +0,0 @@ -use media_type::{MULTIPART, ALTERNATIVE, RELATED, MIXED}; -use vec1::Vec1; - - -use headers::{HeaderMap, ContentId, ContentDisposition}; -use headers::components::{Disposition, MediaType}; -use mail::{Resource, Mail, Builder}; -use mail::error::OtherBuilderErrorKind; - -use ::resource::EmbeddedWithCId; -use ::error::{ExtendedBuilderError, ExtendedBuilderErrorKind}; - - -/// A mail body likely created by a template engine -#[derive(Debug)] -pub struct BodyPart { - /// a body created by a template - pub resource: Resource, - - /// embeddings added by the template engine - /// - /// It is a mapping of the name under which a embedding had been made available in the - /// template engine to the embedding (which has to contain a CId, as it already - /// was used in the template engine and CIds are used to link to the content which should - /// be embedded) - pub embeddings: Vec<EmbeddedWithCId>, - -} - -/// Ext. Trait which adds helper methods to the Builder type. -/// -pub trait BuilderExt { - - fn create_alternate_bodies<HM>( - bodies: Vec1<BodyPart>, - header: HM - ) -> Result<Mail, ExtendedBuilderError> - where HM: Into<Option<HeaderMap>>; - - - fn create_mail_body<HM>( - body: BodyPart, - headers: HM - ) -> Result<Mail, ExtendedBuilderError> - where HM: Into<Option<HeaderMap>>; - - fn create_with_attachments<HM>( - body: Mail, - attachments: Vec<EmbeddedWithCId>, - headers: HM - ) -> Result<Mail, ExtendedBuilderError> - where HM: Into<Option<HeaderMap>>; - - fn create_body_from_resource<HM>( - resource: Resource, - headers: HM - ) -> Result<Mail, ExtendedBuilderError> - where HM: Into<Option<HeaderMap>>; - - fn create_body_with_embeddings<HM, EMB>( - sub_body: Mail, - embeddings: EMB, - headers: HM - ) -> Result<Mail, ExtendedBuilderError> - where HM: Into<Option<HeaderMap>>, - EMB: Iterator<Item=EmbeddedWithCId> + ExactSizeIterator; - - fn create_alternate_bodies_with_embeddings<HM, EMB>( - bodies: Vec1<BodyPart>, - embeddings: EMB, - header: HM - ) -> Result<Mail, ExtendedBuilderError> - where HM: Into<Option<HeaderMap>>, - EMB: Iterator<Item=EmbeddedWithCId> + ExactSizeIterator; -} - - - -impl BuilderExt for Builder { - - fn create_alternate_bodies<HM>( - bodies: Vec1<BodyPart>, - headers: HM - ) -> Result<Mail, ExtendedBuilderError> - where HM: Into<Option<HeaderMap>> - { - let bodies = bodies; - - match bodies.len() { - 0 => return Err(OtherBuilderErrorKind::EmptyMultipartBody.into()), - 1 => return Self::create_mail_body(bodies.into_vec().pop().unwrap(), headers), - _n => {} - } - - let mut builder = Builder::multipart(MediaType::new(MULTIPART, ALTERNATIVE)?)?; - - if let Some(headers) = headers.into() { - builder = builder.headers(headers)?; - } - - for body in bodies { - builder = builder.body(Self::create_mail_body(body, None)?)?; - } - - Ok(builder.build()?) - } - - fn create_alternate_bodies_with_embeddings<HM, EMB>( - bodies: Vec1<BodyPart>, - embeddings: EMB, - headers: HM - ) -> Result<Mail, ExtendedBuilderError> - where HM: Into<Option<HeaderMap>>, - EMB: Iterator<Item=EmbeddedWithCId> + ExactSizeIterator - { - match embeddings.len() { - 0 => { - Self::create_alternate_bodies(bodies, headers) - }, - _n => { - Self::create_body_with_embeddings( - Self::create_alternate_bodies(bodies, None)?, - embeddings, - headers - ) - } - } - } - - fn create_mail_body<HM>( - body: BodyPart, - headers: HM - ) -> Result<Mail, ExtendedBuilderError> - where HM: Into<Option<HeaderMap>> - { - let BodyPart { resource, embeddings } = body; - if embeddings.len() > 0 { - Self::create_body_with_embeddings( - Self::create_body_from_resource(resource, None)?, - embeddings.into_iter(), - headers - ) - } else { - Self::create_body_from_resource(resource, headers) - } - } - - fn create_body_from_resource<HM>( - resource: Resource, - headers: HM - ) -> Result<Mail, ExtendedBuilderError> - where HM: Into<Option<HeaderMap>> - { - let mut builder = Builder::singlepart(resource); - if let Some(headers) = headers.into() { - builder = builder.headers(headers)?; - } - Ok(builder.build()?) - } - - fn create_body_with_embeddings<HM, EMB>( - sub_body: Mail, - embeddings: EMB, - headers: HM - ) -> Result<Mail, ExtendedBuilderError> - where HM: Into<Option<HeaderMap>>, - EMB: Iterator<Item=EmbeddedWithCId> + ExactSizeIterator - { - - if embeddings.len() == 0 { - return Err(ExtendedBuilderErrorKind::EmbeddingMissing.into()); - } - - let mut builder = Builder::multipart(MediaType::new(MULTIPART,RELATED)?)?; - - if let Some(headers) = headers.into() { - builder = builder.headers(headers)?; - } - - - builder = builder.body(sub_body)?; - for embedding in embeddings { - let (content_id, resource) = embedding.into(); - builder = builder.body( - Self::create_body_from_resource(resource , headers! { - ContentId: content_id, - ContentDisposition: Disposition::inline() - }?)? - )?; - } - Ok(builder.build()?) - } - - - fn create_with_attachments<HM>( - body: Mail, - attachments: Vec<EmbeddedWithCId>, - headers: HM - ) -> Result<Mail, ExtendedBuilderError> - where HM: Into<Option<HeaderMap>> - { - - let mut builder = Builder::multipart(MediaType::new(MULTIPART, MIXED)?)?; - - if let Some(headers) = headers.into() { - builder = builder.headers(headers)?; - } - - builder = builder.body(body)?; - - for attachment in attachments { - builder = builder.body(Self::create_body_from_resource( - attachment.into(), - headers! { - ContentDisposition: Disposition::attachment() - }? - )?)?; - } - - Ok(builder.build()?) - } -} - diff --git a/src/compositor/builder.rs b/src/compositor/builder.rs deleted file mode 100644 index 91a03f9..0000000 --- a/src/compositor/builder.rs +++ /dev/null @@ -1,198 +0,0 @@ -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/impl_compose.rs b/src/compositor/impl_compose.rs deleted file mode 100644 index 8856885..0000000 --- a/src/compositor/impl_compose.rs +++ /dev/null @@ -1,162 +0,0 @@ -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::{ - BodyPart, BuilderExt -}; -use ::template_engine::{ - 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.attach |