1use 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#[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#[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 #[serde(default)]
183 pub continent: Box<str>,
184 #[serde(default, rename = "continentCode")]
185 pub continent_code: ContinentCode,
186 #[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 #[serde(default)]
195 pub region: Box<str>,
196 #[serde(default, rename = "regionName")]
197 pub region_name: Box<str>,
198 #[serde(default)]
200 pub city: Box<str>,
201 #[serde(default)]
202 pub district: Box<str>,
203 #[serde(default)]
205 pub zip: Box<str>,
206 #[serde(default)]
208 pub lat: f64,
209 #[serde(default)]
210 pub lon: f64,
211 #[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 #[serde(default)]
226 pub currency: Box<str>,
227 #[serde(default, rename = "callingCode")]
229 pub calling_code: Box<str>,
230 #[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 #[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 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 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 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}