#flags #ip-address #human-friendly #resolver #display #logging #real-ip #ship #forum

ipflag

Human-friendly IP -> country flag display core (resolver-pluggable, no data bundled)

1 unstable release

0.1.0 Jan 31, 2026

#125 in Internationalization (i18n)

Apache-2.0

27KB
214 lines

IP Flag (ipflag)

IP Flag is a tiny, resolver-pluggable Rust crate that turns IP addresses into a human-friendly country display (flag + country code). It is designed for communities, forums, comment sections, moderation logs, analytics dashboards, and anywhere an IP string is hard to read at a glance.

The core idea is simple:

  • IP Flag does display.
  • IP Flag does not ship geolocation data.
  • IP Flag does not call external APIs.
  • IP Flag does not require updates.

If you want real “IP → country” resolution, you provide it via a resolver implementation (or via a separate extension crate). This keeps the core maintenance-free and makes the ecosystem extensible.


Why IP Flag exists

Raw IP addresses are noisy and not human-friendly.

In real community moderation, the question is often not “what is the exact IP?”, but “what pattern do we see across many comments?” A country flag or country code makes it easy to scan a thread and quickly notice clusters and anomalies.

IP Flag does not make claims about intent or manipulation. It only helps humans see patterns more clearly.


What IP Flag does (and does not)

IP Flag does

  • Parse IPv4/IPv6
  • Classify IP scope:
    • Private (LAN / internal)
    • Special (loopback, multicast, documentation ranges, etc.)
    • Public (eligible for resolution)
  • if Public, call your resolver and display:
    • Flag emoji (🇰🇷, 🇺🇸, 🇷🇺, ...)
    • Country code (KR, US, RU, ...)
  • Provide formatting helpers for common UI output styles

IP Flag does not

  • Bundle MaxMind or any GeoIP database
  • Contact third-party APIs
  • Track, store, or log anything
  • Decide what is “suspicious”
  • Maintain an ever-changing list of countries or names

Installation

Add the crate to your Cargo.toml:

ipflag = "0.1.0"

Then import what you need:

  • tag_ip / tag_addr to generate tags
  • IpResolver to plug in your own resolution logic
  • NoopResolver for “display-only” usage without resolution

Core usage

Use IP Flag by itself (no resolver data)

This mode is still useful because it cleanly categorizes internal/special addresses and gives you a consistent display output.

Example:

use ipflag::{tag_ip, NoopResolver};

fn main() {
    let t1 = tag_ip(&NoopResolver, "192.168.0.10").unwrap();
    println!("{}", t1); // 🏠 PRIVATE

    let t2 = tag_ip(&NoopResolver, "127.0.0.1").unwrap();
    println!("{}", t2); // ⚙️ SPECIAL

    let t3 = tag_ip(&NoopResolver, "8.8.8.8").unwrap();
    println!("{}", t3); // 🌐 UNKNOWN
}

What you get here is:

  • A reliable “this is internal” / “this is special” / “this is public but unknown” signal
  • A stable output format suitable for UI and logs
  • Zero dependencies and zero update requirements

2) Provide a resolver (real country flags)

To show real countries, you implement one trait: IpResolver.

This resolver can be anything:

  • a MaxMind reader
  • an IP-to-country map from your own data source
  • a corporate CDN header mapping
  • a custom enterprise threat intel source
  • a quick prototype rule-based resolver

Example resolver (hard-coded for demonstration):

use ipflag::{CountryCode, IpResolver, tag_ip};
use std::net::IpAddr;

struct DemoResolver;

impl IpResolver for DemoResolver {
    type Error = core::convert::Infallible;

    fn resolve(&self, _ip: IpAddr) -> Result<Option<CountryCode>, Self::Error> {
        Ok(CountryCode::new("KR"))
    }
}

fn main() {
    let tag = tag_ip(&DemoResolver, "8.8.8.8").unwrap();
    println!("{}", tag); // 🇰🇷 KR
}

How resolution works

IP Flag calls your resolver only for Public IPs.

  • Private IPs never call the resolver → always 🏠 PRIVATE
  • Special IPs never call the resolver → always ⚙️ SPECIAL
  • Public IPs call the resolver:
    • resolver returns Some(CountryCode)IpTag::Country(code) → displays 🇰🇷 KR
    • resolver returns None → IpTag::Unknown → displays 🌐 UNKNOWN

This is important for correctness, performance, and privacy. It prevents “fake resolution” for private and special ranges and keeps your resolver focused on real public IPs only.


Output formats (UI-friendly)

IpTag implements Display, so you can directly print it:

println!("{}", tag); // default output

If you want different display styles, use TagFormat:

use ipflag::{TagFormat};

println!("{}", tag.format(TagFormat::FlagAndCode)); // "🇰🇷 KR"
println!("{}", tag.format(TagFormat::FlagOnly));    // "🇰🇷"
println!("{}", tag.format(TagFormat::CodeOnly));    // "KR"

For non-country tags, the default output is:

  • 🏠 PRIVATE
  • ⚙️ SPECIAL
  • 🌐 UNKNOWN

This is intentional: it keeps log output consistent and predictable.


Minimal “community comment display” example

A typical usage is to append the tag next to a comment’s “anonymous” label:

use ipflag::{tag_ip, NoopResolver, TagFormat};

fn display_anonymous(ip: &str) -> String {
    let tag = tag_ip(&NoopResolver, ip).unwrap();
    format!("Anonymous {}", tag.format(TagFormat::FlagOnly))
}

fn main() {
    println!("{}", display_anonymous("192.168.1.10")); // Anonymous 🏠
    println!("{}", display_anonymous("8.8.8.8"));      // Anonymous 🌐
}

In production, replace NoopResolver with your real resolver implementation.


Privacy and security notes

  • IP Flag does not perform network requests.
  • IP Flag does not store IPs.
  • IP Flag does not log anything.
  • Your resolver may, depending on your implementation.

In community settings, you should consider privacy laws and policies. A common approach is masking IPs in UI and logs while still using flags/codes for pattern visibility.


Non-goals

  • “Detect manipulation” or “label someone as suspicious”
  • Provide absolute truth about user identity or nationality
  • Replace moderation judgment
  • Provide a global geolocation database

IP Flag is a display utility. The interpretation is yours.


Contributing

Contributions that keep the core minimal and stable are welcome:

  • Bug fixes
  • Better IPv4/IPv6 scope classification edge cases
  • Output formatting improvements
  • More examples and documentation

Anything that adds bundled data sources or external network calls should live in extension crates, not in the core.


License

Apache-2.0

No runtime deps