ipflag 0.1.0

Human-friendly IP -> country flag display core (resolver-pluggable, no data bundled).
Documentation
use crate::CountryCode;
use core::fmt;

/// The display result for an IP address.
///
/// - `Country(code)`: resolver found a country
/// - `Private`: local/internal IP (never resolved)
/// - `Special`: loopback/multicast/documentation/etc. (never resolved)
/// - `Unknown`: public IP but resolver returned `None`
///
/// This is the primary type you render in UI/logs.
///
/// # Example
///
/// ```rust
/// use ipflag::{IpTag, CountryCode};
///
/// let tag = IpTag::Country(CountryCode::new("US").unwrap());
/// assert_eq!(tag.to_string(), "πŸ‡ΊπŸ‡Έ US");
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum IpTag {
    /// Public IP successfully resolved to a country code.
    Country(CountryCode),

    /// Private/internal IP range (LAN).
    Private,

    /// Special non-public ranges.
    Special,

    /// Public IP with no country match.
    Unknown,
}

/// Formatting presets for UI output.
///
/// - `FlagAndCode`: `"πŸ‡°πŸ‡· KR"`
/// - `FlagOnly`: `"πŸ‡°πŸ‡·"`
/// - `CodeOnly`: `"KR"`
/// - `DefaultText`: fallback to [`core::fmt::Display`] output
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TagFormat {
    FlagAndCode,
    FlagOnly,
    CodeOnly,
    DefaultText,
}

impl IpTag {
    /// Returns a single emoji representing this tag.
    ///
    /// - Country β†’ the country flag emoji
    /// - Private β†’ 🏠
    /// - Special β†’ βš™οΈ
    /// - Unknown β†’ 🌐
    ///
    /// This is useful when you want a compact UI label.
    pub fn emoji(&self) -> String {
        match self {
            IpTag::Country(code) => code.flag().unwrap_or_else(|| "🌐".to_string()),
            IpTag::Private => "🏠".to_string(),
            IpTag::Special => "βš™οΈ".to_string(),
            IpTag::Unknown => "🌐".to_string(),
        }
    }

    /// Format this tag using a preset [`TagFormat`].
    ///
    /// # Examples
    ///
    /// ```rust
    /// use ipflag::{IpTag, TagFormat, CountryCode};
    ///
    /// let tag = IpTag::Country(CountryCode::new("KR").unwrap());
    /// assert_eq!(tag.format(TagFormat::FlagAndCode), "πŸ‡°πŸ‡· KR");
    /// assert_eq!(tag.format(TagFormat::FlagOnly), "πŸ‡°πŸ‡·");
    /// assert_eq!(tag.format(TagFormat::CodeOnly), "KR");
    /// ```
    pub fn format(&self, fmt: TagFormat) -> String {
        match (self, fmt) {
            (IpTag::Country(code), TagFormat::FlagAndCode) => {
                let flag = code.flag().unwrap_or_else(|| "🌐".to_string());
                format!("{} {}", flag, code.to_string())
            }
            (IpTag::Country(code), TagFormat::FlagOnly) => {
                code.flag().unwrap_or_else(|| "🌐".to_string())
            }
            (IpTag::Country(code), TagFormat::CodeOnly) => code.to_string(),
            _ => self.to_string(),
        }
    }
}

impl fmt::Display for IpTag {
    /// Default display output for logs/UI.
    ///
    /// - Country β†’ `"πŸ‡°πŸ‡· KR"`
    /// - Private β†’ `"🏠 PRIVATE"`
    /// - Special β†’ `"βš™οΈ SPECIAL"`
    /// - Unknown β†’ `"🌐 UNKNOWN"`
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            IpTag::Country(code) => {
                let flag = code.flag().unwrap_or_else(|| "🌐".to_string());
                write!(f, "{} {}", flag, code.to_string())
            }
            IpTag::Private => write!(f, "🏠 PRIVATE"),
            IpTag::Special => write!(f, "βš™οΈ SPECIAL"),
            IpTag::Unknown => write!(f, "🌐 UNKNOWN"),
        }
    }
}