relay_event_normalization/
geo.rs1use std::fmt;
2use std::path::Path;
3
4use relay_event_schema::protocol::Geo;
5use relay_protocol::Annotated;
6
7#[cfg(feature = "mmap")]
8type ReaderType = maxminddb::Mmap;
9
10#[cfg(not(feature = "mmap"))]
11type ReaderType = Vec<u8>;
12
13pub type GeoIpError = maxminddb::MaxMindDBError;
15
16pub struct GeoIpLookup(maxminddb::Reader<ReaderType>);
18
19impl GeoIpLookup {
20 pub fn open<P>(path: P) -> Result<Self, GeoIpError>
22 where
23 P: AsRef<Path>,
24 {
25 #[cfg(feature = "mmap")]
26 let reader = maxminddb::Reader::open_mmap(path)?;
27 #[cfg(not(feature = "mmap"))]
28 let reader = maxminddb::Reader::open_readfile(path)?;
29 Ok(GeoIpLookup(reader))
30 }
31
32 pub fn lookup(&self, ip_address: &str) -> Result<Option<Geo>, GeoIpError> {
34 let ip_address = match ip_address.parse() {
36 Ok(x) => x,
37 Err(_) => return Ok(None),
38 };
39
40 let city: maxminddb::geoip2::City = match self.0.lookup(ip_address) {
41 Ok(x) => x,
42 Err(GeoIpError::AddressNotFoundError(_)) => return Ok(None),
43 Err(e) => return Err(e),
44 };
45
46 Ok(Some(Geo {
47 country_code: Annotated::from(
48 city.country
49 .as_ref()
50 .and_then(|country| Some(country.iso_code.as_ref()?.to_string())),
51 ),
52 city: Annotated::from(
53 city.city
54 .as_ref()
55 .and_then(|city| Some(city.names.as_ref()?.get("en")?.to_string())),
56 ),
57 subdivision: Annotated::from(city.subdivisions.as_ref().and_then(|subdivisions| {
58 subdivisions.first().and_then(|subdivision| {
59 subdivision.names.as_ref().and_then(|subdivision_names| {
60 subdivision_names
61 .get("en")
62 .map(|subdivision_name| subdivision_name.to_string())
63 })
64 })
65 })),
66 region: Annotated::from(
67 city.country
68 .as_ref()
69 .and_then(|country| Some(country.names.as_ref()?.get("en")?.to_string())),
70 ),
71 ..Default::default()
72 }))
73 }
74}
75
76impl fmt::Debug for GeoIpLookup {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 f.debug_struct("GeoIpLookup").finish()
79 }
80}