mod denic;
mod educause;
mod eurid;
mod generic;
mod jprs;
mod kisa;
mod nic_it;
mod nominet;
mod sidn;
use once_cell::sync::Lazy;
use super::parser::WhoisResponse;
pub use denic::DenicParser;
pub use educause::EducauseParser;
pub use eurid::EuridParser;
pub use generic::GenericParser;
pub use jprs::JprsParser;
pub use kisa::KisaParser;
pub use nic_it::NicItParser;
pub use nominet::NominetParser;
pub use sidn::SidnParser;
pub trait RegistryParser: Send + Sync {
fn supported_tlds(&self) -> &[&str];
fn parse(&self, domain: &str, server: &str, raw: &str) -> WhoisResponse;
}
pub struct ParserRegistry {
parsers: Vec<Box<dyn RegistryParser>>,
fallback: GenericParser,
}
impl ParserRegistry {
pub fn new() -> Self {
Self {
parsers: vec![
Box::new(DenicParser::new()), Box::new(EducauseParser::new()), Box::new(EuridParser::new()), Box::new(JprsParser::new()), Box::new(KisaParser::new()), Box::new(NicItParser::new()), Box::new(NominetParser::new()), Box::new(SidnParser::new()), ],
fallback: GenericParser::new(),
}
}
pub fn parse(&self, domain: &str, server: &str, raw: &str) -> WhoisResponse {
let tld = extract_tld(domain);
let sld_tld = extract_second_level_tld(domain);
for parser in &self.parsers {
let supported = parser.supported_tlds();
if let Some(sld) = &sld_tld {
if supported.contains(&sld.as_str()) {
return parser.parse(domain, server, raw);
}
}
if let Some(tld) = &tld {
if supported.contains(&tld.as_str()) {
return parser.parse(domain, server, raw);
}
}
}
self.fallback.parse(domain, server, raw)
}
}
impl Default for ParserRegistry {
fn default() -> Self {
Self::new()
}
}
pub static PARSER_REGISTRY: Lazy<ParserRegistry> = Lazy::new(ParserRegistry::new);
fn extract_tld(domain: &str) -> Option<String> {
domain.rsplit('.').next().map(|s| s.to_lowercase())
}
fn extract_second_level_tld(domain: &str) -> Option<String> {
let parts: Vec<&str> = domain.rsplit('.').collect();
if parts.len() >= 3 {
Some(format!(
"{}.{}",
parts[1].to_lowercase(),
parts[0].to_lowercase()
))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_tld() {
assert_eq!(extract_tld("example.com"), Some("com".to_string()));
assert_eq!(extract_tld("example.co.uk"), Some("uk".to_string()));
assert_eq!(extract_tld("example.de"), Some("de".to_string()));
}
#[test]
fn test_extract_second_level_tld() {
assert_eq!(
extract_second_level_tld("example.co.uk"),
Some("co.uk".to_string())
);
assert_eq!(extract_second_level_tld("example.com"), None);
}
#[test]
fn test_parser_registry_selects_denic_for_de() {
let registry = ParserRegistry::new();
let result = registry.parse(
"example.de",
"whois.denic.de",
"Domain: example.de\nStatus: connect",
);
assert_eq!(result.domain, "example.de");
}
#[test]
fn test_parser_registry_selects_nominet_for_uk() {
let registry = ParserRegistry::new();
let result = registry.parse(
"example.co.uk",
"whois.nic.uk",
"Domain name:\n example.co.uk",
);
assert_eq!(result.domain, "example.co.uk");
}
#[test]
fn test_parser_registry_uses_generic_for_unknown() {
let registry = ParserRegistry::new();
let result = registry.parse(
"example.com",
"whois.verisign-grs.com",
"Domain Name: example.com",
);
assert_eq!(result.domain, "example.com");
}
}