monovm-whois-rust 1.0.0

Domain WHOIS and RDAP lookups with availability detection, structured record parsing, referral chasing, caching and rate limiting.
Documentation
//! The [`Availability`] verdict.

use std::fmt;
use std::str::FromStr;

use serde::{Deserialize, Serialize};

use crate::error::{Error, Result};

/// What a registry said about whether a name can be registered.
///
/// There is deliberately no `Unknown` variant. A lookup that reaches no
/// conclusion fails with [`Error::Inconclusive`](crate::Error::Inconclusive)
/// instead, because the one outcome a caller must never be handed is an
/// uncertain answer that renders as "available".
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum Availability {
    /// The registry has no record of the name; it can be registered.
    Available,
    /// The name is registered to someone.
    Registered,
    /// Unregistered, but the registry prices it above the standard fee. It can
    /// be bought, usually not at the rate a checkout page would quote.
    Premium,
    /// Withheld by the registry — reserved, restricted, or blocked. Not
    /// registered to a third party and not available either.
    Reserved,
}

impl Availability {
    /// Free to register at the ordinary price.
    ///
    /// [`Availability::Premium`] answers `false` here on purpose: a caller that
    /// treats premium as available quotes the wrong price.
    pub fn is_available(self) -> bool {
        self == Availability::Available
    }

    /// Obtainable by someone, at some price — available or premium.
    pub fn is_obtainable(self) -> bool {
        matches!(self, Availability::Available | Availability::Premium)
    }

    /// Held by a registrant.
    pub fn is_registered(self) -> bool {
        self == Availability::Registered
    }

    /// The lower-case name of the variant, matching its serialised form.
    pub fn as_str(self) -> &'static str {
        match self {
            Availability::Available => "available",
            Availability::Registered => "registered",
            Availability::Premium => "premium",
            Availability::Reserved => "reserved",
        }
    }
}

impl fmt::Display for Availability {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

impl FromStr for Availability {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        match s.trim().to_ascii_lowercase().as_str() {
            "available" | "free" => Ok(Availability::Available),
            // `unavailable` is what several other WHOIS libraries call this state.
            "registered" | "unavailable" | "taken" => Ok(Availability::Registered),
            "premium" => Ok(Availability::Premium),
            "reserved" | "restricted" => Ok(Availability::Reserved),
            _ => Err(Error::Definitions(format!("{s:?} is not an availability"))),
        }
    }
}

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

    #[test]
    fn premium_is_obtainable_but_not_available() {
        assert!(!Availability::Premium.is_available());
        assert!(Availability::Premium.is_obtainable());
        assert!(Availability::Available.is_available());
        assert!(!Availability::Reserved.is_obtainable());
    }

    #[test]
    fn parses_sibling_library_wording() {
        assert_eq!(
            "unavailable".parse::<Availability>().unwrap(),
            Availability::Registered
        );
        assert_eq!(
            "AVAILABLE".parse::<Availability>().unwrap(),
            Availability::Available
        );
        assert!("maybe".parse::<Availability>().is_err());
    }

    #[test]
    fn serialises_as_a_lowercase_string() {
        let json = serde_json::to_string(&Availability::Registered).unwrap();
        assert_eq!(json, "\"registered\"");
        assert_eq!(
            serde_json::from_str::<Availability>("\"premium\"").unwrap(),
            Availability::Premium
        );
    }

    #[test]
    fn display_matches_as_str() {
        for value in [
            Availability::Available,
            Availability::Registered,
            Availability::Premium,
            Availability::Reserved,
        ] {
            assert_eq!(value.to_string(), value.as_str());
            assert_eq!(value.as_str().parse::<Availability>().unwrap(), value);
        }
    }
}