diff options
author | Philipp Korber <p.korber@1aim.com> | 2018-08-24 21:25:30 +0200 |
---|---|---|
committer | Philipp Korber <p.korber@1aim.com> | 2018-08-24 21:25:30 +0200 |
commit | ad91e471abde1a1806baa11b0ea01e6b155394a1 (patch) | |
tree | 2d35c3ee81ceb3b23f30051dd11d4ca796776f44 | |
parent | d2304b8f7496b5653187bed332ba6c429e37248a (diff) |
doc: added more api documentation
-rw-r--r-- | README.md | 107 | ||||
-rw-r--r-- | src/compositor/mod.rs | 8 | ||||
-rw-r--r-- | src/error.rs | 33 | ||||
-rw-r--r-- | src/lib.rs | 106 | ||||
-rw-r--r-- | src/resource/mod.rs | 42 | ||||
-rw-r--r-- | src/template_engine.rs | 82 |
6 files changed, 364 insertions, 14 deletions
@@ -1,15 +1,114 @@ -# mail-template   [![Build Status](https://travis-ci.org/1aim/mail_template.svg?branch=master)](https://travis-ci.org/1aim/mail_template) +# mail-template -**TODO** +**Provides mechanisms for generating mails based on templates** --- -TODO +This crate provides a general interface for using template engine with the mail crate. +It's core is the `TemplateEngine` trait which can be implemented to bind a template engine. +When rendering a template the template engine implementing the `TemplateEngine` trait will +produce a number of (wrapped) `Resource` instances representing the alternate bodies of amail as well as a number of additional `Resources` used for embedded content (e.g. logoimages) and attachments. This crate then takes this parts and composes a multipart mime mail from +it. -Documentation can be [viewed on docs.rs](https://docs.rs/mail-template). +## Template Engine implementations + +A mail template engine has to do more then just taking a single text +template (e.g. a handlebars template) and produce some output using +string magic. It has to: + +1. consider alternate bodies, so it should render at last two + "text templates" (one text/plain, one html) + +2. consider which additional embeddings/attachments should be included + (all in the given template data are included, but you might + add additional ones, e.g. some logo image) + +As such text template engines like `Handle` bar can not directly +be bound to the `TemplateEngine` trait. + +For using text template engine it's recommended to use +the `mail-template-render-engine` (also exposed through the +mail facade) which implements this overhead for any engine +which can "just" render some text and provides default +bindings to some common template engines (e.g. Handlebars). + +## Derive + +This crate requires template data to implement `InspectEmbeddedResources` +which combined with some typing/generic design decisions allows to bind +not just to template engines which use serialization to access template +data but also to such which use static typing (like `askama`). + +As such it re-exports the `InspectEmbeddedResources` derive from +`mail-derive`. Note that if you use the mail facade it also does +re-export the derive. + +## Features + +- `askama-engine`, includes bindings for the askama template engine. +- `serialize-to-content-id`, implements Serialize for `Embedded`, + `EmbeddedWithCId` which serializes the embedded type **into its + content id**. E.g. a image with content id `"q09cu3@example.com"` + will be serialized to the string `"q09cu3@example.com"`. This is + extremely useful for all template engines which use serialization + as their way to access template data. +## Example + +```rust +``` + +## Road Map + +The current implementation has a number of limitations which should be lifted with +future versions: + +- Only a limited subset of headers are/can be set through the template engine + (`Sender`, `From`, `To`, `Subject`) while some headers are set implicitly + when encoding the mail (e.g. `Date`, `Content-Type`, `Content-Disposition`). + But sometimes it would be useful to add some custom headers through the template + engine (both on the outermost and inner bodies). +- `From`, `To`, `Subject` have to be given, but sometimes you might want to just + create the `Mail` type and then set them by yourself (through you _can_ currently + override them) +- Re-use/integration of existing mail instances: Some times you might want to + use a `Mail` instance created some where else as a body for a multipart mail + generated from a template (e.g. some thing generating "special" attachments). + +Also there are some parts which are likely to change: + +- `MailSendData`/`MailSendDataBuilder` the name is + not very good it also needs to change to handle + the thinks listed above +- `Embedded`, `EmbeddedWithCid`, embeddings and attachments + currently a `Embedded` instance is a wrapper around `Resource` + representing something which will become a mail body but is not + a main body (i.e. it not the text/html/.. you send) instead it + something embedded in the mail which is either used as attachment + or as a embedding (e.g. a logo image). Through the content disposition + the `Embedded` instance differs between thing embedded and internally + used or embedded and used as attachment at the same time many different + arrays are sometimes used to differ between them (e.g. in `MailParts`) + but there is no (type system) check to make sure a array of thinks used + as attachments can not contain a `Embedded` instance with content disposition + inline. The result will still be a valid mail, but likely not in the + way you expect it to be. This should be fixed one way or another (making + the different array type safe and lifting disposition to the type level + had been used but didn't play out nicely). +- `serialize-to-content-id`, is a nice idea but has some problems in + some edge cases (e.g. when you want to serialize anything containing + a `Embedded` type for any usage BUT accessing it in an template). So + it might be removed, which means a import like `cid:{{data.mything}}` + (using some mustach pseudo template syntax) would become `cid:{{data.mything.cid}}`. + + +## Documentation + + +Documentation can be [viewed on docs.rs](https://docs.rs/mail-template). +(once published) ## License diff --git a/src/compositor/mod.rs b/src/compositor/mod.rs index 0cdd489..a3cad05 100644 --- a/src/compositor/mod.rs +++ b/src/compositor/mod.rs @@ -229,7 +229,13 @@ impl<'a, TId: ?Sized + 'a, D> Debug for MailSendData<'a, TId, D> } } - +/// Trait for implementing a mechanism to auto-generate display names +/// for from/to headers based on emails. +/// +/// # Stability Note +/// +/// This trait might become deprecated before 1.0 and might be dropped +/// soon. Through this is not yet decided to treat with care. pub trait NameComposer<D> { /// generates a display name used in a From header based on email address and mails data /// diff --git a/src/error.rs b/src/error.rs index d25b7a7..f22f331 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,4 @@ +//! Module contains all custom errors introduced by this crate. use std::fmt::{self, Display, Debug}; use std::mem::drop; @@ -25,6 +26,12 @@ error cases: */ +/// Extension adding a `with_source` method to anything implementing `Fail`. +/// +/// This provides a uniform way to combine a error with the source which +/// caused the error and return them together. The returned `WithSource` +/// instance will implement `Fail` if possible but does not require it, +/// making it compatible with non 'static, Send, Sync sources. pub trait WithSourceExt: Sized + Fail { fn with_source<S>(self, source: S) -> WithSource<Self, S> where S: Debug @@ -52,29 +59,36 @@ impl<E, S> WithSource<E, S> where E: Fail, S: Debug { + /// Create a new instance given an error and it's source. pub fn new(error: E, source: S) -> Self { WithSource { error, source } } + + /// Return a reference to it's source. pub fn source(&self) -> &S { &self.source } + /// Return a reference to the contained error. pub fn error(&self) -> &E { &self.error } + /// Turns this type into it's source. pub fn into_source(self) -> S { let WithSource { error, source } = self; drop(error); source } + /// Turns this type into it's error. pub fn into_error(self) -> E { let WithSource { error, source } = self; drop(source); error } + /// Decomposes this type into its error and its source. pub fn split(self) -> (E, S) { let WithSource { error, source } = self; (error, source) @@ -101,11 +115,15 @@ impl<E, S> Display for WithSource<E, S> } } +/// Error returned when composing a `Mail` failed. #[derive(Debug, Fail)] pub enum CompositionError<TE: Fail> { + /// It failed to use the underlying template engine. #[fail(display = "{}", _0)] Template(TE), + /// It didn't fail to get all `MailParts` but wasn't + /// able to compose them into an mail. #[fail(display = "{}", _0)] Builder(ExtendedBuilderError) } @@ -126,17 +144,24 @@ impl<FT, TE> From<FT> for CompositionError<TE> // } // } +/// Kinds of Error which can be caused by the builder extension. +/// +/// (The builder extension is a trait providing additional methods +/// to the `MailBuilder`) #[derive(Copy, Clone, Debug, Fail, PartialEq, Eq, Hash)] pub enum ExtendedBuilderErrorKind { #[fail(display="need embedding to create a body with an embedding")] EmbeddingMissing, } +/// Error returned if building the mail failed. #[derive(Debug, Fail)] pub enum ExtendedBuilderError { + /// A error covered by `BuilderError` occurred. #[fail(display = "{}", _0)] Normal(BuilderError), + /// An error not covered by `BuilderError` occurred. #[fail(display = "{}", _0)] Extended(Context<ExtendedBuilderErrorKind>) @@ -182,27 +207,35 @@ impl From<ComponentCreationError> for ExtendedBuilderError { } } +/// Error kinds associated with creating `MailSendData` #[derive(Copy, Clone, Debug, Fail, PartialEq, Eq, Hash)] pub enum MailSendDataErrorKind { + /// No `From` was set. #[fail(display = "missing data for From field")] MissingFrom, + /// No `To` was set. #[fail(display = "missing data for To field")] MissingTo, + /// No `Subject` was set. #[fail(display = "missing data for Subject field")] MissingSubject, + /// No `TemplateId` was given. #[fail(display = "missing template id")] MissingTemplateId, + /// No `TemplateData` was given. #[fail(display = "missing template data")] MissingTemplateData, + /// No `Sender` was given in an situation where it's needed. #[fail(display = "multiple mailboxes in from field but no sender field")] MultiFromButNoSender } +/// Error returned when building `MailSendData` with the `MailSendDataBuilder` failed. #[derive(Debug)] pub struct MailSendDataError { inner: Context<MailSendDataErrorKind>, @@ -1,3 +1,109 @@ +//! This crate provides a general interface for using template engine with the mail crate. +//! +//! It's core is the `TemplateEngine` trait which can be implemented to bind a template engine. +//! When rendering a template the template engine implementing the `TemplateEngine` trait will +//! produce a number of (wrapped) `Resource` instances representing the alternate bodies of a mail as well +//! as a number of additional `Resources` used for embedded content (e.g. logo images) and +//! attachments. This crate then takes this parts and composes a multipart mime mail from +//! it. +//! +//! # Template Engine implementations +//! +//! A mail template engine has to do more then just taking a single text +//! template (e.g. a handlebars template) and produce some output using +//! string magic. It has to: +//! +//! 1. consider alternate bodies, so it should render at last two +//! "text templates" (one text/plain, one html) +//! 2. consider which additional embeddings/attachments should be included +//! (all in the given template data are included, but you might +//! add additional ones, e.g. some logo image) +//! +//! As such text template engines like `Handle` bar can not directly +//! be bound to the `TemplateEngine` trait. +//! +//! For using text template engine it's recommended to use +//! the `mail-template-render-engine` (also exposed through the +//! mail facade) which implements this overhead for any engine +//! which can "just" render some text and provides default +//! bindings to some common template engines (e.g. Handlebars). +//! +//! # Derive +//! +//! This crate requires template data to implement `InspectEmbeddedResources` +//! which combined with some typing/generic design decisions allows to bind +//! not just to template engines which use serialization to access template +//! data but also to such which use static typing (like `askama`). +//! +//! As such it re-exports the `InspectEmbeddedResources` derive from +//! `mail-derive`. Note that if you use the mail facade it also does +//! re-export the derive. +//! +//! # Features +//! +//! - `askama-engine`, includes bindings for the askama template engine. +//! - `serialize-to-content-id`, implements Serialize for `Embedded`, +//! `EmbeddedWithCId` which serializes the embedded type **into its +//! content id**. E.g. a image with content id `"q09cu3@example.com"` +//! will be serialized to the string `"q09cu3@example.com"`. This is +//! extremely useful for all template engines which use serialization +//! as their way to access template data. +//! +//! +//! # Example +//! +//! ``` +//! +//! ``` +//! +//! # Road Map +//! +//! The current implementation has a number of limitations which should be lifted with +//! future versions: +//! +//! - Only a limited subset of headers are/can be set through the template engine +//! (`Sender`, `From`, `To`, `Subject`) while some headers are set implicitly +//! when encoding the mail (e.g. `Date`, `Content-Type`, `Content-Disposition`). +//! But sometimes it would be useful to add some custom headers through the template +//! engine (both on the outermost and inner bodies). +//! +//! - `From`, `To`, `Subject` have to be given, but sometimes you might want to just +//! create the `Mail` type and then set them by yourself (through you _can_ currently +//! override them) +//! +//! - Re-use/integration of existing mail instances: Some times you might want to +//! use a `Mail` instance created some where else as a body for a multipart mail +//! generated from a template (e.g. some thing generating "special" attachments). +//! +//! +//! Also there are some parts which are likely to change: +//! +//! - `MailSendData`/`MailSendDataBuilder` the name is +//! not very good it also needs to change to handle +//! the thinks listed above +//! +//! - `Embedded`, `EmbeddedWithCid`, embeddings and attachments +//! currently a `Embedded` instance is a wrapper around `Resource` +//! representing something which will become a mail body but is not +//! a main body (i.e. it not the text/html/.. you send) instead it +//! something embedded in the mail which is either used as attachment +//! or as a embedding (e.g. a logo image). Through the content disposition +//! the `Embedded` instance differs between thing embedded and internally +//! used or embedded and used as attachment at the same time many different +//! arrays are sometimes used to differ between them (e.g. in `MailParts`) +//! but there is no (type system) check to make sure a array of thinks used +//! as attachments can not contain a `Embedded` instance with content disposition +//! inline. The result will still be a valid mail, but likely not in the +//! way you expect it to be. This should be fixed one way or another (making +//! the different array type safe and lifting disposition to the type level +//! had been used but didn't play out nicely). +//! +//! - `serialize-to-content-id`, is a nice idea but has some problems in +//! some edge cases (e.g. when you want to serialize anything containing +//! a `Embedded` type for any usage BUT accessing it in an template). So +//! it might be removed, which means a import like `cid:{{data.mything}}` +//! (using some mustach pseudo template syntax) would become `cid:{{data.mything.cid}}`. +//! extern crate mail_types as mail; extern crate mail_common as common; #[macro_use] diff --git a/src/resource/mod.rs b/src/resource/mod.rs index a22f663..f0777d9 100644 --- a/src/resource/mod.rs +++ b/src/resource/mod.rs @@ -3,14 +3,37 @@ use std::ops::Deref; use mail::Context; use headers::components::ContentId; use mail::Resource; -#[cfg(feature="serialize-content-id")] +#[cfg(feature="serialize-to-content-id")] use serde::{Serialize, Serializer, ser}; pub use headers::components::DispositionKind as Disposition; mod impl_inspect; -/// # Serialize (feature `serialize-content-id`) +/// Represents any leaf body which is not a main body of an mail. +/// +/// This represents a `Resource` (which will be represented as +/// a non multipart body in the mail later one) a content disposition +/// to use for it (inline or attachment) and optionally a content id. +/// +/// Instances of `Embedded` with an inline disposition will normally +/// be placed in a `multipart/related` body with the main mail bodies +/// so that they can be refered to through the content id. Note that +/// thinks like having a text/plain body inline follow by a image followed +/// by a text/plain body to represent a text with an image in it are +/// not supported by the template system (through with the mail +/// crate you can create them "by hand"). Nevertheless this should +/// not be a problem as they are very bad style anyway for most +/// usecases. +/// +/// Instances of `Embedded` with an attachment disposition will normally +/// be used as mail attachments. +/// +/// Note that `InspectEmbeddedResources` is used to find all `Embedded` +/// instances given in the mail template data so that they can be +/// automatically added to the mail _and_ to also give them content ids. +/// +/// # Serialize (feature `serialize-to-content-id`) /// /// If serialized this struct **turns into it's /// content id failing if it has no content id**. @@ -27,14 +50,18 @@ pub struct Embedded { } impl Embedded { + + /// Create a inline embedding from an `Resource`. pub fn inline(resource: Resource) -> Self { Embedded::new(resource, Disposition::Inline) } + /// Create a attachment embedding from an `Resource`. pub fn attachment(resource: Resource) -> Self { Embedded::new(resource, Disposition::Attachment) } + /// Create a new embedding from a resource using given disposition. pub fn new(resource: Resource, disposition: Disposition) -> Self { Embedded { content_id: None, @@ -43,6 +70,7 @@ impl Embedded { } } + /// Create a new embedding from a `Resource` using given disposition and given content id. pub fn with_content_id(resource: Resource, disposition: Disposition, content_id: ContentId) -> Self { Embedded { content_id: Some(content_id), @@ -51,22 +79,27 @@ impl Embedded { } } + /// Return a reference to the contained resource. pub fn resource(&self) -> &Resource { &self.resource } + /// Return a mutable reference to the contained resource. pub fn resource_mut(&mut self) -> &mut Resource { &mut self.resource } + /// Return a reference to the contained content id, if any. pub fn content_id(&self) -> Option<&ContentId> { self.content_id.as_ref() } + /// Return a reference to disposition to use for the embedding. pub fn disposition(&self) -> Disposition { self.disposition } + /// Generate and set a new content id if this embedding doesn't have a content id. pub fn assure_content_id(&mut self, ctx: &impl Context) -> &ContentId { if self.content_id.is_none() { self.content_id = Some(ctx.generate_content_id()); @@ -204,8 +237,9 @@ impl Into<Resource> for Embedded { } } - -/// # Serialize (feature `serialize-content-id`) +/// A wrapper around `Embedded` which guarantees that a cid is given. +/// +/// # Serialize (feature `serialize-to-content-id`) /// /// If serialized this struct **turns into it's /// content id**. diff --git a/src/template_engine.rs b/src/template_engine.rs index 58a503f..6b37c6c 100644 --- a/src/template_engine.rs +++ b/src/template_engine.rs @@ -10,6 +10,39 @@ use mail::Context; use ::resource::{EmbeddedWithCId, InspectEmbeddedResources}; use ::builder_extension::BodyPart; +/// This trait needs to be implemented for creating mails based on templates. +/// +/// As there are many ways to have templates and many different approaches +/// on how to pass data to the template this crate doesn't impement the +/// full mail template stack instead it focuses on creating a mail from +/// the parts produced by the template engine and delegates the work of +/// producing such parts to the implementation of this trait. +/// +/// The work flow is roughly as following (skipping over +/// the `Context` as it's for providing a thread pool and +/// id generation): +/// +//TODO[NOW] workflow doesn't seem healthy +/// +/// - have a template engine +/// - get contextual information like sender, recipient +/// - get template id and template data +/// - use that to generate `MailSendData` +/// - call the method to generate a mail which +/// - will call `TemplateEngine::use_template(id, data, ..)` +/// - use the returned mail parts to generate a `Mail` instance +/// - return the `Mail` instance as result +/// +/// # Implementations +/// +/// There is a default implementation using the `askama` template +/// engine which can be made available with the `askama-engine` feature, +/// but is limited in usefulness due to the way askama works. +/// +/// Additionally there is the `mail-render-template-engine` which provides +/// a implementation just missing some simple text template engine which +/// as default bindings for handlebars (behind a feature flag). +/// /// /// # Why is Context a generic of the Type? /// @@ -21,10 +54,23 @@ use ::builder_extension::BodyPart; /// Such a context type could, for example, provide access to the /// current server configuration, preventing the need for the /// template engine to store a handle to it/copy of it itself. +/// +/// +/// # Why is data a generic of the type? +/// +/// Many template engine in the rust ecosystem use serialization +/// to access the data. Nevertheless there are a view like askama +/// which use a different approach only by being generic in the +/// trait over the data type can we support all of them. pub trait TemplateEngine<C, D> where C: Context { + /// The type used for template ids. + /// + /// Normally this will be `str`. type TemplateId: ?Sized + ToOwned; + + /// The error type returned by the template engine. type Error: Fail; fn use_template( @@ -35,17 +81,43 @@ pub trait TemplateEngine<C, D> ) -> Result<MailParts, Self::Error>; } - +/// Parts which can be used to compose a multipart mail. +/// +/// Instances of this type are produced by the implementor of the +/// `TemplateEngine` trait and consumed by this crate to generate +/// a `Mail` instance. +/// +//TODO[LATER] +/// # Current Limitations +/// +/// Currently there is no way to pass in `MailParts` from an external +/// point to produce a `Mail` instance. I.e. the only way they are +/// used is if `MailSendData` is used to create a mail and the procedure +/// calls the `TemplateEngine` to get it's mail parts. This might change +/// in future versions. pub struct MailParts { + /// A vector of alternate bodies + /// + /// A typical setup would be to have two alternate bodies one text/html and + /// another text/plain as fallback (for which the text/plain body would be + /// the first in the vec and the text/html body the last one). + /// + /// Note that the order in the vector /// a additional text/plainis + /// the same as the order in which they will appear in the mail. I.e. + /// the first one is the last fallback while the last one should be + /// shown if possible. pub alternative_bodies: Vec1<BodyPart>, - /// embeddings shared between alternative_bodies + + /// Embeddings shared between alternative_bodies. + /// + /// Any resource in there can be referenced to by all + /// alternative bodies via CId. pub shared_embeddings: Vec<EmbeddedWithCId>, + + /// Resources added to the mail as attachments. pub attachments: Vec<EmbeddedWithCId> } -//TODO move this to BuilderExt and just use it here (oh and rename it) - - macro_rules! impl_for_1elem_container { ($($name:ident),*) => ($( impl<C, D, T> TemplateEngine<C, D> for $name<T> |