/* * This module provides an abstraction over the maxmind geoip databases * that provides the results ready for templating. */ use maxminddb; use maxminddb::geoip2; use std::collections::BTreeMap; use std::net::IpAddr; /* Datatypes */ // TODO pub enum IsTheNameOf { Continent, Country, Subdivision, City, } #[derive(serde::Deserialize, serde::Serialize, Default)] pub struct NamedLocation { iso_code: Option, name: Option, geoname_id: Option, } #[derive(serde::Deserialize, serde::Serialize, Default)] pub struct LocationCoordinates { latitude: f64, logtitude: f64, } #[derive(serde::Deserialize, serde::Serialize, Default)] pub struct LocationResult { continent: Option, country: Option, registered_country: Option, represented_country: Option, subdivisions: Option>, city: Option, postal_code: Option, time_zone: Option, } #[derive(serde::Deserialize, serde::Serialize, Default)] pub struct AsnResult { asn: Option, name: Option, } struct MMDBCarrier { mmdb: Option>>, } trait QueryLocation { fn query_location_for_ip(&self, address: IpAddr, laguages: &Vec) -> Option; } trait QueryAsn { fn query_asn_for_ip(&self, address: IpAddr) -> Option; } /* Converters */ pub fn extract_localized_name( names: &Option>, languages: &Vec) -> Option { match names { Some(names) => { for language in languages { if let Some(name) = names.get(language.as_str()){ return Some(name.to_string()) } } return None }, None => None } } pub fn geoip2_city_to_named_location(item: geoip2::model::City, languages: &Vec) -> NamedLocation { NamedLocation { iso_code: None, geoname_id: item.geoname_id, name: extract_localized_name(&item.names, languages), } } pub fn geoip2_continent_to_named_location(item: geoip2::model::Continent, languages: &Vec) -> NamedLocation { NamedLocation { iso_code: item.code.map(ToString::to_string), geoname_id: item.geoname_id, name: extract_localized_name(&item.names, languages), } } pub fn geoip2_country_to_named_location(item: geoip2::model::Country, languages: &Vec) -> NamedLocation { NamedLocation { iso_code: item.iso_code.map(ToString::to_string), geoname_id: item.geoname_id, name: extract_localized_name(&item.names, languages), } } pub fn geoip2_represented_country_to_named_location(item: geoip2::model::RepresentedCountry, languages: &Vec) -> NamedLocation { NamedLocation { iso_code: item.iso_code.map(ToString::to_string), geoname_id: item.geoname_id, name: extract_localized_name(&item.names, languages), } } pub fn geoip2_subdivision_to_named_location(item: geoip2::model::Subdivision, languages: &Vec) -> NamedLocation { NamedLocation { iso_code: item.iso_code.map(ToString::to_string), geoname_id: item.geoname_id, name: extract_localized_name(&item.names, languages), } } /* Implementation */ impl QueryAsn for MMDBCarrier { fn query_asn_for_ip(&self, address: IpAddr) -> Option { match self.mmdb { Some(mmdb) => { match mmdb.lookup::(address) { Ok(res) => { Some(AsnResult { asn: res.autonomous_system_number, name: res.autonomous_system_organization.map(ToString::to_string), }) }, Err(e) => { println!("Error while looking up ASN for {address}: {e}"); Default::default() } } }, None => None, } } } impl QueryLocation for MMDBCarrier { fn query_location_for_ip(&self, address: IpAddr, languages: &Vec) -> Option { match self.mmdb { Some(mmdb) => { match mmdb.lookup::(address) { Ok(res) => { Some(LocationResult { continent: res.continent.map(|c| geoip2_continent_to_named_location(c, languages)), country: res.country.map(|c| geoip2_country_to_named_location(c, languages)), registered_country: res.registered_country.map(|c| geoip2_country_to_named_location(c, languages)), represented_country: res.represented_country.map(|c| geoip2_represented_country_to_named_location(c, languages)), city: res.city.map(|c| geoip2_city_to_named_location(c, languages)), subdivisions: match res.subdivisions { Some(sds) => { let subdivisions = Vec::new(); subdivisions.reserve_exact(sds.len()); for sd in sds { subdivisions.push(geoip2_subdivision_to_named_location(sd, languages)); } Some(subdivisions) }, None => None, }, postal_code: match res.postal { Some(p) => p.code.map(ToString::to_string), None => None, }, time_zone: match res.location { Some(loc) => loc.time_zone.map(ToString::to_string), None => None, }, }) }, Err(e) => { println!("Error while looking up ASN for {address}: {e}"); Default::default() } } }, None => None, } } }