relay_event_normalization/
geo.rs

1use 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
13/// An error in the `GeoIpLookup`.
14pub type GeoIpError = maxminddb::MaxMindDBError;
15
16/// A geo ip lookup helper based on maxmind db files.
17pub struct GeoIpLookup(maxminddb::Reader<ReaderType>);
18
19impl GeoIpLookup {
20    /// Opens a maxminddb file by path.
21    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    /// Looks up an IP address.
33    pub fn lookup(&self, ip_address: &str) -> Result<Option<Geo>, GeoIpError> {
34        // XXX: Why do we parse the IP again after deserializing?
35        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}