1#![deny(missing_docs)]
54
55use hyper::body::HttpBody;
56use hyper::{Body, Client, Method, Request, Response};
57use serde::{Deserialize, Serialize};
58use serde_json::json;
59
60#[derive(Clone, Debug)]
62pub enum IpApiError {
63 InvalidQuery,
69
70 PrivateRange,
76
77 RateLimit(u8),
82
83 ReservedRange,
89
90 UnexpectedError(Option<String>),
94}
95
96#[derive(Clone, Debug)]
98pub enum IpApiLanguage {
99 De,
101
102 En,
104
105 Es,
107
108 Fr,
110
111 Ja,
113
114 PtBr,
116
117 Ru,
119
120 ZhCn,
122}
123
124#[derive(Deserialize)]
125struct IpApiMessage {
126 message: Option<String>,
127}
128
129#[derive(Clone, Debug, Serialize, Deserialize)]
163#[serde(rename_all = "camelCase")]
164pub struct IpData {
165 pub continent: Option<String>,
167
168 pub continent_code: Option<String>,
170
171 pub country: Option<String>,
173
174 pub country_code: Option<String>,
177
178 pub region: Option<String>,
180
181 pub region_name: Option<String>,
183
184 pub city: Option<String>,
186
187 pub district: Option<String>,
189
190 pub zip: Option<String>,
192
193 pub lat: Option<f32>,
195
196 pub lon: Option<f32>,
198
199 pub timezone: Option<String>,
201
202 pub offset: Option<i32>,
204
205 pub currency: Option<String>,
207
208 pub isp: Option<String>,
210
211 pub org: Option<String>,
213
214 #[serde(rename = "as")]
225 pub as_field: Option<String>,
226
227 pub asname: Option<String>,
229
230 pub reverse: Option<String>,
232
233 pub mobile: Option<bool>,
235
236 pub proxy: Option<bool>,
238
239 pub hosting: Option<bool>,
241
242 pub query: Option<String>,
244}
245
246#[repr(u32)]
247enum IpDataField {
248 Message = 1 << 15,
250
251 Continent = 1 << 20,
252 ContinentCode = 1 << 21,
253 Country = 1 << 0,
254 CountryCode = 1 << 1,
255 Region = 1 << 2,
256 RegionName = 1 << 3,
257 City = 1 << 4,
258 District = 1 << 19,
259 Zip = 1 << 5,
260 Lat = 1 << 6,
261 Lon = 1 << 7,
262 Timezone = 1 << 8,
263 Offset = 1 << 25,
264 Currency = 1 << 23,
265 Isp = 1 << 9,
266 Org = 1 << 10,
267 AsField = 1 << 11,
268 Asname = 1 << 22,
269 Reverse = 1 << 12,
270 Mobile = 1 << 16,
271 Proxy = 1 << 17,
272 Hosting = 1 << 24,
273 Query = 1 << 13,
274}
275
276#[derive(Clone, Debug)]
279pub struct IpApiConfig {
280 numeric_field: u32,
281 language: IpApiLanguage,
282}
283
284impl IpApiConfig {
285 fn build_uri(
286 resource: &str,
287 target: Option<&str>,
288 fields: u32,
289 language: IpApiLanguage,
290 ) -> String {
291 format!(
292 "http://ip-api.com/{}/{}?fields={}{}",
293 resource,
294 target.unwrap_or(""),
295 fields,
296 match language {
297 IpApiLanguage::De => "&lang=de",
298 IpApiLanguage::En => "",
299 IpApiLanguage::Es => "&lang=es",
300 IpApiLanguage::Fr => "&lang=fr",
301 IpApiLanguage::Ja => "&lang=ja",
302 IpApiLanguage::PtBr => "&lang=pt-BR",
303 IpApiLanguage::Ru => "&lang=ru",
304 IpApiLanguage::ZhCn => "&lang=zh-CN",
305 }
306 )
307 }
308
309 fn check_response(response: &Response<Body>) -> Result<(), IpApiError> {
310 if response.status() == 429 {
311 let Some(header) = response.headers().get("X-Ttl") else {
312 return Err(IpApiError::UnexpectedError(Some(
313 "Failed to get `X-Ttl` header from the response".into(),
314 )));
315 };
316 let Ok(header) = header.to_str() else {
317 return Err(IpApiError::UnexpectedError(Some(
318 "Failed to convert `X-Ttl` header from the response to &str".into(),
319 )));
320 };
321 let Ok(header) = header.parse() else {
322 return Err(IpApiError::UnexpectedError(Some(
323 "Failed to parse `X-Ttl` header from the response".into(),
324 )));
325 };
326
327 return Err(IpApiError::RateLimit(header));
328 }
329
330 Ok(())
331 }
332
333 fn check_error_message(message: Option<String>) -> Result<(), IpApiError> {
334 if let Some(message) = message {
335 return match message.as_str() {
336 "invalid query" => Err(IpApiError::InvalidQuery),
337 "private range" => Err(IpApiError::PrivateRange),
338 "reserved range" => Err(IpApiError::ReservedRange),
339 message => Err(IpApiError::UnexpectedError(Some(message.into()))),
340 };
341 }
342
343 Ok(())
344 }
345
346 async fn parse_response_body(response: &mut Response<Body>) -> Result<String, IpApiError> {
347 let Some(body) = response.body_mut().data().await else {
348 return Err(IpApiError::UnexpectedError(Some(
349 "Response is empty".into(),
350 )));
351 };
352 let Ok(body) = body else {
353 return Err(IpApiError::UnexpectedError(Some(
354 "Failed to retrieve body from the response".into(),
355 )));
356 };
357 let Ok(body) = String::from_utf8(body.to_vec()) else {
358 return Err(IpApiError::UnexpectedError(Some(
359 "Failed to convert body from the response to String".into(),
360 )));
361 };
362
363 Ok(body)
364 }
365
366 pub async fn make_request(self, target: &str) -> Result<IpData, IpApiError> {
370 let uri = Self::build_uri("json", Some(target), self.numeric_field, self.language);
371
372 let client = Client::new();
373 let Ok(uri) = uri.parse() else {
374 return Err(IpApiError::UnexpectedError(Some(
375 "Failed to parse request URI".into(),
376 )));
377 };
378 let Ok(response) = &mut client.get(uri).await else {
379 return Err(IpApiError::UnexpectedError(Some(
380 "Failed to make a request".into(),
381 )));
382 };
383
384 Self::check_response(response)?;
385
386 let body = Self::parse_response_body(response).await?;
387 let Ok(ip_data): Result<IpApiMessage, _> = serde_json::from_str(body.as_str()) else {
388 return Err(IpApiError::UnexpectedError(Some(
389 "Failed to parse body from the response".into(),
390 )));
391 };
392
393 Self::check_error_message(ip_data.message)?;
394
395 let Ok(ip_data): Result<IpData, _> = serde_json::from_str(body.as_str()) else {
396 return Err(IpApiError::UnexpectedError(Some(
397 "Failed to parse body from the response".into(),
398 )));
399 };
400
401 Ok(ip_data)
402 }
403
404 pub async fn make_batch_request(self, targets: Vec<&str>) -> Result<Vec<IpData>, IpApiError> {
408 let uri = Self::build_uri("batch", None, self.numeric_field, self.language);
409
410 let Ok(request) = Request::builder()
411 .method(Method::POST)
412 .uri(uri)
413 .header("content-type", "application/json")
414 .body(Body::from(json!(targets).to_string()))
415 else {
416 return Err(IpApiError::UnexpectedError(Some(
417 "Failed to build a request".into(),
418 )));
419 };
420
421 let client = Client::new();
422 let Ok(response) = &mut client.request(request).await else {
423 return Err(IpApiError::UnexpectedError(Some(
424 "Failed to make a request".into(),
425 )));
426 };
427
428 Self::check_response(response)?;
429
430 let body = Self::parse_response_body(response).await?;
431 let Ok(ip_batch_data): Result<Vec<IpApiMessage>, _> = serde_json::from_str(body.as_str())
432 else {
433 return Err(IpApiError::UnexpectedError(Some(
434 "Failed to parse body from the response".into(),
435 )));
436 };
437
438 for ip_data in ip_batch_data {
439 Self::check_error_message(ip_data.message)?;
440 }
441
442 let Ok(ip_batch_data): Result<Vec<IpData>, _> = serde_json::from_str(body.as_str()) else {
443 return Err(IpApiError::UnexpectedError(Some(
444 "Failed to parse body from the response".into(),
445 )));
446 };
447
448 Ok(ip_batch_data)
449 }
450
451 pub fn include_continent(mut self) -> Self {
453 self.numeric_field |= IpDataField::Continent as u32;
454 self
455 }
456
457 pub fn include_continent_code(mut self) -> Self {
459 self.numeric_field |= IpDataField::ContinentCode as u32;
460 self
461 }
462
463 pub fn include_country(mut self) -> Self {
465 self.numeric_field |= IpDataField::Country as u32;
466 self
467 }
468
469 pub fn include_country_code(mut self) -> Self {
471 self.numeric_field |= IpDataField::CountryCode as u32;
472 self
473 }
474
475 pub fn include_region(mut self) -> Self {
477 self.numeric_field |= IpDataField::Region as u32;
478 self
479 }
480
481 pub fn include_region_name(mut self) -> Self {
483 self.numeric_field |= IpDataField::RegionName as u32;
484 self
485 }
486
487 pub fn include_city(mut self) -> Self {
489 self.numeric_field |= IpDataField::City as u32;
490 self
491 }
492
493 pub fn include_district(mut self) -> Self {
495 self.numeric_field |= IpDataField::District as u32;
496 self
497 }
498
499 pub fn include_zip(mut self) -> Self {
501 self.numeric_field |= IpDataField::Zip as u32;
502 self
503 }
504
505 pub fn include_lat(mut self) -> Self {
507 self.numeric_field |= IpDataField::Lat as u32;
508 self
509 }
510
511 pub fn include_lon(mut self) -> Self {
513 self.numeric_field |= IpDataField::Lon as u32;
514 self
515 }
516
517 pub fn include_timezone(mut self) -> Self {
519 self.numeric_field |= IpDataField::Timezone as u32;
520 self
521 }
522
523 pub fn include_offset(mut self) -> Self {
525 self.numeric_field |= IpDataField::Offset as u32;
526 self
527 }
528
529 pub fn include_currency(mut self) -> Self {
531 self.numeric_field |= IpDataField::Currency as u32;
532 self
533 }
534
535 pub fn include_isp(mut self) -> Self {
537 self.numeric_field |= IpDataField::Isp as u32;
538 self
539 }
540
541 pub fn include_org(mut self) -> Self {
543 self.numeric_field |= IpDataField::Org as u32;
544 self
545 }
546
547 pub fn include_as_field(mut self) -> Self {
549 self.numeric_field |= IpDataField::AsField as u32;
550 self
551 }
552
553 pub fn include_asname(mut self) -> Self {
555 self.numeric_field |= IpDataField::Asname as u32;
556 self
557 }
558
559 pub fn include_reverse(mut self) -> Self {
561 self.numeric_field |= IpDataField::Reverse as u32;
562 self
563 }
564
565 pub fn include_mobile(mut self) -> Self {
567 self.numeric_field |= IpDataField::Mobile as u32;
568 self
569 }
570
571 pub fn include_proxy(mut self) -> Self {
573 self.numeric_field |= IpDataField::Proxy as u32;
574 self
575 }
576
577 pub fn include_hosting(mut self) -> Self {
579 self.numeric_field |= IpDataField::Hosting as u32;
580 self
581 }
582
583 pub fn include_query(mut self) -> Self {
585 self.numeric_field |= IpDataField::Query as u32;
586 self
587 }
588
589 pub fn exclude_continent(mut self) -> Self {
591 self.numeric_field &= !(IpDataField::Continent as u32);
592 self
593 }
594
595 pub fn exclude_continent_code(mut self) -> Self {
597 self.numeric_field &= !(IpDataField::ContinentCode as u32);
598 self
599 }
600
601 pub fn exclude_country(mut self) -> Self {
603 self.numeric_field &= !(IpDataField::Country as u32);
604 self
605 }
606
607 pub fn exclude_country_code(mut self) -> Self {
609 self.numeric_field &= !(IpDataField::CountryCode as u32);
610 self
611 }
612
613 pub fn exclude_region(mut self) -> Self {
615 self.numeric_field &= !(IpDataField::Region as u32);
616 self
617 }
618
619 pub fn exclude_region_name(mut self) -> Self {
621 self.numeric_field &= !(IpDataField::RegionName as u32);
622 self
623 }
624
625 pub fn exclude_city(mut self) -> Self {
627 self.numeric_field &= !(IpDataField::City as u32);
628 self
629 }
630
631 pub fn exclude_district(mut self) -> Self {
633 self.numeric_field &= !(IpDataField::District as u32);
634 self
635 }
636
637 pub fn exclude_zip(mut self) -> Self {
639 self.numeric_field &= !(IpDataField::Zip as u32);
640 self
641 }
642
643 pub fn exclude_lat(mut self) -> Self {
645 self.numeric_field &= !(IpDataField::Lat as u32);
646 self
647 }
648
649 pub fn exclude_lon(mut self) -> Self {
651 self.numeric_field &= !(IpDataField::Lon as u32);
652 self
653 }
654
655 pub fn exclude_timezone(mut self) -> Self {
657 self.numeric_field &= !(IpDataField::Timezone as u32);
658 self
659 }
660
661 pub fn exclude_offset(mut self) -> Self {
663 self.numeric_field &= !(IpDataField::Offset as u32);
664 self
665 }
666
667 pub fn exclude_currency(mut self) -> Self {
669 self.numeric_field &= !(IpDataField::Currency as u32);
670 self
671 }
672
673 pub fn exclude_isp(mut self) -> Self {
675 self.numeric_field &= !(IpDataField::Isp as u32);
676 self
677 }
678
679 pub fn exclude_org(mut self) -> Self {
681 self.numeric_field &= !(IpDataField::Org as u32);
682 self
683 }
684
685 pub fn exclude_as_field(mut self) -> Self {
687 self.numeric_field &= !(IpDataField::AsField as u32);
688 self
689 }
690
691 pub fn exclude_asname(mut self) -> Self {
693 self.numeric_field &= !(IpDataField::Asname as u32);
694 self
695 }
696
697 pub fn exclude_reverse(mut self) -> Self {
699 self.numeric_field &= !(IpDataField::Reverse as u32);
700 self
701 }
702
703 pub fn exclude_mobile(mut self) -> Self {
705 self.numeric_field &= !(IpDataField::Mobile as u32);
706 self
707 }
708
709 pub fn exclude_proxy(mut self) -> Self {
711 self.numeric_field &= !(IpDataField::Proxy as u32);
712 self
713 }
714
715 pub fn exclude_hosting(mut self) -> Self {
717 self.numeric_field &= !(IpDataField::Hosting as u32);
718 self
719 }
720
721 pub fn exclude_query(mut self) -> Self {
723 self.numeric_field &= !(IpDataField::Query as u32);
724 self
725 }
726
727 pub fn set_language(mut self, language: IpApiLanguage) -> Self {
729 self.language = language;
730 self
731 }
732}
733
734pub const fn generate_empty_config() -> IpApiConfig {
736 IpApiConfig {
737 numeric_field: IpDataField::Message as u32,
738 language: IpApiLanguage::En,
739 }
740}
741
742pub const fn generate_minimum_config() -> IpApiConfig {
744 IpApiConfig {
745 numeric_field: IpDataField::Message as u32
746 | IpDataField::CountryCode as u32
747 | IpDataField::City as u32
748 | IpDataField::Timezone as u32
749 | IpDataField::Offset as u32
750 | IpDataField::Currency as u32
751 | IpDataField::Isp as u32,
752 language: IpApiLanguage::En,
753 }
754}
755
756pub const fn generate_maximum_config() -> IpApiConfig {
758 IpApiConfig {
759 numeric_field: IpDataField::Message as u32
760 | IpDataField::Continent as u32
761 | IpDataField::ContinentCode as u32
762 | IpDataField::Country as u32
763 | IpDataField::CountryCode as u32
764 | IpDataField::Region as u32
765 | IpDataField::RegionName as u32
766 | IpDataField::City as u32
767 | IpDataField::District as u32
768 | IpDataField::Zip as u32
769 | IpDataField::Lat as u32
770 | IpDataField::Lon as u32
771 | IpDataField::Timezone as u32
772 | IpDataField::Offset as u32
773 | IpDataField::Currency as u32
774 | IpDataField::Isp as u32
775 | IpDataField::Org as u32
776 | IpDataField::AsField as u32
777 | IpDataField::Asname as u32
778 | IpDataField::Reverse as u32
779 | IpDataField::Mobile as u32
780 | IpDataField::Proxy as u32
781 | IpDataField::Hosting as u32
782 | IpDataField::Query as u32,
783 language: IpApiLanguage::En,
784 }
785}