1use std::net::IpAddr;
4
5use country_code::CountryCode;
6
7use crate::{proxy_type::ProxyType, usage_type::UsageType};
8
9#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
11#[derive(Debug, Clone)]
12pub struct Record {
13 #[cfg_attr(feature = "serde", serde(deserialize_with = "ip_deserialize"))]
14 pub ip_from: IpAddr,
15 #[cfg_attr(feature = "serde", serde(deserialize_with = "ip_deserialize"))]
16 pub ip_to: IpAddr,
17 pub proxy_type: Option<ProxyType>,
18 pub country_code: CountryCode,
19 #[cfg_attr(
20 feature = "serde",
21 serde(default, deserialize_with = "option_box_str_deserialize")
22 )]
23 pub country_name: Option<Box<str>>,
24 #[cfg_attr(
25 feature = "serde",
26 serde(default, deserialize_with = "option_box_str_deserialize")
27 )]
28 pub region_name: Option<Box<str>>,
29 #[cfg_attr(
30 feature = "serde",
31 serde(default, deserialize_with = "option_box_str_deserialize")
32 )]
33 pub city_name: Option<Box<str>>,
34 #[cfg_attr(
35 feature = "serde",
36 serde(default, deserialize_with = "option_box_str_deserialize")
37 )]
38 pub isp: Option<Box<str>>,
39 #[cfg_attr(
40 feature = "serde",
41 serde(default, deserialize_with = "option_box_str_deserialize")
42 )]
43 pub domain: Option<Box<str>>,
44 pub usage_type: Option<UsageType>,
45 #[cfg_attr(
46 feature = "serde",
47 serde(default, deserialize_with = "option_usize_deserialize")
48 )]
49 pub asn: Option<usize>,
50 #[cfg_attr(
51 feature = "serde",
52 serde(default, deserialize_with = "option_box_str_deserialize")
53 )]
54 pub as_name: Option<Box<str>>,
55 #[cfg_attr(
56 feature = "serde",
57 serde(default, deserialize_with = "option_box_str_deserialize")
58 )]
59 pub last_seen: Option<Box<str>>,
60 #[cfg_attr(
61 feature = "serde",
62 serde(default, deserialize_with = "option_box_str_deserialize")
63 )]
64 pub threat: Option<Box<str>>,
65 #[cfg_attr(
66 feature = "serde",
67 serde(default, deserialize_with = "option_box_str_deserialize")
68 )]
69 pub provider: Option<Box<str>>,
70 #[cfg_attr(
71 feature = "serde",
72 serde(default, deserialize_with = "option_box_str_deserialize")
73 )]
74 pub residential: Option<Box<str>>,
75}
76
77#[cfg(feature = "serde")]
78fn ip_deserialize<'de, D>(deserializer: D) -> Result<IpAddr, D::Error>
79where
80 D: serde::Deserializer<'de>,
81{
82 use std::net::{Ipv4Addr, Ipv6Addr};
83
84 use serde::Deserialize as _;
85
86 let s = Box::<str>::deserialize(deserializer)?;
87 if let Ok(v) = s.parse::<u32>() {
88 Ok(Ipv4Addr::from(v).into())
89 } else if let Ok(v) = s.parse::<u128>() {
90 Ok(Ipv6Addr::from(v).into())
91 } else if let Ok(v) = s.parse::<Ipv4Addr>() {
92 Ok(v.into())
93 } else if let Ok(v) = s.parse::<Ipv6Addr>() {
94 Ok(v.into())
95 } else {
96 Err(serde::de::Error::custom(""))
97 }
98}
99
100#[cfg(feature = "serde")]
101fn option_box_str_deserialize<'de, D>(deserializer: D) -> Result<Option<Box<str>>, D::Error>
102where
103 D: serde::Deserializer<'de>,
104{
105 use ip2location_bin_format::content::UNKNOWN_STR;
106 use serde::Deserialize as _;
107
108 let s = Box::<str>::deserialize(deserializer)?;
109 if s == UNKNOWN_STR.into() {
110 Ok(None)
111 } else {
112 Ok(Some(s))
113 }
114}
115
116#[cfg(feature = "serde")]
117fn option_usize_deserialize<'de, D>(deserializer: D) -> Result<Option<usize>, D::Error>
118where
119 D: serde::Deserializer<'de>,
120{
121 use ip2location_bin_format::content::UNKNOWN_STR;
122 use serde::Deserialize as _;
123
124 let s = Box::<str>::deserialize(deserializer)?;
125 if s == UNKNOWN_STR.into() {
126 Ok(None)
127 } else {
128 match s.parse::<usize>() {
129 Ok(v) => Ok(Some(v)),
130 Err(err) => Err(serde::de::Error::custom(err.to_string())),
131 }
132 }
133}
134
135impl Record {
136 pub(crate) fn with_empty(ip_from: IpAddr, ip_to: IpAddr) -> Self {
137 Self {
138 ip_from,
139 ip_to,
140 proxy_type: Default::default(),
141 country_code: Default::default(),
142 country_name: Default::default(),
143 region_name: Default::default(),
144 city_name: Default::default(),
145 isp: Default::default(),
146 domain: Default::default(),
147 usage_type: Default::default(),
148 asn: Default::default(),
149 as_name: Default::default(),
150 last_seen: Default::default(),
151 threat: Default::default(),
152 residential: Default::default(),
153 provider: Default::default(),
154 }
155 }
156}
157
158pub(crate) struct OptionRecord(pub(crate) Option<Record>);
160
161impl
162 TryFrom<(
163 IpAddr,
164 IpAddr,
165 ip2location_bin_format::record_field::RecordFieldContents,
166 )> for OptionRecord
167{
168 type Error = Box<str>;
169
170 fn try_from(
171 (ip_from, ip_to, record_field_contents): (
172 IpAddr,
173 IpAddr,
174 ip2location_bin_format::record_field::RecordFieldContents,
175 ),
176 ) -> Result<Self, Self::Error> {
177 use ip2location_bin_format::record_field::RecordFieldContent;
178
179 let mut record = Record::with_empty(ip_from, ip_to);
180
181 for record_field_content in record_field_contents.iter() {
182 match record_field_content {
183 RecordFieldContent::COUNTRY(_, v, v_name) => {
184 if let Some(v) = v {
185 record.country_code = v
186 .parse::<CountryCode>()
187 .map_err(|err| Box::<str>::from(err.to_string()))?;
188 } else {
189 return Ok(OptionRecord(None));
190 }
191
192 record.country_name = v_name.to_owned();
193 }
194 RecordFieldContent::REGION(_, v) => {
195 record.region_name = v.to_owned();
196 }
197 RecordFieldContent::CITY(_, v) => {
198 record.city_name = v.to_owned();
199 }
200 RecordFieldContent::ISP(_, v) => {
201 record.isp = v.to_owned();
202 }
203 RecordFieldContent::DOMAIN(_, v) => {
204 record.domain = v.to_owned();
205 }
206 RecordFieldContent::LATITUDE(_) => {
208 return Err("Unknown field LATITUDE".into());
209 }
210 RecordFieldContent::LONGITUDE(_) => {
211 return Err("Unknown field LONGITUDE".into());
212 }
213 RecordFieldContent::ZIPCODE(_, _) => {
214 return Err("Unknown field ZIPCODE".into());
215 }
216 RecordFieldContent::TIMEZONE(_, _) => {
217 return Err("Unknown field TIMEZONE".into());
218 }
219 RecordFieldContent::NETSPEED(_, _) => {
220 return Err("Unknown field NETSPEED".into());
221 }
222 RecordFieldContent::PROXYTYPE(_, v) => {
224 if let Some(v) = v {
225 let v = v
226 .parse::<ProxyType>()
227 .map_err(|err| Box::<str>::from(err.to_string()))?;
228 record.proxy_type = Some(v);
229 }
230 }
231 RecordFieldContent::USAGETYPE(_, v) => {
232 if let Some(v) = v {
233 let v = v
234 .parse::<UsageType>()
235 .map_err(|err| Box::<str>::from(err.to_string()))?;
236 record.usage_type = Some(v);
237 }
238 }
239 RecordFieldContent::ASN(_, v) => {
240 if let Some(v) = v {
241 let v = v
242 .parse::<usize>()
243 .map_err(|err| Box::<str>::from(err.to_string()))?;
244 record.asn = Some(v);
245 }
246 }
247 RecordFieldContent::AS(_, v) => {
248 record.as_name = v.to_owned();
249 }
250 RecordFieldContent::LASTSEEN(_, v) => {
251 record.last_seen = v.to_owned();
252 }
253 RecordFieldContent::THREAT(_, v) => {
254 record.threat = v.to_owned();
255 }
256 RecordFieldContent::RESIDENTIAL(_, v) => {
257 record.residential = v.to_owned();
258 }
259 RecordFieldContent::PROVIDER(_, v) => {
260 record.provider = v.to_owned();
261 }
262 }
263 }
264
265 Ok(OptionRecord(Some(record)))
266 }
267}
268
269#[derive(Debug, Clone, Copy)]
273pub enum RecordField {
274 CountryCodeAndName,
275 RegionName,
276 CityName,
277 Isp,
278 Domain,
279 ProxyType,
281 UsageType,
282 Asn,
283 AsName,
284 LastSeen,
285 Threat,
286 Provider,
287 Residential,
288}
289
290impl From<&RecordField> for ip2location_bin_format::record_field::RecordField {
291 fn from(x: &RecordField) -> Self {
292 match x {
293 RecordField::CountryCodeAndName => Self::COUNTRY,
294 RecordField::RegionName => Self::REGION,
295 RecordField::CityName => Self::CITY,
296 RecordField::Isp => Self::ISP,
297 RecordField::Domain => Self::DOMAIN,
298 RecordField::ProxyType => Self::PROXYTYPE,
300 RecordField::UsageType => Self::USAGETYPE,
301 RecordField::Asn => Self::ASN,
302 RecordField::AsName => Self::AS,
303 RecordField::LastSeen => Self::LASTSEEN,
304 RecordField::Threat => Self::THREAT,
305 RecordField::Provider => Self::PROVIDER,
306 RecordField::Residential => Self::RESIDENTIAL,
307 }
308 }
309}