Merge pull request #439 from paolobarbolini/async-01

Refactor async-std support to prepare for more async runtimes
This commit is contained in:
Alexis Mousset
2020-07-26 14:44:44 +02:00
committed by GitHub
9 changed files with 104 additions and 126 deletions

View File

@@ -38,7 +38,7 @@ jobs:
- uses: actions-rs/cargo@v1 - uses: actions-rs/cargo@v1
with: with:
command: test command: test
args: --features=async args: --features=async-std1
check: check:
name: Check name: Check

View File

@@ -53,7 +53,7 @@ harness = false
name = "transport_smtp" name = "transport_smtp"
[features] [features]
async = ["async-std", "async-trait", "async-attributes"] async-std1 = ["async-std", "async-trait", "async-attributes"]
builder = ["mime", "base64", "hyperx", "rand", "quoted_printable"] builder = ["mime", "base64", "hyperx", "rand", "quoted_printable"]
default = ["file-transport", "smtp-transport", "native-tls", "hostname", "r2d2", "sendmail-transport", "builder"] default = ["file-transport", "smtp-transport", "native-tls", "hostname", "r2d2", "sendmail-transport", "builder"]
file-transport = ["serde", "serde_json"] file-transport = ["serde", "serde_json"]
@@ -62,6 +62,9 @@ sendmail-transport = []
smtp-transport = ["bufstream", "base64", "nom"] smtp-transport = ["bufstream", "base64", "nom"]
unstable = [] unstable = []
[package.metadata.docs.rs]
features = ["async-std1"]
[[example]] [[example]]
name = "smtp" name = "smtp"
required-features = ["smtp-transport"] required-features = ["smtp-transport"]

View File

@@ -55,6 +55,8 @@ pub use crate::transport::smtp::r2d2::SmtpConnectionManager;
#[cfg(feature = "smtp-transport")] #[cfg(feature = "smtp-transport")]
pub use crate::transport::smtp::{SmtpTransport, Tls}; pub use crate::transport::smtp::{SmtpTransport, Tls};
pub use crate::{address::Address, transport::stub::StubTransport}; pub use crate::{address::Address, transport::stub::StubTransport};
#[cfg(feature = "async-std1")]
use async_trait::async_trait;
#[cfg(feature = "builder")] #[cfg(feature = "builder")]
use std::convert::TryFrom; use std::convert::TryFrom;
use std::{error::Error as StdError, fmt}; use std::{error::Error as StdError, fmt};
@@ -151,33 +153,24 @@ pub trait Transport {
fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error>; fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error>;
} }
#[cfg(feature = "async")] /// Async Transport method for emails
pub mod r#async { #[cfg(feature = "async-std1")]
#[async_trait]
pub trait AsyncStd1Transport {
/// Result types for the transport
type Ok: fmt::Debug;
type Error: StdError;
use super::*; /// Sends the email
use async_trait::async_trait; #[cfg(feature = "builder")]
// TODO take &Message
#[async_trait] async fn send(&self, message: Message) -> Result<Self::Ok, Self::Error> {
pub trait Transport { let raw = message.formatted();
/// Result types for the transport let envelope = message.envelope();
type Ok: fmt::Debug; self.send_raw(&envelope, &raw).await
type Error: StdError;
/// Sends the email
#[cfg(feature = "builder")]
// TODO take &Message
async fn send(&self, message: Message) -> Result<Self::Ok, Self::Error> {
let raw = message.formatted();
let envelope = message.envelope();
self.send_raw(&envelope, &raw).await
}
async fn send_raw(
&self,
envelope: &Envelope,
email: &[u8],
) -> Result<Self::Ok, Self::Error>;
} }
async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error>;
} }
#[cfg(test)] #[cfg(test)]

View File

