mail_core_ng/context.rs
1//! Provides the context needed for building/encoding mails.
2use std::sync::Arc;
3use std::fmt::Debug;
4
5use futures::{ future::{self, Either}, Future, IntoFuture };
6use utils::SendBoxFuture;
7
8use headers::header_components::{
9 MessageId, ContentId
10};
11
12use crate::{
13 resource::{Source, Data, EncData, Resource},
14 error::ResourceLoadingError
15};
16
17
18/// Represents Data which might already have been transfer encoded.
19pub enum MaybeEncData {
20 /// The data is returned normally.
21 Data(Data),
22
23 /// The data is returned in a already transfer encoded variant.
24 EncData(EncData)
25}
26
27impl MaybeEncData {
28
29 pub fn to_resource(self) -> Resource {
30 match self {
31 MaybeEncData::Data(data) => Resource::Data(data),
32 MaybeEncData::EncData(enc_data) => Resource::EncData(enc_data)
33 }
34 }
35
36 pub fn encode(self, ctx: &impl Context)
37 -> impl Future<Item=EncData,Error=ResourceLoadingError>
38 {
39 match self {
40 MaybeEncData::Data(data) => Either::A(ctx.load_transfer_encoded_resource(&Resource::Data(data))),
41 MaybeEncData::EncData(enc) => Either::B(future::ok(enc))
42 }
43 }
44}
45
46/// This library needs a context for creating/encoding mails.
47///
48/// The context is _not_ meant to be a think you create once
49/// per mail but something you create once one startup and then
50/// re-user wherever it is needed in your application.
51///
52/// A context impl. provides following functionality to this library:
53///
54/// 1. Load a `Resource` based on a `Source` instance.
55/// I.e. an IRI plus some optional meta information.
56/// 2. Generate an unique message id. This should be
57/// an world unique id to comply with the standard(s).
58/// 3. Generate an unique content id. This should be
59/// an world unique id to comply with the standard(s).
60/// 4. A way to "offload" work to some other "place" e.g.
61/// by scheduling it in an thread pool.
62///
63/// The `CompositeContext` provides a impl. for this trait
64/// which delegates the different tasks to the components
65/// it's composed of. This allow to reuse e.g. the message
66/// if generation and offloading but use a custom resource
67/// loading. Generally it's recommended to use the `CompositeContext`.
68///
69/// There is also an default implementation (using the
70/// `CompositeContext` in the `default_impl` module).
71///
72/// # Clone / Send / Sync / 'static ?
73///
74/// `Context` are meant to be easily shareable, cloning them should be
75/// cheap, as such if a implementor contains state it might make sense for an
76/// implementor to have a outer+inner type where the inner type is wrapped
77/// into a `Arc` e.g. `struct SomeCtx { inner: Arc<InnerSomeCtx> }`.
78pub trait Context: Debug + Clone + Send + Sync + 'static {
79
80
81 /// Loads and transfer encodes a `Data` instance.
82 ///
83 /// This is called when a `Mail` instance is converted into
84 /// a encodable mail an a `Resource::Data` instance is found.
85 ///
86 /// This can potentially use the IRI to implement a simple
87 /// caching scheme potentially directly caching the transfer
88 /// encoded instance skipping the loading + encoding step for
89 /// common resources like e.g. a logo.
90 ///
91 /// If the context implementation only caches enc data instances
92 /// but not data instances it might return [`MaybeEncData::EncData`].
93 /// If it newly load the resource it _should_ return a [`MaybeEncData::Data`]
94 /// variant.
95 ///
96 /// # Async Considerations
97 ///
98 /// This function should not block and schedule the encoding
99 /// in some other place e.g. by using the contexts offload
100 /// functionality.
101 fn load_resource(&self, source: &Source)
102 -> SendBoxFuture<MaybeEncData, ResourceLoadingError>;
103
104 /// Loads and Transfer encodes a `Resource` instance.
105 ///
106 /// This is called when a `Mail` instance is converted into
107 /// a encodable mail.
108 ///
109 /// The default impl. of this function just:
110 ///
111 /// 1. calls load_resource and chains a "offloaded" transfer encoding
112 /// if a `Resource::Source` is found.
113 /// 2. transfer encodes the data "offloaded" if `Resource::Data` is found
114 /// 3. just returns the encoded data if `Resource::EncData` is found
115 ///
116 /// A more advances implementation could for example integrate
117 /// a LRU cache.
118 ///
119 /// The default impl is available as the `default_impl_for_load_transfer_encoded_resource`
120 /// function.
121 ///
122 /// # Async Considerations
123 ///
124 /// This function should not block and schedule the encoding
125 /// in some other place e.g. by using the contexts offload
126 /// functionality.
127 fn load_transfer_encoded_resource(&self, resource: &Resource)
128 -> SendBoxFuture<EncData, ResourceLoadingError>
129 {
130 default_impl_for_load_transfer_encoded_resource(self, resource)
131 }
132
133 /// generate a unique content id
134 ///
135 /// As message id's are used to reference messages they should be
136 /// world unique this can be guaranteed through two aspects:
137 ///
138 /// 1. using a domain you own/control on the right hand side
139 /// of the `@` will make sure no id's from other persons/companies/...
140 /// will collide with your ids
141 ///
142 /// 2. using some internal mechanism for the left hand side, like including
143 /// the time and an internal counter, not that you have to make sure this
144 /// stays unique even if you run multiple instances or restart the current
145 /// running instance.
146 ///
147 fn generate_message_id(&self) -> MessageId;
148
149 /// generate a unique content id
150 ///
151 /// Rfc 2045 states that content id's have to be world unique,
152 /// while this really should be the case if it's used in combination
153 /// with an `multipart/external` or similar body for it's other usage
154 /// as reference for embeddings it being mail unique tends to be enough.
155 ///
156 /// As content id and message id are treated mostly the same wrt. the
157 /// constraints applying when generating them this can be implemented
158 /// in terms of calling `generate_message_id`.
159 fn generate_content_id(&self) -> ContentId;
160
161 //TODO[futures/v>=0.2]: integrate this with Context
162 /// offloads the execution of the future `fut` to somewhere else e.g. a cpu pool
163 fn offload<F>(&self, fut: F) -> SendBoxFuture<F::Item, F::Error>
164 where F: Future + Send + 'static,
165 F::Item: Send + 'static,
166 F::Error: Send + 'static;
167
168 //TODO[futures/v>=0.2]: integrate this with Context
169 /// offloads the execution of the function `func` to somewhere else e.g. a cpu pool
170 fn offload_fn<FN, I>(&self, func: FN ) -> SendBoxFuture<I::Item, I::Error>
171 where FN: FnOnce() -> I + Send + 'static,
172 I: IntoFuture + 'static,
173 I::Future: Send + 'static,
174 I::Item: Send + 'static,
175 I::Error: Send + 'static
176 {
177 self.offload( future::lazy( func ) )
178 }
179}
180
181
182/// Provides the default impl for the `load_transfer_encoded_resource` method of `Context`.
183///
184/// This function guarantees to only call `load_resource` and `offload`/`offload_fn` on the
185/// passed in context, to prevent infinite recursion.
186pub fn default_impl_for_load_transfer_encoded_resource(ctx: &impl Context, resource: &Resource)
187 -> SendBoxFuture<EncData, ResourceLoadingError>
188{
189 match resource {
190 Resource::Source(source) => {
191 let ctx2 = ctx.clone();
192 let fut = ctx.load_resource(&source)
193 .and_then(move |me_data| {
194 match me_data {
195 MaybeEncData::Data(data) => {
196 Either::A(ctx2.offload_fn(move || Ok(data.transfer_encode(Default::default()))))
197 },
198 MaybeEncData::EncData(enc_data) => {
199 Either::B(future::ok(enc_data))
200 }
201 }
202 });
203 Box::new(fut)
204 },
205 Resource::Data(data) => {
206 let data = data.clone();
207 ctx.offload_fn(move || Ok(data.transfer_encode(Default::default())))
208 },
209 Resource::EncData(enc_data) => {
210 Box::new(future::ok(enc_data.clone()))
211 }
212 }
213}
214
215/// Trait needed to be implemented for providing the resource loading parts to a`CompositeContext`.
216pub trait ResourceLoaderComponent: Debug + Send + Sync + 'static {
217
218 /// Calls to `Context::load_resource` will be forwarded to this method.
219 ///
220 /// It is the same as `Context::load_resource` except that a reference
221 /// to the context containing this component is passed in. To prevent
222 /// infinite recursion the `Context.load_resource` method _must not_
223 /// be called. Additionally the `Context.load_transfer_encoded_resource` _must not_
224 /// be called if it uses `Context.load_resource`.
225 fn load_resource(&self, source: &Source, ctx: &impl Context)
226 -> SendBoxFuture<MaybeEncData, ResourceLoadingError>;
227
228 /// Calls to `Context::transfer_encode_resource` will be forwarded to this method.
229 ///
230 /// It is the same as `Context::transfer_encode_resource` except that a reference
231 /// to the context containing this component is passed in to make the `offload`
232 /// and `load_resource` methods of `Context` available.
233 ///
234 /// To prevent infinite recursion the `load_transfer_encoded_resource` method
235 /// of the context _must not_ be called.
236 fn load_transfer_encoded_resource(&self, resource: &Resource, ctx: &impl Context)
237 -> SendBoxFuture<EncData, ResourceLoadingError>
238 {
239 default_impl_for_load_transfer_encoded_resource(ctx, resource)
240 }
241}
242
243/// Trait needed to be implemented for providing the offloading parts to a `CompositeContext`.
244pub trait OffloaderComponent: Debug + Send + Sync + 'static {
245
246 /// Calls to `Context::offload` and `Context::offload_fn` will be forwarded to this method.
247 fn offload<F>(&self, fut: F) -> SendBoxFuture<F::Item, F::Error>
248 where F: Future + Send + 'static,
249 F::Item: Send+'static,
250 F::Error: Send+'static;
251}
252
253/// Trait needed to be implemented for providing the id generation parts to a `CompositeContext`.
254///
255/// It is possible/valid to use the same implementation (internal function etc.) for
256/// both message and content ids. While they are used at different places they have
257/// mostly the same constraints (their meaning is still different i.e. it's much
258/// more important for an message id to be "world unique" then for an content id,
259/// expect in some cases where external bodies are used).
260pub trait MailIdGenComponent: Debug + Send + Sync + 'static {
261
262 /// Calls to `Context::generate_message_id` will be forwarded to this method.
263 fn generate_message_id(&self) -> MessageId;
264
265 /// Calls to `Context::generate_content_id` will be forwarded to this method.
266 fn generate_content_id(&self) -> ContentId;
267}
268
269/// The `CompositeContext` is the simplest way to get an `Context` implementation.
270///
271/// Any custom `Context` implementations should be realized through the `CompositeContext`
272/// if possible.
273///
274/// This type consists of 3 components it forward all method calls from `Context` to.
275/// This allows the library to have a single `Context` type but match and mix the
276/// parts about resource loading, offloading and id generation in whichever way
277/// it fits best.
278///
279/// The composite context will store the components inside of an `Arc` so that
280/// it can be easily shared through an application, it also means non of the
281/// components have to implement `Clone`.
282#[derive(Debug)]
283pub struct CompositeContext<
284 R: ResourceLoaderComponent,
285 O: OffloaderComponent,
286 M: MailIdGenComponent
287>{
288 inner: Arc<(R, O, M)>,
289}
290
291impl<R, O, M> Clone for CompositeContext<R, O, M>
292 where R: ResourceLoaderComponent,
293 O: OffloaderComponent,
294 M: MailIdGenComponent
295{
296 fn clone(&self) -> Self {
297 CompositeContext {
298 inner: self.inner.clone(),
299 }
300 }
301}
302
303impl<R, O, M> CompositeContext<R, O, M>
304 where R: ResourceLoaderComponent,
305 O: OffloaderComponent,
306 M: MailIdGenComponent
307{
308 /// Create a new context from the given components.
309 pub fn new(resource_loader: R, offloader: O, message_id_gen: M) -> Self {
310 CompositeContext {
311 inner: Arc::new((resource_loader, offloader, message_id_gen)),
312 }
313 }
314
315 /// Returns a reference to the resource loader component.
316 pub fn resource_loader(&self) -> &R {
317 &self.inner.0
318 }
319
320 /// Returns a reference to the offloader component.
321 pub fn offloader(&self) -> &O {
322 &self.inner.1
323 }
324
325 /// Returns a reference to the id generation component.
326 pub fn id_gen(&self) -> &M {
327 &self.inner.2
328 }
329}
330
331impl<R, O, M> Context for CompositeContext<R, O, M>
332 where R: ResourceLoaderComponent,
333 O: OffloaderComponent,
334 M: MailIdGenComponent
335{
336
337 fn load_resource(&self, source: &Source)
338 -> SendBoxFuture<MaybeEncData, ResourceLoadingError>
339 {
340 self.resource_loader().load_resource(source, self)
341 }
342
343 fn load_transfer_encoded_resource(&self, resource: &Resource)
344 -> SendBoxFuture<EncData, ResourceLoadingError>
345 {
346 self.resource_loader().load_transfer_encoded_resource(resource, self)
347 }
348
349 fn offload<F>(&self, fut: F) -> SendBoxFuture<F::Item, F::Error>
350 where F: Future + Send + 'static,
351 F::Item: Send+'static,
352 F::Error: Send+'static
353 {
354 self.offloader().offload(fut)
355 }
356
357 fn generate_content_id(&self) -> ContentId {
358 self.id_gen().generate_content_id()
359 }
360
361 fn generate_message_id(&self) -> MessageId {
362 self.id_gen().generate_message_id()
363 }
364
365}
366
367/// Allows using a part of an context as an component.
368impl<C> MailIdGenComponent for C
369 where C: Context
370{
371 fn generate_message_id(&self) -> MessageId {
372 <Self as Context>::generate_message_id(self)
373 }
374
375 fn generate_content_id(&self) -> ContentId {
376 <Self as Context>::generate_content_id(self)
377 }
378}
379
380/// Allows using a part of an context as an component.
381impl<C> OffloaderComponent for C
382 where C: Context
383{
384 fn offload<F>(&self, fut: F) -> SendBoxFuture<F::Item, F::Error>
385 where F: Future + Send + 'static,
386 F::Item: Send+'static,
387 F::Error: Send+'static
388 {
389 <Self as Context>::offload(self, fut)
390 }
391}
392
393/// Allows using a part of an context as an component.
394impl<C> ResourceLoaderComponent for C
395 where C: Context
396{
397
398 fn load_resource(&self, source: &Source, _: &impl Context)
399 -> SendBoxFuture<MaybeEncData, ResourceLoadingError>
400 {
401 <Self as Context>::load_resource(self, source)
402 }
403
404 fn load_transfer_encoded_resource(&self, resource: &Resource, _: &impl Context)
405 -> SendBoxFuture<EncData, ResourceLoadingError>
406 {
407 <Self as Context>::load_transfer_encoded_resource(self, resource)
408 }
409}