use std::sync::Arc;
use crate::types::SmtpResponse;
#[non_exhaustive]
#[derive(Debug, Clone, thiserror::Error)]
pub enum Error {
#[error("I/O error: {0}")]
Io(#[source] Arc<std::io::Error>),
#[error("authentication failed: {message}")]
Auth {
message: String,
response: SmtpResponse,
},
#[error("permanent failure ({code}): {message}")]
Permanent {
code: u16,
message: String,
response: SmtpResponse,
},
#[error("transient failure ({code}): {message}")]
Transient {
code: u16,
message: String,
response: SmtpResponse,
},
#[error("protocol error: {0}")]
Protocol(String),
#[error("parse error: {0}")]
Parse(String),
#[error("operation timed out")]
Timeout,
#[error("connection closed")]
Closed,
#[error("STARTTLS not supported by server")]
StartTlsUnavailable,
#[error("all {count} recipients were rejected")]
AllRecipientsFailed {
count: usize,
responses: Vec<SmtpResponse>,
},
#[error(
"message requires SMTPUTF8 but the server does not advertise it \
(RFC 6531 Sections 3.1, 3.3, 3.4)"
)]
SmtpUtf8Required,
}
impl PartialEq for Error {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Io(a), Self::Io(b)) => a.kind() == b.kind(),
(
Self::Auth {
message: m1,
response: r1,
},
Self::Auth {
message: m2,
response: r2,
},
) => m1 == m2 && r1 == r2,
(
Self::Permanent {
code: c1,
message: m1,
response: r1,
},
Self::Permanent {
code: c2,
message: m2,
response: r2,
},
)
| (
Self::Transient {
code: c1,
message: m1,
response: r1,
},
Self::Transient {
code: c2,
message: m2,
response: r2,
},
) => c1 == c2 && m1 == m2 && r1 == r2,
(Self::Protocol(a), Self::Protocol(b)) | (Self::Parse(a), Self::Parse(b)) => a == b,
(Self::Timeout, Self::Timeout)
| (Self::Closed, Self::Closed)
| (Self::StartTlsUnavailable, Self::StartTlsUnavailable)
| (Self::SmtpUtf8Required, Self::SmtpUtf8Required) => true,
(
Self::AllRecipientsFailed {
count: c1,
responses: r1,
},
Self::AllRecipientsFailed {
count: c2,
responses: r2,
},
) => c1 == c2 && r1 == r2,
_ => false,
}
}
}
impl Eq for Error {}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Self::Io(Arc::new(e))
}
}
impl From<crate::types::ValidationError> for Error {
fn from(e: crate::types::ValidationError) -> Self {
Self::Protocol(e.to_string())
}
}
impl Error {
pub fn is_transient(&self) -> bool {
match self {
Self::Transient { .. } | Self::Io(_) | Self::Timeout => true,
Self::Auth { response, .. } => response.is_transient_error(),
_ => false,
}
}
pub fn is_permanent(&self) -> bool {
match self {
Self::Permanent { .. } => true,
Self::Auth { response, .. } => response.is_permanent_error(),
_ => false,
}
}
}
#[cfg(feature = "serde")]
mod serde_support {
use super::{Arc, Error, SmtpResponse};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
fn error_kind_to_str(kind: std::io::ErrorKind) -> &'static str {
match kind {
std::io::ErrorKind::NotFound => "NotFound",
std::io::ErrorKind::PermissionDenied => "PermissionDenied",
std::io::ErrorKind::ConnectionRefused => "ConnectionRefused",
std::io::ErrorKind::ConnectionReset => "ConnectionReset",
std::io::ErrorKind::ConnectionAborted => "ConnectionAborted",
std::io::ErrorKind::NotConnected => "NotConnected",
std::io::ErrorKind::AddrInUse => "AddrInUse",
std::io::ErrorKind::AddrNotAvailable => "AddrNotAvailable",
std::io::ErrorKind::BrokenPipe => "BrokenPipe",
std::io::ErrorKind::AlreadyExists => "AlreadyExists",
std::io::ErrorKind::WouldBlock => "WouldBlock",
std::io::ErrorKind::InvalidInput => "InvalidInput",
std::io::ErrorKind::InvalidData => "InvalidData",
std::io::ErrorKind::TimedOut => "TimedOut",
std::io::ErrorKind::WriteZero => "WriteZero",
std::io::ErrorKind::Interrupted => "Interrupted",
std::io::ErrorKind::Unsupported => "Unsupported",
std::io::ErrorKind::UnexpectedEof => "UnexpectedEof",
std::io::ErrorKind::OutOfMemory => "OutOfMemory",
_ => "Other",
}
}
fn error_kind_from_str(s: &str) -> std::io::ErrorKind {
match s {
"NotFound" => std::io::ErrorKind::NotFound,
"PermissionDenied" => std::io::ErrorKind::PermissionDenied,
"ConnectionRefused" => std::io::ErrorKind::ConnectionRefused,
"ConnectionReset" => std::io::ErrorKind::ConnectionReset,
"ConnectionAborted" => std::io::ErrorKind::ConnectionAborted,
"NotConnected" => std::io::ErrorKind::NotConnected,
"AddrInUse" => std::io::ErrorKind::AddrInUse,
"AddrNotAvailable" => std::io::ErrorKind::AddrNotAvailable,
"BrokenPipe" => std::io::ErrorKind::BrokenPipe,
"AlreadyExists" => std::io::ErrorKind::AlreadyExists,
"WouldBlock" => std::io::ErrorKind::WouldBlock,
"InvalidInput" => std::io::ErrorKind::InvalidInput,
"InvalidData" => std::io::ErrorKind::InvalidData,
"TimedOut" => std::io::ErrorKind::TimedOut,
"WriteZero" => std::io::ErrorKind::WriteZero,
"Interrupted" => std::io::ErrorKind::Interrupted,
"Unsupported" => std::io::ErrorKind::Unsupported,
"UnexpectedEof" => std::io::ErrorKind::UnexpectedEof,
"OutOfMemory" => std::io::ErrorKind::OutOfMemory,
_ => std::io::ErrorKind::Other,
}
}
#[derive(Serialize, Deserialize)]
struct IoFields {
kind: String,
message: String,
}
#[derive(Serialize, Deserialize)]
#[serde(tag = "type", content = "data")]
enum ErrorRepr {
Io(IoFields),
Auth {
message: String,
response: SmtpResponse,
},
Permanent {
code: u16,
message: String,
response: SmtpResponse,
},
Transient {
code: u16,
message: String,
response: SmtpResponse,
},
Protocol {
message: String,
},
Parse {
message: String,
},
Timeout,
Closed,
StartTlsUnavailable,
AllRecipientsFailed {
count: usize,
responses: Vec<SmtpResponse>,
},
SmtpUtf8Required,
}
impl From<&Error> for ErrorRepr {
fn from(err: &Error) -> Self {
match err {
Error::Io(e) => Self::Io(IoFields {
kind: error_kind_to_str(e.kind()).to_owned(),
message: e.to_string(),
}),
Error::Auth { message, response } => Self::Auth {
message: message.clone(),
response: response.clone(),
},
Error::Permanent {
code,
message,
response,
} => Self::Permanent {
code: *code,
message: message.clone(),
response: response.clone(),
},
Error::Transient {
code,
message,
response,
} => Self::Transient {
code: *code,
message: message.clone(),
response: response.clone(),
},
Error::Protocol(msg) => Self::Protocol {
message: msg.clone(),
},
Error::Parse(msg) => Self::Parse {
message: msg.clone(),
},
Error::Timeout => Self::Timeout,
Error::Closed => Self::Closed,
Error::StartTlsUnavailable => Self::StartTlsUnavailable,
Error::AllRecipientsFailed { count, responses } => Self::AllRecipientsFailed {
count: *count,
responses: responses.clone(),
},
Error::SmtpUtf8Required => Self::SmtpUtf8Required,
}
}
}
impl From<ErrorRepr> for Error {
fn from(repr: ErrorRepr) -> Self {
match repr {
ErrorRepr::Io(fields) => {
let kind = error_kind_from_str(&fields.kind);
Self::Io(Arc::new(std::io::Error::new(kind, fields.message)))
}
ErrorRepr::Auth { message, response } => Self::Auth { message, response },
ErrorRepr::Permanent {
code,
message,
response,
} => Self::Permanent {
code,
message,
response,
},
ErrorRepr::Transient {
code,
message,
response,
} => Self::Transient {
code,
message,
response,
},
ErrorRepr::Protocol { message } => Self::Protocol(message),
ErrorRepr::Parse { message } => Self::Parse(message),
ErrorRepr::Timeout => Self::Timeout,
ErrorRepr::Closed => Self::Closed,
ErrorRepr::StartTlsUnavailable => Self::StartTlsUnavailable,
ErrorRepr::AllRecipientsFailed { count, responses } => {
Self::AllRecipientsFailed { count, responses }
}
ErrorRepr::SmtpUtf8Required => Self::SmtpUtf8Required,
}
}
}
impl Serialize for Error {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
ErrorRepr::from(self).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Error {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
ErrorRepr::deserialize(deserializer).map(Self::from)
}
}
}
#[cfg(test)]
#[path = "error_tests.rs"]
mod tests;