Skip to main content

maxminddb/
geoip2.rs

1//! GeoIP2 and GeoLite2 database record structures
2//!
3//! This module provides strongly-typed Rust structures that correspond to the
4//! various GeoIP2 and GeoLite2 database record formats.
5//!
6//! # Record Types
7//!
8//! - [`City`] - Complete city-level geolocation data (most comprehensive)
9//! - [`Country`] - Country-level geolocation data
10//! - [`Enterprise`] - Enterprise database with additional confidence scores
11//! - [`Isp`] - Internet Service Provider information
12//! - [`AnonymousIp`] - Anonymous proxy and VPN detection
13//! - [`ConnectionType`] - Connection type classification
14//! - [`Domain`] - Domain information
15//! - [`Asn`] - Autonomous System Number data
16//! - [`DensityIncome`] - Population density and income data
17//!
18//! # Usage Examples
19//!
20//! ```rust
21//! use maxminddb::{Reader, geoip2};
22//! use std::net::IpAddr;
23//!
24//! # fn main() -> Result<(), maxminddb::MaxMindDbError> {
25//! let reader = Reader::open_readfile(
26//!     "test-data/test-data/GeoIP2-City-Test.mmdb")?;
27//! let ip: IpAddr = "89.160.20.128".parse().unwrap();
28//!
29//! // City lookup - nested structs are always present (default to empty)
30//! let result = reader.lookup(ip)?;
31//! if let Some(city) = result.decode::<geoip2::City>()? {
32//!     // Direct access to nested structs - no Option unwrapping needed
33//!     if let Some(name) = city.city.names.english {
34//!         println!("City: {}", name);
35//!     }
36//!     if let Some(code) = city.country.iso_code {
37//!         println!("Country: {}", code);
38//!     }
39//!     // Subdivisions is a Vec, empty if not present
40//!     for sub in &city.subdivisions {
41//!         if let Some(code) = sub.iso_code {
42//!             println!("Subdivision: {}", code);
43//!         }
44//!     }
45//! }
46//!
47//! // Country-only lookup (smaller/faster)
48//! let result = reader.lookup(ip)?;
49//! if let Some(country) = result.decode::<geoip2::Country>()? {
50//!     if let Some(name) = country.country.names.english {
51//!         println!("Country: {}", name);
52//!     }
53//! }
54//! # Ok(())
55//! # }
56//! ```
57
58use std::fmt;
59use std::marker::PhantomData;
60
61use serde::de::{IgnoredAny, SeqAccess, Visitor};
62use serde::{Deserialize, Deserializer, Serialize};
63
64/// Maximum number of subdivisions accepted while deserializing the built-in
65/// City and Enterprise schemas.
66///
67/// Geographic records ordinarily contain only a small administrative
68/// hierarchy. This input constraint applies to every Serde format, not only
69/// MaxMind DB. The record fields remain public `Vec`s, so callers may still
70/// construct and serialize a value above the limit; deserializing it again will
71/// fail. Custom schemas should likewise apply a semantic limit to collection
72/// fields when decoding data that is not trusted.
73///
74/// An otherwise valid oversized MMDB list that reaches this visitor is reported
75/// as [`crate::MaxMindDbError::Decoding`]. Malformed input may instead produce
76/// [`crate::MaxMindDbError::InvalidDatabase`], and decoder-wide limits reported
77/// as [`crate::MaxMindDbError::ResourceLimit`] take precedence. Other Serde
78/// formats report their own deserializer error type.
79pub const MAX_SUBDIVISIONS: usize = 32;
80
81#[cold]
82fn subdivisions_too_long<E>() -> E
83where
84    E: serde::de::Error,
85{
86    E::custom(format_args!(
87        "subdivisions exceeds maximum length of {MAX_SUBDIVISIONS}"
88    ))
89}
90
91#[inline(always)]
92fn deserialize_subdivisions<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
93where
94    D: Deserializer<'de>,
95    T: Deserialize<'de>,
96{
97    struct SubdivisionsVisitor<T>(PhantomData<T>);
98
99    impl<'de, T> Visitor<'de> for SubdivisionsVisitor<T>
100    where
101        T: Deserialize<'de>,
102    {
103        type Value = Vec<T>;
104
105        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
106            write!(
107                formatter,
108                "an array containing at most {MAX_SUBDIVISIONS} subdivisions"
109            )
110        }
111
112        #[inline(always)]
113        fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
114        where
115            A: SeqAccess<'de>,
116        {
117            if let Some(size) = sequence.size_hint() {
118                if size > MAX_SUBDIVISIONS {
119                    return Err(subdivisions_too_long());
120                }
121
122                let mut subdivisions = Vec::with_capacity(size);
123                while let Some(subdivision) = sequence.next_element()? {
124                    subdivisions.push(subdivision);
125                }
126                return Ok(subdivisions);
127            }
128
129            let mut subdivisions = Vec::new();
130            while subdivisions.len() < MAX_SUBDIVISIONS {
131                let Some(subdivision) = sequence.next_element()? else {
132                    return Ok(subdivisions);
133                };
134                subdivisions.push(subdivision);
135            }
136
137            if sequence.next_element::<IgnoredAny>()?.is_some() {
138                return Err(subdivisions_too_long());
139            }
140            Ok(subdivisions)
141        }
142    }
143
144    deserializer.deserialize_seq(SubdivisionsVisitor(PhantomData))
145}
146
147/// Localized names for geographic entities.
148///
149/// Contains name translations in the languages supported by MaxMind databases.
150/// Access names directly via fields like `names.english` or `names.german`.
151/// Each field is `Option<&str>` - `None` if not available in that language.
152///
153/// # Example
154///
155/// ```
156/// use maxminddb::{Reader, geoip2};
157/// use std::net::IpAddr;
158///
159/// let reader = Reader::open_readfile("test-data/test-data/GeoIP2-City-Test.mmdb").unwrap();
160/// let ip: IpAddr = "89.160.20.128".parse().unwrap();
161/// let result = reader.lookup(ip).unwrap();
162///
163/// if let Some(city) = result.decode::<geoip2::City>().unwrap() {
164///     // Access names directly - Option<&str>
165///     if let Some(name) = city.city.names.english {
166///         println!("City (en): {}", name);
167///     }
168///     if let Some(name) = city.city.names.german {
169///         println!("City (de): {}", name);
170///     }
171/// }
172/// ```
173#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq, Eq)]
174pub struct Names<'a> {
175    /// German name (de)
176    #[serde(
177        borrow,
178        rename = "de",
179        default,
180        skip_serializing_if = "Option::is_none"
181    )]
182    pub german: Option<&'a str>,
183    /// English name (en)
184    #[serde(rename = "en", default, skip_serializing_if = "Option::is_none")]
185    pub english: Option<&'a str>,
186    /// Spanish name (es)
187    #[serde(rename = "es", default, skip_serializing_if = "Option::is_none")]
188    pub spanish: Option<&'a str>,
189    /// French name (fr)
190    #[serde(rename = "fr", default, skip_serializing_if = "Option::is_none")]
191    pub french: Option<&'a str>,
192    /// Japanese name (ja)
193    #[serde(rename = "ja", default, skip_serializing_if = "Option::is_none")]
194    pub japanese: Option<&'a str>,
195    /// Brazilian Portuguese name (pt-BR)
196    #[serde(rename = "pt-BR", default, skip_serializing_if = "Option::is_none")]
197    pub brazilian_portuguese: Option<&'a str>,
198    /// Russian name (ru)
199    #[serde(rename = "ru", default, skip_serializing_if = "Option::is_none")]
200    pub russian: Option<&'a str>,
201    /// Simplified Chinese name (zh-CN)
202    #[serde(rename = "zh-CN", default, skip_serializing_if = "Option::is_none")]
203    pub simplified_chinese: Option<&'a str>,
204}
205
206impl Names<'_> {
207    /// Returns true if all name fields are `None`.
208    #[must_use]
209    pub fn is_empty(&self) -> bool {
210        self.german.is_none()
211            && self.english.is_none()
212            && self.spanish.is_none()
213            && self.french.is_none()
214            && self.japanese.is_none()
215            && self.brazilian_portuguese.is_none()
216            && self.russian.is_none()
217            && self.simplified_chinese.is_none()
218    }
219}
220
221macro_rules! impl_is_empty_via_default {
222    ($ty:ty) => {
223        impl $ty {
224            /// Returns true if all fields are empty/None.
225            #[must_use]
226            pub fn is_empty(&self) -> bool {
227                *self == Self::default()
228            }
229        }
230    };
231}
232
233/// GeoIP2/GeoLite2 Country database record.
234///
235/// Contains country-level geolocation data for an IP address. This is the
236/// simplest geolocation record type, suitable when you only need country
237/// information.
238#[derive(Deserialize, Serialize, Clone, Debug, Default)]
239pub struct Country<'a> {
240    /// Continent data for the IP address.
241    #[serde(borrow, default, skip_serializing_if = "country::Continent::is_empty")]
242    pub continent: country::Continent<'a>,
243    /// Country where MaxMind believes the IP is located.
244    #[serde(default, skip_serializing_if = "country::Country::is_empty")]
245    pub country: country::Country<'a>,
246    /// Country where the ISP has registered the IP block.
247    /// May differ from `country` (e.g., for mobile networks or VPNs).
248    #[serde(default, skip_serializing_if = "country::Country::is_empty")]
249    pub registered_country: country::Country<'a>,
250    /// Country represented by users of this IP (e.g., military base or embassy).
251    #[serde(default, skip_serializing_if = "country::RepresentedCountry::is_empty")]
252    pub represented_country: country::RepresentedCountry<'a>,
253    /// Various traits associated with the IP address.
254    #[serde(default, skip_serializing_if = "country::Traits::is_empty")]
255    pub traits: country::Traits,
256}
257
258/// GeoIP2/GeoLite2 City database record.
259///
260/// Contains city-level geolocation data including location coordinates,
261/// postal code, subdivisions (states/provinces), and country information.
262/// This is the most comprehensive free geolocation record type.
263#[derive(Deserialize, Serialize, Clone, Debug, Default)]
264pub struct City<'a> {
265    /// City data for the IP address.
266    #[serde(borrow, default, skip_serializing_if = "city::City::is_empty")]
267    pub city: city::City<'a>,
268    /// Continent data for the IP address.
269    #[serde(default, skip_serializing_if = "city::Continent::is_empty")]
270    pub continent: city::Continent<'a>,
271    /// Country where MaxMind believes the IP is located.
272    #[serde(default, skip_serializing_if = "city::Country::is_empty")]
273    pub country: city::Country<'a>,
274    /// Location data including coordinates and time zone.
275    #[serde(default, skip_serializing_if = "city::Location::is_empty")]
276    pub location: city::Location<'a>,
277    /// Postal code data for the IP address.
278    #[serde(default, skip_serializing_if = "city::Postal::is_empty")]
279    pub postal: city::Postal<'a>,
280    /// Country where the ISP has registered the IP block.
281    #[serde(default, skip_serializing_if = "city::Country::is_empty")]
282    pub registered_country: city::Country<'a>,
283    /// Country represented by users of this IP (e.g., military base or embassy).
284    #[serde(default, skip_serializing_if = "city::RepresentedCountry::is_empty")]
285    pub represented_country: city::RepresentedCountry<'a>,
286    /// Subdivisions (states, provinces, etc.) ordered from largest to smallest.
287    /// For example, Oxford, UK would have England first, then Oxfordshire.
288    /// Deserialization through any Serde format accepts at most
289    /// [`MAX_SUBDIVISIONS`] entries.
290    #[serde(
291        borrow,
292        default,
293        deserialize_with = "deserialize_subdivisions",
294        skip_serializing_if = "Vec::is_empty"
295    )]
296    pub subdivisions: Vec<city::Subdivision<'a>>,
297    /// Various traits associated with the IP address.
298    #[serde(default, skip_serializing_if = "city::Traits::is_empty")]
299    pub traits: city::Traits,
300}
301
302/// GeoIP2 Enterprise database record.
303///
304/// Contains all City data plus additional confidence scores and traits.
305/// Enterprise records include confidence values (0-100) indicating MaxMind's
306/// certainty about the accuracy of each field.
307#[derive(Deserialize, Serialize, Clone, Debug, Default)]
308pub struct Enterprise<'a> {
309    /// City data with confidence score.
310    #[serde(borrow, default, skip_serializing_if = "enterprise::City::is_empty")]
311    pub city: enterprise::City<'a>,
312    /// Continent data for the IP address.
313    #[serde(default, skip_serializing_if = "enterprise::Continent::is_empty")]
314    pub continent: enterprise::Continent<'a>,
315    /// Country data with confidence score.
316    #[serde(default, skip_serializing_if = "enterprise::Country::is_empty")]
317    pub country: enterprise::Country<'a>,
318    /// Location data including coordinates and time zone.
319    #[serde(default, skip_serializing_if = "enterprise::Location::is_empty")]
320    pub location: enterprise::Location<'a>,
321    /// Postal code data with confidence score.
322    #[serde(default, skip_serializing_if = "enterprise::Postal::is_empty")]
323    pub postal: enterprise::Postal<'a>,
324    /// Country where the ISP has registered the IP block.
325    #[serde(default, skip_serializing_if = "enterprise::Country::is_empty")]
326    pub registered_country: enterprise::Country<'a>,
327    /// Country represented by users of this IP (e.g., military base or embassy).
328    #[serde(
329        default,
330        skip_serializing_if = "enterprise::RepresentedCountry::is_empty"
331    )]
332    pub represented_country: enterprise::RepresentedCountry<'a>,
333    /// Subdivisions with confidence scores, ordered from largest to smallest.
334    /// Deserialization through any Serde format accepts at most
335    /// [`MAX_SUBDIVISIONS`] entries.
336    #[serde(
337        borrow,
338        default,
339        deserialize_with = "deserialize_subdivisions",
340        skip_serializing_if = "Vec::is_empty"
341    )]
342    pub subdivisions: Vec<enterprise::Subdivision<'a>>,
343    /// Extended traits including ISP, organization, and connection information.
344    #[serde(default, skip_serializing_if = "enterprise::Traits::is_empty")]
345    pub traits: enterprise::Traits<'a>,
346}
347
348/// GeoIP2 ISP database record.
349///
350/// Contains Internet Service Provider and organization information for an IP.
351#[derive(Deserialize, Serialize, Clone, Debug)]
352pub struct Isp<'a> {
353    /// The autonomous system number (ASN) for the IP address.
354    #[serde(skip_serializing_if = "Option::is_none")]
355    pub autonomous_system_number: Option<u32>,
356    /// The organization associated with the registered ASN.
357    #[serde(skip_serializing_if = "Option::is_none")]
358    pub autonomous_system_organization: Option<&'a str>,
359    /// The name of the ISP associated with the IP address.
360    #[serde(skip_serializing_if = "Option::is_none")]
361    pub isp: Option<&'a str>,
362    /// The mobile country code (MCC) associated with the IP.
363    /// See <https://en.wikipedia.org/wiki/Mobile_country_code>.
364    #[serde(skip_serializing_if = "Option::is_none")]
365    pub mobile_country_code: Option<&'a str>,
366    /// The mobile network code (MNC) associated with the IP.
367    /// See <https://en.wikipedia.org/wiki/Mobile_network_code>.
368    #[serde(skip_serializing_if = "Option::is_none")]
369    pub mobile_network_code: Option<&'a str>,
370    /// The name of the organization associated with the IP address.
371    #[serde(skip_serializing_if = "Option::is_none")]
372    pub organization: Option<&'a str>,
373}
374
375/// GeoIP2 Connection-Type database record.
376///
377/// Contains the connection type for an IP address.
378#[derive(Deserialize, Serialize, Clone, Debug)]
379pub struct ConnectionType<'a> {
380    /// The connection type. Possible values include "Dialup", "Cable/DSL",
381    /// "Corporate", "Cellular", and "Satellite". Additional values may be
382    /// added in the future.
383    #[serde(skip_serializing_if = "Option::is_none")]
384    pub connection_type: Option<&'a str>,
385}
386
387/// GeoIP2 Anonymous IP database record.
388///
389/// Contains information about whether an IP address is associated with
390/// anonymous or proxy services.
391#[derive(Deserialize, Serialize, Clone, Debug)]
392pub struct AnonymousIp {
393    /// True if the IP belongs to any sort of anonymous network.
394    #[serde(skip_serializing_if = "Option::is_none")]
395    pub is_anonymous: Option<bool>,
396    /// True if the IP is registered to an anonymous VPN provider.
397    /// Note: If a VPN provider does not register subnets under names associated
398    /// with them, we will likely only flag their IP ranges using `is_hosting_provider`.
399    #[serde(skip_serializing_if = "Option::is_none")]
400    pub is_anonymous_vpn: Option<bool>,
401    /// True if the IP belongs to a hosting or VPN provider.
402    #[serde(skip_serializing_if = "Option::is_none")]
403    pub is_hosting_provider: Option<bool>,
404    /// True if the IP belongs to a public proxy.
405    #[serde(skip_serializing_if = "Option::is_none")]
406    pub is_public_proxy: Option<bool>,
407    /// True if the IP is on a suspected anonymizing network and belongs to
408    /// a residential ISP.
409    #[serde(skip_serializing_if = "Option::is_none")]
410    pub is_residential_proxy: Option<bool>,
411    /// True if the IP is a Tor exit node.
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub is_tor_exit_node: Option<bool>,
414}
415
416/// GeoIP2 DensityIncome database record.
417///
418/// Contains population density and income data for an IP address location.
419#[derive(Deserialize, Serialize, Clone, Debug)]
420pub struct DensityIncome {
421    /// The average income in US dollars associated with the IP address.
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub average_income: Option<u32>,
424    /// The estimated number of people per square kilometer.
425    #[serde(skip_serializing_if = "Option::is_none")]
426    pub population_density: Option<u32>,
427}
428
429/// GeoIP2 Domain database record.
430///
431/// Contains the second-level domain associated with an IP address.
432#[derive(Deserialize, Serialize, Clone, Debug)]
433pub struct Domain<'a> {
434    /// The second-level domain associated with the IP address
435    /// (e.g., "example.com").
436    #[serde(skip_serializing_if = "Option::is_none")]
437    pub domain: Option<&'a str>,
438}
439
440/// GeoLite2 ASN database record.
441///
442/// Contains Autonomous System Number (ASN) data for an IP address.
443#[derive(Deserialize, Serialize, Clone, Debug)]
444pub struct Asn<'a> {
445    /// The autonomous system number for the IP address.
446    #[serde(skip_serializing_if = "Option::is_none")]
447    pub autonomous_system_number: Option<u32>,
448    /// The organization associated with the registered ASN.
449    #[serde(skip_serializing_if = "Option::is_none")]
450    pub autonomous_system_organization: Option<&'a str>,
451}
452
453/// Country/City database model structs.
454///
455/// These structs are used by both [`crate::geoip2::Country`] and
456/// [`crate::geoip2::City`] records.
457pub mod country {
458    use super::Names;
459    use serde::{Deserialize, Serialize};
460
461    /// Continent data for an IP address.
462    #[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
463    pub struct Continent<'a> {
464        /// Two-character continent code (e.g., "NA" for North America, "EU" for Europe).
465        #[serde(default, skip_serializing_if = "Option::is_none")]
466        pub code: Option<&'a str>,
467        /// GeoNames ID for the continent.
468        #[serde(default, skip_serializing_if = "Option::is_none")]
469        pub geoname_id: Option<u32>,
470        /// Localized continent names.
471        #[serde(borrow, default, skip_serializing_if = "Names::is_empty")]
472        pub names: Names<'a>,
473    }
474
475    impl_is_empty_via_default!(Continent<'_>);
476
477    /// Country data for an IP address.
478    #[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
479    pub struct Country<'a> {
480        /// GeoNames ID for the country.
481        #[serde(default, skip_serializing_if = "Option::is_none")]
482        pub geoname_id: Option<u32>,
483        /// True if the country is a member state of the European Union.
484        #[serde(default, skip_serializing_if = "Option::is_none")]
485        pub is_in_european_union: Option<bool>,
486        /// Two-character ISO 3166-1 alpha-2 country code.
487        /// See <https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2>.
488        #[serde(default, skip_serializing_if = "Option::is_none")]
489        pub iso_code: Option<&'a str>,
490        /// Localized country names.
491        #[serde(borrow, default, skip_serializing_if = "Names::is_empty")]
492        pub names: Names<'a>,
493    }
494
495    impl_is_empty_via_default!(Country<'_>);
496
497    /// Represented country data.
498    ///
499    /// The represented country is the country represented by something like a
500    /// military base or embassy.
501    #[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
502    pub struct RepresentedCountry<'a> {
503        /// GeoNames ID for the represented country.
504        #[serde(default, skip_serializing_if = "Option::is_none")]
505        pub geoname_id: Option<u32>,
506        /// True if the represented country is a member state of the European Union.
507        #[serde(default, skip_serializing_if = "Option::is_none")]
508        pub is_in_european_union: Option<bool>,
509        /// Two-character ISO 3166-1 alpha-2 country code.
510        /// See <https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2>.
511        #[serde(default, skip_serializing_if = "Option::is_none")]
512        pub iso_code: Option<&'a str>,
513        /// Localized country names.
514        #[serde(borrow, default, skip_serializing_if = "Names::is_empty")]
515        pub names: Names<'a>,
516        /// Type of entity representing the country (e.g., "military").
517        #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
518        pub representation_type: Option<&'a str>,
519    }
520
521    impl_is_empty_via_default!(RepresentedCountry<'_>);
522
523    /// Traits data for Country/City records.
524    #[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
525    pub struct Traits {
526        /// True if the IP belongs to an anycast network.
527        /// See <https://en.wikipedia.org/wiki/Anycast>.
528        #[serde(default, skip_serializing_if = "Option::is_none")]
529        pub is_anycast: Option<bool>,
530    }
531
532    impl_is_empty_via_default!(Traits);
533}
534
535/// City database model structs.
536///
537/// City-specific structs. Country-level structs are re-exported from
538/// [`crate::geoip2::country`].
539pub mod city {
540    use super::Names;
541    use serde::{Deserialize, Serialize};
542
543    pub use super::country::{Continent, Country, RepresentedCountry, Traits};
544
545    /// City data for an IP address.
546    #[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
547    pub struct City<'a> {
548        /// GeoNames ID for the city.
549        #[serde(default, skip_serializing_if = "Option::is_none")]
550        pub geoname_id: Option<u32>,
551        /// Localized city names.
552        #[serde(borrow, default, skip_serializing_if = "Names::is_empty")]
553        pub names: Names<'a>,
554    }
555
556    impl_is_empty_via_default!(City<'_>);
557
558    /// Location data for an IP address.
559    #[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
560    pub struct Location<'a> {
561        /// Approximate accuracy radius in kilometers around the coordinates.
562        /// This is the radius where we have a 67% confidence that the device
563        /// using the IP address resides within.
564        #[serde(default, skip_serializing_if = "Option::is_none")]
565        pub accuracy_radius: Option<u16>,
566        /// Approximate latitude of the location. This value is not precise and
567        /// should not be used to identify a particular address or household.
568        #[serde(default, skip_serializing_if = "Option::is_none")]
569        pub latitude: Option<f64>,
570        /// Approximate longitude of the location. This value is not precise and
571        /// should not be used to identify a particular address or household.
572        #[serde(default, skip_serializing_if = "Option::is_none")]
573        pub longitude: Option<f64>,
574        /// Metro code for the location, used for targeting advertisements.
575        ///
576        /// **Deprecated:** Metro codes are no longer maintained and should not be used.
577        #[serde(default, skip_serializing_if = "Option::is_none")]
578        pub metro_code: Option<u16>,
579        /// Time zone associated with the location, as specified by the
580        /// IANA Time Zone Database (e.g., "America/New_York").
581        #[serde(default, skip_serializing_if = "Option::is_none")]
582        pub time_zone: Option<&'a str>,
583    }
584
585    impl_is_empty_via_default!(Location<'_>);
586
587    /// Postal data for an IP address.
588    #[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
589    pub struct Postal<'a> {
590        /// Postal code for the location. Not available for all countries.
591        /// In some countries, this will only contain part of the postal code.
592        #[serde(default, skip_serializing_if = "Option::is_none")]
593        pub code: Option<&'a str>,
594    }
595
596    impl_is_empty_via_default!(Postal<'_>);
597
598    /// Subdivision (state, province, etc.) data for an IP address.
599    #[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
600    pub struct Subdivision<'a> {
601        /// GeoNames ID for the subdivision.
602        #[serde(default, skip_serializing_if = "Option::is_none")]
603        pub geoname_id: Option<u32>,
604        /// ISO 3166-2 subdivision code (up to 3 characters).
605        /// See <https://en.wikipedia.org/wiki/ISO_3166-2>.
606        #[serde(default, skip_serializing_if = "Option::is_none")]
607        pub iso_code: Option<&'a str>,
608        /// Localized subdivision names.
609        #[serde(borrow, default, skip_serializing_if = "Names::is_empty")]
610        pub names: Names<'a>,
611    }
612
613    impl_is_empty_via_default!(Subdivision<'_>);
614}
615
616/// Enterprise database model structs.
617///
618/// Enterprise-specific structs with confidence scores. Some structs are
619/// re-exported from [`crate::geoip2::country`].
620pub mod enterprise {
621    use super::Names;
622    use serde::{Deserialize, Serialize};
623
624    pub use super::country::{Continent, RepresentedCountry};
625
626    /// City data with confidence score.
627    #[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
628    pub struct City<'a> {
629        /// Confidence score (0-100) indicating MaxMind's certainty that the
630        /// city is correct.
631        #[serde(default, skip_serializing_if = "Option::is_none")]
632        pub confidence: Option<u8>,
633        /// GeoNames ID for the city.
634        #[serde(default, skip_serializing_if = "Option::is_none")]
635        pub geoname_id: Option<u32>,
636        /// Localized city names.
637        #[serde(borrow, default, skip_serializing_if = "Names::is_empty")]
638        pub names: Names<'a>,
639    }
640
641    impl_is_empty_via_default!(City<'_>);
642
643    /// Country data with confidence score.
644    #[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
645    pub struct Country<'a> {
646        /// Confidence score (0-100) indicating MaxMind's certainty that the
647        /// country is correct.
648        #[serde(default, skip_serializing_if = "Option::is_none")]
649        pub confidence: Option<u8>,
650        /// GeoNames ID for the country.
651        #[serde(default, skip_serializing_if = "Option::is_none")]
652        pub geoname_id: Option<u32>,
653        /// True if the country is a member state of the European Union.
654        #[serde(default, skip_serializing_if = "Option::is_none")]
655        pub is_in_european_union: Option<bool>,
656        /// Two-character ISO 3166-1 alpha-2 country code.
657        /// See <https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2>.
658        #[serde(default, skip_serializing_if = "Option::is_none")]
659        pub iso_code: Option<&'a str>,
660        /// Localized country names.
661        #[serde(borrow, default, skip_serializing_if = "Names::is_empty")]
662        pub names: Names<'a>,
663    }
664
665    impl_is_empty_via_default!(Country<'_>);
666
667    /// Location data for an IP address.
668    #[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
669    pub struct Location<'a> {
670        /// Approximate accuracy radius in kilometers around the coordinates.
671        /// This is the radius where we have a 67% confidence that the device
672        /// using the IP address resides within.
673        #[serde(default, skip_serializing_if = "Option::is_none")]
674        pub accuracy_radius: Option<u16>,
675        /// Approximate latitude of the location. This value is not precise and
676        /// should not be used to identify a particular address or household.
677        #[serde(default, skip_serializing_if = "Option::is_none")]
678        pub latitude: Option<f64>,
679        /// Approximate longitude of the location. This value is not precise and
680        /// should not be used to identify a particular address or household.
681        #[serde(default, skip_serializing_if = "Option::is_none")]
682        pub longitude: Option<f64>,
683        /// Metro code for the location, used for targeting advertisements.
684        ///
685        /// **Deprecated:** Metro codes are no longer maintained and should not be used.
686        #[serde(default, skip_serializing_if = "Option::is_none")]
687        pub metro_code: Option<u16>,
688        /// Time zone associated with the location, as specified by the
689        /// IANA Time Zone Database (e.g., "America/New_York").
690        #[serde(default, skip_serializing_if = "Option::is_none")]
691        pub time_zone: Option<&'a str>,
692    }
693
694    impl_is_empty_via_default!(Location<'_>);
695
696    /// Postal data with confidence score.
697    #[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
698    pub struct Postal<'a> {
699        /// Postal code for the location. Not available for all countries.
700        /// In some countries, this will only contain part of the postal code.
701        #[serde(default, skip_serializing_if = "Option::is_none")]
702        pub code: Option<&'a str>,
703        /// Confidence score (0-100) indicating MaxMind's certainty that the
704        /// postal code is correct.
705        #[serde(default, skip_serializing_if = "Option::is_none")]
706        pub confidence: Option<u8>,
707    }
708
709    impl_is_empty_via_default!(Postal<'_>);
710
711    /// Subdivision data with confidence score.
712    #[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
713    pub struct Subdivision<'a> {
714        /// Confidence score (0-100) indicating MaxMind's certainty that the
715        /// subdivision is correct.
716        #[serde(default, skip_serializing_if = "Option::is_none")]
717        pub confidence: Option<u8>,
718        /// GeoNames ID for the subdivision.
719        #[serde(default, skip_serializing_if = "Option::is_none")]
720        pub geoname_id: Option<u32>,
721        /// ISO 3166-2 subdivision code (up to 3 characters).
722        /// See <https://en.wikipedia.org/wiki/ISO_3166-2>.
723        #[serde(default, skip_serializing_if = "Option::is_none")]
724        pub iso_code: Option<&'a str>,
725        /// Localized subdivision names.
726        #[serde(borrow, default, skip_serializing_if = "Names::is_empty")]
727        pub names: Names<'a>,
728    }
729
730    impl_is_empty_via_default!(Subdivision<'_>);
731
732    /// Extended traits data for Enterprise records.
733    ///
734    /// Contains ISP, organization, connection type, and anonymity information.
735    #[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
736    pub struct Traits<'a> {
737        /// The autonomous system number (ASN) for the IP address.
738        #[serde(default, skip_serializing_if = "Option::is_none")]
739        pub autonomous_system_number: Option<u32>,
740        /// The organization associated with the registered ASN.
741        #[serde(default, skip_serializing_if = "Option::is_none")]
742        pub autonomous_system_organization: Option<&'a str>,
743        /// The connection type. Possible values include "Dialup", "Cable/DSL",
744        /// "Corporate", "Cellular", and "Satellite".
745        #[serde(default, skip_serializing_if = "Option::is_none")]
746        pub connection_type: Option<&'a str>,
747        /// The second-level domain associated with the IP address
748        /// (e.g., "example.com").
749        #[serde(default, skip_serializing_if = "Option::is_none")]
750        pub domain: Option<&'a str>,
751        /// True if the IP belongs to any sort of anonymous network.
752        #[serde(default, skip_serializing_if = "Option::is_none")]
753        pub is_anonymous: Option<bool>,
754        /// True if the IP is registered to an anonymous VPN provider.
755        #[serde(default, skip_serializing_if = "Option::is_none")]
756        pub is_anonymous_vpn: Option<bool>,
757        /// True if the IP belongs to an anycast network.
758        /// See <https://en.wikipedia.org/wiki/Anycast>.
759        #[serde(default, skip_serializing_if = "Option::is_none")]
760        pub is_anycast: Option<bool>,
761        /// True if the IP belongs to a hosting or VPN provider.
762        #[serde(default, skip_serializing_if = "Option::is_none")]
763        pub is_hosting_provider: Option<bool>,
764        /// The name of the ISP associated with the IP address.
765        #[serde(default, skip_serializing_if = "Option::is_none")]
766        pub isp: Option<&'a str>,
767        /// True if the IP belongs to a public proxy.
768        #[serde(default, skip_serializing_if = "Option::is_none")]
769        pub is_public_proxy: Option<bool>,
770        /// True if the IP is on a suspected anonymizing network and belongs to
771        /// a residential ISP.
772        #[serde(default, skip_serializing_if = "Option::is_none")]
773        pub is_residential_proxy: Option<bool>,
774        /// True if the IP is a Tor exit node.
775        #[serde(default, skip_serializing_if = "Option::is_none")]
776        pub is_tor_exit_node: Option<bool>,
777        /// The mobile country code (MCC) associated with the IP.
778        /// See <https://en.wikipedia.org/wiki/Mobile_country_code>.
779        #[serde(default, skip_serializing_if = "Option::is_none")]
780        pub mobile_country_code: Option<&'a str>,
781        /// The mobile network code (MNC) associated with the IP.
782        /// See <https://en.wikipedia.org/wiki/Mobile_network_code>.
783        #[serde(default, skip_serializing_if = "Option::is_none")]
784        pub mobile_network_code: Option<&'a str>,
785        /// The name of the organization associated with the IP address.
786        #[serde(default, skip_serializing_if = "Option::is_none")]
787        pub organization: Option<&'a str>,
788        /// The user type associated with the IP address. Possible values include
789        /// "business", "cafe", "cellular", "college", "government", "hosting",
790        /// "library", "military", "residential", "router", "school",
791        /// "search_engine_spider", and "traveler".
792        #[serde(default, skip_serializing_if = "Option::is_none")]
793        pub user_type: Option<&'a str>,
794    }
795
796    impl_is_empty_via_default!(Traits<'_>);
797}
798
799#[cfg(test)]
800mod tests {
801    use serde::de::value::{Error as ValueError, SeqDeserializer, U8Deserializer};
802    use serde::Deserialize;
803
804    use super::{deserialize_subdivisions, City, Enterprise, MAX_SUBDIVISIONS};
805    use crate::{decoder::Decoder, MaxMindDbError};
806
807    fn record_with_declared_subdivisions(declared_count: usize, encoded_count: usize) -> Vec<u8> {
808        assert!((29..=284).contains(&declared_count));
809        assert!(encoded_count <= declared_count);
810
811        let mut encoded = vec![0xe1, 0x4c]; // one-entry map, 12-byte key
812        encoded.extend_from_slice(b"subdivisions");
813        encoded.extend_from_slice(&[0x1d, 0x04, (declared_count - 29) as u8]);
814        encoded.resize(encoded.len() + encoded_count, 0xe0); // empty subdivision maps
815        encoded
816    }
817
818    fn record_with_subdivisions(count: usize) -> Vec<u8> {
819        record_with_declared_subdivisions(count, count)
820    }
821
822    struct UnknownSize<I>(I);
823
824    impl<I> Iterator for UnknownSize<I>
825    where
826        I: Iterator,
827    {
828        type Item = I::Item;
829
830        fn next(&mut self) -> Option<Self::Item> {
831            self.0.next()
832        }
833
834        fn size_hint(&self) -> (usize, Option<usize>) {
835            (0, None)
836        }
837    }
838
839    fn subdivisions_without_size_hint(count: usize) -> Result<Vec<u8>, ValueError> {
840        let values = (0..count).map(|_| U8Deserializer::<ValueError>::new(0));
841        deserialize_subdivisions(SeqDeserializer::new(UnknownSize(values)))
842    }
843
844    #[test]
845    fn city_accepts_maximum_subdivisions() {
846        let encoded = record_with_subdivisions(MAX_SUBDIVISIONS);
847        let city = City::deserialize(&mut Decoder::new(&encoded, 0)).unwrap();
848
849        assert_eq!(city.subdivisions.len(), MAX_SUBDIVISIONS);
850    }
851
852    #[test]
853    fn city_rejects_excessive_subdivisions() {
854        let encoded = record_with_subdivisions(MAX_SUBDIVISIONS + 1);
855        let err = City::deserialize(&mut Decoder::new(&encoded, 0)).unwrap_err();
856
857        assert!(matches!(*err, MaxMindDbError::Decoding { .. }));
858        assert!(err
859            .to_string()
860            .contains("subdivisions exceeds maximum length of 32"));
861    }
862
863    #[test]
864    fn enterprise_rejects_excessive_subdivisions() {
865        let encoded = record_with_subdivisions(MAX_SUBDIVISIONS + 1);
866        let err = Enterprise::deserialize(&mut Decoder::new(&encoded, 0)).unwrap_err();
867
868        assert!(matches!(*err, MaxMindDbError::Decoding { .. }));
869        assert!(err
870            .to_string()
871            .contains("subdivisions exceeds maximum length of 32"));
872    }
873
874    #[test]
875    fn subdivision_limit_does_not_depend_on_a_size_hint() {
876        let subdivisions = subdivisions_without_size_hint(MAX_SUBDIVISIONS).unwrap();
877        assert_eq!(subdivisions.len(), MAX_SUBDIVISIONS);
878
879        let err = subdivisions_without_size_hint(MAX_SUBDIVISIONS + 1).unwrap_err();
880        assert!(err
881            .to_string()
882            .contains("subdivisions exceeds maximum length of 32"));
883    }
884
885    #[test]
886    fn city_json_deserialization_enforces_subdivision_limit() {
887        let mut city = City::default();
888        city.subdivisions
889            .resize_with(MAX_SUBDIVISIONS, Default::default);
890        let json = serde_json::to_string(&city).unwrap();
891        let decoded = serde_json::from_str::<City<'_>>(&json).unwrap();
892        assert_eq!(decoded.subdivisions.len(), MAX_SUBDIVISIONS);
893
894        city.subdivisions.push(Default::default());
895        let json = serde_json::to_string(&city).unwrap();
896        let err = serde_json::from_str::<City<'_>>(&json).unwrap_err();
897        assert!(err
898            .to_string()
899            .contains("subdivisions exceeds maximum length of 32"));
900    }
901
902    #[test]
903    fn city_rejects_truncated_oversized_subdivisions() {
904        let encoded = record_with_declared_subdivisions(MAX_SUBDIVISIONS + 1, 1);
905        let err = City::deserialize(&mut Decoder::new(&encoded, 0)).unwrap_err();
906
907        assert!(matches!(*err, MaxMindDbError::InvalidDatabase { .. }));
908        assert!(err.to_string().contains("unexpected end of buffer"));
909    }
910
911    #[test]
912    fn enterprise_rejects_truncated_oversized_subdivisions() {
913        let encoded = record_with_declared_subdivisions(MAX_SUBDIVISIONS + 1, 1);
914        let err = Enterprise::deserialize(&mut Decoder::new(&encoded, 0)).unwrap_err();
915
916        assert!(matches!(*err, MaxMindDbError::InvalidDatabase { .. }));
917        assert!(err.to_string().contains("unexpected end of buffer"));
918    }
919}