diff --git a/src/host.rs b/src/host.rs new file mode 100644 index 0000000..22dec8d --- /dev/null +++ b/src/host.rs @@ -0,0 +1,166 @@ +use std::{fmt, str::FromStr}; + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct Host { + pub subdomain: Option, + pub domain: String, + pub tld: String, +} + +impl fmt::Display for Host { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(ref sub) = self.subdomain { + write!(f, "{sub}.{}.{}", self.domain, self.tld) + } else { + write!(f, "{}.{}", self.domain, self.tld) + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ParseHostError { + MissingSeparator, + EmptyDomain, + EmptyTld, + EmptySubdomain, + IllegalWhitespace, +} + +impl fmt::Display for ParseHostError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self:?}") + } +} + +impl std::error::Error for ParseHostError {} + +impl FromStr for Host { + type Err = ParseHostError; + + fn from_str(s: &str) -> Result { + if s.contains(|x: char| x.is_whitespace()) { + return Err(ParseHostError::IllegalWhitespace); + } + if let Some((domain, tld)) = s.rsplit_once('.') { + if domain.is_empty() { + Err(ParseHostError::EmptyDomain) + } else if tld.is_empty() { + Err(ParseHostError::EmptyTld) + } else if let Some((subdomain, domain)) = domain.rsplit_once('.') { + if subdomain.is_empty() { + Err(ParseHostError::EmptySubdomain) + } else if domain.is_empty() { + Err(ParseHostError::EmptyDomain) + } else { + Ok(Host { + subdomain: Some(subdomain.to_string()), + domain: domain.to_string(), + tld: tld.to_string() + }) + } + } else { + Ok(Host { + subdomain: None, + domain: domain.to_string(), + tld: tld.to_string(), + }) + } + } else { + Err(ParseHostError::MissingSeparator) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_missing_separator() { + assert_eq!( + "exampledotcom".parse::(), + Err(ParseHostError::MissingSeparator) + ); + } + + #[test] + fn parse_empty_tld() { + assert_eq!( + "example.".parse::(), + Err(ParseHostError::EmptyTld) + ); + } + + #[test] + fn parse_empty_domain() { + assert_eq!( + ".com".parse::(), + Err(ParseHostError::EmptyDomain) + ); + assert_eq!( + "example..com".parse::(), + Err(ParseHostError::EmptyDomain) + ); + } + + #[test] + fn parse_empty_subdomain() { + assert_eq!( + ".example.com".parse::(), + Err(ParseHostError::EmptySubdomain) + ); + } + + #[test] + fn parse_illegal_whitespace() { + assert_eq!( + "exam\tple.com".parse::(), + Err(ParseHostError::IllegalWhitespace) + ); + } + + #[test] + fn parse_host() { + assert!("example.com".parse::().is_ok()); + } + + #[test] + fn parse_host_with_subdomain() { + let host: Host = "mail.example.com".parse().unwrap(); + 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")) + ); + } + + #[test] + fn fmt() { + let host = Host { + subdomain: None, + domain: String::from("example"), + tld: String::from("com") + }; + let host_str = host.to_string(); + assert_eq!(host_str.as_str(), "example.com"); + } + + #[test] + fn fmt_with_subdomain() { + let host = Host { + subdomain: Some(String::from("mail")), + domain: String::from("example"), + 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 8280fc7..63b323c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +pub mod host; +pub mod request; pub mod response; pub mod status; diff --git a/src/request.rs b/src/request.rs new file mode 100644 index 0000000..7e7efc1 --- /dev/null +++ b/src/request.rs @@ -0,0 +1,15 @@ +use crate::host::{Host, ParseHostError}; +use std::{fmt, str::FromStr}; + +#[derive(Clone, Debug, PartialEq)] +pub struct Request { + user: String, + host: Host, + message: String, +} + +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) + } +} diff --git a/src/response.rs b/src/response.rs index a4bd42f..33be283 100644 --- a/src/response.rs +++ b/src/response.rs @@ -92,5 +92,14 @@ mod tests { let response = "20 message delivered\n".parse::(); assert_eq!(response, Err(ParseResponseError::Malformed)); } + + #[test] + fn parse_badint() { + let response = "twenty message deliverred\r\n".parse::(); + match response { + Err(ParseResponseError::ParseInt(_)) => {}, + _ => panic!(), + } + } }