ipwhois-rust 1.2.0

Official Rust client for the ipwhois.io IP Geolocation API. Simple, supports single and bulk IP lookups.
Documentation
//! Error type returned by every fallible operation in this crate.
//!
//! The library does not intentionally panic — every failure (network
//! outage, rate limit, bad IP, bad options, …) flows through the
//! [`Error`] enum below. Three categories cover the failure modes
//! (`api`, `network`, `invalid_argument`); each variant carries a
//! human-readable `message`, and API errors additionally carry HTTP
//! status and a free-plan `Retry-After` value when available.

use thiserror::Error as ThisError;

/// All errors returned by this crate.
///
/// Every variant carries a human-readable `message`. API errors additionally
/// carry the upstream HTTP status code, and HTTP 429 responses on the **free
/// plan** also carry the `Retry-After` value the API asked us to honour
/// (the paid endpoint does not send the header).
///
/// Match on the variant when you want to branch on the category, or call
/// [`Error::error_type`] to get a stable category string
/// (`"api"`, `"network"`, `"invalid_argument"`).
#[derive(Debug, Clone, ThisError)]
#[non_exhaustive]
pub enum Error {
    /// The ipwhois.io API itself returned an error. Covers HTTP 4xx / 5xx
    /// responses, malformed JSON bodies, and HTTP 2xx responses where the
    /// API sets `success: false` (e.g. "Invalid IP address",
    /// "Reserved range").
    #[error("{message}")]
    Api {
        /// Human-readable description from the API or synthesised by the
        /// client when the body is unparseable.
        message: String,
        /// HTTP status code, when the failure was an HTTP 4xx / 5xx.
        /// Absent for `success: false` bodies returned with HTTP 2xx.
        http_status: Option<u16>,
        /// Value of the `Retry-After` header, in seconds. Only present on
        /// HTTP 429 responses from the **free plan** (`ipwho.is`); the paid
        /// endpoint (`ipwhois.pro`) does not send the header.
        retry_after: Option<u64>,
    },

    /// Transport-level failure: DNS, connection refused, TLS handshake,
    /// timeout, etc. The request never reached the API meaningfully.
    #[error("Network error: {message}")]
    Network {
        /// Description of the underlying transport failure.
        message: String,
    },

    /// The caller supplied an invalid argument before any request was made
    /// (unsupported language, empty bulk list, more than 100 IPs, …).
    #[error("Invalid argument: {message}")]
    InvalidArgument {
        /// Human-readable description of what was wrong.
        message: String,
    },
}

impl Error {
    /// Stable string identifying the category of this error.
    ///
    /// Returns one of: `"api"`, `"network"`, or `"invalid_argument"`.
    pub fn error_type(&self) -> &'static str {
        match self {
            Error::Api { .. } => "api",
            Error::Network { .. } => "network",
            Error::InvalidArgument { .. } => "invalid_argument",
        }
    }

    /// Human-readable message for this error.
    pub fn message(&self) -> &str {
        match self {
            Error::Api { message, .. } => message,
            Error::Network { message } => message,
            Error::InvalidArgument { message } => message,
        }
    }

    /// HTTP status code, if this is an [`Error::Api`] variant carrying one.
    pub fn http_status(&self) -> Option<u16> {
        match self {
            Error::Api { http_status, .. } => *http_status,
            _ => None,
        }
    }

    /// Value of the `Retry-After` header in seconds, if available.
    /// Only present on HTTP 429 responses from the free plan.
    pub fn retry_after(&self) -> Option<u64> {
        match self {
            Error::Api { retry_after, .. } => *retry_after,
            _ => None,
        }
    }

    pub(crate) fn invalid_argument(msg: impl Into<String>) -> Self {
        Error::InvalidArgument {
            message: msg.into(),
        }
    }

    pub(crate) fn network(msg: impl Into<String>) -> Self {
        Error::Network {
            message: msg.into(),
        }
    }

    pub(crate) fn api(
        message: impl Into<String>,
        http_status: Option<u16>,
        retry_after: Option<u64>,
    ) -> Self {
        Error::Api {
            message: message.into(),
            http_status,
            retry_after,
        }
    }
}

impl From<reqwest::Error> for Error {
    fn from(e: reqwest::Error) -> Self {
        Error::Network {
            message: e.to_string(),
        }
    }
}