dory/src/host.rs

154 lines
4 KiB
Rust

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> {
if s.contains(char::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::<Host>(),
Err(ParseHostError::MissingSeparator)
);
}
#[test]
fn parse_empty_tld() {
assert_eq!("example.".parse::<Host>(), Err(ParseHostError::EmptyTld));
}
#[test]
fn parse_empty_domain() {
assert_eq!(".com".parse::<Host>(), Err(ParseHostError::EmptyDomain));
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();
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");
}
}