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§
- Backend context
- error 🔒
- Backend feature
- Backend feature mapper
- Backend pool
Macros§
- Macro for defining
BackendBuilder
feature getter and setters.
Structs§
- The basic backend implementation.
- The runtime backend builder.
Enums§
- The global
Error
enum of the module.
Type Aliases§
- The global
Result
alias of the module.