summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilipp Korber <p.korber@1aim.com>2018-08-24 21:25:30 +0200
committerPhilipp Korber <p.korber@1aim.com>2018-08-24 21:25:30 +0200
commitad91e471abde1a1806baa11b0ea01e6b155394a1 (patch)
tree2d35c3ee81ceb3b23f30051dd11d4ca796776f44
parentd2304b8f7496b5653187bed332ba6c429e37248a (diff)
doc: added more api documentation
-rw-r--r--README.md107
-rw-r--r--src/compositor/mod.rs8
-rw-r--r--src/error.rs33
-rw-r--r--src/lib.rs106
-rw-r--r--src/resource/mod.rs42
-rw-r--r--src/template_engine.rs82
6 files changed, 364 insertions, 14 deletions
diff --git a/README.md b/README.md
index 66d5ab3..104c9a8 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,114 @@
-# mail-template &emsp; [![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>,
diff --git a/src/lib.rs b/src/lib.rs
index 58f187a..7788bb0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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>