flexd 0.2.3

Hardened web server and reverse proxy: TLS/HTTP3, ACME, rate limiting, host policy, GeoIP, A/B splitting, privilege dropping
Documentation
use anyhow::{Context, Result};
use maxminddb::path;
use std::net::IpAddr;
use std::path::Path;
use std::sync::Arc;

pub struct GeoIpService {
    reader: Arc<maxminddb::Reader<Vec<u8>>>,
}

impl GeoIpService {
    pub fn new(db_path: &str) -> Result<Self> {
        let path = Path::new(db_path);
        if !path.exists() {
            anyhow::bail!("GeoIP database not found: {}", db_path);
        }

        let reader = maxminddb::Reader::open_readfile(path)
            .with_context(|| format!("Failed to open GeoIP database: {}", db_path))?;

        Ok(Self {
            reader: Arc::new(reader),
        })
    }

    pub fn lookup(&self, ip: IpAddr) -> Option<GeoIpInfo> {
        let lookup = self.reader.lookup(ip).ok()?;
        if !lookup.has_data() {
            return None;
        }

        let country_iso: Option<String> = lookup.decode_path(&path!["country", "iso_code"]).ok().flatten();
        let country_name: Option<String> = lookup.decode_path(&path!["country", "names", "en"]).ok().flatten();
        let city_name: Option<String> = lookup.decode_path(&path!["city", "names", "en"]).ok().flatten();
        let continent_name: Option<String> = lookup.decode_path(&path!["continent", "names", "en"]).ok().flatten();

        Some(GeoIpInfo {
            country: country_name,
            city: city_name,
            continent: continent_name,
            iso_code: country_iso,
        })
    }
}

#[derive(Debug, Clone)]
pub struct GeoIpInfo {
    pub country: Option<String>,
    pub city: Option<String>,
    pub continent: Option<String>,
    pub iso_code: Option<String>,
}