From 509a623a275354feb712dfa58641105cdae81fac Mon Sep 17 00:00:00 2001 From: Alexis Mousset Date: Sun, 14 Mar 2021 07:42:52 +0000 Subject: [PATCH] feat(transport): Seal file and sendmail error types (#567) --- src/lib.rs | 3 + src/transport/file/error.rs | 108 ++++++++++++++++++++++++-------- src/transport/file/mod.rs | 24 +++---- src/transport/sendmail/error.rs | 106 +++++++++++++++++++++++-------- src/transport/sendmail/mod.rs | 44 ++++++------- src/transport/smtp/error.rs | 16 ++--- 6 files changed, 205 insertions(+), 96 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8045a93..d10f9b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,6 +102,9 @@ use crate::{address::Envelope, error::Error}; #[cfg(feature = "smtp-transport")] pub use crate::transport::smtp::SmtpTransport; +use std::error::Error as StdError; + +pub(crate) type BoxError = Box; #[cfg(test)] #[cfg(feature = "builder")] diff --git a/src/transport/file/error.rs b/src/transport/file/error.rs index d83de8f..cd84e9e 100644 --- a/src/transport/file/error.rs +++ b/src/transport/file/error.rs @@ -1,38 +1,96 @@ //! Error and result type for file transport -use self::Error::*; -use std::{ - error::Error as StdError, - fmt::{self, Display, Formatter}, - io, -}; +use crate::BoxError; +use std::{error::Error as StdError, fmt}; -/// An enum of all error kinds. -#[derive(Debug)] -pub enum Error { - /// IO error - Io(io::Error), - /// JSON error - #[cfg(feature = "file-transport-envelope")] - Json(serde_json::Error), +/// The Errors that may occur when sending an email over SMTP +pub struct Error { + inner: Box, } -impl Display for Error { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> { - match &self { - Io(err) => err.fmt(fmt), - #[cfg(feature = "file-transport-envelope")] - Json(err) => err.fmt(fmt), +struct Inner { + kind: Kind, + source: Option, +} + +impl Error { + pub(crate) fn new(kind: Kind, source: Option) -> Error + where + E: Into, + { + Error { + inner: Box::new(Inner { + kind, + source: source.map(Into::into), + }), } } + + /// Returns true if the error is a file I/O error + pub fn is_io(&self) -> bool { + matches!(self.inner.kind, Kind::Io) + } + + /// Returns true if the error is an envelope serialization or deserialization error + #[cfg(feature = "file-transport-envelope")] + pub fn is_envelope(&self) -> bool { + matches!(self.inner.kind, Kind::Envelope) + } +} + +#[derive(Debug)] +pub(crate) enum Kind { + /// File I/O error + Io, + /// Envelope serialization/deserialization error + #[cfg(feature = "file-transport-envelope")] + Envelope, +} + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut builder = f.debug_struct("lettre::transport::file::Error"); + + builder.field("kind", &self.inner.kind); + + if let Some(ref source) = self.inner.source { + builder.field("source", source); + } + + builder.finish() + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.inner.kind { + Kind::Io => f.write_str("response error")?, + #[cfg(feature = "file-transport-envelope")] + Kind::Envelope => f.write_str("internal client error")?, + }; + + if let Some(ref e) = self.inner.source { + write!(f, ": {}", e)?; + } + + Ok(()) + } } impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { - match &self { - Io(err) => Some(&*err), - #[cfg(feature = "file-transport-envelope")] - Json(err) => Some(&*err), - } + self.inner.source.as_ref().map(|e| { + let r: &(dyn std::error::Error + 'static) = &**e; + r + }) } } + +pub(crate) fn io>(e: E) -> Error { + Error::new(Kind::Io, Some(e)) +} + +#[cfg(feature = "file-transport-envelope")] +pub(crate) fn envelope>(e: E) -> Error { + Error::new(Kind::Envelope, Some(e)) +} diff --git a/src/transport/file/mod.rs b/src/transport/file/mod.rs index ab05f59..5377503 100644 --- a/src/transport/file/mod.rs +++ b/src/transport/file/mod.rs @@ -206,11 +206,11 @@ impl FileTransport { use std::fs; let eml_file = self.path.join(format!("{}.eml", email_id)); - let eml = fs::read(eml_file).map_err(Error::Io)?; + let eml = fs::read(eml_file).map_err(error::io)?; let json_file = self.path.join(format!("{}.json", email_id)); - let json = fs::read(&json_file).map_err(Error::Io)?; - let envelope = serde_json::from_slice(&json).map_err(Error::Json)?; + let json = fs::read(&json_file).map_err(error::io)?; + let envelope = serde_json::from_slice(&json).map_err(error::envelope)?; Ok((envelope, eml)) } @@ -253,11 +253,11 @@ where #[cfg(feature = "file-transport-envelope")] pub async fn read(&self, email_id: &str) -> Result<(Envelope, Vec), Error> { let eml_file = self.inner.path.join(format!("{}.eml", email_id)); - let eml = E::fs_read(&eml_file).await.map_err(Error::Io)?; + let eml = E::fs_read(&eml_file).await.map_err(error::io)?; let json_file = self.inner.path.join(format!("{}.json", email_id)); - let json = E::fs_read(&json_file).await.map_err(Error::Io)?; - let envelope = serde_json::from_slice(&json).map_err(Error::Json)?; + let json = E::fs_read(&json_file).await.map_err(error::io)?; + let envelope = serde_json::from_slice(&json).map_err(error::envelope)?; Ok((envelope, eml)) } @@ -273,14 +273,14 @@ impl Transport for FileTransport { let email_id = Uuid::new_v4(); let file = self.path(&email_id, "eml"); - fs::write(file, email).map_err(Error::Io)?; + fs::write(file, email).map_err(error::io)?; #[cfg(feature = "file-transport-envelope")] { if self.save_envelope { let file = self.path(&email_id, "json"); - let buf = serde_json::to_string(&envelope).map_err(Error::Json)?; - fs::write(file, buf).map_err(Error::Io)?; + let buf = serde_json::to_string(&envelope).map_err(error::envelope)?; + fs::write(file, buf).map_err(error::io)?; } } // use envelope anyway @@ -303,14 +303,14 @@ where let email_id = Uuid::new_v4(); let file = self.inner.path(&email_id, "eml"); - E::fs_write(&file, email).await.map_err(Error::Io)?; + E::fs_write(&file, email).await.map_err(error::io)?; #[cfg(feature = "file-transport-envelope")] { if self.inner.save_envelope { let file = self.inner.path(&email_id, "json"); - let buf = serde_json::to_vec(&envelope).map_err(Error::Json)?; - E::fs_write(&file, &buf).await.map_err(Error::Io)?; + let buf = serde_json::to_vec(&envelope).map_err(error::envelope)?; + E::fs_write(&file, &buf).await.map_err(error::io)?; } } // use envelope anyway diff --git a/src/transport/sendmail/error.rs b/src/transport/sendmail/error.rs index 2ac4856..a2d80ee 100644 --- a/src/transport/sendmail/error.rs +++ b/src/transport/sendmail/error.rs @@ -1,40 +1,92 @@ //! Error and result type for sendmail transport -use self::Error::*; -use std::{ - error::Error as StdError, - fmt::{self, Display, Formatter}, - io, - string::FromUtf8Error, -}; +use crate::BoxError; +use std::{error::Error as StdError, fmt}; -/// An enum of all error kinds. -#[derive(Debug)] -pub enum Error { - /// Internal client error - Client(String), - /// Error parsing UTF8 in response - Utf8Parsing(FromUtf8Error), - /// IO error - Io(io::Error), +/// The Errors that may occur when sending an email over sendmail +pub struct Error { + inner: Box, } -impl Display for Error { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> { - match &self { - Client(err) => err.fmt(fmt), - Utf8Parsing(err) => err.fmt(fmt), - Io(err) => err.fmt(fmt), +struct Inner { + kind: Kind, + source: Option, +} + +impl Error { + pub(crate) fn new(kind: Kind, source: Option) -> Error + where + E: Into, + { + Error { + inner: Box::new(Inner { + kind, + source: source.map(Into::into), + }), } } + + /// Returns true if the error is from client + pub fn is_client(&self) -> bool { + matches!(self.inner.kind, Kind::Client) + } + + /// Returns true if the error comes from the response + pub fn is_response(&self) -> bool { + matches!(self.inner.kind, Kind::Response) + } +} + +#[derive(Debug)] +pub(crate) enum Kind { + /// Error parsing a response + Response, + /// Internal client error + Client, +} + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut builder = f.debug_struct("lettre::transport::sendmail::Error"); + + builder.field("kind", &self.inner.kind); + + if let Some(ref source) = self.inner.source { + builder.field("source", source); + } + + builder.finish() + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.inner.kind { + Kind::Response => f.write_str("response error")?, + Kind::Client => f.write_str("internal client error")?, + }; + + if let Some(ref e) = self.inner.source { + write!(f, ": {}", e)?; + } + + Ok(()) + } } impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { - match &self { - Io(err) => Some(&*err), - Utf8Parsing(err) => Some(&*err), - _ => None, - } + self.inner.source.as_ref().map(|e| { + let r: &(dyn std::error::Error + 'static) = &**e; + r + }) } } + +pub(crate) fn response>(e: E) -> Error { + Error::new(Kind::Response, Some(e)) +} + +pub(crate) fn client>(e: E) -> Error { + Error::new(Kind::Client, Some(e)) +} diff --git a/src/transport/sendmail/mod.rs b/src/transport/sendmail/mod.rs index 601c8fa..d2d9ebc 100644 --- a/src/transport/sendmail/mod.rs +++ b/src/transport/sendmail/mod.rs @@ -117,7 +117,7 @@ use std::{ mod error; -const DEFAUT_SENDMAIL: &str = "/usr/sbin/sendmail"; +const DEFAULT_SENDMAIL: &str = "/usr/sbin/sendmail"; /// Sends emails using the `sendmail` command #[derive(Debug, Clone)] @@ -144,7 +144,7 @@ impl SendmailTransport { /// Creates a new transport with the default `/usr/sbin/sendmail` command pub fn new() -> SendmailTransport { SendmailTransport { - command: DEFAUT_SENDMAIL.into(), + command: DEFAULT_SENDMAIL.into(), } } @@ -269,21 +269,21 @@ impl Transport for SendmailTransport { fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result { // Spawn the sendmail command - let mut process = self.command(envelope).spawn().map_err(Error::Io)?; + let mut process = self.command(envelope).spawn().map_err(error::client)?; process .stdin .as_mut() .unwrap() .write_all(email) - .map_err(Error::Io)?; - let output = process.wait_with_output().map_err(Error::Io)?; + .map_err(error::client)?; + let output = process.wait_with_output().map_err(error::client)?; if output.status.success() { Ok(()) } else { - let stderr = String::from_utf8(output.stderr).map_err(Error::Utf8Parsing)?; - Err(Error::Client(stderr)) + let stderr = String::from_utf8(output.stderr).map_err(error::response)?; + Err(error::client(stderr)) } } } @@ -300,7 +300,7 @@ impl AsyncTransport for AsyncSendmailTransport { let mut command = self.async_std_command(envelope); // Spawn the sendmail command - let mut process = command.spawn().map_err(Error::Io)?; + let mut process = command.spawn().map_err(error::client)?; process .stdin @@ -308,14 +308,14 @@ impl AsyncTransport for AsyncSendmailTransport { .unwrap() .write_all(&email) .await - .map_err(Error::Io)?; - let output = process.output().await.map_err(Error::Io)?; + .map_err(error::client)?; + let output = process.output().await.map_err(error::client)?; if output.status.success() { Ok(()) } else { - let stderr = String::from_utf8(output.stderr).map_err(Error::Utf8Parsing)?; - Err(Error::Client(stderr)) + let stderr = String::from_utf8(output.stderr).map_err(error::response)?; + Err(error::client(stderr)) } } } @@ -332,7 +332,7 @@ impl AsyncTransport for AsyncSendmailTransport { let mut command = self.tokio02_command(envelope); // Spawn the sendmail command - let mut process = command.spawn().map_err(Error::Io)?; + let mut process = command.spawn().map_err(error::client)?; process .stdin @@ -340,14 +340,14 @@ impl AsyncTransport for AsyncSendmailTransport { .unwrap() .write_all(&email) .await - .map_err(Error::Io)?; - let output = process.wait_with_output().await.map_err(Error::Io)?; + .map_err(error::client)?; + let output = process.wait_with_output().await.map_err(error::client)?; if output.status.success() { Ok(()) } else { - let stderr = String::from_utf8(output.stderr).map_err(Error::Utf8Parsing)?; - Err(Error::Client(stderr)) + let stderr = String::from_utf8(output.stderr).map_err(error::response)?; + Err(error::client(stderr)) } } } @@ -364,7 +364,7 @@ impl AsyncTransport for AsyncSendmailTransport { let mut command = self.tokio1_command(envelope); // Spawn the sendmail command - let mut process = command.spawn().map_err(Error::Io)?; + let mut process = command.spawn().map_err(error::client)?; process .stdin @@ -372,14 +372,14 @@ impl AsyncTransport for AsyncSendmailTransport { .unwrap() .write_all(&email) .await - .map_err(Error::Io)?; - let output = process.wait_with_output().await.map_err(Error::Io)?; + .map_err(error::client)?; + let output = process.wait_with_output().await.map_err(error::client)?; if output.status.success() { Ok(()) } else { - let stderr = String::from_utf8(output.stderr).map_err(Error::Utf8Parsing)?; - Err(Error::Client(stderr)) + let stderr = String::from_utf8(output.stderr).map_err(error::response)?; + Err(error::client(stderr)) } } } diff --git a/src/transport/smtp/error.rs b/src/transport/smtp/error.rs index 10c4aa6..d8b79c3 100644 --- a/src/transport/smtp/error.rs +++ b/src/transport/smtp/error.rs @@ -1,7 +1,10 @@ //! Error and result type for SMTP clients -use crate::transport::smtp::response::{Code, Severity}; -use std::{error::Error as StdError, fmt, io}; +use crate::{ + transport::smtp::response::{Code, Severity}, + BoxError, +}; +use std::{error::Error as StdError, fmt}; // Inspired by https://github.com/seanmonstar/reqwest/blob/a8566383168c0ef06c21f38cbc9213af6ff6db31/src/error.rs @@ -10,8 +13,6 @@ pub struct Error { inner: Box, } -pub(crate) type BoxError = Box; - struct Inner { kind: Kind, source: Option, @@ -80,11 +81,6 @@ impl Error { _ => None, } } - - #[allow(unused)] - pub(crate) fn into_io(self) -> io::Error { - io::Error::new(io::ErrorKind::Other, self) - } } #[derive(Debug)] @@ -113,7 +109,7 @@ pub(crate) enum Kind { impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut builder = f.debug_struct("lettre::Error"); + let mut builder = f.debug_struct("lettre::transport::smtp::Error"); builder.field("kind", &self.inner.kind);