whoizz 0.1.0

Tiny, dependency-light RFC 3912 whois client with best-effort field extraction
Documentation
use std::time::Duration;

use crate::{WhoisError, client};

/// IANA's whois server. It knows which server is authoritative per TLD.
pub(crate) const IANA_SERVER: &str = "whois.iana.org";

/// Ask IANA for the authoritative whois server for the given TLD.
pub(crate) fn lookup_server(tld: &str, timeout: Duration) -> Result<String, WhoisError> {
    let body = client::query(IANA_SERVER, client::WHOIS_PORT, tld, timeout)?;
    extract_refer(&body).ok_or_else(|| WhoisError::NoReferral(tld.to_string()))
}

/// Parse an IANA whois response to find the referred server. IANA uses
/// either `refer:` or `whois:` as the field name, depending on age of
/// the record.
pub(crate) fn extract_refer(body: &str) -> Option<String> {
    for line in body.lines() {
        let line = line.trim();
        for key in ["refer:", "whois:"] {
            if let Some(rest) = line.strip_prefix(key) {
                let v = rest.trim();
                if !v.is_empty() {
                    return Some(v.to_string());
                }
            }
        }
    }
    None
}

#[cfg(test)]
mod tests {
    use super::extract_refer;

    #[test]
    fn extracts_refer_line() {
        let body = "% IANA WHOIS server\n\ndomain:       ORG\norganisation: Public Interest Registry\nrefer:        whois.publicinterestregistry.org\n";
        assert_eq!(
            extract_refer(body).as_deref(),
            Some("whois.publicinterestregistry.org")
        );
    }

    #[test]
    fn falls_back_to_whois_field() {
        let body = "domain:  AQ\nwhois:   whois.nic.aq\n";
        assert_eq!(extract_refer(body).as_deref(), Some("whois.nic.aq"));
    }

    #[test]
    fn returns_none_without_refer() {
        assert!(extract_refer("no refer here").is_none());
    }
}