use std::fmt; #[derive(Debug, Clone, PartialEq)] pub struct ParseStatusError; impl fmt::Display for ParseStatusError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") } } impl std::error::Error for ParseStatusError {} #[derive(Debug, Clone, PartialEq)] #[repr(u8)] pub enum Status { /// These codes are reserved, and must not be sent by a Misfin server. Input = 10, /// Status codes beginning with 2 are SUCCESS status codes, which mean /// that the client's message has been delivered. Success = 20, /// The mailbox has moved to a different address, and this message should /// be resent to that address. Redirect(Redirect) = 30, /// Status codes beginning with 5 are PERMANENT FAILURE status codes, which /// mean the request did not succeed, and should not be retried. TemporaryFailure(TemporaryFailure) = 40, /// Something is wrong with the mailserver, and you should not try to /// resend your message. PermanentFailure(PermanentFailure) = 50, /// Status codes beginning with 6 are AUTHENTICATION FAILURE status codes, /// which mean that there was an issue with the client's certificate. AuthenticationFailure(AuthenticationFailure) = 60, } impl From for u8 { fn from(value: Status) -> Self { match value { Status::Input | Status::Success => value.into(), Status::Redirect(n) => 30 + n as u8, Status::TemporaryFailure(n) => 40 + n as u8, Status::PermanentFailure(n) => 50 + n as u8, Status::AuthenticationFailure(n) => 60 + n as u8, } } } impl TryFrom for Status { type Error = ParseStatusError; fn try_from(value: u8) -> Result { match value / 10 { 1 => Ok(Self::Input), 2 => Ok(Self::Success), 3 => Ok(Self::Redirect((value % 30).try_into()?)), 4 => Ok(Self::TemporaryFailure((value % 40).try_into()?)), 5 => Ok(Self::PermanentFailure((value % 50).try_into()?)), 6 => Ok(Self::AuthenticationFailure((value % 60).try_into()?)), _ => Err(ParseStatusError), } } } #[derive(Debug, Clone, PartialEq)] pub enum Redirect { /// The mailbox has moved to a different address, and this message /// should be resent to that address. Temporary = 0, /// The mailbox has moved to a different address, and all future /// messages should be sent to that address. Permanent = 1, Other, } impl TryFrom for Redirect { type Error = ParseStatusError; fn try_from(value: u8) -> Result { match value { 0 => Ok(Self::Temporary), 1 => Ok(Self::Permanent), n if n < 10 => Ok(Self::Other), _ => Err(ParseStatusError), } } } #[derive(Debug, Clone, PartialEq)] pub enum TemporaryFailure { /// The mailserver experienced a transient issue, and the message /// should be resent. TemporaryError = 0, /// The mailserver can't accept mail right now. ServerUnavailable = 1, /// A mailserver script ran for your message, but experienced an error. CgiError = 2, /// There was a problem accepting mail for that domain, but it might /// resolve itself. ProxyError = 3, /// You are being rate limited - wait before trying to send more mail. RateLimit = 4, /// The mailbox isn't accepting mail right now, but it might in the future. MailboxFull = 5, Other, } impl TryFrom for TemporaryFailure { type Error = ParseStatusError; fn try_from(value: u8) -> Result { match value { 0 => Ok(Self::TemporaryError), 1 => Ok(Self::ServerUnavailable), 2 => Ok(Self::CgiError), 3 => Ok(Self::ProxyError), 4 => Ok(Self::RateLimit), 5 => Ok(Self::MailboxFull), n if n < 10 => Ok(Self::Other), _ => Err(ParseStatusError), } } } #[derive(Debug, Clone, PartialEq)] pub enum PermanentFailure { PermanentError = 0, MailboxNonexistent = 1, MailboxGone = 2, DomainNotServiced = 3, BadRequest = 9, Other, } impl TryFrom for PermanentFailure { type Error = ParseStatusError; fn try_from(value: u8) -> Result { match value { 0 => Ok(Self::PermanentError), 1 => Ok(Self::MailboxNonexistent), 2 => Ok(Self::MailboxGone), 3 => Ok(Self::DomainNotServiced), 9 => Ok(Self::BadRequest), n if n < 10 => Ok(Self::Other), _ => Err(ParseStatusError), } } } #[derive(Debug, Clone, PartialEq)] pub enum AuthenticationFailure { CertificateRequired = 0, UnauthorizedSender = 1, CertificateInvalid = 2, IdentityMismatch = 3, ProofRequired = 4, Other, } impl TryFrom for AuthenticationFailure { type Error = ParseStatusError; fn try_from(value: u8) -> Result { match value { 0 => Ok(Self::CertificateRequired), 1 => Ok(Self::UnauthorizedSender), 2 => Ok(Self::CertificateInvalid), 3 => Ok(Self::IdentityMismatch), 4 => Ok(Self::ProofRequired), n if n < 10 => Ok(Self::Other), _ => Err(ParseStatusError), } } } #[cfg(test)] mod tests { use super::*; #[test] fn parse_status_success() { let status = Status::try_from(21).unwrap(); assert_eq!(status, Status::Success); } #[test] fn parse_redirect_err() { assert!(Redirect::try_from(42).is_err()); } #[test] fn parse_status_redirect() { assert_eq!( Status::try_from(30).unwrap(), Status::Redirect(Redirect::Temporary) ); assert_eq!( Status::try_from(31).unwrap(), Status::Redirect(Redirect::Permanent) ); assert_eq!( Status::try_from(32).unwrap(), Status::Redirect(Redirect::Other) ); } #[test] fn parse_temporary_err() { assert!(TemporaryFailure::try_from(42).is_err()); } #[test] fn parse_status_temporary() { assert_eq!( Status::try_from(40).unwrap(), Status::TemporaryFailure(TemporaryFailure::TemporaryError) ); assert_eq!( Status::try_from(41).unwrap(), Status::TemporaryFailure(TemporaryFailure::ServerUnavailable) ); assert_eq!( Status::try_from(42).unwrap(), Status::TemporaryFailure(TemporaryFailure::CgiError) ); assert_eq!( Status::try_from(43).unwrap(), Status::TemporaryFailure(TemporaryFailure::ProxyError) ); assert_eq!( Status::try_from(44).unwrap(), Status::TemporaryFailure(TemporaryFailure::RateLimit) ); assert_eq!( Status::try_from(45).unwrap(), Status::TemporaryFailure(TemporaryFailure::MailboxFull) ); assert_eq!( Status::try_from(46).unwrap(), Status::TemporaryFailure(TemporaryFailure::Other) ); } #[test] fn parse_permanent_err() { assert!(PermanentFailure::try_from(42).is_err()); } #[test] fn parse_status_permanent() { assert_eq!( Status::try_from(50).unwrap(), Status::PermanentFailure(PermanentFailure::PermanentError) ); assert_eq!( Status::try_from(51).unwrap(), Status::PermanentFailure(PermanentFailure::MailboxNonexistent) ); assert_eq!( Status::try_from(52).unwrap(), Status::PermanentFailure(PermanentFailure::MailboxGone) ); assert_eq!( Status::try_from(53).unwrap(), Status::PermanentFailure(PermanentFailure::DomainNotServiced) ); assert_eq!( Status::try_from(59).unwrap(), Status::PermanentFailure(PermanentFailure::BadRequest) ); assert_eq!( Status::try_from(57).unwrap(), Status::PermanentFailure(PermanentFailure::Other) ); } #[test] fn parse_auth_err() { assert!(AuthenticationFailure::try_from(42).is_err()); } #[test] fn parse_status_auth() { assert_eq!( Status::try_from(60).unwrap(), Status::AuthenticationFailure(AuthenticationFailure::CertificateRequired) ); assert_eq!( Status::try_from(61).unwrap(), Status::AuthenticationFailure(AuthenticationFailure::UnauthorizedSender) ); assert_eq!( Status::try_from(62).unwrap(), Status::AuthenticationFailure(AuthenticationFailure::CertificateInvalid) ); assert_eq!( Status::try_from(63).unwrap(), Status::AuthenticationFailure(AuthenticationFailure::IdentityMismatch) ); assert_eq!( Status::try_from(64).unwrap(), Status::AuthenticationFailure(AuthenticationFailure::ProofRequired) ); assert_eq!( Status::try_from(65).unwrap(), Status::AuthenticationFailure(AuthenticationFailure::Other) ); } }