From 2173bc5f430ba454489b8fb72d17fb2960370b13 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Sun, 26 Jul 2020 17:11:54 +0200 Subject: [PATCH] Add tokio ^0.2 support (#440) * Use fs::write for writing files * Fix running tests without tokio --- .github/workflows/test.yml | 4 ++++ Cargo.toml | 3 +++ src/lib.rs | 21 ++++++++++++++++- src/transport/file/mod.rs | 32 ++++++++++++++++++------- src/transport/sendmail/mod.rs | 44 ++++++++++++++++++++++++++++++++++- src/transport/stub/mod.rs | 15 +++++++++++- tests/transport_file.rs | 34 ++++++++++++++++++++++++++- tests/transport_sendmail.rs | 24 ++++++++++++++++++- tests/transport_stub.rs | 25 +++++++++++++++++++- 9 files changed, 187 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0e1d645..39f0703 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,6 +39,10 @@ jobs: with: command: test args: --features=async-std1 + - uses: actions-rs/cargo@v1 + with: + command: test + args: --features=tokio02 check: name: Check diff --git a/Cargo.toml b/Cargo.toml index 7ec0973..548f313 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ maintenance = { status = "actively-developed" } async-attributes = { version = "1.1", optional = true } async-std = { version = "1.5", optional = true, features = ["unstable"] } async-trait = { version = "0.1", optional = true } +tokio02_crate = { package = "tokio", version = "0.2.7", features = ["fs", "process", "io-util"], optional = true } base64 = { version = "0.12", optional = true } bufstream = { version = "0.1", optional = true } hostname = { version = "0.3", optional = true } @@ -46,6 +47,7 @@ criterion = "0.3" env_logger = "0.7" glob = "0.3" walkdir = "2" +tokio02_crate = { package = "tokio", version = "0.2.7", features = ["macros", "rt-threaded"] } [[bench]] harness = false @@ -53,6 +55,7 @@ name = "transport_smtp" [features] async-std1 = ["async-std", "async-trait", "async-attributes"] +tokio02 = ["tokio02_crate", "async-trait"] builder = ["mime", "base64", "hyperx", "rand", "quoted_printable"] default = ["file-transport", "smtp-transport", "native-tls", "hostname", "r2d2", "sendmail-transport", "builder"] file-transport = ["serde", "serde_json"] diff --git a/src/lib.rs b/src/lib.rs index 1e19b11..2f0ef0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,7 +55,7 @@ pub use crate::transport::smtp::r2d2::SmtpConnectionManager; #[cfg(feature = "smtp-transport")] pub use crate::transport::smtp::{SmtpTransport, Tls}; pub use crate::{address::Address, transport::stub::StubTransport}; -#[cfg(feature = "async-std1")] +#[cfg(any(feature = "async-std1", feature = "tokio02"))] use async_trait::async_trait; #[cfg(feature = "builder")] use std::convert::TryFrom; @@ -173,6 +173,25 @@ pub trait AsyncStd1Transport { async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result; } +#[cfg(feature = "tokio02")] +#[async_trait] +pub trait Tokio02Transport { + /// Result types for the transport + type Ok: fmt::Debug; + type Error: StdError; + + /// Sends the email + #[cfg(feature = "builder")] + // TODO take &Message + async fn send(&self, message: Message) -> Result { + let raw = message.formatted(); + let envelope = message.envelope(); + self.send_raw(&envelope, &raw).await + } + + async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result; +} + #[cfg(test)] mod test { use super::*; diff --git a/src/transport/file/mod.rs b/src/transport/file/mod.rs index dda3292..6c463a3 100644 --- a/src/transport/file/mod.rs +++ b/src/transport/file/mod.rs @@ -37,8 +37,10 @@ #[cfg(feature = "async-std1")] use crate::AsyncStd1Transport; +#[cfg(feature = "tokio02")] +use crate::Tokio02Transport; use crate::{transport::file::error::Error, Envelope, Transport}; -#[cfg(feature = "async-std1")] +#[cfg(any(feature = "async-std1", feature = "tokio02"))] use async_trait::async_trait; use std::{ path::{Path, PathBuf}, @@ -106,13 +108,11 @@ impl Transport for FileTransport { type Error = Error; fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result { - use std::fs::File; - use std::io::Write; + use std::fs; let (email_id, file, serialized) = self.send_raw_impl(envelope, email)?; - let mut file = File::create(file)?; - file.write_all(serialized.as_bytes())?; + fs::write(file, serialized)?; Ok(email_id.to_string()) } } @@ -124,13 +124,27 @@ impl AsyncStd1Transport for FileTransport { type Error = Error; async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result { - use async_std::fs::File; - use async_std::io::prelude::WriteExt; + use async_std::fs; let (email_id, file, serialized) = self.send_raw_impl(envelope, email)?; - let mut file = File::create(file).await?; - file.write_all(serialized.as_bytes()).await?; + fs::write(file, serialized).await?; + Ok(email_id.to_string()) + } +} + +#[cfg(feature = "tokio02")] +#[async_trait] +impl Tokio02Transport for FileTransport { + type Ok = Id; + type Error = Error; + + async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result { + use tokio02_crate::fs; + + let (email_id, file, serialized) = self.send_raw_impl(envelope, email)?; + + fs::write(file, serialized).await?; Ok(email_id.to_string()) } } diff --git a/src/transport/sendmail/mod.rs b/src/transport/sendmail/mod.rs index 3a9df98..fb8e828 100644 --- a/src/transport/sendmail/mod.rs +++ b/src/transport/sendmail/mod.rs @@ -25,8 +25,10 @@ #[cfg(feature = "async-std1")] use crate::AsyncStd1Transport; +#[cfg(feature = "tokio02")] +use crate::Tokio02Transport; use crate::{transport::sendmail::error::Error, Envelope, Transport}; -#[cfg(feature = "async-std1")] +#[cfg(any(feature = "async-std1", feature = "tokio02"))] use async_trait::async_trait; use std::{ convert::AsRef, @@ -71,6 +73,21 @@ impl SendmailTransport { .stdout(Stdio::piped()); c } + + #[cfg(feature = "tokio02")] + fn tokio02_command(&self, envelope: &Envelope) -> tokio02_crate::process::Command { + use tokio02_crate::process::Command; + + let mut c = Command::new(&self.command); + c.kill_on_drop(true); + c.arg("-i") + .arg("-f") + .arg(envelope.from().map(|f| f.as_ref()).unwrap_or("\"\"")) + .args(envelope.to()) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()); + c + } } impl Transport for SendmailTransport { @@ -119,3 +136,28 @@ impl AsyncStd1Transport for SendmailTransport { } } } + +#[cfg(feature = "tokio02")] +#[async_trait] +impl Tokio02Transport for SendmailTransport { + type Ok = (); + type Error = Error; + + async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result { + use tokio02_crate::io::AsyncWriteExt; + + let mut command = self.tokio02_command(envelope); + + // Spawn the sendmail command + let mut process = command.spawn()?; + + process.stdin.as_mut().unwrap().write_all(&email).await?; + let output = process.wait_with_output().await?; + + if output.status.success() { + Ok(()) + } else { + Err(Error::Client(String::from_utf8(output.stderr)?)) + } + } +} diff --git a/src/transport/stub/mod.rs b/src/transport/stub/mod.rs index 0ab2a86..3129719 100644 --- a/src/transport/stub/mod.rs +++ b/src/transport/stub/mod.rs @@ -24,8 +24,10 @@ #[cfg(feature = "async-std1")] use crate::AsyncStd1Transport; +#[cfg(feature = "tokio02")] +use crate::Tokio02Transport; use crate::{Envelope, Transport}; -#[cfg(feature = "async-std1")] +#[cfg(any(feature = "async-std1", feature = "tokio02"))] use async_trait::async_trait; use std::{error::Error as StdError, fmt}; @@ -88,3 +90,14 @@ impl AsyncStd1Transport for StubTransport { self.response } } + +#[cfg(feature = "tokio02")] +#[async_trait] +impl Tokio02Transport for StubTransport { + type Ok = (); + type Error = Error; + + async fn send_raw(&self, _envelope: &Envelope, _email: &[u8]) -> Result { + self.response + } +} diff --git a/tests/transport_file.rs b/tests/transport_file.rs index 769918a..8fd5084 100644 --- a/tests/transport_file.rs +++ b/tests/transport_file.rs @@ -8,6 +8,9 @@ mod test { io::Read, }; + #[cfg(feature = "tokio02")] + use tokio02_crate as tokio; + #[test] fn file_transport() { use lettre::Transport; @@ -37,7 +40,7 @@ mod test { #[cfg(feature = "async-std1")] #[async_attributes::test] - async fn file_transport_async() { + async fn file_transport_asyncstd1() { use lettre::AsyncStd1Transport; let sender = FileTransport::new(temp_dir()); @@ -63,4 +66,33 @@ mod test { "{\"envelope\":{\"forward_path\":[\"hei@domain.tld\"],\"reverse_path\":\"nobody@domain.tld\"},\"raw_message\":null,\"message\":\"From: NoBody \\r\\nReply-To: Yuin \\r\\nTo: Hei \\r\\nSubject: Happy new year\\r\\nDate: Tue, 15 Nov 1994 08:12:31 GMT\\r\\n\\r\\nBe happy!\"}"); remove_file(file).unwrap(); } + + #[cfg(feature = "tokio02")] + #[tokio::test] + async fn file_transport_tokio02() { + use lettre::Tokio02Transport; + + let sender = FileTransport::new(temp_dir()); + let email = Message::builder() + .from("NoBody ".parse().unwrap()) + .reply_to("Yuin ".parse().unwrap()) + .to("Hei ".parse().unwrap()) + .subject("Happy new year") + .date("Tue, 15 Nov 1994 08:12:31 GMT".parse().unwrap()) + .body("Be happy!") + .unwrap(); + + let result = sender.send(email).await; + let id = result.unwrap(); + + let file = temp_dir().join(format!("{}.json", id)); + let mut f = File::open(file.clone()).unwrap(); + let mut buffer = String::new(); + let _ = f.read_to_string(&mut buffer); + + assert_eq!( + buffer, + "{\"envelope\":{\"forward_path\":[\"hei@domain.tld\"],\"reverse_path\":\"nobody@domain.tld\"},\"raw_message\":null,\"message\":\"From: NoBody \\r\\nReply-To: Yuin \\r\\nTo: Hei \\r\\nSubject: Happy new year\\r\\nDate: Tue, 15 Nov 1994 08:12:31 GMT\\r\\n\\r\\nBe happy!\"}"); + remove_file(file).unwrap(); + } } diff --git a/tests/transport_sendmail.rs b/tests/transport_sendmail.rs index 2ca2748..becc470 100644 --- a/tests/transport_sendmail.rs +++ b/tests/transport_sendmail.rs @@ -3,6 +3,9 @@ mod test { use lettre::{transport::sendmail::SendmailTransport, Message}; + #[cfg(feature = "tokio02")] + use tokio02_crate as tokio; + #[test] fn sendmail_transport() { use lettre::Transport; @@ -22,7 +25,7 @@ mod test { #[cfg(feature = "async-std1")] #[async_attributes::test] - async fn sendmail_transport_async() { + async fn sendmail_transport_asyncstd1() { use lettre::AsyncStd1Transport; let sender = SendmailTransport::new(); @@ -38,4 +41,23 @@ mod test { let result = sender.send(email).await; assert!(result.is_ok()); } + + #[cfg(feature = "tokio02")] + #[tokio::test] + async fn sendmail_transport_tokio02() { + use lettre::Tokio02Transport; + + let sender = SendmailTransport::new(); + let email = Message::builder() + .from("NoBody ".parse().unwrap()) + .reply_to("Yuin ".parse().unwrap()) + .to("Hei ".parse().unwrap()) + .subject("Happy new year") + .date("Tue, 15 Nov 1994 08:12:31 GMT".parse().unwrap()) + .body("Be happy!") + .unwrap(); + + let result = sender.send(email).await; + assert!(result.is_ok()); + } } diff --git a/tests/transport_stub.rs b/tests/transport_stub.rs index 905107f..6bd7286 100644 --- a/tests/transport_stub.rs +++ b/tests/transport_stub.rs @@ -1,5 +1,8 @@ use lettre::{transport::stub::StubTransport, Message}; +#[cfg(feature = "tokio02")] +use tokio02_crate as tokio; + #[test] fn stub_transport() { use lettre::Transport; @@ -19,7 +22,7 @@ fn stub_transport() { #[cfg(feature = "async-std1")] #[async_attributes::test] -async fn stub_transport_async() { +async fn stub_transport_asyncstd1() { use lettre::AsyncStd1Transport; let sender_ok = StubTransport::new_ok(); @@ -36,3 +39,23 @@ async fn stub_transport_async() { sender_ok.send(email.clone()).await.unwrap(); sender_ko.send(email).await.unwrap_err(); } + +#[cfg(feature = "tokio02")] +#[tokio::test] +async fn stub_transport_tokio02() { + use lettre::Tokio02Transport; + + let sender_ok = StubTransport::new_ok(); + let sender_ko = StubTransport::new_error(); + let email = Message::builder() + .from("NoBody ".parse().unwrap()) + .reply_to("Yuin ".parse().unwrap()) + .to("Hei ".parse().unwrap()) + .subject("Happy new year") + .date("Tue, 15 Nov 1994 08:12:31 GMT".parse().unwrap()) + .body("Be happy!") + .unwrap(); + + sender_ok.send(email.clone()).await.unwrap(); + sender_ko.send(email).await.unwrap_err(); +}