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 pub fn country_code(&self) -> Option<&str> {
25 self.inner.country.as_ref()?.iso_code
26 }
27
28 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 pub fn registered_country_iso_code(&self) -> Option<&str> {
41 self.inner.registered_country.as_ref()?.iso_code
42 }
43
44 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 pub fn represented_country_iso_code(&self) -> Option<&str> {
60 self.inner.represented_country.as_ref()?.iso_code
61 }
62
63 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 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 pub fn city_geoname_id(&self) -> Option<u32> {
92 self.inner.city.as_ref()?.geoname_id
93 }
94
95 pub fn subdivision_geoname_id(&self, idx: usize) -> Option<u32> {
99 self.inner.subdivisions.as_ref()?.get(idx)?.geoname_id
100 }
101
102 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 pub fn subdivision_iso_code(&self, idx: usize) -> Option<&str> {
120 self.inner.subdivisions.as_ref()?.get(idx)?.iso_code
121 }
122
123 pub fn continent_code(&self) -> Option<&str> {
127 self.inner.continent.as_ref()?.code
128 }
129
130 pub fn continent_geoname_id(&self) -> Option<u32> {
133 self.inner.continent.as_ref()?.geoname_id
134 }
135
136 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 pub fn postal_code(&self) -> Option<&str> {
152 self.inner.postal.as_ref()?.code
153 }
154
155 pub fn time_zone(&self) -> Option<&str> {
158 self.inner.location.as_ref()?.time_zone
159 }
160
161 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}