use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Domain(String);
impl Domain {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl FromStr for Domain {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
return Err("domain must not be empty".to_string());
}
if s.len() > 253 {
return Err(format!("domain too long ({} > 253 chars)", s.len()));
}
if !s
.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '.' || c == '-')
{
return Err(format!(
"domain '{s}' contains invalid characters (allowed: a-z, 0-9, '.', '-')"
));
}
if s.starts_with('.') || s.ends_with('.') {
return Err(format!("domain '{s}' must not start or end with '.'"));
}
if s.contains("..") {
return Err(format!(
"domain '{s}' contains empty label (consecutive dots)"
));
}
for label in s.split('.') {
if label.len() > 63 {
return Err(format!("domain '{s}' has label exceeding 63 chars"));
}
if label.starts_with('-') || label.ends_with('-') {
return Err(format!(
"domain '{s}' has label starting or ending with '-'"
));
}
}
Ok(Self(s.to_owned()))
}
}
impl std::fmt::Display for Domain {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}