use forge_core::ForgeError;
use std::net::IpAddr;
use std::path::Path;
use std::sync::Arc;
#[derive(Debug, Clone, Default)]
pub struct GeoInfo {
pub country: Option<String>,
pub city: Option<String>,
}
#[cfg(feature = "geoip")]
enum Backend {
Embedded(db_ip::DbIpDatabase<db_ip::CountryCode>),
Mmdb(maxminddb::Reader<Vec<u8>>),
}
#[cfg(not(feature = "geoip"))]
enum Backend {
Stub,
}
#[derive(Clone)]
pub struct GeoIpResolver {
backend: Arc<Backend>,
}
impl Default for GeoIpResolver {
fn default() -> Self {
Self::new()
}
}
impl GeoIpResolver {
pub fn new() -> Self {
#[cfg(feature = "geoip")]
{
Self {
backend: Arc::new(Backend::Embedded(db_ip::include_country_code_database!())),
}
}
#[cfg(not(feature = "geoip"))]
{
Self {
backend: Arc::new(Backend::Stub),
}
}
}
#[cfg(feature = "geoip")]
pub fn from_mmdb(path: &Path) -> Result<Self, ForgeError> {
let reader = maxminddb::Reader::open_readfile(path).map_err(|e| {
ForgeError::config_with(
format!("failed to load GeoIP database {}", path.display()),
e,
)
})?;
Ok(Self {
backend: Arc::new(Backend::Mmdb(reader)),
})
}
#[cfg(not(feature = "geoip"))]
pub fn from_mmdb(_path: &Path) -> Result<Self, ForgeError> {
Err(ForgeError::config(
"geoip MMDB support requires the `geoip` feature on forge-runtime",
))
}
pub fn lookup(&self, ip_str: &str) -> GeoInfo {
let _ip: IpAddr = match ip_str.parse() {
Ok(ip) => ip,
Err(_) => return GeoInfo::default(),
};
match self.backend.as_ref() {
#[cfg(feature = "geoip")]
Backend::Embedded(db) => GeoInfo {
country: db.get(&_ip).map(|c| c.as_str().to_string()),
city: None,
},
#[cfg(feature = "geoip")]
Backend::Mmdb(reader) => match reader.lookup(_ip) {
Ok(lookup) => match lookup.decode::<maxminddb::geoip2::City>() {
Ok(Some(record)) => GeoInfo {
country: record.country.iso_code.map(|s| s.to_string()),
city: record.city.names.english.map(|s| s.to_string()),
},
_ => GeoInfo::default(),
},
Err(_) => GeoInfo::default(),
},
#[cfg(not(feature = "geoip"))]
Backend::Stub => GeoInfo::default(),
}
}
}