maxminddb_uf/
lib.rs

1pub struct NormalizedDatabase {
2    inner: maxminddb::Reader<Vec<u8>>,
3}
4
5#[allow(unused)]
6impl NormalizedDatabase {
7    pub fn lookup(
8        &self,
9        ip: std::net::IpAddr,
10    ) -> Result<NormalizedCityRecord, maxminddb::MaxMindDBError> {
11        Ok(NormalizedCityRecord {
12            inner: self.inner.lookup(ip)?,
13        })
14    }
15}
16
17pub struct NormalizedCityRecord<'a> {
18    inner: maxminddb::geoip2::City<'a>,
19}
20
21#[allow(unused)]
22impl<'a> NormalizedCityRecord<'a> {
23    /// Returns the country iso code
24    pub fn country_code(&self) -> Option<&str> {
25        self.inner.country.as_ref()?.iso_code
26    }
27
28    /// Returns the country name
29    pub fn country_name(&self, language: Option<&'a str>) -> Option<String> {
30        self.inner
31            .country
32            .as_ref()?
33            .names
34            .as_ref()?
35            .get(language.unwrap_or("en"))
36            .map(|s| s.to_string())
37    }
38
39    /// Returns the registered country iso code of record [`NormalizedCityRecord`].
40    pub fn registered_country_iso_code(&self) -> Option<&str> {
41        self.inner.registered_country.as_ref()?.iso_code
42    }
43
44    /// Returns the registered country name of record [`NormalizedCityRecord`].
45    /// Accepts a language code.
46    /// Returns `None` if the country is not available.
47    pub fn registered_country_name(&self, language: Option<&'a str>) -> Option<String> {
48        self.inner
49            .registered_country
50            .as_ref()?
51            .names
52            .as_ref()?
53            .get(language.unwrap_or("en"))
54            .map(|s| s.to_string())
55    }
56
57    /// Returns the registered country name of record [`NormalizedCityRecord`].
58    /// Returns `None` if the country iso code is not available.
59    pub fn represented_country_iso_code(&self) -> Option<&str> {
60        self.inner.represented_country.as_ref()?.iso_code
61    }
62
63    /// Returns the registered country name of record [`NormalizedCityRecord`].
64    /// Accepts a language code.
65    /// Returns `None` if the country is not available.
66    pub fn represented_country_name(&self, language: Option<&'a str>) -> Option<String> {
67        self.inner
68            .represented_country
69            .as_ref()?
70            .names
71            .as_ref()?
72            .get(language.unwrap_or("en"))
73            .map(|s| s.to_string())
74    }
75
76    /// Returns the city name of record [`NormalizedCityRecord`].
77    /// Accepts a language code.
78    /// Returns `None` if the city is not available.
79    pub fn city_name(&self, language: Option<&'a str>) -> Option<String> {
80        self.inner
81            .city
82            .as_ref()?
83            .names
84            .as_ref()?
85            .get(language.unwrap_or("en"))
86            .map(|s| s.to_string())
87    }
88
89    /// Returns the city geoname id of record [`NormalizedCityRecord`].
90    /// Returns `None` if the city geoname id is not available.
91    pub fn city_geoname_id(&self) -> Option<u32> {
92        self.inner.city.as_ref()?.geoname_id
93    }
94
95    /// Returns the subdivision geoname id of record [`NormalizedCityRecord`].
96    /// Accepts index of subdivision.
97    /// Returns `None` if the subdivision geoname id is not available.
98    pub fn subdivision_geoname_id(&self, idx: usize) -> Option<u32> {
99        self.inner.subdivisions.as_ref()?.get(idx)?.geoname_id
100    }
101
102    /// Returns the subdivision name of record [`NormalizedCityRecord`].
103    /// Accepts index of subdivision and preferred language.
104    /// Returns `None` if the subdivision name is not available.
105    pub fn subdivision_name(&self, idx: usize, language: Option<&'a str>) -> Option<String> {
106        self.inner
107            .subdivisions
108            .as_ref()?
109            .get(idx)?
110            .names
111            .as_ref()?
112            .get(language.unwrap_or("en"))
113            .map(|s| s.to_string())
114    }
115
116    /// Returns the subdivision iso code of record [`NormalizedCityRecord`].
117    /// Accepts index of subdivision.
118    /// Returns `None` if the subdivision iso code is not available.
119    pub fn subdivision_iso_code(&self, idx: usize) -> Option<&str> {
120        self.inner.subdivisions.as_ref()?.get(idx)?.iso_code
121    }
122
123    /// Returns the continent code of record [`NormalizedCityRecord`].
124    /// Returns `None` if the continent code is not available.
125
126    pub fn continent_code(&self) -> Option<&str> {
127        self.inner.continent.as_ref()?.code
128    }
129
130    /// Returns the continent geoname id of record [`NormalizedCityRecord`].
131    /// Returns `None` if the continent geoname id is not available.
132    pub fn continent_geoname_id(&self) -> Option<u32> {
133        self.inner.continent.as_ref()?.geoname_id
134    }
135
136    /// Returns the continent name of record [`NormalizedCityRecord`].
137    /// Accepts a language code.
138    /// Returns `None` if the continent name is not available.
139    pub fn continent_name(&self, language: Option<&'a str>) -> Option<String> {
140        self.inner
141            .continent
142            .as_ref()?
143            .names
144            .as_ref()?
145            .get(language.unwrap_or("en"))
146            .map(|s| s.to_string())
147    }
148
149    /// Returns the postal code of record [`NormalizedCityRecord`].
150    /// Returns `None` if the postal code is not available.
151    pub fn postal_code(&self) -> Option<&str> {
152        self.inner.postal.as_ref()?.code
153    }
154
155    /// Returns the timezone of record [`NormalizedCityRecord`].
156    /// Returns `None` if the timezone is not available.
157    pub fn time_zone(&self) -> Option<&str> {
158        self.inner.location.as_ref()?.time_zone
159    }
160
161    /// Returns the [`Option<(f64, f64)>`] of record [`NormalizedCityRecord`].
162    /// Returns `None` if the latitude is not available.
163    pub fn lon_and_lat(&self) -> Option<(f64, f64)> {
164        Some((
165            self.inner.location.as_ref()?.longitude?,
166            self.inner.location.as_ref()?.latitude?,
167        ))
168    }
169}
170
171impl<'a> From<maxminddb::geoip2::City<'a>> for NormalizedCityRecord<'a> {
172    fn from(city: maxminddb::geoip2::City<'a>) -> Self {
173        NormalizedCityRecord { inner: city }
174    }
175}
176
177impl From<maxminddb::Reader<Vec<u8>>> for NormalizedDatabase {
178    fn from(reader: maxminddb::Reader<Vec<u8>>) -> Self {
179        NormalizedDatabase { inner: reader }
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use std::str::FromStr;
186
187    use crate::NormalizedDatabase;
188
189    #[test]
190    fn lookup_ip() {
191        let db = maxminddb::Reader::open_readfile("./GeoLite2-City.mmdb").unwrap();
192        let ndb = NormalizedDatabase::from(db);
193        let addr = std::net::IpAddr::from_str("1.1.1.1").unwrap();
194
195        let record = ndb.lookup(addr);
196
197        assert!(record.is_ok());
198    }
199
200    #[test]
201    fn validate_results() {
202        let db = maxminddb::Reader::open_readfile("./GeoLite2-City.mmdb").unwrap();
203        let ndb = NormalizedDatabase::from(db);
204        let addr = std::net::IpAddr::from_str("8.8.8.8").unwrap();
205
206        let record = ndb.lookup(addr);
207        assert!(record.is_ok());
208
209        let record = record.unwrap();
210        assert_eq!(Some("America/Chicago"), record.time_zone());
211        assert_eq!(None, record.postal_code());
212        assert_eq!(None, record.city_name(None));
213        assert_eq!(
214            Some("North America".to_string()),
215            record.continent_name(None)
216        );
217        assert_eq!(Some("US"), record.registered_country_iso_code());
218        assert_eq!(
219            Some("United States".to_string()),
220            record.registered_country_name(None)
221        );
222    }
223}