@@ -35,10 +35,12 @@
//! TODO //! TODO
//! ``` //! ```
#[cfg(feature = "async-std1")]
use crate::AsyncStd1Transport;
use crate::{transport::file::error::Error, Envelope, Transport}; use crate::{transport::file::error::Error, Envelope, Transport};
#[cfg(feature = "async-std1")]
use async_trait::async_trait;
use std::{ use std::{
fs::File,
io::prelude::*,
path::{Path, PathBuf}, path::{Path, PathBuf},
str, str,
}; };
@@ -72,11 +74,12 @@ struct SerializableEmail<'a> {
message: Option<&'a str>, message: Option<&'a str>,
} }
impl Transport for FileTransport { impl FileTransport {
type Ok = Id; fn send_raw_impl(
type Error = Error; &self,
envelope: &Envelope,
fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> { email: &[u8],
) -> Result<(Uuid, PathBuf, String), serde_json::Error> {
let email_id = Uuid::new_v4(); let email_id = Uuid::new_v4();
let file = self.path.join(format!("{}.json", email_id)); let file = self.path.join(format!("{}.json", email_id));
@@ -94,51 +97,40 @@ impl Transport for FileTransport {
}), }),
}?; }?;
File::create(file.as_path())?.write_all(serialized.as_bytes())?; Ok((email_id, file, serialized))
}
}
impl Transport for FileTransport {
type Ok = Id;
type Error = Error;
fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
use std::fs::File;
use std::io::Write;
let (email_id, file, serialized) = self.send_raw_impl(envelope, email)?;
let mut file = File::create(file)?;
file.write_all(serialized.as_bytes())?;
Ok(email_id.to_string()) Ok(email_id.to_string())
} }
} }
#[cfg(feature = "async")] #[cfg(feature = "async-std1")]
pub mod r#async { #[async_trait]
use super::{FileTransport, Id, SerializableEmail}; impl AsyncStd1Transport for FileTransport {
use crate::{r#async::Transport, transport::file::error::Error, Envelope}; type Ok = Id;
use async_std::fs::File; type Error = Error;
use async_std::prelude::*;
use async_trait::async_trait;
use std::str;
use uuid::Uuid;
#[async_trait] async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
impl Transport for FileTransport { use async_std::fs::File;
type Ok = Id; use async_std::io::prelude::WriteExt;
type Error = Error;
async fn send_raw( let (email_id, file, serialized) = self.send_raw_impl(envelope, email)?;
&self,
envelope: &Envelope,
email: &[u8],
) -> Result<Self::Ok, Self::Error> {
let email_id = Uuid::new_v4();
let file = self.path.join(format!("{}.json", email_id));
let serialized = match str::from_utf8(email) { let mut file = File::create(file).await?;
// Serialize as UTF-8 string if possible file.write_all(serialized.as_bytes()).await?;
Ok(m) => serde_json::to_string(&SerializableEmail { Ok(email_id.to_string())
envelope: envelope.clone(),
message: Some(m),
raw_message: None,
}),
Err(_) => serde_json::to_string(&SerializableEmail {
envelope: envelope.clone(),
message: None,
raw_message: Some(email),
}),
}?;
let mut file = File::create(file.as_path()).await?;
file.write_all(serialized.as_bytes()).await?;
Ok(email_id.to_string())
}
} }
} }

View File

@@ -23,7 +23,11 @@
//! # } //! # }
//! ``` //! ```
#[cfg(feature = "async-std1")]
use crate::AsyncStd1Transport;
use crate::{transport::sendmail::error::Error, Envelope, Transport}; use crate::{transport::sendmail::error::Error, Envelope, Transport};
#[cfg(feature = "async-std1")]
use async_trait::async_trait;
use std::{ use std::{
convert::AsRef, convert::AsRef,
ffi::OsString, ffi::OsString,
@@ -88,41 +92,30 @@ impl Transport for SendmailTransport {
} }
} }
#[cfg(feature = "async")] #[cfg(feature = "async-std1")]
pub mod r#async { #[async_trait]
use super::SendmailTransport; impl AsyncStd1Transport for SendmailTransport {
use crate::{r#async::Transport, transport::sendmail::error::Error, Envelope}; type Ok = ();
use async_trait::async_trait; type Error = Error;
use std::io::Write;
#[async_trait] async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
impl Transport for SendmailTransport { let mut command = self.command(envelope);
type Ok = (); let email = email.to_vec();
type Error = Error;
// TODO: Convert to real async, once async-std has a process implementation. // TODO: Convert to real async, once async-std has a process implementation.
async fn send_raw( let output = async_std::task::spawn_blocking(move || {
&self, // Spawn the sendmail command
envelope: &Envelope, let mut process = command.spawn()?;
email: &[u8],
) -> Result<Self::Ok, Self::Error> {
let mut command = self.command(envelope);
let email = email.to_vec();
let output = async_std::task::spawn_blocking(move || { process.stdin.as_mut().unwrap().write_all(&email)?;
// Spawn the sendmail command process.wait_with_output()
let mut process = command.spawn()?; })
.await?;
process.stdin.as_mut().unwrap().write_all(&email)?; if output.status.success() {
process.wait_with_output() Ok(())
}) } else {
.await?; Err(Error::Client(String::from_utf8(output.stderr)?))
if output.status.success() {
Ok(())
} else {
Err(Error::Client(String::from_utf8(output.stderr)?))
}
} }
} }
} }

