rok-mail 0.6.0

Email support for the rok ecosystem — Mailable trait, log/SMTP drivers
Documentation
use std::{
    future::Future,
    pin::Pin,
    sync::Arc,
    task::{Context, Poll},
};

use axum::http::Request;
use tower::{Layer, Service};

use crate::{mail::scope_config, MailConfig};

/// Tower layer that scopes a `MailConfig` into every request via task-local
/// storage. After installing this layer, call `Mail::send()` without an
/// explicit config from anywhere inside a request handler.
///
/// When the `queue` feature is enabled you can also attach a `rok_queue::Queue`
/// via [`MailLayer::with_queue`] so that `Mail::queue()` and `Mail::later()`
/// work inside request handlers.
#[derive(Clone)]
pub struct MailLayer {
    config: Arc<MailConfig>,
    #[cfg(feature = "queue")]
    queue: Option<crate::queue_job::QueueRef>,
}

impl MailLayer {
    pub fn new(config: MailConfig) -> Self {
        Self {
            config: Arc::new(config),
            #[cfg(feature = "queue")]
            queue: None,
        }
    }

    /// Attach a queue so that `Mail::queue()` / `Mail::later()` work in
    /// request handlers behind this layer.
    #[cfg(feature = "queue")]
    pub fn with_queue(mut self, queue: crate::queue_job::QueueRef) -> Self {
        self.queue = Some(queue);
        self
    }
}

impl<S> Layer<S> for MailLayer {
    type Service = MailService<S>;

    fn layer(&self, inner: S) -> Self::Service {
        MailService {
            inner,
            config: self.config.clone(),
            #[cfg(feature = "queue")]
            queue: self.queue.clone(),
        }
    }
}

#[derive(Clone)]
pub struct MailService<S> {
    inner: S,
    config: Arc<MailConfig>,
    #[cfg(feature = "queue")]
    queue: Option<crate::queue_job::QueueRef>,
}

impl<S, ReqBody> Service<Request<ReqBody>> for MailService<S>
where
    S: Service<Request<ReqBody>> + Send + Clone + 'static,
    S::Future: Send + 'static,
    ReqBody: Send + 'static,
{
    type Response = S::Response;
    type Error = S::Error;
    type Future = Pin<Box<dyn Future<Output = Result<S::Response, S::Error>> + Send + 'static>>;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)
    }

    fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future {
        let config = self.config.clone();
        req.extensions_mut().insert(config.clone());
        #[cfg(feature = "queue")]
        let queue = self.queue.clone();
        #[cfg(feature = "queue")]
        if let Some(ref q) = queue {
            req.extensions_mut().insert(q.clone());
        }
        let future = self.inner.call(req);

        #[cfg(feature = "queue")]
        {
            if let Some(q) = queue {
                return Box::pin(crate::mail::scope_config(config, crate::mail::scope_queue(q, future)));
            }
        }

        Box::pin(scope_config(config, future))
    }
}