161 lines
4.5 KiB
Rust
161 lines
4.5 KiB
Rust
|
|
//! SMTP client error types.
|
||
|
|
|
||
|
|
use serde::Serialize;
|
||
|
|
|
||
|
|
/// Errors that can occur during SMTP client operations.
|
||
|
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||
|
|
pub enum SmtpClientError {
|
||
|
|
#[error("Connection error: {message}")]
|
||
|
|
ConnectionError { message: String },
|
||
|
|
|
||
|
|
#[error("Timeout: {message}")]
|
||
|
|
TimeoutError { message: String },
|
||
|
|
|
||
|
|
#[error("TLS error: {message}")]
|
||
|
|
TlsError { message: String },
|
||
|
|
|
||
|
|
#[error("Authentication failed: {message}")]
|
||
|
|
AuthenticationError { message: String },
|
||
|
|
|
||
|
|
#[error("Protocol error ({code}): {message}")]
|
||
|
|
ProtocolError { code: u16, message: String },
|
||
|
|
|
||
|
|
#[error("Pool exhausted: {message}")]
|
||
|
|
PoolExhausted { message: String },
|
||
|
|
|
||
|
|
#[error("Invalid configuration: {message}")]
|
||
|
|
ConfigError { message: String },
|
||
|
|
}
|
||
|
|
|
||
|
|
impl SmtpClientError {
|
||
|
|
/// Whether this error is retryable (temporary failure).
|
||
|
|
/// Permanent failures (5xx, auth failures) are not retryable.
|
||
|
|
pub fn is_retryable(&self) -> bool {
|
||
|
|
match self {
|
||
|
|
SmtpClientError::ConnectionError { .. } => true,
|
||
|
|
SmtpClientError::TimeoutError { .. } => true,
|
||
|
|
SmtpClientError::TlsError { .. } => false,
|
||
|
|
SmtpClientError::AuthenticationError { .. } => false,
|
||
|
|
SmtpClientError::ProtocolError { code, .. } => *code >= 400 && *code < 500,
|
||
|
|
SmtpClientError::PoolExhausted { .. } => true,
|
||
|
|
SmtpClientError::ConfigError { .. } => false,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// The error type as a string for IPC serialization.
|
||
|
|
pub fn error_type(&self) -> &'static str {
|
||
|
|
match self {
|
||
|
|
SmtpClientError::ConnectionError { .. } => "connection",
|
||
|
|
SmtpClientError::TimeoutError { .. } => "timeout",
|
||
|
|
SmtpClientError::TlsError { .. } => "tls",
|
||
|
|
SmtpClientError::AuthenticationError { .. } => "authentication",
|
||
|
|
SmtpClientError::ProtocolError { .. } => "protocol",
|
||
|
|
SmtpClientError::PoolExhausted { .. } => "pool_exhausted",
|
||
|
|
SmtpClientError::ConfigError { .. } => "config",
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// The SMTP code if this is a protocol error.
|
||
|
|
pub fn smtp_code(&self) -> Option<u16> {
|
||
|
|
match self {
|
||
|
|
SmtpClientError::ProtocolError { code, .. } => Some(*code),
|
||
|
|
_ => None,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(test)]
|
||
|
|
mod tests {
|
||
|
|
use super::*;
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_retryable_errors() {
|
||
|
|
assert!(SmtpClientError::ConnectionError {
|
||
|
|
message: "refused".into()
|
||
|
|
}
|
||
|
|
.is_retryable());
|
||
|
|
assert!(SmtpClientError::TimeoutError {
|
||
|
|
message: "timed out".into()
|
||
|
|
}
|
||
|
|
.is_retryable());
|
||
|
|
assert!(SmtpClientError::PoolExhausted {
|
||
|
|
message: "full".into()
|
||
|
|
}
|
||
|
|
.is_retryable());
|
||
|
|
assert!(SmtpClientError::ProtocolError {
|
||
|
|
code: 421,
|
||
|
|
message: "try later".into()
|
||
|
|
}
|
||
|
|
.is_retryable());
|
||
|
|
assert!(SmtpClientError::ProtocolError {
|
||
|
|
code: 450,
|
||
|
|
message: "mailbox busy".into()
|
||
|
|
}
|
||
|
|
.is_retryable());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_non_retryable_errors() {
|
||
|
|
assert!(!SmtpClientError::AuthenticationError {
|
||
|
|
message: "bad creds".into()
|
||
|
|
}
|
||
|
|
.is_retryable());
|
||
|
|
assert!(!SmtpClientError::TlsError {
|
||
|
|
message: "cert invalid".into()
|
||
|
|
}
|
||
|
|
.is_retryable());
|
||
|
|
assert!(!SmtpClientError::ProtocolError {
|
||
|
|
code: 550,
|
||
|
|
message: "no such user".into()
|
||
|
|
}
|
||
|
|
.is_retryable());
|
||
|
|
assert!(!SmtpClientError::ProtocolError {
|
||
|
|
code: 554,
|
||
|
|
message: "rejected".into()
|
||
|
|
}
|
||
|
|
.is_retryable());
|
||
|
|
assert!(!SmtpClientError::ConfigError {
|
||
|
|
message: "bad config".into()
|
||
|
|
}
|
||
|
|
.is_retryable());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_error_type_strings() {
|
||
|
|
assert_eq!(
|
||
|
|
SmtpClientError::ConnectionError {
|
||
|
|
message: "x".into()
|
||
|
|
}
|
||
|
|
.error_type(),
|
||
|
|
"connection"
|
||
|
|
);
|
||
|
|
assert_eq!(
|
||
|
|
SmtpClientError::ProtocolError {
|
||
|
|
code: 550,
|
||
|
|
message: "x".into()
|
||
|
|
}
|
||
|
|
.error_type(),
|
||
|
|
"protocol"
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn test_smtp_code() {
|
||
|
|
assert_eq!(
|
||
|
|
SmtpClientError::ProtocolError {
|
||
|
|
code: 550,
|
||
|
|
message: "x".into()
|
||
|
|
}
|
||
|
|
.smtp_code(),
|
||
|
|
Some(550)
|
||
|
|
);
|
||
|
|
assert_eq!(
|
||
|
|
SmtpClientError::ConnectionError {
|
||
|
|
message: "x".into()
|
||
|
|
}
|
||
|
|
.smtp_code(),
|
||
|
|
None
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|