use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EnhancedStatusCode {
pub class: u8,
pub subject: u8,
pub detail: u8,
}
impl EnhancedStatusCode {
pub const fn new(class: u8, subject: u8, detail: u8) -> Self {
Self {
class,
subject,
detail,
}
}
pub fn parse(s: &str) -> Option<Self> {
let parts: Vec<&str> = s.split('.').collect();
if parts.len() != 3 {
return None;
}
let class = parts[0].parse().ok()?;
let subject = parts[1].parse().ok()?;
let detail = parts[2].parse().ok()?;
Some(Self::new(class, subject, detail))
}
pub fn is_success(&self) -> bool {
self.class == 2
}
pub fn is_transient(&self) -> bool {
self.class == 4
}
pub fn is_permanent(&self) -> bool {
self.class == 5
}
}
impl fmt::Display for EnhancedStatusCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.class, self.subject, self.detail)
}
}
#[allow(dead_code)]
impl EnhancedStatusCode {
pub const SUCCESS: Self = Self::new(2, 0, 0);
pub const BAD_DESTINATION_MAILBOX: Self = Self::new(5, 1, 1); pub const BAD_DESTINATION_SYSTEM: Self = Self::new(5, 1, 2); pub const BAD_DESTINATION_SYNTAX: Self = Self::new(5, 1, 3); pub const DESTINATION_AMBIGUOUS: Self = Self::new(5, 1, 4); pub const DESTINATION_VALID: Self = Self::new(2, 1, 5); pub const MAILBOX_MOVED: Self = Self::new(5, 1, 6); pub const BAD_SENDER_ADDRESS: Self = Self::new(5, 1, 7); pub const BAD_SENDER_SYSTEM: Self = Self::new(5, 1, 8);
pub const MAILBOX_DISABLED: Self = Self::new(5, 2, 1); pub const MAILBOX_FULL: Self = Self::new(5, 2, 2); pub const MAILBOX_FULL_TEMP: Self = Self::new(4, 2, 2); pub const MESSAGE_TOO_LARGE: Self = Self::new(5, 2, 3); pub const MAILING_LIST_EXPANSION: Self = Self::new(5, 2, 4);
pub const SYSTEM_FULL: Self = Self::new(4, 3, 1); pub const SYSTEM_NOT_ACCEPTING: Self = Self::new(4, 3, 2); pub const SYSTEM_CAPABILITY: Self = Self::new(5, 3, 3); pub const MESSAGE_TOO_BIG: Self = Self::new(5, 3, 4); pub const SYSTEM_INCORRECTLY_CONFIGURED: Self = Self::new(5, 3, 5);
pub const NO_ANSWER: Self = Self::new(4, 4, 1); pub const CONNECTION_DROPPED: Self = Self::new(4, 4, 2); pub const ROUTING_SERVER_FAILURE: Self = Self::new(4, 4, 3); pub const NETWORK_CONGESTION: Self = Self::new(4, 4, 5); pub const ROUTING_LOOP: Self = Self::new(5, 4, 6); pub const DELIVERY_TIME_EXPIRED: Self = Self::new(4, 4, 7);
pub const INVALID_COMMAND: Self = Self::new(5, 5, 1); pub const SYNTAX_ERROR: Self = Self::new(5, 5, 2); pub const TOO_MANY_RECIPIENTS: Self = Self::new(5, 5, 3); pub const INVALID_PARAMETERS: Self = Self::new(5, 5, 4); pub const WRONG_PROTOCOL: Self = Self::new(5, 5, 5);
pub const MEDIA_NOT_SUPPORTED: Self = Self::new(5, 6, 1); pub const CONVERSION_REQUIRED: Self = Self::new(5, 6, 2); pub const CONVERSION_NOT_POSSIBLE: Self = Self::new(5, 6, 3); pub const CONVERSION_LOST: Self = Self::new(5, 6, 4); pub const CONVERSION_FAILED: Self = Self::new(5, 6, 5);
pub const DELIVERY_NOT_AUTHORIZED: Self = Self::new(5, 7, 1); pub const MAILING_LIST_EXPANSION_PROHIBITED: Self = Self::new(5, 7, 2); pub const SECURITY_CONVERSION_REQUIRED: Self = Self::new(5, 7, 3); pub const SECURITY_FEATURES_NOT_SUPPORTED: Self = Self::new(5, 7, 4); pub const CRYPTOGRAPHIC_FAILURE: Self = Self::new(5, 7, 5); pub const CRYPTOGRAPHIC_ALGORITHM_NOT_SUPPORTED: Self = Self::new(5, 7, 6); pub const MESSAGE_INTEGRITY_FAILURE: Self = Self::new(5, 7, 7); pub const AUTHENTICATION_CREDENTIALS_INVALID: Self = Self::new(5, 7, 8); pub const AUTHENTICATION_MECHANISM_TOO_WEAK: Self = Self::new(5, 7, 9); pub const ENCRYPTION_NEEDED: Self = Self::new(5, 7, 11); pub const SENDER_ADDRESS_INVALID: Self = Self::new(5, 7, 12); pub const MESSAGE_REFUSED: Self = Self::new(5, 7, 13); pub const TRUST_RELATIONSHIP_REQUIRED: Self = Self::new(5, 7, 14); pub const PRIORITY_TOO_LOW: Self = Self::new(5, 7, 15); pub const MESSAGE_TOO_BIG_FOR_POLICY: Self = Self::new(5, 7, 17); pub const MAILBOX_OWNER_CHANGED: Self = Self::new(5, 7, 18); pub const RRVS_CANNOT_VALIDATE: Self = Self::new(5, 7, 19); }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FailureReason {
UserUnknown,
QuotaExceeded,
MessageTooLarge,
ContentRejected,
RelayDenied,
TemporaryFailure,
NetworkUnreachable,
ConnectionTimeout,
InvalidAddress,
MailboxDisabled,
SystemNotAccepting,
AuthenticationRequired,
SpamDetected,
VirusDetected,
Other,
}
impl FailureReason {
pub fn enhanced_code(&self) -> EnhancedStatusCode {
match self {
Self::UserUnknown => EnhancedStatusCode::BAD_DESTINATION_MAILBOX,
Self::QuotaExceeded => EnhancedStatusCode::MAILBOX_FULL,
Self::MessageTooLarge => EnhancedStatusCode::MESSAGE_TOO_LARGE,
Self::ContentRejected => EnhancedStatusCode::MESSAGE_REFUSED,
Self::RelayDenied => EnhancedStatusCode::DELIVERY_NOT_AUTHORIZED,
Self::TemporaryFailure => EnhancedStatusCode::new(4, 0, 0),
Self::NetworkUnreachable => EnhancedStatusCode::NO_ANSWER,
Self::ConnectionTimeout => EnhancedStatusCode::CONNECTION_DROPPED,
Self::InvalidAddress => EnhancedStatusCode::BAD_DESTINATION_SYNTAX,
Self::MailboxDisabled => EnhancedStatusCode::MAILBOX_DISABLED,
Self::SystemNotAccepting => EnhancedStatusCode::SYSTEM_NOT_ACCEPTING,
Self::AuthenticationRequired => EnhancedStatusCode::DELIVERY_NOT_AUTHORIZED,
Self::SpamDetected => EnhancedStatusCode::MESSAGE_REFUSED,
Self::VirusDetected => EnhancedStatusCode::MESSAGE_REFUSED,
Self::Other => EnhancedStatusCode::new(5, 0, 0),
}
}
pub fn description(&self) -> &'static str {
match self {
Self::UserUnknown => "The recipient's email address does not exist",
Self::QuotaExceeded => "The recipient's mailbox is full",
Self::MessageTooLarge => "The message is too large to be delivered",
Self::ContentRejected => "The message content was rejected by policy",
Self::RelayDenied => "Relay access denied",
Self::TemporaryFailure => "Temporary failure, will retry delivery",
Self::NetworkUnreachable => "The destination mail server could not be reached",
Self::ConnectionTimeout => "Connection to the mail server timed out",
Self::InvalidAddress => "The recipient's email address is invalid",
Self::MailboxDisabled => "The recipient's mailbox is disabled",
Self::SystemNotAccepting => "The mail system is not accepting messages",
Self::AuthenticationRequired => "Authentication is required for this delivery",
Self::SpamDetected => "The message was identified as spam",
Self::VirusDetected => "The message contains a virus",
Self::Other => "An unknown error occurred",
}
}
pub fn is_permanent(&self) -> bool {
matches!(
self,
Self::UserUnknown
| Self::QuotaExceeded
| Self::MessageTooLarge
| Self::ContentRejected
| Self::RelayDenied
| Self::InvalidAddress
| Self::MailboxDisabled
| Self::SpamDetected
| Self::VirusDetected
)
}
pub fn from_smtp_code(code: u16) -> Self {
match code {
421 => Self::ConnectionTimeout,
450 => Self::TemporaryFailure,
451 => Self::TemporaryFailure,
452 => Self::QuotaExceeded,
550 => Self::UserUnknown,
551 => Self::RelayDenied,
552 => Self::QuotaExceeded,
553 => Self::InvalidAddress,
554 => Self::ContentRejected,
_ if (400..500).contains(&code) => Self::TemporaryFailure,
_ => Self::Other,
}
}
}
impl fmt::Display for FailureReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description())
}
}
pub fn smtp_to_enhanced_code(smtp_code: u16) -> EnhancedStatusCode {
match smtp_code {
250 => EnhancedStatusCode::SUCCESS,
251 => EnhancedStatusCode::DESTINATION_VALID,
421 => EnhancedStatusCode::CONNECTION_DROPPED,
450 => EnhancedStatusCode::new(4, 2, 1), 451 => EnhancedStatusCode::new(4, 3, 0), 452 => EnhancedStatusCode::MAILBOX_FULL_TEMP,
454 => EnhancedStatusCode::new(4, 7, 0),
500 => EnhancedStatusCode::SYNTAX_ERROR,
501 => EnhancedStatusCode::INVALID_PARAMETERS,
502 => EnhancedStatusCode::INVALID_COMMAND,
503 => EnhancedStatusCode::INVALID_COMMAND,
504 => EnhancedStatusCode::INVALID_PARAMETERS,
550 => EnhancedStatusCode::BAD_DESTINATION_MAILBOX,
551 => EnhancedStatusCode::MAILBOX_MOVED,
552 => EnhancedStatusCode::MAILBOX_FULL,
553 => EnhancedStatusCode::BAD_DESTINATION_SYNTAX,
554 => EnhancedStatusCode::DELIVERY_NOT_AUTHORIZED,
_ if (200..300).contains(&smtp_code) => EnhancedStatusCode::SUCCESS,
_ if (400..500).contains(&smtp_code) => EnhancedStatusCode::new(4, 0, 0),
_ if (500..600).contains(&smtp_code) => EnhancedStatusCode::new(5, 0, 0),
_ => EnhancedStatusCode::new(5, 0, 0),
}
}
pub fn smtp_diagnostic_text(smtp_code: u16) -> &'static str {
match smtp_code {
421 => "Service not available, closing transmission channel",
450 => "Requested mail action not taken: mailbox unavailable",
451 => "Requested action aborted: local error in processing",
452 => "Requested action not taken: insufficient system storage",
454 => "Temporary authentication failure",
500 => "Syntax error, command unrecognized",
501 => "Syntax error in parameters or arguments",
502 => "Command not implemented",
503 => "Bad sequence of commands",
504 => "Command parameter not implemented",
550 => "Requested action not taken: mailbox unavailable",
551 => "User not local; please try forward path",
552 => "Requested mail action aborted: exceeded storage allocation",
553 => "Requested action not taken: mailbox name not allowed",
554 => "Transaction failed",
_ => "Unknown error",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_enhanced_status_code_display() {
let code = EnhancedStatusCode::new(5, 1, 1);
assert_eq!(code.to_string(), "5.1.1");
}
#[test]
fn test_enhanced_status_code_parse() {
let code = EnhancedStatusCode::parse("5.1.1").unwrap();
assert_eq!(code.class, 5);
assert_eq!(code.subject, 1);
assert_eq!(code.detail, 1);
}
#[test]
fn test_enhanced_status_code_parse_invalid() {
assert!(EnhancedStatusCode::parse("5.1").is_none());
assert!(EnhancedStatusCode::parse("5.1.1.1").is_none());
assert!(EnhancedStatusCode::parse("abc").is_none());
}
#[test]
fn test_enhanced_status_code_is_success() {
assert!(EnhancedStatusCode::new(2, 0, 0).is_success());
assert!(!EnhancedStatusCode::new(4, 0, 0).is_success());
assert!(!EnhancedStatusCode::new(5, 0, 0).is_success());
}
#[test]
fn test_enhanced_status_code_is_transient() {
assert!(!EnhancedStatusCode::new(2, 0, 0).is_transient());
assert!(EnhancedStatusCode::new(4, 0, 0).is_transient());
assert!(!EnhancedStatusCode::new(5, 0, 0).is_transient());
}
#[test]
fn test_enhanced_status_code_is_permanent() {
assert!(!EnhancedStatusCode::new(2, 0, 0).is_permanent());
assert!(!EnhancedStatusCode::new(4, 0, 0).is_permanent());
assert!(EnhancedStatusCode::new(5, 0, 0).is_permanent());
}
#[test]
fn test_failure_reason_enhanced_code() {
assert_eq!(
FailureReason::UserUnknown.enhanced_code(),
EnhancedStatusCode::BAD_DESTINATION_MAILBOX
);
assert_eq!(
FailureReason::QuotaExceeded.enhanced_code(),
EnhancedStatusCode::MAILBOX_FULL
);
assert_eq!(
FailureReason::MessageTooLarge.enhanced_code(),
EnhancedStatusCode::MESSAGE_TOO_LARGE
);
}
#[test]
fn test_failure_reason_is_permanent() {
assert!(FailureReason::UserUnknown.is_permanent());
assert!(!FailureReason::TemporaryFailure.is_permanent());
assert!(FailureReason::InvalidAddress.is_permanent());
}
#[test]
fn test_failure_reason_from_smtp_code() {
assert_eq!(
FailureReason::from_smtp_code(550),
FailureReason::UserUnknown
);
assert_eq!(
FailureReason::from_smtp_code(452),
FailureReason::QuotaExceeded
);
assert_eq!(
FailureReason::from_smtp_code(421),
FailureReason::ConnectionTimeout
);
}
#[test]
fn test_smtp_to_enhanced_code_421() {
assert_eq!(
smtp_to_enhanced_code(421),
EnhancedStatusCode::CONNECTION_DROPPED
);
}
#[test]
fn test_smtp_to_enhanced_code_450() {
let code = smtp_to_enhanced_code(450);
assert_eq!(code.class, 4);
assert_eq!(code.subject, 2);
assert_eq!(code.detail, 1);
}
#[test]
fn test_smtp_to_enhanced_code_451() {
let code = smtp_to_enhanced_code(451);
assert_eq!(code.class, 4);
assert_eq!(code.subject, 3);
assert_eq!(code.detail, 0);
}
#[test]
fn test_smtp_to_enhanced_code_452() {
assert_eq!(
smtp_to_enhanced_code(452),
EnhancedStatusCode::MAILBOX_FULL_TEMP
);
}
#[test]
fn test_smtp_to_enhanced_code_550() {
assert_eq!(
smtp_to_enhanced_code(550),
EnhancedStatusCode::BAD_DESTINATION_MAILBOX
);
}
#[test]
fn test_smtp_to_enhanced_code_551() {
assert_eq!(
smtp_to_enhanced_code(551),
EnhancedStatusCode::MAILBOX_MOVED
);
}
#[test]
fn test_smtp_to_enhanced_code_552() {
assert_eq!(smtp_to_enhanced_code(552), EnhancedStatusCode::MAILBOX_FULL);
}
#[test]
fn test_smtp_to_enhanced_code_553() {
assert_eq!(
smtp_to_enhanced_code(553),
EnhancedStatusCode::BAD_DESTINATION_SYNTAX
);
}
#[test]
fn test_smtp_to_enhanced_code_554() {
assert_eq!(
smtp_to_enhanced_code(554),
EnhancedStatusCode::DELIVERY_NOT_AUTHORIZED
);
}
#[test]
fn test_smtp_diagnostic_text() {
assert_eq!(
smtp_diagnostic_text(421),
"Service not available, closing transmission channel"
);
assert_eq!(
smtp_diagnostic_text(550),
"Requested action not taken: mailbox unavailable"
);
}
#[test]
fn test_smtp_diagnostic_text_unknown() {
assert_eq!(smtp_diagnostic_text(999), "Unknown error");
}
#[test]
fn test_enhanced_code_constants() {
assert_eq!(EnhancedStatusCode::SUCCESS.to_string(), "2.0.0");
assert_eq!(
EnhancedStatusCode::BAD_DESTINATION_MAILBOX.to_string(),
"5.1.1"
);
assert_eq!(EnhancedStatusCode::MAILBOX_FULL.to_string(), "5.2.2");
}
#[test]
fn test_failure_reason_description() {
assert!(!FailureReason::UserUnknown.description().is_empty());
assert!(!FailureReason::QuotaExceeded.description().is_empty());
}
#[test]
fn test_failure_reason_display() {
let reason = FailureReason::UserUnknown;
assert_eq!(reason.to_string(), reason.description());
}
#[test]
fn test_smtp_to_enhanced_code_success_range() {
let code = smtp_to_enhanced_code(250);
assert_eq!(code, EnhancedStatusCode::SUCCESS);
}
#[test]
fn test_smtp_to_enhanced_code_temp_failure_range() {
let code = smtp_to_enhanced_code(499);
assert!(code.is_transient());
}
#[test]
fn test_smtp_to_enhanced_code_perm_failure_range() {
let code = smtp_to_enhanced_code(599);
assert!(code.is_permanent());
}
#[test]
fn test_all_failure_reasons() {
let reasons = [
FailureReason::UserUnknown,
FailureReason::QuotaExceeded,
FailureReason::MessageTooLarge,
FailureReason::ContentRejected,
FailureReason::RelayDenied,
FailureReason::TemporaryFailure,
FailureReason::NetworkUnreachable,
FailureReason::ConnectionTimeout,
FailureReason::InvalidAddress,
FailureReason::MailboxDisabled,
FailureReason::SystemNotAccepting,
FailureReason::AuthenticationRequired,
FailureReason::SpamDetected,
FailureReason::VirusDetected,
FailureReason::Other,
];
for reason in &reasons {
let _ = reason.enhanced_code();
let _ = reason.description();
let _ = reason.is_permanent();
}
}
}