mail_core_ng/
compose.rs

1//! This module provides utilities for composing multipart mails.
2//!
3//! While the `Mail` type on itself can represent any multipart
4//! mail most mails have a certain pattern to their structure,
5//! consisting mainly of `multipart/mixed` for attachments,
6//! `multipart/alternative` for alternative bodies and
7//! `multipart/related` for including embedded resources which
8//! can be used in the mail bodies  like e.g. a logo.
9//!
10//! This module provides the needed utilities to more simply
11//! create a `Mail` instance which represents this kind of
12//! mails.
13
14//-------------------------------------------------------------\\
15// NOTE: Implementations for creating (composing) mails are    ||
16// split from the type dev, and normal impl blocks and placed  ||
17// in the later part of the file for better readability.       ||
18//-------------------------------------------------------------//
19
20use media_type::{MULTIPART, ALTERNATIVE, RELATED, MIXED};
21use vec1::Vec1;
22
23use headers::{
24    HeaderKind,
25    headers,
26    header_components::{
27        Disposition,
28        DispositionKind,
29        MediaType
30    }
31};
32
33use crate::{
34    mail::Mail,
35    resource::Resource
36};
37
38/// Parts used to create a mail body (in a multipart mail).
39///
40/// This type contains a `Resource` which is normally used
41/// to create a alternative body in a `multipart/alternative`
42/// section. As well as a number of "embeddings" which depending
43/// on there disposition can are either used as attachments
44/// or embedded resource to which the body can referre to
45/// by it's content id.
46#[derive(Debug)]
47pub struct BodyPart {
48
49    /// A body created by a template.
50    pub resource: Resource,
51
52    /// A number of embeddings which should be displayed inline.
53    ///
54    /// This is normally used to embed images then displayed in
55    /// a html body. It is not in the scope of this part of the
56    /// library to bind content id's to resources to thinks using
57    /// them to display the embeddings. This part of the library
58    /// does "just" handle that they are correctly placed in the
59    /// resulting Mail. The `mail-templates` crate provides more
60    /// functionality for better ergonomics.
61    ///
62    /// Note that embeddings placed in a `BodyPart` instance are potentially
63    /// only usable in the body specified in the same `BodyPart` instance.
64    pub inline_embeddings: Vec<Resource>,
65
66    /// A number of embeddings which should be treated as attachments.
67    ///
68    /// Attachments of a `BodyPart` instance will be combined with
69    /// the attachments of other instances and the ones in the
70    /// `MailParts` instance.
71    pub attachments: Vec<Resource>
72
73}
74
75/// Parts which can be used to compose a multipart mail.
76///
77/// This can be used to crate a mail, possible having
78/// attachments with multiple alternative bodies having
79/// embedded resources which can be referred to by the
80/// bodies with content ids. This embeddings can be both
81/// body specific or shared between bodies.
82///
83/// # Limitations
84///
85/// Any non alternative body will be either an attachment
86/// or an body with a inline disposition header in a
87/// `multipart/related` body. Which means you can not
88/// use this mechanism to e.g. create a `multipart/mixed`
89/// body with multiple disposition inline sub-bodies
90/// which should be displayed side-by-side. Generally this
91/// is not a very good way to create a mail, through a
92/// valid way nevertheless.
93///
94pub struct MailParts {
95    /// A vector of alternative bodies
96    ///
97    /// A typical setup would be to have two alternative bodies one text/html and
98    /// another text/plain as fallback (for which the text/plain body would be
99    /// the first in the vec and the text/html body the last one).
100    ///
101    /// Note that the order in the vector     /// a additional text/plainis
102    /// the same as the order in which they will appear in the mail. I.e.
103    /// the first one is the last fallback while the last one should be
104    /// shown if possible.
105    pub alternative_bodies: Vec1<BodyPart>,
106
107    /// A number of embeddings which should be displayed inline.
108    ///
109    /// This is normally used to embed images then displayed in
110    /// a html body. It is not in the scope of this part of the
111    /// library to bind content id's to resources to thinks using
112    /// them to display the embeddings. This part of the library
113    /// does "just" handle that they are correctly placed in the
114    /// resulting Mail. The `mail-templates` crate provides more
115    /// functionality for better ergonomics.
116    pub inline_embeddings: Vec<Resource>,
117
118    /// A number of embeddings which should be treated as attachments
119    pub attachments: Vec<Resource>
120}
121
122//-------------------------------------------------------\\
123//  implementations for creating mails are from here on  ||
124//-------------------------------------------------------//
125
126
127impl MailParts {
128
129
130    /// Create a `Mail` instance based on this `MailParts` instance.
131    ///
132    ///
133    /// If this instance contains any attachments then the
134    /// returned mail will be a `multipart/mixed` mail with
135    /// the first body containing the actual mail and the
136    /// other bodies containing the attachments.
137    ///
138    /// If the `MailParts.inline_embeddings` is not empty then
139    /// the mail will be wrapped in `multipart/related` (inside
140    /// any potential `multipart/mixed`) containing the
141    /// actual mail in the first body and the inline embeddings
142    /// in the other bodies.
143    ///
144    /// The mail will have a `multipart/alternative` body
145    /// if it has more then one alternative body
146    /// (inside a potential `multipart/related` inside a
147    /// potential `multipart/mixed` body). This body contains
148    /// one sub-body for each `BodyPart` instance in
149    /// `MailParts.alternative_bodies`.
150    ///
151    /// Each sub-body created for a `BodyPart` will be wrapped
152    /// inside a `multipart/related` if it has body specific
153    /// embeddings (with content disposition inline).
154    pub fn compose(self)
155        -> Mail
156    {
157        let MailParts {
158            alternative_bodies,
159            inline_embeddings,
160            attachments
161        } = self;
162
163        let mut attachments = attachments.into_iter()
164            .map(|atta| atta.create_mail_with_disposition(DispositionKind::Attachment))
165            .collect::<Vec<_>>();
166
167        let mut alternatives = alternative_bodies.into_iter()
168            .map(|body| body.create_mail(&mut attachments))
169            .collect::<Vec<_>>();
170
171        //UNWRAP_SAFE: bodies is Vec1, i.e. we have at last one
172        let mail = alternatives.pop().unwrap();
173        let mail =
174            if alternatives.is_empty() {
175                mail
176            } else {
177                mail.wrap_with_alternatives(alternatives)
178            };
179
180        let mail =
181            if inline_embeddings.is_empty() {
182                mail
183            } else {
184                let related = inline_embeddings.into_iter()
185                    .map(|embedding| {
186                        embedding.create_mail_with_disposition(DispositionKind::Inline)
187                    })
188                    .collect::<Vec<_>>();
189                mail.wrap_with_related(related)
190            };
191
192        let mail =
193            if attachments.is_empty() {
194                mail
195            } else {
196                mail.wrap_with_mixed(attachments)
197            };
198
199        mail
200    }
201}
202
203impl BodyPart {
204
205    /// Creates a `Mail` instance from this `BodyPart` instance.
206    ///
207    /// All embeddings in `BodyPart.embeddings` which have a
208    /// attachment content disposition are placed into the
209    /// `attachments_out` parameter, as attachments should
210    /// always be handled on the outer most level but the
211    /// produced mail is likely not the outer most level.
212    ///
213    /// This will create a non-multipart body for the
214    /// body `Resource`, if there are any embeddings which
215    /// have a `Inline` disposition that body will be
216    /// wrapped into a `multipart/related` body containing
217    /// them.
218    pub fn create_mail(
219        self,
220        attachments_out: &mut Vec<Mail>,
221    ) -> Mail {
222        let BodyPart {
223            resource,
224            inline_embeddings,
225            attachments
226        } = self;
227
228        let body = resource.create_mail();
229
230        for attachment in attachments.into_iter() {
231            let mail = attachment.create_mail_with_disposition(DispositionKind::Attachment);
232            attachments_out.push(mail)
233        }
234
235        if inline_embeddings.is_empty() {
236            body
237        } else {
238            let related = inline_embeddings.into_iter()
239                .map(|embedding| {
240                    embedding.create_mail_with_disposition(DispositionKind::Inline)
241                })
242                .collect::<Vec<_>>();
243            body.wrap_with_related(related)
244        }
245    }
246}
247
248impl Resource {
249
250    /// Create a `Mail` instance representing this `Resource`.
251    ///
252    /// This is not a complete mail, i.e. it will not contain
253    /// headers like `From` or `To` and in many cases the
254    /// returned `Mail` instance will be wrapped into other
255    /// mail instances adding alternative bodies, embedded
256    /// resources and attachments.
257    pub fn create_mail(self) -> Mail {
258        Mail::new_singlepart_mail(self)
259    }
260
261    pub fn create_mail_with_disposition(self, disposition_kind: DispositionKind) -> Mail {
262        let mut mail = self.create_mail();
263        //TODO[1.0] grab meta from resource
264        let disposition = Disposition::new(disposition_kind, Default::default());
265        mail.insert_header(headers::ContentDisposition::body(disposition));
266        mail
267    }
268}
269
270impl Mail {
271
272    /// Create a `multipart/mixed` `Mail` instance containing this mail as
273    /// first body and one additional body for each attachment.
274    ///
275    /// Normally this is used with embeddings having a attachment
276    /// disposition creating a mail with attachments.
277    pub fn wrap_with_mixed(self, other_bodies: Vec<Mail>)
278        -> Mail
279    {
280        let mut bodies = other_bodies;
281        bodies.push(self);
282        new_multipart(&MIXED, bodies)
283    }
284
285    /// Create a `multipart/alternative` `Mail` instance containing this
286    /// mail as the _main_ body with given alternatives.
287    ///
288    /// The "priority" of alternative bodies is ascending with the body
289    /// which should be shown only if all other bodies can't be displayed
290    /// first. I.e. the order is the same order as
291    /// specified by `multipart/alternative`.
292    /// This also means that _this_ body will be the last body as it is
293    /// meant to be the _main_ body.
294    pub fn wrap_with_alternatives(self, alternates: Vec<Mail>)
295        -> Mail
296    {
297        let mut bodies = alternates;
298        //TODO[opt] accept iter and prepend instead of insert in vec
299        bodies.insert(0, self);
300        new_multipart(&ALTERNATIVE, bodies)
301    }
302
303    /// Creates a `multipart/related` `Mail` instance containing this
304    /// mail first and then all related bodies.
305    pub fn wrap_with_related(self, related: Vec<Mail>)
306        -> Mail
307    {
308        let mut bodies = related;
309        bodies.insert(0, self);
310        new_multipart(&RELATED, bodies)
311    }
312
313}
314
315/// Creates a `multipart/<sub_type>` mail with given bodies.
316///
317/// # Panic
318///
319/// If `sub_type` can not be used to create a multipart content
320/// type this will panic.
321fn new_multipart(sub_type: &'static str, bodies: Vec<Mail>)
322    -> Mail
323{
324    let content_type = MediaType::new(MULTIPART, sub_type)
325        .unwrap();
326    Mail::new_multipart_mail(content_type, bodies)
327}