2023-05-22 10:12:28 -04:00
|
|
|
use std::{fmt, str::FromStr};
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Default, PartialEq)]
|
|
|
|
pub struct Host {
|
|
|
|
pub subdomain: Option<String>,
|
|
|
|
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<Self, Self::Err> {
|
2023-05-22 15:45:34 -04:00
|
|
|
if s.contains(char::is_whitespace) {
|
2023-05-22 10:12:28 -04:00
|
|
|
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(),
|
2023-05-22 15:45:34 -04:00
|
|
|
tld: tld.to_string(),
|
2023-05-22 10:12:28 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
} 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::<Host>(),
|
|
|
|
Err(ParseHostError::MissingSeparator)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_empty_tld() {
|
2023-05-22 15:45:34 -04:00
|
|
|
assert_eq!("example.".parse::<Host>(), Err(ParseHostError::EmptyTld));
|
2023-05-22 10:12:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_empty_domain() {
|
2023-05-22 15:45:34 -04:00
|
|
|
assert_eq!(".com".parse::<Host>(), Err(ParseHostError::EmptyDomain));
|
2023-05-22 10:12:28 -04:00
|
|
|
assert_eq!(
|
|
|
|
"example..com".parse::<Host>(),
|
|
|
|
Err(ParseHostError::EmptyDomain)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_empty_subdomain() {
|
|
|
|
assert_eq!(
|
|
|
|
".example.com".parse::<Host>(),
|
|
|
|
Err(ParseHostError::EmptySubdomain)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_illegal_whitespace() {
|
|
|
|
assert_eq!(
|
|
|
|
"exam\tple.com".parse::<Host>(),
|
|
|
|
Err(ParseHostError::IllegalWhitespace)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_host() {
|
|
|
|
assert!("example.com".parse::<Host>().is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_host_with_subdomain() {
|
|
|
|
let host: Host = "mail.example.com".parse().unwrap();
|
2023-05-22 15:45:34 -04:00
|
|
|
assert_eq!(host.subdomain, Some(String::from("mail")));
|
2023-05-22 10:12:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_complex_subdomain() {
|
|
|
|
let host: Host = "mail.sender.example.com".parse().unwrap();
|
2023-05-22 15:45:34 -04:00
|
|
|
assert_eq!(host.subdomain, Some(String::from("mail.sender")));
|
2023-05-22 10:12:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn fmt() {
|
|
|
|
let host = Host {
|
|
|
|
subdomain: None,
|
|
|
|
domain: String::from("example"),
|
2023-05-22 15:45:34 -04:00
|
|
|
tld: String::from("com"),
|
2023-05-22 10:12:28 -04:00
|
|
|
};
|
|
|
|
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"),
|
2023-05-22 15:45:34 -04:00
|
|
|
tld: String::from("com"),
|
2023-05-22 10:12:28 -04:00
|
|
|
};
|
|
|
|
let host_str = host.to_string();
|
|
|
|
assert_eq!(host_str.as_str(), "mail.example.com");
|
|
|
|
}
|
|
|
|
}
|