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}