Module email::backend

source ·
Expand description

§Backend

A backend is a set of features like adding folder, listing envelopes or sending message. This module exposes everything you need to create your own backend.

§Dynamic backend

A dynamic backend is composed of features defined at runtime. Calling an undefined feature leads to a runtime error. Such backend is useful when you do not know in advance which feature is enabled or disabled (for example, from a user configuration file).

The simplest way to build a dynamic backend is to use the BackendBuilder. It allows you to dynamically enable or disable features using the builder pattern. The build method consumes the builder to build the final backend. This module comes with two backend implementations:

  • Backend, a basic backend instance exposing features directly

  • [BackendPool], a backend where multiple contexts are built and put in a pool, which allow you to execute features in parallel

You can create your own instance by implementing the AsyncTryIntoBackendFeatures trait.

See a full example at ../../tests/dynamic_backend.rs.

use async_trait::async_trait;
use email::{
    account::config::{passwd::PasswdConfig, AccountConfig},
    backend::{
        context::BackendContextBuilder, feature::BackendFeature, macros::BackendContext,
        mapper::SomeBackendContextBuilderMapper, Backend, BackendBuilder,
    },
    folder::{list::ListFolders, Folder, FolderKind},
    imap::{
        config::{ImapAuthConfig, ImapConfig, ImapEncryptionKind},
        ImapContextBuilder, ImapContextSync,
    },
    smtp::{SmtpContextBuilder, SmtpContextSync},
    AnyResult,
};
use email_testing_server::with_email_testing_server;
use secret::Secret;
use std::sync::Arc;

#[tokio::test(flavor = "multi_thread")]
async fn test_dynamic_backend() {
    env_logger::builder().is_test(true).init();

    with_email_testing_server(|ports| async move {
        let account_config = Arc::new(AccountConfig::default());

        let imap_config = Arc::new(ImapConfig {
            host: "localhost".into(),
            port: ports.imap,
            encryption: Some(ImapEncryptionKind::None),
            login: "bob".into(),
            auth: ImapAuthConfig::Passwd(PasswdConfig(Secret::new_raw("password"))),
            ..Default::default()
        });

        // 1. define custom context

        #[derive(BackendContext)]
        struct DynamicContext {
            imap: Option<ImapContextSync>,
            smtp: Option<SmtpContextSync>,
        }

        // 2. implement AsRef for mapping features

        impl AsRef<Option<ImapContextSync>> for DynamicContext {
            fn as_ref(&self) -> &Option<ImapContextSync> {
                &self.imap
            }
        }

        impl AsRef<Option<SmtpContextSync>> for DynamicContext {
            fn as_ref(&self) -> &Option<SmtpContextSync> {
                &self.smtp
            }
        }

        // 3. define custom context builder

        #[derive(Clone)]
        struct DynamicContextBuilder {
            imap: Option<ImapContextBuilder>,
            smtp: Option<SmtpContextBuilder>,
        }

        // 4. implement backend context builder

        #[async_trait]
        impl BackendContextBuilder for DynamicContextBuilder {
            type Context = DynamicContext;

            // override the list folders feature using the imap builder
            fn list_folders(&self) -> Option<BackendFeature<Self::Context, dyn ListFolders>> {
                self.list_folders_with_some(&self.imap)
            }

            async fn build(self) -> AnyResult<Self::Context> {
                let imap = match self.imap {
                    Some(imap) => Some(imap.build().await?),
                    None => None,
                };

                let smtp = match self.smtp {
                    Some(smtp) => Some(smtp.build().await?),
                    None => None,
                };

                Ok(DynamicContext { imap, smtp })
            }
        }

        // 5. plug all together

        let ctx_builder = DynamicContextBuilder {
            imap: Some(ImapContextBuilder::new(
                account_config.clone(),
                imap_config.clone(),
            )),
            smtp: None,
        };
        let backend_builder = BackendBuilder::new(account_config.clone(), ctx_builder);
        let backend: Backend<DynamicContext> = backend_builder.build().await.unwrap();
        let folders = backend.list_folders().await.unwrap();

        assert!(folders.contains(&Folder {
            kind: Some(FolderKind::Inbox),
            name: "INBOX".into(),
            desc: "".into()
        }));
    })
    .await
}

§Static backend

A static backend is composed of features defined at compilation time. Such backend is useful when you know in advance which feature should be enabled or disabled. It mostly relies on traits. You will have to create your own backend instance as well as manually implement backend features.

See a full example at ../../tests/static_backend.rs.

use async_trait::async_trait;
use email::{
    account::config::{passwd::PasswdConfig, AccountConfig},
    backend::{context::BackendContextBuilder, macros::BackendContext},
    folder::{
        list::{imap::ListImapFolders, ListFolders},
        Folder, FolderKind, Folders,
    },
    imap::{
        config::{ImapAuthConfig, ImapConfig, ImapEncryptionKind},
        ImapContextBuilder, ImapContextSync,
    },
    message::send::{smtp::SendSmtpMessage, SendMessage},
    smtp::{
        config::{SmtpAuthConfig, SmtpConfig, SmtpEncryptionKind},
        SmtpContextBuilder, SmtpContextSync,
    },
    AnyResult,
};
use email_testing_server::with_email_testing_server;
use secret::Secret;
use std::sync::Arc;

#[tokio::test(flavor = "multi_thread")]
async fn test_static_backend() {
    env_logger::builder().is_test(true).init();

    with_email_testing_server(|ports| async move {
        let account_config = Arc::new(AccountConfig::default());

        let imap_config = Arc::new(ImapConfig {
            host: "localhost".into(),
            port: ports.imap,
            encryption: Some(ImapEncryptionKind::None),
            login: "bob".into(),
            auth: ImapAuthConfig::Passwd(PasswdConfig(Secret::new_raw("password"))),
            ..Default::default()
        });

        let smtp_config = Arc::new(SmtpConfig {
            host: "localhost".into(),
            port: ports.smtp,
            encryption: Some(SmtpEncryptionKind::None),
            login: "alice".into(),
            auth: SmtpAuthConfig::Passwd(PasswdConfig(Secret::new_raw("password"))),
        });

        // 1. define custom context made of subcontexts

        #[derive(BackendContext)]
        struct StaticContext {
            imap: ImapContextSync,
            smtp: SmtpContextSync,
        }

        // 2. define custom backend

        struct StaticBackend(StaticContext);

        // 3. implement desired backend features

        #[async_trait]
        impl ListFolders for StaticBackend {
            async fn list_folders(&self) -> AnyResult<Folders> {
                ListImapFolders::new(&self.0.imap).list_folders().await
            }
        }

        #[async_trait]
        impl SendMessage for StaticBackend {
            async fn send_message(&self, msg: &[u8]) -> AnyResult<()> {
                SendSmtpMessage::new(&self.0.smtp).send_message(msg).await
            }
        }

        // 4. plug all together

        let backend = StaticBackend(StaticContext {
            imap: ImapContextBuilder::new(account_config.clone(), imap_config)
                .build()
                .await
                .unwrap(),
            smtp: SmtpContextBuilder::new(account_config, smtp_config)
                .build()
                .await
                .unwrap(),
        });

        let folders = backend.list_folders().await.unwrap();

        assert!(folders.contains(&Folder {
            kind: Some(FolderKind::Inbox),
            name: "INBOX".into(),
            desc: "".into()
        }));
    })
    .await
}

Modules§

Macros§

Structs§

Enums§

  • The global Error enum of the module.

Type Aliases§

  • The global Result alias of the module.