1use std::fmt;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct EnhancedStatusCode {
16 pub class: u8,
17 pub subject: u8,
18 pub detail: u8,
19}
20
21impl EnhancedStatusCode {
22 pub const fn new(class: u8, subject: u8, detail: u8) -> Self {
24 Self {
25 class,
26 subject,
27 detail,
28 }
29 }
30
31 pub fn parse(s: &str) -> Option<Self> {
33 let parts: Vec<&str> = s.split('.').collect();
34 if parts.len() != 3 {
35 return None;
36 }
37
38 let class = parts[0].parse().ok()?;
39 let subject = parts[1].parse().ok()?;
40 let detail = parts[2].parse().ok()?;
41
42 Some(Self::new(class, subject, detail))
43 }
44
45 pub fn is_success(&self) -> bool {
47 self.class == 2
48 }
49
50 pub fn is_transient(&self) -> bool {
52 self.class == 4
53 }
54
55 pub fn is_permanent(&self) -> bool {
57 self.class == 5
58 }
59}
60
61impl fmt::Display for EnhancedStatusCode {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 write!(f, "{}.{}.{}", self.class, self.subject, self.detail)
64 }
65}
66
67#[allow(dead_code)]
69impl EnhancedStatusCode {
70 pub const SUCCESS: Self = Self::new(2, 0, 0);
72
73 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); }
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub enum FailureReason {
142 UserUnknown,
144 QuotaExceeded,
146 MessageTooLarge,
148 ContentRejected,
150 RelayDenied,
152 TemporaryFailure,
154 NetworkUnreachable,
156 ConnectionTimeout,
158 InvalidAddress,
160 MailboxDisabled,
162 SystemNotAccepting,
164 AuthenticationRequired,
166 SpamDetected,
168 VirusDetected,
170 Other,
172}
173
174impl FailureReason {
175 pub fn enhanced_code(&self) -> EnhancedStatusCode {
177 match self {
178 Self::UserUnknown => EnhancedStatusCode::BAD_DESTINATION_MAILBOX,
179 Self::QuotaExceeded => EnhancedStatusCode::MAILBOX_FULL,
180 Self::MessageTooLarge => EnhancedStatusCode::MESSAGE_TOO_LARGE,
181 Self::ContentRejected => EnhancedStatusCode::MESSAGE_REFUSED,
182 Self::RelayDenied => EnhancedStatusCode::DELIVERY_NOT_AUTHORIZED,
183 Self::TemporaryFailure => EnhancedStatusCode::new(4, 0, 0),
184 Self::NetworkUnreachable => EnhancedStatusCode::NO_ANSWER,
185 Self::ConnectionTimeout => EnhancedStatusCode::CONNECTION_DROPPED,
186 Self::InvalidAddress => EnhancedStatusCode::BAD_DESTINATION_SYNTAX,
187 Self::MailboxDisabled => EnhancedStatusCode::MAILBOX_DISABLED,
188 Self::SystemNotAccepting => EnhancedStatusCode::SYSTEM_NOT_ACCEPTING,
189 Self::AuthenticationRequired => EnhancedStatusCode::DELIVERY_NOT_AUTHORIZED,
190 Self::SpamDetected => EnhancedStatusCode::MESSAGE_REFUSED,
191 Self::VirusDetected => EnhancedStatusCode::MESSAGE_REFUSED,
192 Self::Other => EnhancedStatusCode::new(5, 0, 0),
193 }
194 }
195
196 pub fn description(&self) -> &'static str {
198 match self {
199 Self::UserUnknown => "The recipient's email address does not exist",
200 Self::QuotaExceeded => "The recipient's mailbox is full",
201 Self::MessageTooLarge => "The message is too large to be delivered",
202 Self::ContentRejected => "The message content was rejected by policy",
203 Self::RelayDenied => "Relay access denied",
204 Self::TemporaryFailure => "Temporary failure, will retry delivery",
205 Self::NetworkUnreachable => "The destination mail server could not be reached",
206 Self::ConnectionTimeout => "Connection to the mail server timed out",
207 Self::InvalidAddress => "The recipient's email address is invalid",
208 Self::MailboxDisabled => "The recipient's mailbox is disabled",
209 Self::SystemNotAccepting => "The mail system is not accepting messages",
210 Self::AuthenticationRequired => "Authentication is required for this delivery",
211 Self::SpamDetected => "The message was identified as spam",
212 Self::VirusDetected => "The message contains a virus",
213 Self::Other => "An unknown error occurred",
214 }
215 }
216
217 pub fn is_permanent(&self) -> bool {
219 matches!(
220 self,
221 Self::UserUnknown
222 | Self::QuotaExceeded
223 | Self::MessageTooLarge
224 | Self::ContentRejected
225 | Self::RelayDenied
226 | Self::InvalidAddress
227 | Self::MailboxDisabled
228 | Self::SpamDetected
229 | Self::VirusDetected
230 )
231 }
232
233 pub fn from_smtp_code(code: u16) -> Self {
235 match code {
236 421 => Self::ConnectionTimeout,
237 450 => Self::TemporaryFailure,
238 451 => Self::TemporaryFailure,
239 452 => Self::QuotaExceeded,
240 550 => Self::UserUnknown,
241 551 => Self::RelayDenied,
242 552 => Self::QuotaExceeded,
243 553 => Self::InvalidAddress,
244 554 => Self::ContentRejected,
245 _ if (400..500).contains(&code) => Self::TemporaryFailure,
246 _ => Self::Other,
247 }
248 }
249}
250
251impl fmt::Display for FailureReason {
252 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253 write!(f, "{}", self.description())
254 }
255}
256
257pub fn smtp_to_enhanced_code(smtp_code: u16) -> EnhancedStatusCode {
259 match smtp_code {
260 250 => EnhancedStatusCode::SUCCESS,
262 251 => EnhancedStatusCode::DESTINATION_VALID,
263
264 421 => EnhancedStatusCode::CONNECTION_DROPPED,
266 450 => EnhancedStatusCode::new(4, 2, 1), 451 => EnhancedStatusCode::new(4, 3, 0), 452 => EnhancedStatusCode::MAILBOX_FULL_TEMP,
269 454 => EnhancedStatusCode::new(4, 7, 0), 500 => EnhancedStatusCode::SYNTAX_ERROR,
273 501 => EnhancedStatusCode::INVALID_PARAMETERS,
274 502 => EnhancedStatusCode::INVALID_COMMAND,
275 503 => EnhancedStatusCode::INVALID_COMMAND,
276 504 => EnhancedStatusCode::INVALID_PARAMETERS,
277 550 => EnhancedStatusCode::BAD_DESTINATION_MAILBOX,
278 551 => EnhancedStatusCode::MAILBOX_MOVED,
279 552 => EnhancedStatusCode::MAILBOX_FULL,
280 553 => EnhancedStatusCode::BAD_DESTINATION_SYNTAX,
281 554 => EnhancedStatusCode::DELIVERY_NOT_AUTHORIZED,
282
283 _ if (200..300).contains(&smtp_code) => EnhancedStatusCode::SUCCESS,
285 _ if (400..500).contains(&smtp_code) => EnhancedStatusCode::new(4, 0, 0),
286 _ if (500..600).contains(&smtp_code) => EnhancedStatusCode::new(5, 0, 0),
287 _ => EnhancedStatusCode::new(5, 0, 0),
288 }
289}
290
291pub fn smtp_diagnostic_text(smtp_code: u16) -> &'static str {
293 match smtp_code {
294 421 => "Service not available, closing transmission channel",
295 450 => "Requested mail action not taken: mailbox unavailable",
296 451 => "Requested action aborted: local error in processing",
297 452 => "Requested action not taken: insufficient system storage",
298 454 => "Temporary authentication failure",
299 500 => "Syntax error, command unrecognized",
300 501 => "Syntax error in parameters or arguments",
301 502 => "Command not implemented",
302 503 => "Bad sequence of commands",
303 504 => "Command parameter not implemented",
304 550 => "Requested action not taken: mailbox unavailable",
305 551 => "User not local; please try forward path",
306 552 => "Requested mail action aborted: exceeded storage allocation",
307 553 => "Requested action not taken: mailbox name not allowed",
308 554 => "Transaction failed",
309 _ => "Unknown error",
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316
317 #[test]
318 fn test_enhanced_status_code_display() {
319 let code = EnhancedStatusCode::new(5, 1, 1);
320 assert_eq!(code.to_string(), "5.1.1");
321 }
322
323 #[test]
324 fn test_enhanced_status_code_parse() {
325 let code = EnhancedStatusCode::parse("5.1.1").unwrap();
326 assert_eq!(code.class, 5);
327 assert_eq!(code.subject, 1);
328 assert_eq!(code.detail, 1);
329 }
330
331 #[test]
332 fn test_enhanced_status_code_parse_invalid() {
333 assert!(EnhancedStatusCode::parse("5.1").is_none());
334 assert!(EnhancedStatusCode::parse("5.1.1.1").is_none());
335 assert!(EnhancedStatusCode::parse("abc").is_none());
336 }
337
338 #[test]
339 fn test_enhanced_status_code_is_success() {
340 assert!(EnhancedStatusCode::new(2, 0, 0).is_success());
341 assert!(!EnhancedStatusCode::new(4, 0, 0).is_success());
342 assert!(!EnhancedStatusCode::new(5, 0, 0).is_success());
343 }
344
345 #[test]
346 fn test_enhanced_status_code_is_transient() {
347 assert!(!EnhancedStatusCode::new(2, 0, 0).is_transient());
348 assert!(EnhancedStatusCode::new(4, 0, 0).is_transient());
349 assert!(!EnhancedStatusCode::new(5, 0, 0).is_transient());
350 }
351
352 #[test]
353 fn test_enhanced_status_code_is_permanent() {
354 assert!(!EnhancedStatusCode::new(2, 0, 0).is_permanent());
355 assert!(!EnhancedStatusCode::new(4, 0, 0).is_permanent());
356 assert!(EnhancedStatusCode::new(5, 0, 0).is_permanent());
357 }
358
359 #[test]
360 fn test_failure_reason_enhanced_code() {
361 assert_eq!(
362 FailureReason::UserUnknown.enhanced_code(),
363 EnhancedStatusCode::BAD_DESTINATION_MAILBOX
364 );
365 assert_eq!(
366 FailureReason::QuotaExceeded.enhanced_code(),
367 EnhancedStatusCode::MAILBOX_FULL
368 );
369 assert_eq!(
370 FailureReason::MessageTooLarge.enhanced_code(),
371 EnhancedStatusCode::MESSAGE_TOO_LARGE
372 );
373 }
374
375 #[test]
376 fn test_failure_reason_is_permanent() {
377 assert!(FailureReason::UserUnknown.is_permanent());
378 assert!(!FailureReason::TemporaryFailure.is_permanent());
379 assert!(FailureReason::InvalidAddress.is_permanent());
380 }
381
382 #[test]
383 fn test_failure_reason_from_smtp_code() {
384 assert_eq!(
385 FailureReason::from_smtp_code(550),
386 FailureReason::UserUnknown
387 );
388 assert_eq!(
389 FailureReason::from_smtp_code(452),
390 FailureReason::QuotaExceeded
391 );
392 assert_eq!(
393 FailureReason::from_smtp_code(421),
394 FailureReason::ConnectionTimeout
395 );
396 }
397
398 #[test]
399 fn test_smtp_to_enhanced_code_421() {
400 assert_eq!(
401 smtp_to_enhanced_code(421),
402 EnhancedStatusCode::CONNECTION_DROPPED
403 );
404 }
405
406 #[test]
407 fn test_smtp_to_enhanced_code_450() {
408 let code = smtp_to_enhanced_code(450);
409 assert_eq!(code.class, 4);
410 assert_eq!(code.subject, 2);
411 assert_eq!(code.detail, 1);
412 }
413
414 #[test]
415 fn test_smtp_to_enhanced_code_451() {
416 let code = smtp_to_enhanced_code(451);
417 assert_eq!(code.class, 4);
418 assert_eq!(code.subject, 3);
419 assert_eq!(code.detail, 0);
420 }
421
422 #[test]
423 fn test_smtp_to_enhanced_code_452() {
424 assert_eq!(
425 smtp_to_enhanced_code(452),
426 EnhancedStatusCode::MAILBOX_FULL_TEMP
427 );
428 }
429
430 #[test]
431 fn test_smtp_to_enhanced_code_550() {
432 assert_eq!(
433 smtp_to_enhanced_code(550),
434 EnhancedStatusCode::BAD_DESTINATION_MAILBOX
435 );
436 }
437
438 #[test]
439 fn test_smtp_to_enhanced_code_551() {
440 assert_eq!(
441 smtp_to_enhanced_code(551),
442 EnhancedStatusCode::MAILBOX_MOVED
443 );
444 }
445
446 #[test]
447 fn test_smtp_to_enhanced_code_552() {
448 assert_eq!(smtp_to_enhanced_code(552), EnhancedStatusCode::MAILBOX_FULL);
449 }
450
451 #[test]
452 fn test_smtp_to_enhanced_code_553() {
453 assert_eq!(
454 smtp_to_enhanced_code(553),
455 EnhancedStatusCode::BAD_DESTINATION_SYNTAX
456 );
457 }
458
459 #[test]
460 fn test_smtp_to_enhanced_code_554() {
461 assert_eq!(
462 smtp_to_enhanced_code(554),
463 EnhancedStatusCode::DELIVERY_NOT_AUTHORIZED
464 );
465 }
466
467 #[test]
468 fn test_smtp_diagnostic_text() {
469 assert_eq!(
470 smtp_diagnostic_text(421),
471 "Service not available, closing transmission channel"
472 );
473 assert_eq!(
474 smtp_diagnostic_text(550),
475 "Requested action not taken: mailbox unavailable"
476 );
477 }
478
479 #[test]
480 fn test_smtp_diagnostic_text_unknown() {
481 assert_eq!(smtp_diagnostic_text(999), "Unknown error");
482 }
483
484 #[test]
485 fn test_enhanced_code_constants() {
486 assert_eq!(EnhancedStatusCode::SUCCESS.to_string(), "2.0.0");
487 assert_eq!(
488 EnhancedStatusCode::BAD_DESTINATION_MAILBOX.to_string(),
489 "5.1.1"
490 );
491 assert_eq!(EnhancedStatusCode::MAILBOX_FULL.to_string(), "5.2.2");
492 }
493
494 #[test]
495 fn test_failure_reason_description() {
496 assert!(!FailureReason::UserUnknown.description().is_empty());
497 assert!(!FailureReason::QuotaExceeded.description().is_empty());
498 }
499
500 #[test]
501 fn test_failure_reason_display() {
502 let reason = FailureReason::UserUnknown;
503 assert_eq!(reason.to_string(), reason.description());
504 }
505
506 #[test]
507 fn test_smtp_to_enhanced_code_success_range() {
508 let code = smtp_to_enhanced_code(250);
509 assert_eq!(code, EnhancedStatusCode::SUCCESS);
510 }
511
512 #[test]
513 fn test_smtp_to_enhanced_code_temp_failure_range() {
514 let code = smtp_to_enhanced_code(499);
515 assert!(code.is_transient());
516 }
517
518 #[test]
519 fn test_smtp_to_enhanced_code_perm_failure_range() {
520 let code = smtp_to_enhanced_code(599);
521 assert!(code.is_permanent());
522 }
523
524 #[test]
525 fn test_all_failure_reasons() {
526 let reasons = [
528 FailureReason::UserUnknown,
529 FailureReason::QuotaExceeded,
530 FailureReason::MessageTooLarge,
531 FailureReason::ContentRejected,
532 FailureReason::RelayDenied,
533 FailureReason::TemporaryFailure,
534 FailureReason::NetworkUnreachable,
535 FailureReason::ConnectionTimeout,
536 FailureReason::InvalidAddress,
537 FailureReason::MailboxDisabled,
538 FailureReason::SystemNotAccepting,
539 FailureReason::AuthenticationRequired,
540 FailureReason::SpamDetected,
541 FailureReason::VirusDetected,
542 FailureReason::Other,
543 ];
544
545 for reason in &reasons {
546 let _ = reason.enhanced_code();
547 let _ = reason.description();
548 let _ = reason.is_permanent();
549 }
550 }
551}