use std::sync::Arc;
use crate::types::ResponseCode;
#[non_exhaustive]
#[derive(Debug, Clone, thiserror::Error)]
pub enum Error {
#[error("I/O error: {0}")]
Io(#[source] Arc<std::io::Error>),
#[error("authentication failed: {text}")]
Auth {
text: String,
code: Option<ResponseCode>,
},
#[error("server rejected command: {text}")]
No {
text: String,
code: Option<ResponseCode>,
},
#[error("server reported bad command: {text}")]
Bad {
text: String,
code: Option<ResponseCode>,
},
#[error("server closing connection: {text}")]
Bye {
text: String,
code: Option<ResponseCode>,
},
#[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("missing required capability: {0}")]
MissingCapability(String),
#[error("message size {size} exceeds server APPENDLIMIT of {limit}")]
AppendLimit {
size: u64,
limit: u64,
},
#[error("invalid APPEND date-time: {0}")]
InvalidAppendDate(String),
#[error("internal error: {0}")]
Internal(String),
#[error("driver task panicked: {0}")]
DriverPanicked(String),
#[error("driver task gone")]
DriverGone,
}
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 From<crate::codec::encode::EncodeError> for Error {
fn from(e: crate::codec::encode::EncodeError) -> Self {
match e {
crate::codec::encode::EncodeError::MissingCapability { cmd, cap } => {
Self::MissingCapability(format!("{cmd} requires {cap}"))
}
crate::codec::encode::EncodeError::Validation(msg) => Self::Protocol(msg),
}
}
}
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 { text: t1, code: c1 }, Self::Auth { text: t2, code: c2 })
| (Self::No { text: t1, code: c1 }, Self::No { text: t2, code: c2 })
| (Self::Bad { text: t1, code: c1 }, Self::Bad { text: t2, code: c2 })
| (Self::Bye { text: t1, code: c1 }, Self::Bye { text: t2, code: c2 }) => {
t1 == t2 && c1 == c2
}
(Self::Protocol(a), Self::Protocol(b))
| (Self::Parse(a), Self::Parse(b))
| (Self::MissingCapability(a), Self::MissingCapability(b))
| (Self::InvalidAppendDate(a), Self::InvalidAppendDate(b))
| (Self::Internal(a), Self::Internal(b))
| (Self::DriverPanicked(a), Self::DriverPanicked(b)) => a == b,
(Self::Timeout, Self::Timeout)
| (Self::Closed, Self::Closed)
| (Self::StartTlsUnavailable, Self::StartTlsUnavailable)
| (Self::DriverGone, Self::DriverGone) => true,
(
Self::AppendLimit {
size: s1,
limit: l1,
},
Self::AppendLimit {
size: s2,
limit: l2,
},
) => s1 == s2 && l1 == l2,
_ => false,
}
}
}
impl Eq for Error {}
impl Error {
pub(crate) fn no_with_code(text: String, code: Option<ResponseCode>) -> Self {
Self::No { text, code }
}
pub(crate) fn bad_with_code(text: String, code: Option<ResponseCode>) -> Self {
Self::Bad { text, code }
}
pub(crate) fn auth_with_code(text: String, code: Option<ResponseCode>) -> Self {
Self::Auth { text, code }
}
pub(crate) fn bye_with_code(text: String, code: Option<ResponseCode>) -> Self {
Self::Bye { text, code }
}
}
#[cfg(feature = "serde")]
mod serde_support {
use super::{Arc, Error, ResponseCode};
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 {
text: String,
code: Option<ResponseCode>,
},
No {
text: String,
code: Option<ResponseCode>,
},
Bad {
text: String,
code: Option<ResponseCode>,
},
Bye {
text: String,
code: Option<ResponseCode>,
},
Protocol {
message: String,
},
Parse {
message: String,
},
Timeout,
Closed,
StartTlsUnavailable,
MissingCapability {
capability: String,
},
AppendLimit {
size: u64,
limit: u64,
},
InvalidAppendDate {
date: String,
},
Internal {
message: String,
},
DriverPanicked {
message: String,
},
DriverGone,
}
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 { text, code } => Self::Auth {
text: text.clone(),
code: code.clone(),
},
Error::No { text, code } => Self::No {
text: text.clone(),
code: code.clone(),
},
Error::Bad { text, code } => Self::Bad {
text: text.clone(),
code: code.clone(),
},
Error::Bye { text, code } => Self::Bye {
text: text.clone(),
code: code.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::MissingCapability(cap) => Self::MissingCapability {
capability: cap.clone(),
},
Error::AppendLimit { size, limit } => Self::AppendLimit {
size: *size,
limit: *limit,
},
Error::InvalidAppendDate(msg) => Self::InvalidAppendDate { date: msg.clone() },
Error::Internal(msg) => Self::Internal {
message: msg.clone(),
},
Error::DriverPanicked(msg) => Self::DriverPanicked {
message: msg.clone(),
},
Error::DriverGone => Self::DriverGone,
}
}
}
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 { text, code } => Self::Auth { text, code },
ErrorRepr::No { text, code } => Self::No { text, code },
ErrorRepr::Bad { text, code } => Self::Bad { text, code },
ErrorRepr::Bye { text, code } => Self::Bye { text, code },
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::MissingCapability { capability } => Self::MissingCapability(capability),
ErrorRepr::AppendLimit { size, limit } => Self::AppendLimit { size, limit },
ErrorRepr::InvalidAppendDate { date } => Self::InvalidAppendDate(date),
ErrorRepr::Internal { message } => Self::Internal(message),
ErrorRepr::DriverPanicked { message } => Self::DriverPanicked(message),
ErrorRepr::DriverGone => Self::DriverGone,
}
}
}
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;