ipflag 0.1.0

Human-friendly IP -> country flag display core (resolver-pluggable, no data bundled).
Documentation
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

/// High-level IP classification used by IP Flag.
///
/// This classification is intentionally simple and UI-oriented:
/// - `Private`: local network / internal addresses (LAN), never resolved
/// - `Special`: loopback/multicast/documentation/unspecified/broadcast, never resolved
/// - `Public`: eligible for resolution via [`crate::IpResolver`]
///
/// Note: Different applications may want deeper classification. IP Flag keeps this minimal.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum IpScope {
    /// Private/local/internal ranges (e.g., 192.168.x.x, 10.x.x.x, fc00::/7)
    Private,

    /// Non-public special ranges (loopback, multicast, documentation ranges, etc.)
    Special,

    /// Public IP addresses. Only these are passed to the resolver.
    Public,
}

/// Parse a string into [`IpAddr`].
///
/// This is a small helper around `str::parse::<IpAddr>()`.
///
/// # Example
///
/// ```rust
/// use ipflag::parse_ip;
///
/// assert!(parse_ip("8.8.8.8").is_ok());
/// assert!(parse_ip("not-an-ip").is_err());
/// ```
pub fn parse_ip(ip: &str) -> Result<IpAddr, std::net::AddrParseError> {
    ip.parse::<IpAddr>()
}

/// Classify an [`IpAddr`] into [`IpScope`].
///
/// IP Flag will only call user resolvers for `Public` addresses.
///
/// # Example
///
/// ```rust
/// use ipflag::{classify_ip, parse_ip, IpScope};
///
/// let ip = parse_ip("192.168.0.1").unwrap();
/// assert_eq!(classify_ip(ip), IpScope::Private);
/// ```
pub fn classify_ip(ip: IpAddr) -> IpScope {
    match ip {
        IpAddr::V4(v4) => classify_v4(v4),
        IpAddr::V6(v6) => classify_v6(v6),
    }
}

/// Classify an IPv4 address into [`IpScope`].
///
/// This function is public so that downstream crates can reuse
/// IP Flag's exact IPv4 classification logic without reimplementing it.
///
/// Classification rules:
/// - Private: RFC1918 + link-local ranges
/// - Special: loopback, multicast, documentation, unspecified, broadcast
/// - Public: everything else
///
/// This function never performs resolution by itself.
pub fn classify_v4(v4: Ipv4Addr) -> IpScope {
    // Private + link-local are treated as Private for UI purposes.
    if v4.is_private() || v4.is_link_local() {
        return IpScope::Private;
    }

    // Special ranges are never resolved.
    if v4.is_loopback()
        || v4.is_multicast()
        || v4.is_unspecified()
        || v4.is_documentation()
        || v4.is_broadcast()
    {
        return IpScope::Special;
    }

    IpScope::Public
}

/// Classify an IPv6 address into [`IpScope`].
///
/// This mirrors the behavior of [`classify_v4`] but for IPv6:
/// - Private: unique-local (fc00::/7) and unicast link-local (fe80::/10)
/// - Special: loopback, multicast, unspecified
/// - Public: all other global unicast addresses
///
/// This function is public to allow reuse by custom resolvers
/// or higher-level network tooling.
pub fn classify_v6(v6: Ipv6Addr) -> IpScope {
    // Unique local + unicast link-local are treated as Private.
    if v6.is_unique_local() || v6.is_unicast_link_local() {
        return IpScope::Private;
    }

    // Special ranges are never resolved.
    if v6.is_loopback() || v6.is_multicast() || v6.is_unspecified() {
        return IpScope::Special;
    }

    IpScope::Public
}