diff --git a/src/host.rs b/src/host.rs index 22dec8d..d510cfe 100644 --- a/src/host.rs +++ b/src/host.rs @@ -38,7 +38,7 @@ impl FromStr for Host { type Err = ParseHostError; fn from_str(s: &str) -> Result { - if s.contains(|x: char| x.is_whitespace()) { + if s.contains(char::is_whitespace) { return Err(ParseHostError::IllegalWhitespace); } if let Some((domain, tld)) = s.rsplit_once('.') { @@ -55,7 +55,7 @@ impl FromStr for Host { Ok(Host { subdomain: Some(subdomain.to_string()), domain: domain.to_string(), - tld: tld.to_string() + tld: tld.to_string(), }) } } else { @@ -85,18 +85,12 @@ mod tests { #[test] fn parse_empty_tld() { - assert_eq!( - "example.".parse::(), - Err(ParseHostError::EmptyTld) - ); + assert_eq!("example.".parse::(), Err(ParseHostError::EmptyTld)); } #[test] fn parse_empty_domain() { - assert_eq!( - ".com".parse::(), - Err(ParseHostError::EmptyDomain) - ); + assert_eq!(".com".parse::(), Err(ParseHostError::EmptyDomain)); assert_eq!( "example..com".parse::(), Err(ParseHostError::EmptyDomain) @@ -127,19 +121,13 @@ mod tests { #[test] fn parse_host_with_subdomain() { let host: Host = "mail.example.com".parse().unwrap(); - assert_eq!( - host.subdomain, - Some(String::from("mail")) - ); + assert_eq!(host.subdomain, Some(String::from("mail"))); } #[test] fn parse_complex_subdomain() { let host: Host = "mail.sender.example.com".parse().unwrap(); - assert_eq!( - host.subdomain, - Some(String::from("mail.sender")) - ); + assert_eq!(host.subdomain, Some(String::from("mail.sender"))); } #[test] @@ -147,7 +135,7 @@ mod tests { let host = Host { subdomain: None, domain: String::from("example"), - tld: String::from("com") + tld: String::from("com"), }; let host_str = host.to_string(); assert_eq!(host_str.as_str(), "example.com"); @@ -158,7 +146,7 @@ mod tests { let host = Host { subdomain: Some(String::from("mail")), domain: String::from("example"), - tld: String::from("com") + tld: String::from("com"), }; let host_str = host.to_string(); assert_eq!(host_str.as_str(), "mail.example.com"); diff --git a/src/lib.rs b/src/lib.rs index 63b323c..54d0cf0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ +#![warn(clippy::all, clippy::pedantic)] pub mod host; pub mod request; pub mod response; pub mod status; - diff --git a/src/request.rs b/src/request.rs index 96c3d67..810cd4f 100644 --- a/src/request.rs +++ b/src/request.rs @@ -10,7 +10,11 @@ pub struct Request { impl fmt::Display for Request { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "misfin://{}@{} {}\r\n", self.user, self.host, self.message) + write!( + f, + "misfin://{}@{} {}\r\n", + self.user, self.host, self.message + ) } } @@ -28,7 +32,7 @@ impl fmt::Display for ParseRequestError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::ParseHostError(e) => write!(f, "{e}"), - _ => write!(f, "{self:?}") + _ => write!(f, "{self:?}"), } } } @@ -52,23 +56,28 @@ impl FromStr for Request { type Err = ParseRequestError; fn from_str(s: &str) -> Result { - if !s.ends_with("\r\n") { - return Err(ParseRequestError::Malformed); - } if let Some((user, message)) = s.split_once(' ') { - let message = message.trim_end().to_string(); + let Some(message) = message.strip_suffix("\r\n").map(ToString::to_string) else { + return Err(ParseRequestError::Malformed); + }; if message.is_empty() { return Err(ParseRequestError::EmptyMessage); } if let Some((user, host)) = user.rsplit_once('@') { if host.is_empty() { return Err(ParseRequestError::EmptyHost); - } else if user.is_empty() { + } else if user == "misfin://" { return Err(ParseRequestError::EmptyUser); } let host = host.parse()?; - let user = user.to_string(); - Ok(Request { user, host, message }) + let Some(user) = user.strip_prefix("misfin://").map(ToString::to_string) else { + return Err(ParseRequestError::Malformed); + }; + Ok(Request { + user, + host, + message, + }) } else { Err(ParseRequestError::MissingSeparator) } @@ -77,3 +86,91 @@ impl FromStr for Request { } } } + +#[cfg(test)] +mod tests { + use super::*; + + const REQ_STR: &'static str = "misfin://john@misfin.example.com Anyone seen Jane?\r\n"; + + fn req() -> Request { + Request { + user: "john".to_string(), + host: Host { + subdomain: Some("misfin".into()), + domain: "example".into(), + tld: "com".into(), + }, + message: "Anyone seen Jane?".to_string(), + } + } + + #[test] + fn to_string() { + assert_eq!(req().to_string().as_str(), REQ_STR,); + } + + #[test] + fn parse() { + assert_eq!(REQ_STR.parse::().unwrap(), req()); + } + + #[test] + fn parse_missing_sep() { + let req = "misfin://john@example.comHelloWorld!\r\n".parse::(); + assert_eq!( + req, + Err(ParseRequestError::MissingSeparator) + ); + } + + #[test] + fn parse_malformed() { + let req = "misfin://john@example.com Hello World!\n".parse::(); + assert_eq!( + req, + Err(ParseRequestError::Malformed) + ); + let req = "mail://john@example.com Hello World!\r\n".parse::(); + assert_eq!( + req, + Err(ParseRequestError::Malformed) + ); + } + + #[test] + fn parse_empty_user() { + let req = "misfin://@example.com Hello World!\r\n".parse::(); + assert_eq!( + req, + Err(ParseRequestError::EmptyUser) + ); + } + + #[test] + fn parse_empty_host() { + let req = "misfin://john@ Hello World!\r\n".parse::(); + assert_eq!( + req, + Err(ParseRequestError::EmptyHost) + ); + } + + #[test] + fn parse_empty_msg() { + let req = "misfin://john@example.com \r\n".parse::(); + assert_eq!( + req, + Err(ParseRequestError::EmptyMessage) + ); + } + + #[test] + fn parse_host_illegal_whitespace() { + let req = "misfin://john@example\tfairy.com Hello World!\r\n".parse::(); + assert_eq!( + req, + Err(ParseRequestError::ParseHostError(ParseHostError::IllegalWhitespace)) + ); + } +} diff --git a/src/response.rs b/src/response.rs index 33be283..e26040f 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1,6 +1,6 @@ -use std::{fmt, str::FromStr, num::ParseIntError}; +use std::{fmt, num::ParseIntError, str::FromStr}; -use crate::status::{Status, StatusError}; +use crate::status::{Status, ParseStatusError}; #[derive(Clone, Debug, PartialEq)] pub struct Response { @@ -48,8 +48,8 @@ impl From for ParseResponseError { } } -impl From for ParseResponseError { - fn from(_value: StatusError) -> Self { +impl From for ParseResponseError { + fn from(_value: ParseStatusError) -> Self { Self::StatusError } } @@ -97,9 +97,8 @@ mod tests { fn parse_badint() { let response = "twenty message deliverred\r\n".parse::(); match response { - Err(ParseResponseError::ParseInt(_)) => {}, + Err(ParseResponseError::ParseInt(_)) => {} _ => panic!(), } } } - diff --git a/src/status.rs b/src/status.rs index 5215ccf..a0991d6 100644 --- a/src/status.rs +++ b/src/status.rs @@ -1,15 +1,15 @@ use std::fmt; #[derive(Debug, Clone, PartialEq)] -pub struct StatusError; +pub struct ParseStatusError; -impl fmt::Display for StatusError { +impl fmt::Display for ParseStatusError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") } } -impl std::error::Error for StatusError {} +impl std::error::Error for ParseStatusError {} #[derive(Debug, Clone, PartialEq)] #[repr(u8)] @@ -46,7 +46,7 @@ impl From for u8 { } impl TryFrom for Status { - type Error = StatusError; + type Error = ParseStatusError; fn try_from(value: u8) -> Result { match value / 10 { @@ -56,7 +56,7 @@ impl TryFrom for Status { 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(StatusError), + _ => Err(ParseStatusError), } } } @@ -73,14 +73,14 @@ pub enum Redirect { } impl TryFrom for Redirect { - type Error = StatusError; + 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(StatusError), + _ => Err(ParseStatusError), } } } @@ -105,7 +105,7 @@ pub enum TemporaryFailure { } impl TryFrom for TemporaryFailure { - type Error = StatusError; + type Error = ParseStatusError; fn try_from(value: u8) -> Result { match value { @@ -116,7 +116,7 @@ impl TryFrom for TemporaryFailure { 4 => Ok(Self::RateLimit), 5 => Ok(Self::MailboxFull), n if n < 10 => Ok(Self::Other), - _ => Err(StatusError), + _ => Err(ParseStatusError), } } } @@ -132,7 +132,7 @@ pub enum PermanentFailure { } impl TryFrom for PermanentFailure { - type Error = StatusError; + type Error = ParseStatusError; fn try_from(value: u8) -> Result { match value { @@ -142,7 +142,7 @@ impl TryFrom for PermanentFailure { 3 => Ok(Self::DomainNotServiced), 9 => Ok(Self::BadRequest), n if n < 10 => Ok(Self::Other), - _ => Err(StatusError), + _ => Err(ParseStatusError), } } } @@ -158,7 +158,7 @@ pub enum AuthenticationFailure { } impl TryFrom for AuthenticationFailure { - type Error = StatusError; + type Error = ParseStatusError; fn try_from(value: u8) -> Result { match value { @@ -168,7 +168,7 @@ impl TryFrom for AuthenticationFailure { 3 => Ok(Self::IdentityMismatch), 4 => Ok(Self::ProofRequired), n if n < 10 => Ok(Self::Other), - _ => Err(StatusError), + _ => Err(ParseStatusError), } } } @@ -273,7 +273,7 @@ mod tests { Status::PermanentFailure(PermanentFailure::Other) ); } - + #[test] fn parse_auth_err() { assert!(AuthenticationFailure::try_from(42).is_err());