feat(transport): Seal file and sendmail error types (#567)

This commit is contained in:
Alexis Mousset
2021-03-14 07:42:52 +00:00
committed by GitHub
parent a681c6b49d
commit 509a623a27
6 changed files with 205 additions and 96 deletions

View File

@@ -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<dyn StdError + Send + Sync>;
#[cfg(test)]
#[cfg(feature = "builder")]

View File

@@ -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<Inner>,
}
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<BoxError>,
}
impl Error {
pub(crate) fn new<E>(kind: Kind, source: Option<E>) -> Error
where
E: Into<BoxError>,
{
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: Into<BoxError>>(e: E) -> Error {
Error::new(Kind::Io, Some(e))
}
#[cfg(feature = "file-transport-envelope")]
pub(crate) fn envelope<E: Into<BoxError>>(e: E) -> Error {
Error::new(Kind::Envelope, Some(e))
}

View File

@@ -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<u8>), 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

View File

@@ -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<Inner>,
}
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<BoxError>,
}
impl Error {
pub(crate) fn new<E>(kind: Kind, source: Option<E>) -> Error
where
E: Into<BoxError>,
{
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: Into<BoxError>>(e: E) -> Error {
Error::new(Kind::Response, Some(e))
}
pub(crate) fn client<E: Into<BoxError>>(e: E) -> Error {
Error::new(Kind::Client, Some(e))
}

View File

@@ -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<Self::Ok, Self::Error> {
// 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<AsyncStd1Executor> {
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<AsyncStd1Executor> {
.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<Tokio02Executor> {
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<Tokio02Executor> {
.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<Tokio1Executor> {
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<Tokio1Executor> {
.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))
}
}
}

View File

@@ -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<Inner>,
}
pub(crate) type BoxError = Box<dyn StdError + Send + Sync>;
struct Inner {
kind: Kind,
source: Option<BoxError>,
@@ -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);