View File

@@ -22,7 +22,11 @@
//! assert!(result.is_ok()); //! assert!(result.is_ok());
//! ``` //! ```
#[cfg(feature = "async-std1")]
use crate::AsyncStd1Transport;
use crate::{Envelope, Transport}; use crate::{Envelope, Transport};
#[cfg(feature = "async-std1")]
use async_trait::async_trait;
use std::{error::Error as StdError, fmt}; use std::{error::Error as StdError, fmt};
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
@@ -74,23 +78,13 @@ impl Transport for StubTransport {
} }
} }
#[cfg(feature = "async")] #[cfg(feature = "async-std1")]
pub mod r#async { #[async_trait]
use super::StubTransport; impl AsyncStd1Transport for StubTransport {
use crate::{r#async::Transport, transport::stub::Error, Envelope}; type Ok = ();
use async_trait::async_trait; type Error = Error;
#[async_trait] async fn send_raw(&self, _envelope: &Envelope, _email: &[u8]) -> Result<Self::Ok, Self::Error> {
impl Transport for StubTransport { self.response
type Ok = ();
type Error = Error;
async fn send_raw(
&self,
_envelope: &Envelope,
_email: &[u8],
) -> Result<Self::Ok, Self::Error> {
self.response
}
} }
} }

View File

@@ -35,10 +35,11 @@ mod test {
remove_file(file).unwrap(); remove_file(file).unwrap();
} }
#[cfg(feature = "async")] #[cfg(feature = "async-std1")]
#[async_attributes::test] #[async_attributes::test]
async fn file_transport_async() { async fn file_transport_async() {
use lettre::r#async::Transport; use lettre::AsyncStd1Transport;
let sender = FileTransport::new(temp_dir()); let sender = FileTransport::new(temp_dir());
let email = Message::builder() let email = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap()) .from("NoBody <nobody@domain.tld>".parse().unwrap())

View File

@@ -20,10 +20,11 @@ mod test {
assert!(result.is_ok()); assert!(result.is_ok());
} }
#[cfg(feature = "async")] #[cfg(feature = "async-std1")]
#[async_attributes::test] #[async_attributes::test]
async fn sendmail_transport_async() { async fn sendmail_transport_async() {
use lettre::r#async::Transport; use lettre::AsyncStd1Transport;
let sender = SendmailTransport::new(); let sender = SendmailTransport::new();
let email = Message::builder() let email = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap()) .from("NoBody <nobody@domain.tld>".parse().unwrap())

View File

@@ -17,10 +17,11 @@ fn stub_transport() {
sender_ko.send(&email).unwrap_err(); sender_ko.send(&email).unwrap_err();
} }
#[cfg(feature = "async")] #[cfg(feature = "async-std1")]
#[async_attributes::test] #[async_attributes::test]
async fn stub_transport_async() { async fn stub_transport_async() {
use lettre::r#async::Transport; use lettre::AsyncStd1Transport;
let sender_ok = StubTransport::new_ok(); let sender_ok = StubTransport::new_ok();
let sender_ko = StubTransport::new_error(); let sender_ko = StubTransport::new_error();
let email = Message::builder() let email = Message::builder()