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}