diff --git a/Cargo.toml b/Cargo.toml index c487c623..116430ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ env_logger = "0.10" criterion = "0.5" fake = "2.4" rayon = "1.5" +serde_json = "1.0" [[bench]] name = "lookup" diff --git a/src/maxminddb/geoip2.rs b/src/maxminddb/geoip2.rs index ac89c41c..f2a26949 100644 --- a/src/maxminddb/geoip2.rs +++ b/src/maxminddb/geoip2.rs @@ -4,10 +4,15 @@ use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Country<'a> { #[serde(borrow)] + #[serde(skip_serializing_if = "Option::is_none")] pub continent: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub country: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub registered_country: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub represented_country: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub traits: Option, } @@ -15,14 +20,23 @@ pub struct Country<'a> { #[derive(Deserialize, Serialize, Clone, Debug)] pub struct City<'a> { #[serde(borrow)] + #[serde(skip_serializing_if = "Option::is_none")] pub city: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub continent: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub country: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub location: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub postal: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub registered_country: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub represented_country: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub subdivisions: Option>>, + #[serde(skip_serializing_if = "Option::is_none")] pub traits: Option, } @@ -30,62 +44,89 @@ pub struct City<'a> { #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Enterprise<'a> { #[serde(borrow)] + #[serde(skip_serializing_if = "Option::is_none")] pub city: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub continent: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub country: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub location: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub postal: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub registered_country: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub represented_country: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub subdivisions: Option>>, + #[serde(skip_serializing_if = "Option::is_none")] pub traits: Option>, } /// GeoIP2 ISP record #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Isp<'a> { + #[serde(skip_serializing_if = "Option::is_none")] pub autonomous_system_number: Option, + #[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>, } /// GeoIP2 Connection-Type record #[derive(Deserialize, Serialize, Clone, Debug)] pub struct ConnectionType<'a> { + #[serde(skip_serializing_if = "Option::is_none")] pub connection_type: Option<&'a str>, } /// GeoIP2 Anonymous Ip record #[derive(Deserialize, Serialize, Clone, Debug)] pub struct AnonymousIp { + #[serde(skip_serializing_if = "Option::is_none")] pub is_anonymous: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub is_anonymous_vpn: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub is_hosting_provider: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub is_public_proxy: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub is_residential_proxy: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub is_tor_exit_node: Option, } /// GeoIP2 DensityIncome record #[derive(Deserialize, Serialize, Clone, Debug)] pub struct DensityIncome { + #[serde(skip_serializing_if = "Option::is_none")] pub average_income: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub population_density: Option, } /// GeoIP2 Domain record #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Domain<'a> { + #[serde(skip_serializing_if = "Option::is_none")] pub domain: Option<&'a str>, } /// GeoIP2 Asn record #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Asn<'a> { + #[serde(skip_serializing_if = "Option::is_none")] pub autonomous_system_number: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub autonomous_system_organization: Option<&'a str>, } @@ -96,33 +137,48 @@ pub mod country { #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Continent<'a> { + #[serde(skip_serializing_if = "Option::is_none")] pub code: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] pub geoname_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub names: Option>, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Country<'a> { + #[serde(skip_serializing_if = "Option::is_none")] pub geoname_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub is_in_european_union: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub iso_code: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] pub names: Option>, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct RepresentedCountry<'a> { + #[serde(skip_serializing_if = "Option::is_none")] pub geoname_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub is_in_european_union: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub iso_code: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] pub names: Option>, #[serde(rename = "type")] + #[serde(skip_serializing_if = "Option::is_none")] pub representation_type: Option<&'a str>, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Traits { + #[serde(skip_serializing_if = "Option::is_none")] pub is_anonymous_proxy: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub is_anycast: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub is_satellite_provider: Option, } } @@ -136,29 +192,40 @@ pub mod city { #[derive(Deserialize, Serialize, Clone, Debug)] pub struct City<'a> { + #[serde(skip_serializing_if = "Option::is_none")] pub geoname_id: Option, #[serde(borrow)] + #[serde(skip_serializing_if = "Option::is_none")] pub names: Option>, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Location<'a> { + #[serde(skip_serializing_if = "Option::is_none")] pub accuracy_radius: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub latitude: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub longitude: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub metro_code: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub time_zone: Option<&'a str>, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Postal<'a> { + #[serde(skip_serializing_if = "Option::is_none")] pub code: Option<&'a str>, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Subdivision<'a> { + #[serde(skip_serializing_if = "Option::is_none")] pub geoname_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub iso_code: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] pub names: Option>, } } @@ -172,63 +239,100 @@ pub mod enterprise { #[derive(Deserialize, Serialize, Clone, Debug)] pub struct City<'a> { + #[serde(skip_serializing_if = "Option::is_none")] pub confidence: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub geoname_id: Option, #[serde(borrow)] + #[serde(skip_serializing_if = "Option::is_none")] pub names: Option>, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Country<'a> { + #[serde(skip_serializing_if = "Option::is_none")] pub confidence: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub geoname_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub is_in_european_union: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub iso_code: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] pub names: Option>, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Location<'a> { + #[serde(skip_serializing_if = "Option::is_none")] pub accuracy_radius: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub latitude: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub longitude: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub metro_code: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub time_zone: Option<&'a str>, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Postal<'a> { + #[serde(skip_serializing_if = "Option::is_none")] pub code: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] pub confidence: Option, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Subdivision<'a> { + #[serde(skip_serializing_if = "Option::is_none")] pub confidence: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub geoname_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub iso_code: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] pub names: Option>, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Traits<'a> { + #[serde(skip_serializing_if = "Option::is_none")] pub autonomous_system_number: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub autonomous_system_organization: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] pub connection_type: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] pub domain: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] pub is_anonymous: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub is_anonymous_proxy: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub is_anonymous_vpn: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub is_anycast: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub is_hosting_provider: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub isp: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] pub is_public_proxy: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub is_residential_proxy: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub is_satellite_provider: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub is_tor_exit_node: Option, + #[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>, + #[serde(skip_serializing_if = "Option::is_none")] pub user_type: Option<&'a str>, } } diff --git a/src/maxminddb/reader_test.rs b/src/maxminddb/reader_test.rs index 4c8bf3b5..38e73932 100644 --- a/src/maxminddb/reader_test.rs +++ b/src/maxminddb/reader_test.rs @@ -2,6 +2,7 @@ use std::net::IpAddr; use std::str::FromStr; use serde::Deserialize; +use serde_json::json; use super::{MaxMindDBError, Reader}; @@ -483,3 +484,23 @@ fn check_ip>(reader: &Reader, ip_version: usize) { } } } + +#[test] +fn test_json_serialize() { + use super::geoip2::City; + let _ = env_logger::try_init(); + + let filename = "test-data/test-data/GeoIP2-City-Test.mmdb"; + + let reader = Reader::open_readfile(filename).unwrap(); + + let ip: IpAddr = FromStr::from_str("89.160.20.112").unwrap(); + let city: City = reader.lookup(ip).unwrap(); + + let json_string = json!(city).to_string(); + + assert_eq!( + json_string, + r#"{"city":{"geoname_id":2694762,"names":{"de":"Linköping","en":"Linköping","fr":"Linköping","ja":"リンシェーピング","zh-CN":"林雪平"}},"continent":{"code":"EU","geoname_id":6255148,"names":{"de":"Europa","en":"Europe","es":"Europa","fr":"Europe","ja":"ヨーロッパ","pt-BR":"Europa","ru":"Европа","zh-CN":"欧洲"}},"country":{"geoname_id":2661886,"is_in_european_union":true,"iso_code":"SE","names":{"de":"Schweden","en":"Sweden","es":"Suecia","fr":"Suède","ja":"スウェーデン王国","pt-BR":"Suécia","ru":"Швеция","zh-CN":"瑞典"}},"location":{"accuracy_radius":76,"latitude":58.4167,"longitude":15.6167,"time_zone":"Europe/Stockholm"},"registered_country":{"geoname_id":2921044,"is_in_european_union":true,"iso_code":"DE","names":{"de":"Deutschland","en":"Germany","es":"Alemania","fr":"Allemagne","ja":"ドイツ連邦共和国","pt-BR":"Alemanha","ru":"Германия","zh-CN":"德国"}},"subdivisions":[{"geoname_id":2685867,"iso_code":"E","names":{"en":"Östergötland County","fr":"Comté d'Östergötland"}}]}"# + ); +}