From 90641f79f7729b846b96ae29ed5dfa67d024c697 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 24 May 2022 13:30:52 +0200 Subject: [PATCH 1/6] feat: update deps --- Cargo.toml | 8 ++++---- src/smtp/client/inner.rs | 4 ++-- src/smtp/error.rs | 8 ++++---- src/smtp/response.rs | 5 ++++- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 45c88d7..16ea303 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ is-it-maintained-open-issues = { repository = "async-email/async-smtp" } [dependencies] log = "^0.4" -nom = { version = "^5.0", optional = true } +nom = { version = "^7.0", optional = true } bufstream = { version = "^0.1", optional = true } base64 = { version = "^0.13", optional = true } hostname = { version = "0.3.1", optional = true } @@ -27,15 +27,15 @@ serde = { version = "^1.0", optional = true } serde_json = { version = "^1.0", optional = true } serde_derive = { version = "^1.0", optional = true } fast-socks5 = { version = "^0.4", optional = true } -async-native-tls = { version = "0.3.3" } -async-std = { version = "1.6.0", features = ["unstable"] } +async-native-tls = { version = "0.4" } +async-std = { version = "1.11", features = ["unstable"] } async-trait = "0.1.17" pin-project = "1" pin-utils = "0.1.0" thiserror = "1.0.9" [dev-dependencies] -env_logger = "^0.8" +env_logger = "^0.9" glob = "^0.3" criterion = "^0.3" async-attributes = "1.1.1" diff --git a/src/smtp/client/inner.rs b/src/smtp/client/inner.rs index 20c8b81..8debc55 100644 --- a/src/smtp/client/inner.rs +++ b/src/smtp/client/inner.rs @@ -282,11 +282,11 @@ impl InnerClient { return Err(response.into()); } Err(nom::Err::Failure(e)) => { - return Err(Error::Parsing(e.1)); + return Err(Error::Parsing(e.code)); } Err(nom::Err::Incomplete(_)) => { /* read more */ } Err(nom::Err::Error(e)) => { - return Err(Error::Parsing(e.1)); + return Err(Error::Parsing(e.code)); } } } diff --git a/src/smtp/error.rs b/src/smtp/error.rs index 665d020..ee618bb 100644 --- a/src/smtp/error.rs +++ b/src/smtp/error.rs @@ -62,12 +62,12 @@ pub enum Error { Socks5Error(#[from] fast_socks5::SocksError), } -impl From> for Error { - fn from(err: nom::Err<(&str, nom::error::ErrorKind)>) -> Error { +impl From>> for Error { + fn from(err: nom::Err>) -> Error { Parsing(match err { nom::Err::Incomplete(_) => nom::error::ErrorKind::Complete, - nom::Err::Failure((_, k)) => k, - nom::Err::Error((_, k)) => k, + nom::Err::Failure(e) => e.code, + nom::Err::Error(e) => e.code, }) } } diff --git a/src/smtp/response.rs b/src/smtp/response.rs index 1cd4e4d..1e79513 100644 --- a/src/smtp/response.rs +++ b/src/smtp/response.rs @@ -253,7 +253,10 @@ pub(crate) fn parse_response(i: &str) -> IResult<&str, Response> { // Check that all codes are equal. if !lines.iter().all(|&(ref code, _, _)| *code == last_code) { - return Err(nom::Err::Failure(("", nom::error::ErrorKind::Not))); + return Err(nom::Err::Failure(nom::error::Error { + input: "", + code: nom::error::ErrorKind::Not, + })); } // Extract text from lines, and append last line. From f074a78dd361d1e659b7db2995de92abe3293cad Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 22 Jun 2022 21:55:18 +0200 Subject: [PATCH 2/6] feat: add support for tokio Allows configuration of either tokio or async-std. Drops support for `socks5` with `async-std`, as the underlying library `fast-sockst5` has done so in the latest release. --- Cargo.toml | 34 ++++-- src/file/error.rs | 2 +- src/file/mod.rs | 14 +-- src/lib.rs | 42 +++++++ src/sendmail/error.rs | 5 +- src/sendmail/mod.rs | 29 +++-- src/smtp/client/codec.rs | 8 +- src/smtp/client/inner.rs | 67 +++++++++-- src/smtp/client/mock.rs | 76 ++++++++++--- src/smtp/client/net.rs | 219 +++++++++++++++++++++++++++++------- src/smtp/error.rs | 4 + src/smtp/smtp_client.rs | 39 +++---- src/types.rs | 36 +++++- tests/transport_file.rs | 51 +++++---- tests/transport_sendmail.rs | 34 +++--- tests/transport_smtp.rs | 57 +++++----- tests/transport_stub.rs | 7 +- 17 files changed, 528 insertions(+), 196 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 16ea303..8b668c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,12 @@ is-it-maintained-open-issues = { repository = "async-email/async-smtp" } [dependencies] log = "^0.4" +async-trait = "0.1.17" +pin-project = "1" +pin-utils = "0.1.0" +thiserror = "1.0.9" +futures = "0.3.21" + nom = { version = "^7.0", optional = true } bufstream = { version = "^0.1", optional = true } base64 = { version = "^0.13", optional = true } @@ -26,33 +32,41 @@ hostname = { version = "0.3.1", optional = true } serde = { version = "^1.0", optional = true } serde_json = { version = "^1.0", optional = true } serde_derive = { version = "^1.0", optional = true } -fast-socks5 = { version = "^0.4", optional = true } -async-native-tls = { version = "0.4" } -async-std = { version = "1.11", features = ["unstable"] } -async-trait = "0.1.17" -pin-project = "1" -pin-utils = "0.1.0" -thiserror = "1.0.9" +fast-socks5 = { version = "^0.8", optional = true } + +async-std = { version = "1.11", features = ["unstable"], optional = true } +tokio = { version = "1", features = ["fs", "rt", "time", "net"], optional = true } +async-native-tls = { version = "0.4", default-features = false } + [dev-dependencies] env_logger = "^0.9" glob = "^0.3" criterion = "^0.3" -async-attributes = "1.1.1" +async-std = { version = "1.11", features = ["unstable", "attributes"] } +tokio = { version = "1", features = ["fs", "rt", "time", "net", "macros"] } [[bench]] name = "transport_smtp" harness = false [features] -default = ["file-transport", "smtp-transport", "sendmail-transport"] -unstable = [] +default = [ + "file-transport", + "smtp-transport", + "sendmail-transport", + "runtime-tokio" +] + serde-impls = ["serde", "serde_derive"] file-transport = ["serde-impls", "serde_json"] smtp-transport = ["bufstream", "base64", "nom", "hostname"] sendmail-transport = [] native-tls-vendored = ["async-native-tls/vendored"] socks5 = ["fast-socks5"] +runtime-async-std = ["async-std", "async-native-tls/runtime-async-std"] +runtime-tokio = ["tokio", "async-native-tls/runtime-tokio"] + [[example]] name = "smtp" diff --git a/src/file/error.rs b/src/file/error.rs index d8343d3..4676309 100644 --- a/src/file/error.rs +++ b/src/file/error.rs @@ -1,6 +1,6 @@ //! Error and result type for file transport -use async_std::io; +use futures::io; /// An enum of all error kinds. #[derive(thiserror::Error, Debug)] diff --git a/src/file/mod.rs b/src/file/mod.rs index c615bd8..f8caf91 100644 --- a/src/file/mod.rs +++ b/src/file/mod.rs @@ -3,12 +3,14 @@ //! It can be useful for testing purposes, or if you want to keep track of sent messages. //! +use std::path::Path; use std::{path::PathBuf, time::Duration}; -use async_std::fs::File; -use async_std::path::Path; -use async_std::prelude::*; +#[cfg(feature = "runtime-async-std")] +use async_std::fs::write; use async_trait::async_trait; +#[cfg(feature = "runtime-tokio")] +use tokio::fs::write; use crate::file::error::FileResult; use crate::Envelope; @@ -64,10 +66,8 @@ impl<'a> Transport<'a> for FileTransport { message: email.message_to_string().await?.as_bytes().to_vec(), })?; - File::create(file) - .await? - .write_all(serialized.as_bytes()) - .await?; + write(file, serialized.as_bytes()).await?; + Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 1a5ced8..ba22bd4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,12 @@ clippy::unwrap_used )] +#[cfg(not(any(feature = "runtime-tokio", feature = "runtime-async-std")))] +compile_error!("one of 'runtime-async-std' or 'runtime-tokio' features must be enabled"); + +#[cfg(all(feature = "runtime-tokio", feature = "runtime-async-std"))] +compile_error!("only one of 'runtime-async-std' or 'runtime-tokio' features must be enabled"); + pub mod error; #[cfg(feature = "file-transport")] pub mod file; @@ -53,3 +59,39 @@ pub trait Transport<'a> { timeout: Option<&Duration>, ) -> Self::Result; } + +#[macro_export] +macro_rules! async_test { + ($name:ident, $block:block) => { + #[cfg(feature = "runtime-tokio")] + #[tokio::test] + async fn $name() { + $block + } + + #[cfg(feature = "runtime-async-std")] + #[async_std::test] + async fn $name() { + $block + } + }; +} + +#[macro_export] +macro_rules! async_test_ignore { + ($name:ident, $block:block) => { + #[cfg(feature = "runtime-tokio")] + #[tokio::test] + #[ignore] + async fn $name() { + $block + } + + #[cfg(feature = "runtime-async-std")] + #[async_std::test] + #[ignore] + async fn $name() { + $block + } + }; +} diff --git a/src/sendmail/error.rs b/src/sendmail/error.rs index cf34353..77d5928 100644 --- a/src/sendmail/error.rs +++ b/src/sendmail/error.rs @@ -2,7 +2,7 @@ use std::string::FromUtf8Error; -use async_std::io; +use futures::io; /// An enum of all error kinds. #[derive(thiserror::Error, Debug)] @@ -16,6 +16,9 @@ pub enum Error { /// IO error #[error("io error: {0}")] Io(#[from] io::Error), + #[cfg(feature = "runtime-tokio")] + #[error("join: {0}")] + Join(#[from] tokio::task::JoinError), } /// sendmail result type diff --git a/src/sendmail/mod.rs b/src/sendmail/mod.rs index 89b2ae7..6c38b1f 100644 --- a/src/sendmail/mod.rs +++ b/src/sendmail/mod.rs @@ -5,7 +5,6 @@ use crate::sendmail::error::SendmailResult; use crate::SendableEmail; use crate::Transport; -use async_std::prelude::*; use async_trait::async_trait; use log::info; use std::convert::AsRef; @@ -15,6 +14,12 @@ use std::{ time::Duration, }; +#[cfg(feature = "runtime-tokio")] +use tokio::{io::AsyncReadExt, task::spawn_blocking}; + +#[cfg(feature = "runtime-async-std")] +use async_std::{io::ReadExt, task::spawn_blocking}; + pub mod error; /// Sends an email using the `sendmail` command @@ -63,7 +68,7 @@ impl<'a> Transport<'a> for SendmailTransport { let _ = email.message().read_to_string(&mut message_content).await; // TODO: Convert to real async, once async-std has a process implementation. - let output = async_std::task::spawn_blocking(move || { + let res = spawn_blocking(move || { // Spawn the sendmail command let mut process = Command::new(command) .arg("-i") @@ -83,14 +88,22 @@ impl<'a> Transport<'a> for SendmailTransport { info!("Wrote {} message to stdin", message_id); - process.wait_with_output() + let output = process.wait_with_output()?; + if output.status.success() { + Ok(()) + } else { + Err(error::Error::Client(String::from_utf8(output.stderr)?)) + } }) - .await?; + .await; - if output.status.success() { - Ok(()) - } else { - Err(error::Error::Client(String::from_utf8(output.stderr)?)) + #[cfg(feature = "runtime-tokio")] + { + res? + } + #[cfg(feature = "runtime-async-std")] + { + res } } diff --git a/src/smtp/client/codec.rs b/src/smtp/client/codec.rs index c57946c..23f25b5 100644 --- a/src/smtp/client/codec.rs +++ b/src/smtp/client/codec.rs @@ -1,5 +1,9 @@ -use async_std::io::{self, Write}; -use async_std::prelude::*; +#[cfg(feature = "runtime-async-std")] +use async_std::io::{Write, WriteExt}; +#[cfg(feature = "runtime-tokio")] +use tokio::io::{AsyncWrite as Write, AsyncWriteExt}; + +use futures::io; /// The codec used for transparency #[derive(Default, Clone, Copy, Debug)] diff --git a/src/smtp/client/inner.rs b/src/smtp/client/inner.rs index 8debc55..350b3f8 100644 --- a/src/smtp/client/inner.rs +++ b/src/smtp/client/inner.rs @@ -1,14 +1,21 @@ use std::fmt::{Debug, Display}; +use std::net::SocketAddr; +use std::pin::Pin; use std::string::String; use std::time::Duration; -use async_std::io::{self, BufReader, Read, Write}; -use async_std::net::ToSocketAddrs; -use async_std::pin::Pin; -use async_std::prelude::*; +use futures::io; +use futures::prelude::*; use log::debug; use pin_project::pin_project; +#[cfg(feature = "runtime-async-std")] +use async_std::io::{BufReader, Read, Write}; +#[cfg(feature = "runtime-tokio")] +use tokio::io::{ + AsyncBufReadExt, AsyncRead as Read, AsyncReadExt, AsyncWrite as Write, AsyncWriteExt, BufReader, +}; + use crate::smtp::authentication::{Credentials, Mechanism}; use crate::smtp::client::net::{ClientTlsParameters, Connector, NetworkStream}; use crate::smtp::client::ClientCodec; @@ -21,6 +28,11 @@ use crate::smtp::Socks5Config; #[cfg(feature = "socks5")] use crate::ServerAddress; +#[cfg(feature = "runtime-async-std")] +use async_std::net::ToSocketAddrs; +#[cfg(feature = "runtime-tokio")] +use tokio::net::ToSocketAddrs; + /// Returns the string replacing all the CRLF with "\" /// Used for debug displays fn escape_crlf(string: &str) -> String { @@ -115,7 +127,7 @@ impl InnerClient { timeout: Option, tls_parameters: Option<&ClientTlsParameters>, ) -> Result<(), Error> { - let mut addresses = addr.to_socket_addrs().await?; + let mut addresses = lookup_host(addr).await?; let server_addr = match addresses.next() { Some(addr) => addr, @@ -295,17 +307,52 @@ impl InnerClient { } } +#[cfg(feature = "runtime-tokio")] +pub(crate) async fn lookup_host( + addr: A, +) -> io::Result> { + tokio::net::lookup_host(addr).await +} + +#[cfg(feature = "runtime-async-std")] +pub(crate) async fn lookup_host( + addr: A, +) -> io::Result> { + addr.to_socket_addrs().await +} + /// Execute io operations with an optional timeout using. +#[cfg(feature = "runtime-tokio")] pub(crate) async fn with_timeout( timeout: Option<&Duration>, f: F, ) -> std::result::Result where F: Future>, - E: From, + E: From, { if let Some(timeout) = timeout { - let res = async_std::future::timeout(*timeout, f).await??; + let res = tokio::time::timeout(*timeout, f).await??; + Ok(res) + } else { + f.await + } +} + +/// Execute io operations with an optional timeout using. +#[cfg(feature = "runtime-async-std")] +pub(crate) async fn with_timeout( + timeout: Option<&Duration>, + f: F, +) -> std::result::Result +where + F: Future>, + E: From, +{ + if let Some(timeout) = timeout { + let res = async_std::future::timeout(*timeout, f) + .await + .map_err(|e| io::Error::new(io::ErrorKind::TimedOut, e))??; Ok(res) } else { f.await @@ -315,10 +362,10 @@ where #[cfg(test)] mod test { use super::escape_crlf; + use crate::async_test; use crate::smtp::client::ClientCodec; - #[async_attributes::test] - async fn test_codec() { + async_test! { test_codec, { let mut codec = ClientCodec::new(); let mut buf: Vec = vec![]; @@ -335,7 +382,7 @@ mod test { String::from_utf8(buf).unwrap(), "test\r\n..\r\n\r\ntestte\r\n..\r\nsttesttest.test\n.test\ntest" ); - } + }} #[test] fn test_escape_crlf() { diff --git a/src/smtp/client/mock.rs b/src/smtp/client/mock.rs index 6dc2f52..644dd4d 100644 --- a/src/smtp/client/mock.rs +++ b/src/smtp/client/mock.rs @@ -1,8 +1,16 @@ #![allow(missing_docs)] -use async_std::io::{self, Cursor, Read, Write}; -use async_std::pin::Pin; -use async_std::task::{Context, Poll}; +use std::pin::Pin; +use std::task::{Context, Poll}; + +#[cfg(feature = "runtime-async-std")] +use async_std::io::{Cursor, Read, Write}; +#[cfg(feature = "runtime-tokio")] +use std::io::Cursor; +#[cfg(feature = "runtime-tokio")] +use tokio::io::{AsyncRead as Read, AsyncWrite as Write}; + +use futures::io; use pin_project::pin_project; pub type MockCursor = Cursor>; @@ -66,6 +74,20 @@ impl MockStream { } } +#[cfg(feature = "runtime-tokio")] +impl Read for MockStream { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> Poll> { + let this = self.project(); + let _: Pin<&mut _> = this.reader; + this.reader.poll_read(cx, buf) + } +} + +#[cfg(feature = "runtime-tokio")] impl Write for MockStream { fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll> { let this = self.project(); @@ -79,13 +101,14 @@ impl Write for MockStream { this.writer.poll_flush(cx) } - fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { let this = self.project(); let _: Pin<&mut _> = this.writer; - this.writer.poll_close(cx) + this.writer.poll_shutdown(cx) } } +#[cfg(feature = "runtime-async-std")] impl Read for MockStream { fn poll_read( self: Pin<&mut Self>, @@ -98,34 +121,57 @@ impl Read for MockStream { } } +#[cfg(feature = "runtime-async-std")] +impl Write for MockStream { + fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll> { + let this = self.project(); + let _: Pin<&mut _> = this.writer; + this.writer.poll_write(cx, buf) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let this = self.project(); + let _: Pin<&mut _> = this.writer; + this.writer.poll_flush(cx) + } + + fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let this = self.project(); + let _: Pin<&mut _> = this.writer; + this.writer.poll_close(cx) + } +} + #[cfg(test)] mod test { use super::*; - use async_std::prelude::*; - #[async_attributes::test] - async fn write_take_test() { + use crate::async_test; + #[cfg(feature = "runtime-async-std")] + use async_std::io::{ReadExt, WriteExt}; + #[cfg(feature = "runtime-tokio")] + use tokio::io::{AsyncReadExt, AsyncWriteExt}; + + async_test! { write_take_test, { let mut mock = MockStream::new(); // write to mock stream mock.write_all(&[1, 2, 3]).await.unwrap(); assert_eq!(mock.take_vec(), vec![1, 2, 3]); - } + }} - #[async_attributes::test] - async fn read_with_vec_test() { + async_test! { read_with_vec_test, { let mut mock = MockStream::with_vec(vec![4, 5]); let mut vec = Vec::new(); mock.read_to_end(&mut vec).await.unwrap(); assert_eq!(vec, vec![4, 5]); - } + }} - #[async_attributes::test] - async fn swap_test() { + async_test! { swap_test, { let mut mock = MockStream::new(); let mut vec = Vec::new(); mock.write_all(&[8, 9, 10]).await.unwrap(); mock.swap(); mock.read_to_end(&mut vec).await.unwrap(); assert_eq!(vec, vec![8, 9, 10]); - } + }} } diff --git a/src/smtp/client/net.rs b/src/smtp/client/net.rs index afb666e..c64a09d 100644 --- a/src/smtp/client/net.rs +++ b/src/smtp/client/net.rs @@ -1,18 +1,26 @@ //! A trait to represent a stream use std::fmt; +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; +use std::pin::Pin; +use std::task::{Context, Poll}; use std::time::Duration; use async_native_tls::{TlsConnector, TlsStream}; -use async_std::io::{self, ErrorKind, Read, Write}; -use async_std::net::{Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4, TcpStream}; -use async_std::pin::Pin; -use async_std::task::{Context, Poll}; +#[cfg(feature = "runtime-async-std")] +use async_std::{io::Read, io::Write, net::TcpStream}; use async_trait::async_trait; #[cfg(feature = "socks5")] use fast_socks5::client::Socks5Stream; +use futures::io::{self, ErrorKind}; use pin_project::pin_project; +#[cfg(feature = "runtime-tokio")] +use tokio::{ + io::{AsyncRead as Read, AsyncWrite as Write}, + net::TcpStream, +}; +use super::inner::with_timeout; use crate::smtp::client::mock::MockStream; #[cfg(feature = "socks5")] @@ -78,20 +86,152 @@ impl NetworkStream { } } - /// Shutdowns the connection - pub fn shutdown(&self, how: Shutdown) -> io::Result<()> { + /// Shutdowns the connection. + #[cfg(feature = "runtime-tokio")] + pub async fn shutdown(&mut self) -> io::Result<()> { + use tokio::io::AsyncWriteExt; match *self { - NetworkStream::Tcp(ref s) => s.shutdown(how), - NetworkStream::Tls(ref s) => s.get_ref().shutdown(how), + NetworkStream::Tcp(ref mut s) => s.shutdown().await, + NetworkStream::Tls(ref mut s) => s.get_mut().shutdown().await, #[cfg(feature = "socks5")] - NetworkStream::Socks5Stream(ref s) => s.get_socket_ref().shutdown(how), + NetworkStream::Socks5Stream(ref mut s) => s.get_socket_mut().shutdown().await, #[cfg(feature = "socks5")] - NetworkStream::TlsSocks5Stream(ref s) => s.get_ref().get_socket_ref().shutdown(how), + NetworkStream::TlsSocks5Stream(ref mut s) => { + s.get_mut().get_socket_mut().shutdown().await + } + NetworkStream::Mock(_) => Ok(()), + } + } + + /// Shutdowns the connection. + #[cfg(feature = "runtime-async-std")] + pub async fn shutdown(&mut self) -> io::Result<()> { + use std::net::Shutdown; + + match *self { + NetworkStream::Tcp(ref s) => s.shutdown(Shutdown::Both), + NetworkStream::Tls(ref s) => s.get_ref().shutdown(Shutdown::Both), + #[cfg(feature = "socks5")] + NetworkStream::Socks5Stream(ref s) => s.get_socket_ref().shutdown(Shutdown::Both), + #[cfg(feature = "socks5")] + NetworkStream::TlsSocks5Stream(ref s) => { + s.get_ref().get_socket_ref().shutdown(Shutdown::Both) + } NetworkStream::Mock(_) => Ok(()), } } } +#[cfg(feature = "runtime-tokio")] +impl Read for NetworkStream { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> Poll> { + match self.project() { + NetworkStreamProj::Tcp(s) => { + let _: Pin<&mut TcpStream> = s; + s.poll_read(cx, buf) + } + NetworkStreamProj::Tls(s) => { + let _: Pin<&mut TlsStream> = s; + s.poll_read(cx, buf) + } + #[cfg(feature = "socks5")] + NetworkStreamProj::Socks5Stream(s) => { + let _: Pin<&mut Socks5Stream> = s; + s.poll_read(cx, buf) + } + #[cfg(feature = "socks5")] + NetworkStreamProj::TlsSocks5Stream(s) => { + let _: Pin<&mut TlsStream>> = s; + s.poll_read(cx, buf) + } + NetworkStreamProj::Mock(s) => { + let _: Pin<&mut MockStream> = s; + s.poll_read(cx, buf) + } + } + } +} + +#[cfg(feature = "runtime-tokio")] +impl Write for NetworkStream { + fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll> { + match self.project() { + NetworkStreamProj::Tcp(s) => { + let _: Pin<&mut TcpStream> = s; + s.poll_write(cx, buf) + } + NetworkStreamProj::Tls(s) => { + let _: Pin<&mut TlsStream> = s; + s.poll_write(cx, buf) + } + #[cfg(feature = "socks5")] + NetworkStreamProj::Socks5Stream(s) => s.poll_write(cx, buf), + #[cfg(feature = "socks5")] + NetworkStreamProj::TlsSocks5Stream(s) => { + let _: Pin<&mut TlsStream>> = s; + s.poll_write(cx, buf) + } + NetworkStreamProj::Mock(s) => { + let _: Pin<&mut MockStream> = s; + s.poll_write(cx, buf) + } + } + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + match self.project() { + NetworkStreamProj::Tcp(s) => { + let _: Pin<&mut TcpStream> = s; + s.poll_flush(cx) + } + NetworkStreamProj::Tls(s) => { + let _: Pin<&mut TlsStream> = s; + s.poll_flush(cx) + } + #[cfg(feature = "socks5")] + NetworkStreamProj::Socks5Stream(s) => s.poll_flush(cx), + #[cfg(feature = "socks5")] + NetworkStreamProj::TlsSocks5Stream(s) => { + let _: Pin<&mut TlsStream>> = s; + s.poll_flush(cx) + } + NetworkStreamProj::Mock(s) => { + let _: Pin<&mut MockStream> = s; + s.poll_flush(cx) + } + } + } + + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + match self.project() { + NetworkStreamProj::Tcp(s) => { + let _: Pin<&mut TcpStream> = s; + s.poll_shutdown(cx) + } + NetworkStreamProj::Tls(s) => { + let _: Pin<&mut TlsStream> = s; + s.poll_shutdown(cx) + } + #[cfg(feature = "socks5")] + NetworkStreamProj::Socks5Stream(s) => s.poll_shutdown(cx), + #[cfg(feature = "socks5")] + NetworkStreamProj::TlsSocks5Stream(s) => { + let _: Pin<&mut TlsStream>> = s; + s.poll_shutdown(cx) + } + NetworkStreamProj::Mock(s) => { + let _: Pin<&mut MockStream> = s; + s.poll_shutdown(cx) + } + } + } +} + +#[cfg(feature = "runtime-async-std")] impl Read for NetworkStream { fn poll_read( self: Pin<&mut Self>, @@ -125,6 +265,7 @@ impl Read for NetworkStream { } } +#[cfg(feature = "runtime-async-std")] impl Write for NetworkStream { fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll> { match self.project() { @@ -231,27 +372,26 @@ impl Connector for NetworkStream { tls_parameters: Option<&ClientTlsParameters>, ) -> io::Result { let tcp_stream = match timeout { - Some(duration) => io::timeout(duration, TcpStream::connect(addr)).await?, + Some(ref duration) => with_timeout(Some(duration), TcpStream::connect(addr)).await?, None => TcpStream::connect(addr).await?, }; match tls_parameters { - Some(context) => match timeout { - Some(duration) => async_std::future::timeout( - duration, - context.connector.connect(&context.domain, tcp_stream), - ) - .await - .map_err(|e| io::Error::new(ErrorKind::TimedOut, e))? - .map(NetworkStream::Tls) - .map_err(|e| io::Error::new(ErrorKind::Other, e)), - None => context - .connector - .connect(&context.domain, tcp_stream) - .await - .map(NetworkStream::Tls) - .map_err(|e| io::Error::new(ErrorKind::Other, e)), - }, + Some(context) => { + let connector = async { + context + .connector + .connect(&context.domain, tcp_stream) + .await + .map(NetworkStream::Tls) + .map_err(|e| io::Error::new(ErrorKind::Other, e)) + }; + + match timeout { + Some(ref duration) => with_timeout(Some(duration), connector).await, + None => connector.await, + } + } None => Ok(NetworkStream::Tcp(tcp_stream)), } } @@ -266,22 +406,17 @@ impl Connector for NetworkStream { let socks5_stream = socks5.connect(addr, timeout).await?; match tls_parameters { - Some(context) => match timeout { - Some(duration) => async_std::future::timeout( - duration, - context.connector.connect(&context.domain, socks5_stream), - ) + Some(context) => { + with_timeout(timeout.as_ref(), async { + context + .connector + .connect(&context.domain, socks5_stream) + .await + .map(NetworkStream::TlsSocks5Stream) + .map_err(|e| io::Error::new(ErrorKind::Other, e)) + }) .await - .map_err(|e| io::Error::new(ErrorKind::TimedOut, e))? - .map(NetworkStream::TlsSocks5Stream) - .map_err(|e| io::Error::new(ErrorKind::Other, e)), - None => context - .connector - .connect(&context.domain, socks5_stream) - .await - .map(NetworkStream::TlsSocks5Stream) - .map_err(|e| io::Error::new(ErrorKind::Other, e)), - }, + } None => Ok(NetworkStream::Socks5Stream(socks5_stream)), } } diff --git a/src/smtp/error.rs b/src/smtp/error.rs index ee618bb..2630533 100644 --- a/src/smtp/error.rs +++ b/src/smtp/error.rs @@ -47,6 +47,10 @@ pub enum Error { /// Parsing error #[error("parsing: {0:?}")] Parsing(nom::error::ErrorKind), + #[cfg(feature = "runtime-tokio")] + #[error("timeout: {0}")] + Timeout(#[from] tokio::time::error::Elapsed), + #[cfg(feature = "runtime-async-std")] #[error("timeout: {0}")] Timeout(#[from] async_std::future::TimeoutError), #[error("no stream")] diff --git a/src/smtp/smtp_client.rs b/src/smtp/smtp_client.rs index 0568bad..9b21cb3 100644 --- a/src/smtp/smtp_client.rs +++ b/src/smtp/smtp_client.rs @@ -1,12 +1,22 @@ use std::fmt::Display; +use std::pin::Pin; use std::time::Duration; -use async_std::net::ToSocketAddrs; -use async_std::pin::Pin; +#[cfg(all(feature = "runtime-async-std", feature = "socks5"))] +use async_std::io; +#[cfg(all(feature = "runtime-async-std", feature = "socks5"))] +use async_std::net::TcpStream; use async_trait::async_trait; +#[cfg(feature = "socks5")] +use fast_socks5::client::{Config, Socks5Stream}; use log::{debug, info}; use pin_project::pin_project; +#[cfg(all(feature = "runtime-tokio", feature = "socks5"))] +use tokio::io; +#[cfg(all(feature = "runtime-tokio", feature = "socks5"))] +use tokio::net::TcpStream; +use super::client::lookup_host; use crate::smtp::authentication::{ Credentials, Mechanism, DEFAULT_ENCRYPTED_MECHANISMS, DEFAULT_UNENCRYPTED_MECHANISMS, }; @@ -16,13 +26,6 @@ use crate::smtp::commands::*; use crate::smtp::error::{Error, SmtpResult}; use crate::smtp::extension::{ClientId, Extension, MailBodyParameter, MailParameter, ServerInfo}; use crate::{SendableEmail, Transport}; -#[cfg(feature = "socks5")] -use async_std::io::{self, ErrorKind}; - -#[cfg(feature = "socks5")] -use async_std::net::TcpStream; -#[cfg(feature = "socks5")] -use fast_socks5::client::{Config, Socks5Stream}; // Registered port numbers: // https://www.iana. @@ -108,13 +111,8 @@ impl Socks5Config { target_addr: &ServerAddress, timeout: Option, ) -> io::Result> { - if let Some(timeout) = timeout { - async_std::future::timeout(timeout, self.connect_without_timeout(target_addr)) - .await - .map_err(|e| io::Error::new(ErrorKind::TimedOut, e))? - } else { - self.connect_without_timeout(target_addr).await - } + super::client::with_timeout(timeout.as_ref(), self.connect_without_timeout(target_addr)) + .await } async fn connect_without_timeout( @@ -146,7 +144,7 @@ impl Socks5Config { match socks_stream { Ok(socks_stream) => io::Result::Ok(socks_stream), Err(e) => io::Result::Err(io::Error::new( - ErrorKind::ConnectionRefused, + std::io::ErrorKind::ConnectionRefused, Error::Socks5Error(e), )), } @@ -451,12 +449,7 @@ impl<'a> SmtpTransport { match &self.client_info.connection_type { ConnectionType::Direct => { // Perform dns lookup if needed - let mut addresses = self - .client_info - .server_addr - .to_string() - .to_socket_addrs() - .await?; + let mut addresses = lookup_host(self.client_info.server_addr.to_string()).await?; let mut last_err = None; loop { diff --git a/src/types.rs b/src/types.rs index 6a4b1e7..9221f6b 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,12 +1,17 @@ use std::ffi::OsStr; use std::fmt::{self, Display, Formatter}; +use std::pin::Pin; use std::str::FromStr; +use std::task::{Context, Poll}; -use async_std::io::{self, Cursor, Read}; -use async_std::pin::Pin; -use async_std::prelude::*; -use async_std::task::{Context, Poll}; +#[cfg(feature = "runtime-async-std")] +use async_std::io::{Cursor, Read, ReadExt}; +use futures::io; use pin_project::pin_project; +#[cfg(feature = "runtime-tokio")] +use std::io::Cursor; +#[cfg(feature = "runtime-tokio")] +use tokio::io::{AsyncRead as Read, AsyncReadExt}; use crate::error::EmailResult; use crate::error::Error; @@ -106,6 +111,29 @@ pub enum Message { Bytes(#[pin] Cursor>), } +#[cfg(feature = "runtime-tokio")] +impl Read for Message { + #[allow(unsafe_code)] + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> Poll> { + match self.project() { + MessageProj::Reader(mut rdr) => { + // Probably safe.. + let r: Pin<&mut _> = unsafe { Pin::new_unchecked(&mut **rdr) }; + r.poll_read(cx, buf) + } + MessageProj::Bytes(rdr) => { + let _: Pin<&mut _> = rdr; + rdr.poll_read(cx, buf) + } + } + } +} + +#[cfg(feature = "runtime-async-std")] impl Read for Message { #[allow(unsafe_code)] fn poll_read( diff --git a/tests/transport_file.rs b/tests/transport_file.rs index f826d11..ce0efac 100644 --- a/tests/transport_file.rs +++ b/tests/transport_file.rs @@ -2,40 +2,41 @@ #[cfg(feature = "file-transport")] mod test { use async_smtp::file::FileTransport; - use async_smtp::{EmailAddress, Envelope, SendableEmail, Transport}; + use async_smtp::{async_test, EmailAddress, Envelope, SendableEmail, Transport}; use std::env::temp_dir; use std::fs::remove_file; use std::fs::File; use std::io::Read; - #[async_attributes::test] - async fn file_transport() { - let mut sender = FileTransport::new(temp_dir()); - let email = SendableEmail::new( - Envelope::new( - Some(EmailAddress::new("user@localhost".to_string()).unwrap()), - vec![EmailAddress::new("root@localhost".to_string()).unwrap()], - ) - .unwrap(), - "id".to_string(), - "Hello ß☺ example".to_string().into_bytes(), - ); - let message_id = email.message_id().to_string(); + async_test! { + file_transport, { + let mut sender = FileTransport::new(temp_dir()); + let email = SendableEmail::new( + Envelope::new( + Some(EmailAddress::new("user@localhost".to_string()).unwrap()), + vec![EmailAddress::new("root@localhost".to_string()).unwrap()], + ) + .unwrap(), + "id".to_string(), + "Hello ß☺ example".to_string().into_bytes(), + ); + let message_id = email.message_id().to_string(); - let result = sender.send(email).await; - assert!(result.is_ok()); + let result = sender.send(email).await; + assert!(result.is_ok()); - let file = format!("{}/{}.json", temp_dir().to_str().unwrap(), message_id); - let mut f = File::open(file.clone()).unwrap(); - let mut buffer = String::new(); - let _ = f.read_to_string(&mut buffer); + let file = format!("{}/{}.json", temp_dir().to_str().unwrap(), message_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\":[\"root@localhost\"],\"reverse_path\":\"user@localhost\"},\"message_id\":\"id\",\"message\":[72,101,108,108,111,32,195,159,226,152,186,32,101,120,97,109,112,108,101]}" - ); + assert_eq!( + buffer, + "{\"envelope\":{\"forward_path\":[\"root@localhost\"],\"reverse_path\":\"user@localhost\"},\"message_id\":\"id\",\"message\":[72,101,108,108,111,32,195,159,226,152,186,32,101,120,97,109,112,108,101]}" + ); - remove_file(file).unwrap(); + remove_file(file).unwrap(); + } } } diff --git a/tests/transport_sendmail.rs b/tests/transport_sendmail.rs index 38422d1..87321d0 100644 --- a/tests/transport_sendmail.rs +++ b/tests/transport_sendmail.rs @@ -2,23 +2,25 @@ #[cfg(feature = "sendmail-transport")] mod test { use async_smtp::sendmail::SendmailTransport; - use async_smtp::{EmailAddress, Envelope, SendableEmail, Transport}; + use async_smtp::{async_test, EmailAddress, Envelope, SendableEmail, Transport}; - #[async_attributes::test] - async fn sendmail_transport_simple() { - let mut sender = SendmailTransport::new(); - let email = SendableEmail::new( - Envelope::new( - Some(EmailAddress::new("user@localhost".to_string()).unwrap()), - vec![EmailAddress::new("root@localhost".to_string()).unwrap()], - ) - .unwrap(), - "id".to_string(), - "Hello ß☺ example".to_string().into_bytes(), - ); + async_test! { + sendmail_transport_simple, + { + let mut sender = SendmailTransport::new(); + let email = SendableEmail::new( + Envelope::new( + Some(EmailAddress::new("user@localhost".to_string()).unwrap()), + vec![EmailAddress::new("root@localhost".to_string()).unwrap()], + ) + .unwrap(), + "id".to_string(), + "Hello ß☺ example".to_string().into_bytes(), + ); - let result = sender.send(email).await; - println!("{:?}", result); - assert!(result.is_ok()); + let result = sender.send(email).await; + println!("{:?}", result); + assert!(result.is_ok()); + } } } diff --git a/tests/transport_smtp.rs b/tests/transport_smtp.rs index 83fa534..bf3ddf2 100644 --- a/tests/transport_smtp.rs +++ b/tests/transport_smtp.rs @@ -1,35 +1,36 @@ #[cfg(test)] #[cfg(feature = "smtp-transport")] mod test { - use async_smtp::{ClientSecurity, Envelope, SendableEmail, ServerAddress, SmtpClient}; + use async_smtp::{ + async_test_ignore, ClientSecurity, Envelope, SendableEmail, ServerAddress, SmtpClient, + }; - #[async_attributes::test] - #[ignore] // ignored as this needs a running server - async fn smtp_transport_simple() { - let email = SendableEmail::new( - Envelope::new( - Some("user@localhost".parse().unwrap()), - vec!["root@localhost".parse().unwrap()], - ) - .unwrap(), - "id", - "From: user@localhost\r\n\ - Content-Type: text/plain\r\n\ - \r\n\ - Hello example", - ); - - println!("connecting"); - let mut transport = SmtpClient::with_security( - ServerAddress { - host: "127.0.0.1".to_string(), - port: 3025, - }, - ClientSecurity::None, + // ignored as this needs a running server + async_test_ignore! { smtp_transport_simple, { + let email = SendableEmail::new( + Envelope::new( + Some("user@localhost".parse().unwrap()), + vec!["root@localhost".parse().unwrap()], ) - .into_transport(); + .unwrap(), + "id", + "From: user@localhost\r\n\ + Content-Type: text/plain\r\n\ + \r\n\ + Hello example", + ); + + println!("connecting"); + let mut transport = SmtpClient::with_security( + ServerAddress { + host: "127.0.0.1".to_string(), + port: 3025, + }, + ClientSecurity::None, + ) + .into_transport(); - println!("sending"); - transport.connect_and_send(email).await.unwrap(); - } + println!("sending"); + transport.connect_and_send(email).await.unwrap(); + }} } diff --git a/tests/transport_stub.rs b/tests/transport_stub.rs index a8dc854..6aa0c1c 100644 --- a/tests/transport_stub.rs +++ b/tests/transport_stub.rs @@ -2,10 +2,9 @@ #[cfg(feature = "smtp-transport")] mod test { use async_smtp::stub::StubTransport; - use async_smtp::{EmailAddress, Envelope, SendableEmail, Transport}; + use async_smtp::{async_test, EmailAddress, Envelope, SendableEmail, Transport}; - #[async_attributes::test] - async fn stub_transport() { + async_test! { stub_transport, { let mut sender_ok = StubTransport::new_positive(); let mut sender_ko = StubTransport::new(Err(())); let email_ok = SendableEmail::new( @@ -29,5 +28,5 @@ mod test { sender_ok.send(email_ok).await.unwrap(); sender_ko.send(email_ko).await.unwrap_err(); - } + }} } From 30e2dfc0d1845abd7ca27a9ed2493b4c2aa2c552 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 22 Jun 2022 21:58:38 +0200 Subject: [PATCH 3/6] ci: update workflow --- .github/workflows/ci.yml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05dd4e0..9710082 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,11 +38,23 @@ jobs: command: check args: --benches - - name: tests + - name: tests: tokio uses: actions-rs/cargo@v1 with: command: test - args: --all --no-default-features --features file-transport smtp-transport + args: --all --no-default-features --features file-transport,smtp-transport,sendmail-transport,runtime-tokio + + - name: tests: tokio with socks5 + uses: actions-rs/cargo@v1 + with: + command: test + args: --all --no-default-features --features file-transport,smtp-transport,sendmail-transport,runtime-tokio,socks5 + + - name: tests: async-std + uses: actions-rs/cargo@v1 + with: + command: test + args: --all --no-default-features --features file-transport,smtp-transport,sendmail-transport,runtime-async-std check_fmt_and_docs: name: Checking fmt and docs @@ -75,4 +87,3 @@ jobs: - uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features From 3110d74dc7eb2e44eea8c73f5af1373d0b62ddce Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 22 Jun 2022 22:02:46 +0200 Subject: [PATCH 4/6] fixup --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9710082..a305ab1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,19 +38,19 @@ jobs: command: check args: --benches - - name: tests: tokio + - name: tests tokio uses: actions-rs/cargo@v1 with: command: test args: --all --no-default-features --features file-transport,smtp-transport,sendmail-transport,runtime-tokio - - name: tests: tokio with socks5 + - name: tests tokio with socks5 uses: actions-rs/cargo@v1 with: command: test args: --all --no-default-features --features file-transport,smtp-transport,sendmail-transport,runtime-tokio,socks5 - - name: tests: async-std + - name: tests async-std uses: actions-rs/cargo@v1 with: command: test From 7a59b5b0f38861497104b35f88ec287671882e4d Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 22 Jun 2022 22:42:30 +0200 Subject: [PATCH 5/6] fixup --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a305ab1..9b05339 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,19 +42,19 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --all --no-default-features --features file-transport,smtp-transport,sendmail-transport,runtime-tokio + args: --all --no-default-features --features file-transport,smtp-transport,runtime-tokio - name: tests tokio with socks5 uses: actions-rs/cargo@v1 with: command: test - args: --all --no-default-features --features file-transport,smtp-transport,sendmail-transport,runtime-tokio,socks5 + args: --all --no-default-features --features file-transport,smtp-transport,runtime-tokio,socks5 - name: tests async-std uses: actions-rs/cargo@v1 with: command: test - args: --all --no-default-features --features file-transport,smtp-transport,sendmail-transport,runtime-async-std + args: --all --no-default-features --features file-transport,smtp-transport,runtime-async-std check_fmt_and_docs: name: Checking fmt and docs From fd8e0093d7d2338aef28f901100be9a621583780 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 22 Jun 2022 23:28:03 +0200 Subject: [PATCH 6/6] happy clippy --- src/smtp/commands.rs | 24 ++++++++++++------------ src/smtp/extension.rs | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/smtp/commands.rs b/src/smtp/commands.rs index 5a0fdb1..f5eefaa 100644 --- a/src/smtp/commands.rs +++ b/src/smtp/commands.rs @@ -11,7 +11,7 @@ use std::convert::AsRef; use std::fmt::{self, Display, Formatter}; /// EHLO command -#[derive(PartialEq, Clone, Debug)] +#[derive(PartialEq, Eq, Clone, Debug)] #[cfg_attr( feature = "serde-impls", derive(serde_derive::Serialize, serde_derive::Deserialize) @@ -34,7 +34,7 @@ impl EhloCommand { } /// STARTTLS command -#[derive(PartialEq, Clone, Debug, Copy)] +#[derive(PartialEq, Eq, Clone, Debug, Copy)] #[cfg_attr( feature = "serde-impls", derive(serde_derive::Serialize, serde_derive::Deserialize) @@ -48,7 +48,7 @@ impl Display for StarttlsCommand { } /// MAIL command -#[derive(PartialEq, Clone, Debug)] +#[derive(PartialEq, Eq, Clone, Debug)] #[cfg_attr( feature = "serde-impls", derive(serde_derive::Serialize, serde_derive::Deserialize) @@ -80,7 +80,7 @@ impl MailCommand { } /// RCPT command -#[derive(PartialEq, Clone, Debug)] +#[derive(PartialEq, Eq, Clone, Debug)] #[cfg_attr( feature = "serde-impls", derive(serde_derive::Serialize, serde_derive::Deserialize) @@ -111,7 +111,7 @@ impl RcptCommand { } /// DATA command -#[derive(PartialEq, Clone, Debug, Copy)] +#[derive(PartialEq, Eq, Clone, Debug, Copy)] #[cfg_attr( feature = "serde-impls", derive(serde_derive::Serialize, serde_derive::Deserialize) @@ -125,7 +125,7 @@ impl Display for DataCommand { } /// QUIT command -#[derive(PartialEq, Clone, Debug, Copy)] +#[derive(PartialEq, Eq, Clone, Debug, Copy)] #[cfg_attr( feature = "serde-impls", derive(serde_derive::Serialize, serde_derive::Deserialize) @@ -139,7 +139,7 @@ impl Display for QuitCommand { } /// NOOP command -#[derive(PartialEq, Clone, Debug, Copy)] +#[derive(PartialEq, Eq, Clone, Debug, Copy)] #[cfg_attr( feature = "serde-impls", derive(serde_derive::Serialize, serde_derive::Deserialize) @@ -153,7 +153,7 @@ impl Display for NoopCommand { } /// HELP command -#[derive(PartialEq, Clone, Debug)] +#[derive(PartialEq, Eq, Clone, Debug)] #[cfg_attr( feature = "serde-impls", derive(serde_derive::Serialize, serde_derive::Deserialize) @@ -180,7 +180,7 @@ impl HelpCommand { } /// VRFY command -#[derive(PartialEq, Clone, Debug)] +#[derive(PartialEq, Eq, Clone, Debug)] #[cfg_attr( feature = "serde-impls", derive(serde_derive::Serialize, serde_derive::Deserialize) @@ -203,7 +203,7 @@ impl VrfyCommand { } /// EXPN command -#[derive(PartialEq, Clone, Debug)] +#[derive(PartialEq, Eq, Clone, Debug)] #[cfg_attr( feature = "serde-impls", derive(serde_derive::Serialize, serde_derive::Deserialize) @@ -226,7 +226,7 @@ impl ExpnCommand { } /// RSET command -#[derive(PartialEq, Clone, Debug, Copy)] +#[derive(PartialEq, Eq, Clone, Debug, Copy)] #[cfg_attr( feature = "serde-impls", derive(serde_derive::Serialize, serde_derive::Deserialize) @@ -240,7 +240,7 @@ impl Display for RsetCommand { } /// AUTH command -#[derive(PartialEq, Clone, Debug)] +#[derive(PartialEq, Eq, Clone, Debug)] #[cfg_attr( feature = "serde-impls", derive(serde_derive::Serialize, serde_derive::Deserialize) diff --git a/src/smtp/extension.rs b/src/smtp/extension.rs index 51a80c7..c4f18be 100644 --- a/src/smtp/extension.rs +++ b/src/smtp/extension.rs @@ -156,7 +156,7 @@ impl ServerInfo { } let split: Vec<&str> = line.split_whitespace().collect(); - match split.get(0).copied() { + match split.first().copied() { Some("8BITMIME") => { features.insert(Extension::EightBitMime); }