BREAKING CHANGE(smtp-client): Replace the legacy TypeScript SMTP client with a new Rust-based SMTP client and IPC bridge for outbound delivery
This commit is contained in:
160
rust/crates/mailer-smtp/src/client/error.rs
Normal file
160
rust/crates/mailer-smtp/src/client/error.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
//! 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
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user