use std::fmt;
use std::marker::PhantomData;
use serde::de::{IgnoredAny, SeqAccess, Visitor};
use serde::{Deserialize, Deserializer, Serialize};
pub const MAX_SUBDIVISIONS: usize = 32;
#[cold]
fn subdivisions_too_long<E>() -> E
where
E: serde::de::Error,
{
E::custom(format_args!(
"subdivisions exceeds maximum length of {MAX_SUBDIVISIONS}"
))
}
#[inline(always)]
fn deserialize_subdivisions<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
struct SubdivisionsVisitor<T>(PhantomData<T>);
impl<'de, T> Visitor<'de> for SubdivisionsVisitor<T>
where
T: Deserialize<'de>,
{
type Value = Vec<T>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(
formatter,
"an array containing at most {MAX_SUBDIVISIONS} subdivisions"
)
}
#[inline(always)]
fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
if let Some(size) = sequence.size_hint() {
if size > MAX_SUBDIVISIONS {
return Err(subdivisions_too_long());
}
let mut subdivisions = Vec::with_capacity(size);
while let Some(subdivision) = sequence.next_element()? {
subdivisions.push(subdivision);
}
return Ok(subdivisions);
}
let mut subdivisions = Vec::new();
while subdivisions.len() < MAX_SUBDIVISIONS {
let Some(subdivision) = sequence.next_element()? else {
return Ok(subdivisions);
};
subdivisions.push(subdivision);
}
if sequence.next_element::<IgnoredAny>()?.is_some() {
return Err(subdivisions_too_long());
}
Ok(subdivisions)
}
}
deserializer.deserialize_seq(SubdivisionsVisitor(PhantomData))
}
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq, Eq)]
pub struct Names<'a> {
#[serde(
borrow,
rename = "de",
default,
skip_serializing_if = "Option::is_none"
)]
pub german: Option<&'a str>,
#[serde(rename = "en", default, skip_serializing_if = "Option::is_none")]
pub english: Option<&'a str>,
#[serde(rename = "es", default, skip_serializing_if = "Option::is_none")]
pub spanish: Option<&'a str>,
#[serde(rename = "fr", default, skip_serializing_if = "Option::is_none")]
pub french: Option<&'a str>,
#[serde(rename = "ja", default, skip_serializing_if = "Option::is_none")]
pub japanese: Option<&'a str>,
#[serde(rename = "pt-BR", default, skip_serializing_if = "Option::is_none")]
pub brazilian_portuguese: Option<&'a str>,
#[serde(rename = "ru", default, skip_serializing_if = "Option::is_none")]
pub russian: Option<&'a str>,
#[serde(rename = "zh-CN", default, skip_serializing_if = "Option::is_none")]
pub simplified_chinese: Option<&'a str>,
}
impl Names<'_> {
#[must_use]
pub fn is_empty(&self) -> bool {
self.german.is_none()
&& self.english.is_none()
&& self.spanish.is_none()
&& self.french.is_none()
&& self.japanese.is_none()
&& self.brazilian_portuguese.is_none()
&& self.russian.is_none()
&& self.simplified_chinese.is_none()
}
}
macro_rules! impl_is_empty_via_default {
($ty:ty) => {
impl $ty {
#[must_use]
pub fn is_empty(&self) -> bool {
*self == Self::default()
}
}
};
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct Country<'a> {
#[serde(borrow, default, skip_serializing_if = "country::Continent::is_empty")]
pub continent: country::Continent<'a>,
#[serde(default, skip_serializing_if = "country::Country::is_empty")]
pub country: country::Country<'a>,
#[serde(default, skip_serializing_if = "country::Country::is_empty")]
pub registered_country: country::Country<'a>,
#[serde(default, skip_serializing_if = "country::RepresentedCountry::is_empty")]
pub represented_country: country::RepresentedCountry<'a>,
#[serde(default, skip_serializing_if = "country::Traits::is_empty")]
pub traits: country::Traits,
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct City<'a> {
#[serde(borrow, default, skip_serializing_if = "city::City::is_empty")]
pub city: city::City<'a>,
#[serde(default, skip_serializing_if = "city::Continent::is_empty")]
pub continent: city::Continent<'a>,
#[serde(default, skip_serializing_if = "city::Country::is_empty")]
pub country: city::Country<'a>,
#[serde(default, skip_serializing_if = "city::Location::is_empty")]
pub location: city::Location<'a>,
#[serde(default, skip_serializing_if = "city::Postal::is_empty")]
pub postal: city::Postal<'a>,
#[serde(default, skip_serializing_if = "city::Country::is_empty")]
pub registered_country: city::Country<'a>,
#[serde(default, skip_serializing_if = "city::RepresentedCountry::is_empty")]
pub represented_country: city::RepresentedCountry<'a>,
#[serde(
borrow,
default,
deserialize_with = "deserialize_subdivisions",
skip_serializing_if = "Vec::is_empty"
)]
pub subdivisions: Vec<city::Subdivision<'a>>,
#[serde(default, skip_serializing_if = "city::Traits::is_empty")]
pub traits: city::Traits,
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct Enterprise<'a> {
#[serde(borrow, default, skip_serializing_if = "enterprise::City::is_empty")]
pub city: enterprise::City<'a>,
#[serde(default, skip_serializing_if = "enterprise::Continent::is_empty")]
pub continent: enterprise::Continent<'a>,
#[serde(default, skip_serializing_if = "enterprise::Country::is_empty")]
pub country: enterprise::Country<'a>,
#[serde(default, skip_serializing_if = "enterprise::Location::is_empty")]
pub location: enterprise::Location<'a>,
#[serde(default, skip_serializing_if = "enterprise::Postal::is_empty")]
pub postal: enterprise::Postal<'a>,
#[serde(default, skip_serializing_if = "enterprise::Country::is_empty")]
pub registered_country: enterprise::Country<'a>,
#[serde(
default,
skip_serializing_if = "enterprise::RepresentedCountry::is_empty"
)]
pub represented_country: enterprise::RepresentedCountry<'a>,
#[serde(
borrow,
default,
deserialize_with = "deserialize_subdivisions",
skip_serializing_if = "Vec::is_empty"
)]
pub subdivisions: Vec<enterprise::Subdivision<'a>>,
#[serde(default, skip_serializing_if = "enterprise::Traits::is_empty")]
pub traits: enterprise::Traits<'a>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Isp<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
pub autonomous_system_number: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub autonomous_system_organization: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub isp: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mobile_country_code: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mobile_network_code: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub organization: Option<&'a str>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct ConnectionType<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
pub connection_type: Option<&'a str>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct AnonymousIp {
#[serde(skip_serializing_if = "Option::is_none")]
pub is_anonymous: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_anonymous_vpn: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_hosting_provider: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_public_proxy: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_residential_proxy: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_tor_exit_node: Option<bool>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct DensityIncome {
#[serde(skip_serializing_if = "Option::is_none")]
pub average_income: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub population_density: Option<u32>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Domain<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
pub domain: Option<&'a str>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct Asn<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
pub autonomous_system_number: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub autonomous_system_organization: Option<&'a str>,
}
pub mod country {
use super::Names;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
pub struct Continent<'a> {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub code: Option<&'a str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub geoname_id: Option<u32>,
#[serde(borrow, default, skip_serializing_if = "Names::is_empty")]
pub names: Names<'a>,
}
impl_is_empty_via_default!(Continent<'_>);
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
pub struct Country<'a> {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub geoname_id: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub is_in_european_union: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub iso_code: Option<&'a str>,
#[serde(borrow, default, skip_serializing_if = "Names::is_empty")]
pub names: Names<'a>,
}
impl_is_empty_via_default!(Country<'_>);
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
pub struct RepresentedCountry<'a> {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub geoname_id: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub is_in_european_union: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub iso_code: Option<&'a str>,
#[serde(borrow, default, skip_serializing_if = "Names::is_empty")]
pub names: Names<'a>,
#[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
pub representation_type: Option<&'a str>,
}
impl_is_empty_via_default!(RepresentedCountry<'_>);
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
pub struct Traits {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub is_anycast: Option<bool>,
}
impl_is_empty_via_default!(Traits);
}
pub mod city {
use super::Names;
use serde::{Deserialize, Serialize};
pub use super::country::{Continent, Country, RepresentedCountry, Traits};
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
pub struct City<'a> {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub geoname_id: Option<u32>,
#[serde(borrow, default, skip_serializing_if = "Names::is_empty")]
pub names: Names<'a>,
}
impl_is_empty_via_default!(City<'_>);
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
pub struct Location<'a> {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub accuracy_radius: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub latitude: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub longitude: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metro_code: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub time_zone: Option<&'a str>,
}
impl_is_empty_via_default!(Location<'_>);
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
pub struct Postal<'a> {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub code: Option<&'a str>,
}
impl_is_empty_via_default!(Postal<'_>);
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
pub struct Subdivision<'a> {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub geoname_id: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub iso_code: Option<&'a str>,
#[serde(borrow, default, skip_serializing_if = "Names::is_empty")]
pub names: Names<'a>,
}
impl_is_empty_via_default!(Subdivision<'_>);
}
pub mod enterprise {
use super::Names;
use serde::{Deserialize, Serialize};
pub use super::country::{Continent, RepresentedCountry};
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
pub struct City<'a> {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub confidence: Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub geoname_id: Option<u32>,
#[serde(borrow, default, skip_serializing_if = "Names::is_empty")]
pub names: Names<'a>,
}
impl_is_empty_via_default!(City<'_>);
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
pub struct Country<'a> {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub confidence: Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub geoname_id: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub is_in_european_union: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub iso_code: Option<&'a str>,
#[serde(borrow, default, skip_serializing_if = "Names::is_empty")]
pub names: Names<'a>,
}
impl_is_empty_via_default!(Country<'_>);
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
pub struct Location<'a> {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub accuracy_radius: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub latitude: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub longitude: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metro_code: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub time_zone: Option<&'a str>,
}
impl_is_empty_via_default!(Location<'_>);
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
pub struct Postal<'a> {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub code: Option<&'a str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub confidence: Option<u8>,
}
impl_is_empty_via_default!(Postal<'_>);
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
pub struct Subdivision<'a> {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub confidence: Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub geoname_id: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub iso_code: Option<&'a str>,
#[serde(borrow, default, skip_serializing_if = "Names::is_empty")]
pub names: Names<'a>,
}
impl_is_empty_via_default!(Subdivision<'_>);
#[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq)]
pub struct Traits<'a> {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub autonomous_system_number: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub autonomous_system_organization: Option<&'a str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub connection_type: Option<&'a str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub domain: Option<&'a str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub is_anonymous: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub is_anonymous_vpn: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub is_anycast: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub is_hosting_provider: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub isp: Option<&'a str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub is_public_proxy: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub is_residential_proxy: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub is_tor_exit_node: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mobile_country_code: Option<&'a str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mobile_network_code: Option<&'a str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub organization: Option<&'a str>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub user_type: Option<&'a str>,
}
impl_is_empty_via_default!(Traits<'_>);
}
#[cfg(test)]
mod tests {
use serde::de::value::{Error as ValueError, SeqDeserializer, U8Deserializer};
use serde::Deserialize;
use super::{deserialize_subdivisions, City, Enterprise, MAX_SUBDIVISIONS};
use crate::{decoder::Decoder, MaxMindDbError};
fn record_with_declared_subdivisions(declared_count: usize, encoded_count: usize) -> Vec<u8> {
assert!((29..=284).contains(&declared_count));
assert!(encoded_count <= declared_count);
let mut encoded = vec![0xe1, 0x4c]; encoded.extend_from_slice(b"subdivisions");
encoded.extend_from_slice(&[0x1d, 0x04, (declared_count - 29) as u8]);
encoded.resize(encoded.len() + encoded_count, 0xe0); encoded
}
fn record_with_subdivisions(count: usize) -> Vec<u8> {
record_with_declared_subdivisions(count, count)
}
struct UnknownSize<I>(I);
impl<I> Iterator for UnknownSize<I>
where
I: Iterator,
{
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, None)
}
}
fn subdivisions_without_size_hint(count: usize) -> Result<Vec<u8>, ValueError> {
let values = (0..count).map(|_| U8Deserializer::<ValueError>::new(0));
deserialize_subdivisions(SeqDeserializer::new(UnknownSize(values)))
}
#[test]
fn city_accepts_maximum_subdivisions() {
let encoded = record_with_subdivisions(MAX_SUBDIVISIONS);
let city = City::deserialize(&mut Decoder::new(&encoded, 0)).unwrap();
assert_eq!(city.subdivisions.len(), MAX_SUBDIVISIONS);
}
#[test]
fn city_rejects_excessive_subdivisions() {
let encoded = record_with_subdivisions(MAX_SUBDIVISIONS + 1);
let err = City::deserialize(&mut Decoder::new(&encoded, 0)).unwrap_err();
assert!(matches!(err, MaxMindDbError::Decoding { .. }));
assert!(err
.to_string()
.contains("subdivisions exceeds maximum length of 32"));
}
#[test]
fn enterprise_rejects_excessive_subdivisions() {
let encoded = record_with_subdivisions(MAX_SUBDIVISIONS + 1);
let err = Enterprise::deserialize(&mut Decoder::new(&encoded, 0)).unwrap_err();
assert!(matches!(err, MaxMindDbError::Decoding { .. }));
assert!(err
.to_string()
.contains("subdivisions exceeds maximum length of 32"));
}
#[test]
fn subdivision_limit_does_not_depend_on_a_size_hint() {
let subdivisions = subdivisions_without_size_hint(MAX_SUBDIVISIONS).unwrap();
assert_eq!(subdivisions.len(), MAX_SUBDIVISIONS);
let err = subdivisions_without_size_hint(MAX_SUBDIVISIONS + 1).unwrap_err();
assert!(err
.to_string()
.contains("subdivisions exceeds maximum length of 32"));
}
#[test]
fn city_json_deserialization_enforces_subdivision_limit() {
let mut city = City::default();
city.subdivisions
.resize_with(MAX_SUBDIVISIONS, Default::default);
let json = serde_json::to_string(&city).unwrap();
let decoded = serde_json::from_str::<City<'_>>(&json).unwrap();
assert_eq!(decoded.subdivisions.len(), MAX_SUBDIVISIONS);
city.subdivisions.push(Default::default());
let json = serde_json::to_string(&city).unwrap();
let err = serde_json::from_str::<City<'_>>(&json).unwrap_err();
assert!(err
.to_string()
.contains("subdivisions exceeds maximum length of 32"));
}
#[test]
fn city_rejects_truncated_oversized_subdivisions() {
let encoded = record_with_declared_subdivisions(MAX_SUBDIVISIONS + 1, 1);
let err = City::deserialize(&mut Decoder::new(&encoded, 0)).unwrap_err();
assert!(matches!(err, MaxMindDbError::InvalidDatabase { .. }));
assert!(err.to_string().contains("unexpected end of buffer"));
}
#[test]
fn enterprise_rejects_truncated_oversized_subdivisions() {
let encoded = record_with_declared_subdivisions(MAX_SUBDIVISIONS + 1, 1);
let err = Enterprise::deserialize(&mut Decoder::new(&encoded, 0)).unwrap_err();
assert!(matches!(err, MaxMindDbError::InvalidDatabase { .. }));
assert!(err.to_string().contains("unexpected end of buffer"));
}
}