ip_api_api/endpoints/
json.rs

1//! https://members.ip-api.com/docs/json
2//! https://ip-api.com/docs/api:json
3
4use std::net::IpAddr;
5
6use chrono::{DateTime, Utc};
7use chrono_tz::Tz;
8use continent_code::ContinentCode;
9use country_code::CountryCode;
10use http_api_client_endpoint::{
11    http::{header::ACCEPT, Method},
12    Body, Endpoint, Request, Response, MIME_APPLICATION_JSON,
13};
14use serde::{de, Deserialize, Deserializer};
15use serde_json::{Map, Value};
16use url::Url;
17
18use crate::{
19    endpoints::{common::EndpointError, helper::get_n_from_headers_by_key, URL_BASE, URL_BASE_PRO},
20    objects::rate_limit::{RateLimit, RESPONSE_HEADER_KEY_X_RL, RESPONSE_HEADER_KEY_X_TTL},
21    types::lang::Lang,
22};
23
24//
25#[derive(Debug, Clone)]
26pub struct Json {
27    pub query: Box<str>,
28    pub key: Option<Box<str>>,
29    pub fields: Option<Box<str>>,
30    pub lang: Option<Lang>,
31}
32
33impl Json {
34    pub fn new(query: impl AsRef<str>, key: Option<Box<str>>) -> Self {
35        Self {
36            query: query.as_ref().into(),
37            key,
38            fields: None,
39            lang: None,
40        }
41    }
42
43    pub fn fields(mut self, fields: impl AsRef<str>) -> Self {
44        self.fields = Some(fields.as_ref().into());
45        self
46    }
47
48    pub fn lang(mut self, lang: Lang) -> Self {
49        self.lang = Some(lang);
50        self
51    }
52}
53
54impl Endpoint for Json {
55    type RenderRequestError = EndpointError;
56
57    type ParseResponseOutput = (JsonResponseBodyJson, Option<RateLimit>);
58    type ParseResponseError = EndpointError;
59
60    fn render_request(&self) -> Result<Request<Body>, Self::RenderRequestError> {
61        let url = format!(
62            "{}/json/{}",
63            if self.key.is_some() {
64                URL_BASE_PRO
65            } else {
66                URL_BASE
67            },
68            self.query
69        );
70        let mut url = Url::parse(url.as_str()).map_err(EndpointError::MakeRequestUrlFailed)?;
71
72        if let Some(key) = &self.key {
73            url.query_pairs_mut().append_pair("key", key);
74        }
75        if let Some(fields) = &self.fields {
76            url.query_pairs_mut().append_pair("fields", fields);
77        }
78        if let Some(lang) = &self.lang {
79            url.query_pairs_mut()
80                .append_pair("lang", lang.to_string().as_str());
81        }
82
83        let request = Request::builder()
84            .method(Method::GET)
85            .uri(url.as_str())
86            .header(ACCEPT, MIME_APPLICATION_JSON)
87            .body(vec![])
88            .map_err(EndpointError::MakeRequestFailed)?;
89
90        Ok(request)
91    }
92
93    fn parse_response(
94        &self,
95        response: Response<Body>,
96    ) -> Result<Self::ParseResponseOutput, Self::ParseResponseError> {
97        let json = serde_json::from_slice(response.body())
98            .map_err(EndpointError::DeResponseBodyJsonFailed)?;
99
100        let rate_limit = if self.key.is_some() {
101            None
102        } else {
103            Some(RateLimit {
104                remaining: get_n_from_headers_by_key(response.headers(), RESPONSE_HEADER_KEY_X_RL)
105                    .ok(),
106                seconds_until_reset: get_n_from_headers_by_key(
107                    response.headers(),
108                    RESPONSE_HEADER_KEY_X_TTL,
109                )
110                .ok(),
111            })
112        };
113
114        Ok((json, rate_limit))
115    }
116}
117
118//
119//
120//
121#[derive(Debug, Clone)]
122pub enum JsonResponseBodyJson {
123    Success(Box<JsonResponseBodySuccessJson>),
124    Fail(JsonResponseBodyFailJson),
125}
126
127impl<'de> Deserialize<'de> for JsonResponseBodyJson {
128    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
129    where
130        D: Deserializer<'de>,
131    {
132        let map = Map::deserialize(deserializer)?;
133
134        if let Some(status) = map.get("status") {
135            let status: Box<str> = Deserialize::deserialize(status).map_err(de::Error::custom)?;
136            match status.as_ref() {
137                "success" => JsonResponseBodySuccessJson::deserialize(Value::Object(map))
138                    .map(|x| JsonResponseBodyJson::Success(x.into()))
139                    .map_err(de::Error::custom),
140                "fail" => JsonResponseBodyFailJson::deserialize(Value::Object(map))
141                    .map(JsonResponseBodyJson::Fail)
142                    .map_err(de::Error::custom),
143                s => Err(de::Error::custom(format!("status [{}] mismatch", s))),
144            }
145        } else if map.get("message").is_some() {
146            JsonResponseBodyFailJson::deserialize(Value::Object(map))
147                .map(JsonResponseBodyJson::Fail)
148                .map_err(de::Error::custom)
149        } else {
150            JsonResponseBodySuccessJson::deserialize(Value::Object(map))
151                .map(|x| JsonResponseBodyJson::Success(x.into()))
152                .map_err(de::Error::custom)
153        }
154    }
155}
156
157impl JsonResponseBodyJson {
158    pub fn is_success(&self) -> bool {
159        matches!(self, Self::Success(_))
160    }
161
162    pub fn as_success(&self) -> Option<&JsonResponseBodySuccessJson> {
163        match self {
164            Self::Success(x) => Some(x),
165            Self::Fail(_) => None,
166        }
167    }
168
169    pub fn as_fail(&self) -> Option<&JsonResponseBodyFailJson> {
170        match self {
171            Self::Success(_) => None,
172            Self::Fail(x) => Some(x),
173        }
174    }
175}
176
177#[derive(Deserialize, Debug, Clone)]
178pub struct JsonResponseBodySuccessJson {
179    #[serde(default = "serde_field_default::default_ip_addr")]
180    pub query: IpAddr,
181    //
182    #[serde(default)]
183    pub continent: Box<str>,
184    #[serde(default, rename = "continentCode")]
185    pub continent_code: ContinentCode,
186    //
187    #[serde(default)]
188    pub country: Box<str>,
189    #[serde(default, rename = "countryCode")]
190    pub country_code: CountryCode,
191    #[serde(default, rename = "countryCode3")]
192    pub country_code3: Box<str>,
193    //
194    #[serde(default)]
195    pub region: Box<str>,
196    #[serde(default, rename = "regionName")]
197    pub region_name: Box<str>,
198    //
199    #[serde(default)]
200    pub city: Box<str>,
201    #[serde(default)]
202    pub district: Box<str>,
203    //
204    #[serde(default)]
205    pub zip: Box<str>,
206    //
207    #[serde(default)]
208    pub lat: f64,
209    #[serde(default)]
210    pub lon: f64,
211    //
212    #[serde(
213        default = "serde_field_default::chrono_tz::default_tz",
214        deserialize_with = "serde_field_with::from_str"
215    )]
216    pub timezone: Tz,
217    #[serde(default)]
218    pub offset: isize,
219    #[serde(
220        default = "serde_field_default::chrono::default_date_time_utc",
221        rename = "currentTime"
222    )]
223    pub current_time: DateTime<Utc>,
224    //
225    #[serde(default)]
226    pub currency: Box<str>,
227    //
228    #[serde(default, rename = "callingCode")]
229    pub calling_code: Box<str>,
230    //
231    #[serde(default)]
232    pub isp: Box<str>,
233    #[serde(default)]
234    pub org: Box<str>,
235    #[serde(default)]
236    pub r#as: Box<str>,
237    #[serde(default)]
238    pub asname: Box<str>,
239    #[serde(default)]
240    pub reverse: Box<str>,
241    //
242    #[serde(default)]
243    pub mobile: bool,
244    #[serde(default)]
245    pub proxy: bool,
246    #[serde(default)]
247    pub hosting: bool,
248}
249
250#[derive(Deserialize, Debug, Clone)]
251pub struct JsonResponseBodyFailJson {
252    #[serde(default)]
253    pub query: Box<str>,
254    //
255    pub message: Box<str>,
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261
262    #[test]
263    fn test_render_request() {
264        let json = Json::new("24.48.0.1", None);
265        let req = json.render_request().unwrap();
266        assert_eq!(req.uri(), "http://ip-api.com/json/24.48.0.1");
267
268        //
269        let json = Json::new("24.48.0.1", Some("foo".into()));
270        let req = json.render_request().unwrap();
271        assert_eq!(req.uri(), "https://pro.ip-api.com/json/24.48.0.1?key=foo");
272
273        let json = json.fields("status,message,country,query");
274        let req = json.render_request().unwrap();
275        assert_eq!(
276            req.uri(),
277            "https://pro.ip-api.com/json/24.48.0.1?key=foo&fields=status%2Cmessage%2Ccountry%2Cquery"
278        );
279
280        let json = json.lang(Lang::EN);
281        let req = json.render_request().unwrap();
282        assert_eq!(
283            req.uri(),
284            "https://pro.ip-api.com/json/24.48.0.1?key=foo&fields=status%2Cmessage%2Ccountry%2Cquery&lang=en"
285        );
286    }
287
288    #[test]
289    fn test_de_response_body_json() {
290        match serde_json::from_str::<JsonResponseBodyJson>(include_str!(
291            "../../tests/response_body_json_files/json_default.json"
292        )) {
293            Ok(JsonResponseBodyJson::Success(ok_json)) => {
294                assert_eq!(ok_json.query.to_string(), "24.48.0.1");
295                assert_eq!(ok_json.continent_code, ContinentCode::AS);
296                assert_eq!(ok_json.country_code, CountryCode::CA);
297            }
298            ret => panic!("{:?}", ret),
299        }
300
301        match serde_json::from_str::<JsonResponseBodyJson>(include_str!(
302            "../../tests/response_body_json_files/json_full_fields.json"
303        )) {
304            Ok(JsonResponseBodyJson::Success(ok_json)) => {
305                assert_eq!(ok_json.query.to_string(), "24.48.0.1");
306                assert_eq!(ok_json.continent_code, ContinentCode::NA);
307                assert_eq!(ok_json.country_code, CountryCode::CA);
308            }
309            ret => panic!("{:?}", ret),
310        }
311
312        match serde_json::from_str::<JsonResponseBodyJson>(include_str!(
313            "../../tests/response_body_json_files/json_full_fields_and_zh-CN_lang.json"
314        )) {
315            Ok(JsonResponseBodyJson::Success(ok_json)) => {
316                assert_eq!(ok_json.query.to_string(), "24.48.0.1");
317                assert_eq!(ok_json.continent_code, ContinentCode::NA);
318                assert_eq!(ok_json.country_code, CountryCode::CA);
319            }
320            ret => panic!("{:?}", ret),
321        }
322
323        //
324        match serde_json::from_str::<JsonResponseBodyJson>(include_str!(
325            "../../tests/response_body_json_files/json_err_1.json"
326        )) {
327            Ok(JsonResponseBodyJson::Fail(err_json)) => {
328                assert_eq!(err_json.query, "".into());
329            }
330            ret => panic!("{:?}", ret),
331        }
332
333        match serde_json::from_str::<JsonResponseBodyJson>(include_str!(
334            "../../tests/response_body_json_files/json_err_2.json"
335        )) {
336            Ok(JsonResponseBodyJson::Fail(err_json)) => {
337                assert_eq!(err_json.query, "24".into());
338            }
339            ret => panic!("{:?}", ret),
340        }
341
342        match serde_json::from_str::<JsonResponseBodyJson>(include_str!(
343            "../../tests/response_body_json_files/json_err_3.json"
344        )) {
345            Ok(JsonResponseBodyJson::Fail(err_json)) => {
346                assert_eq!(err_json.query, "".into());
347            }
348            ret => panic!("{:?}", ret),
349        }
350    }
351}