ip_api_client/
lib.rs

1//! The client (based on [ip-api.com API](https://ip-api.com/docs/api:json))
2//! allows you to get information about the IP address
3//!
4//! # Example
5//!
6//! ```rust
7//! # #[tokio::main]
8//! # async fn main() {
9//! use ip_api_client as Client;
10//! use ip_api_client::{IpApiLanguage, IpData};
11//!
12//! // You can
13//! // `generate_empty_config` (to create your own config from scratch)
14//! // `generate_minimum_config` (that includes only important fields)
15//! // `generate_maximum_config` (that includes all fields)
16//! let ip_data: IpData = Client::generate_empty_config()
17//!     // or `exclude_country` if this field is already included
18//!     // in the generated config
19//!     .include_country()
20//!     // or `exclude_currency` if this field is already included in
21//!     // the generated config
22//!     .include_currency()
23//!     // available languages: de/en (default)/es/fr/ja/pt-Br/ru/zh-CN
24//!     .set_language(IpApiLanguage::De)
25//!     // `make_request` takes
26//!     // "ip"/"domain"/"empty string (if you want to request your ip)"
27//!     .make_request("1.1.1.1")
28//!     .await
29//!     .unwrap();
30//!
31//! println!(
32//!     "{}'s national currency is {}",
33//!     ip_data.country.unwrap(),
34//!     ip_data.currency.unwrap(),
35//! );
36//!
37//! // If you want to request more than one ip, you can use `make_batch_request`
38//! let ip_batch_data: Vec<IpData> = Client::generate_empty_config()
39//!     .include_isp()
40//!     // `make_batch_request` takes "IPv4"/"IPv6"
41//!     .make_batch_request(vec!["1.1.1.1", "8.8.8.8"])
42//!     .await
43//!     .unwrap();
44//!
45//! println!(
46//!     "1.1.1.1 belongs to `{}` and 8.8.8.8 belongs to `{}`",
47//!     ip_batch_data.first().unwrap().isp.as_ref().unwrap(),
48//!     ip_batch_data.last().unwrap().isp.as_ref().unwrap(),
49//! );
50//! # }
51//! ```
52
53#![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/// Represents all the ways that a request can fail
61#[derive(Clone, Debug)]
62pub enum IpApiError {
63    /// Incorrect IP address or non-existent domain
64    ///
65    /// # Example
66    ///
67    /// 1.1.1.one **OR** test.google.com
68    InvalidQuery,
69
70    /// IPs in your network
71    ///
72    /// # Example
73    ///
74    /// 192.168.1.1
75    PrivateRange,
76
77    /// [ip-api.com API](https://ip-api.com/docs/api:json) is limited to 45 requests per minute
78    /// from one IP address
79    ///
80    /// Contains the remaining time before a possible re-request in seconds
81    RateLimit(u8),
82
83    /// Reserved Range
84    ///
85    /// # Example
86    ///
87    /// 127.0.0.1 **OR** localhost
88    ReservedRange,
89
90    /// Unexpected Error
91    ///
92    /// May contain additional information
93    UnexpectedError(Option<String>),
94}
95
96/// Represents all available languages for [`IpData`]
97#[derive(Clone, Debug)]
98pub enum IpApiLanguage {
99    /// Deutsch (German)
100    De,
101
102    /// English (default)
103    En,
104
105    /// Español (Spanish)
106    Es,
107
108    /// Français (French)
109    Fr,
110
111    /// 日本語 (Japanese)
112    Ja,
113
114    /// Português - Brasil (Portuguese - Brasil)
115    PtBr,
116
117    /// Русский (Russian)
118    Ru,
119
120    /// 中国 (Chinese)
121    ZhCn,
122}
123
124#[derive(Deserialize)]
125struct IpApiMessage {
126    message: Option<String>,
127}
128
129/// The data that will be received after the making a request
130///
131/// # Example response
132///
133/// ```rust
134/// # use ip_api_client::IpData;
135/// #
136/// IpData {
137///     continent: Some("Oceania".to_string()),
138///     continent_code: Some("OC".to_string()),
139///     country: Some("Australia".to_string()),
140///     country_code: Some("AU".to_string()),
141///     region: Some("QLD".to_string()),
142///     region_name: Some("Queensland".to_string()),
143///     city: Some("South Brisbane".to_string()),
144///     district: Some("".to_string()),
145///     zip: Some("4101".to_string()),
146///     lat: Some(-27.4766),
147///     lon: Some(153.0166),
148///     timezone: Some("Australia/Brisbane".to_string()),
149///     offset: Some(36000),
150///     currency: Some("AUD".to_string()),
151///     isp: Some("Cloudflare, Inc".to_string()),
152///     org: Some("APNIC and Cloudflare DNS Resolver project".to_string()),
153///     as_field: Some("AS13335 Cloudflare, Inc.".to_string()),
154///     asname: Some("CLOUDFLARENET".to_string()),
155///     reverse: Some("one.one.one.one".to_string()),
156///     mobile: Some(false),
157///     proxy: Some(false),
158///     hosting: Some(true),
159///     query: Some("1.1.1.1".to_string()),
160/// };
161/// ```
162#[derive(Clone, Debug, Serialize, Deserialize)]
163#[serde(rename_all = "camelCase")]
164pub struct IpData {
165    /// Continent name
166    pub continent: Option<String>,
167
168    /// Two-letter continent code
169    pub continent_code: Option<String>,
170
171    /// Country name
172    pub country: Option<String>,
173
174    /// Two-letter country code
175    /// [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
176    pub country_code: Option<String>,
177
178    /// Region/state short code (FIPS or ISO)
179    pub region: Option<String>,
180
181    /// Region/state
182    pub region_name: Option<String>,
183
184    /// City
185    pub city: Option<String>,
186
187    /// District (subdivision of city)
188    pub district: Option<String>,
189
190    /// Zip code
191    pub zip: Option<String>,
192
193    /// Latitude
194    pub lat: Option<f32>,
195
196    /// Longitude
197    pub lon: Option<f32>,
198
199    /// Timezone (tz)
200    pub timezone: Option<String>,
201
202    /// Timezone UTC DST offset in seconds
203    pub offset: Option<i32>,
204
205    /// National currency
206    pub currency: Option<String>,
207
208    /// ISP name
209    pub isp: Option<String>,
210
211    /// Organization name
212    pub org: Option<String>,
213
214    /// AS number and organization, separated by space (RIR).
215    /// Empty for IP blocks not being announced in BGP tables.
216    ///
217    /// # Notice
218    ///
219    /// We use `as_field` instead of `as`
220    /// (As stated in the [ip-api.com API documentation](https://ip-api.com/docs/api:json#as))
221    /// since it's a
222    /// [strict keyword](https://doc.rust-lang.org/reference/keywords.html#strict-keywords) in rust,
223    /// such as `pub`, `impl` or `struct`.
224    #[serde(rename = "as")]
225    pub as_field: Option<String>,
226
227    /// AS name (RIR). Empty for IP blocks not being announced in BGP tables.
228    pub asname: Option<String>,
229
230    /// Reverse DNS of the IP (can delay response)
231    pub reverse: Option<String>,
232
233    /// Mobile (cellular) connection
234    pub mobile: Option<bool>,
235
236    /// Proxy, VPN or Tor exit address
237    pub proxy: Option<bool>,
238
239    /// Hosting, colocated or data center
240    pub hosting: Option<bool>,
241
242    /// IP/Domain used for the query
243    pub query: Option<String>,
244}
245
246#[repr(u32)]
247enum IpDataField {
248    // Status = 1 << 14, // Never used
249    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/// Configuration structure allows you to customize the requested fields in the request
277/// to save traffic
278#[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    /// Making a request to [ip-api.com API](https://ip-api.com/docs/api:json)
367    ///
368    /// `target` can be "ip"/"domain"/"empty string (if you want to request your ip)"
369    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    /// Making a batch request to [ip-api.com API](https://ip-api.com/docs/api:batch)
405    ///
406    /// `target` can be "IPv4"/"IPv6"
407    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    /// Include [`continent`](struct.IpData.html#structfield.continent) in request
452    pub fn include_continent(mut self) -> Self {
453        self.numeric_field |= IpDataField::Continent as u32;
454        self
455    }
456
457    /// Include [`continent_code`](struct.IpData.html#structfield.continent_code) in request
458    pub fn include_continent_code(mut self) -> Self {
459        self.numeric_field |= IpDataField::ContinentCode as u32;
460        self
461    }
462
463    /// Include [`country`](struct.IpData.html#structfield.country) in request
464    pub fn include_country(mut self) -> Self {
465        self.numeric_field |= IpDataField::Country as u32;
466        self
467    }
468
469    /// Include [`country_code`](struct.IpData.html#structfield.country_code) in request
470    pub fn include_country_code(mut self) -> Self {
471        self.numeric_field |= IpDataField::CountryCode as u32;
472        self
473    }
474
475    /// Include [`region`](struct.IpData.html#structfield.region) in request
476    pub fn include_region(mut self) -> Self {
477        self.numeric_field |= IpDataField::Region as u32;
478        self
479    }
480
481    /// Include [`region_name`](struct.IpData.html#structfield.region_name) in request
482    pub fn include_region_name(mut self) -> Self {
483        self.numeric_field |= IpDataField::RegionName as u32;
484        self
485    }
486
487    /// Include [`city`](struct.IpData.html#structfield.city) in request
488    pub fn include_city(mut self) -> Self {
489        self.numeric_field |= IpDataField::City as u32;
490        self
491    }
492
493    /// Include [`district`](struct.IpData.html#structfield.district) in request
494    pub fn include_district(mut self) -> Self {
495        self.numeric_field |= IpDataField::District as u32;
496        self
497    }
498
499    /// Include [`zip`](struct.IpData.html#structfield.zip) in request
500    pub fn include_zip(mut self) -> Self {
501        self.numeric_field |= IpDataField::Zip as u32;
502        self
503    }
504
505    /// Include [`lat`](struct.IpData.html#structfield.lat) in request
506    pub fn include_lat(mut self) -> Self {
507        self.numeric_field |= IpDataField::Lat as u32;
508        self
509    }
510
511    /// Include [`lon`](struct.IpData.html#structfield.lon) in request
512    pub fn include_lon(mut self) -> Self {
513        self.numeric_field |= IpDataField::Lon as u32;
514        self
515    }
516
517    /// Include [`timezone`](struct.IpData.html#structfield.timezone) in request
518    pub fn include_timezone(mut self) -> Self {
519        self.numeric_field |= IpDataField::Timezone as u32;
520        self
521    }
522
523    /// Include [`offset`](struct.IpData.html#structfield.offset) in request
524    pub fn include_offset(mut self) -> Self {
525        self.numeric_field |= IpDataField::Offset as u32;
526        self
527    }
528
529    /// Include [`currency`](struct.IpData.html#structfield.currency) in request
530    pub fn include_currency(mut self) -> Self {
531        self.numeric_field |= IpDataField::Currency as u32;
532        self
533    }
534
535    /// Include [`isp`](struct.IpData.html#structfield.isp) in request
536    pub fn include_isp(mut self) -> Self {
537        self.numeric_field |= IpDataField::Isp as u32;
538        self
539    }
540
541    /// Include [`org`](struct.IpData.html#structfield.org) in request
542    pub fn include_org(mut self) -> Self {
543        self.numeric_field |= IpDataField::Org as u32;
544        self
545    }
546
547    /// Include [`as_field`](struct.IpData.html#structfield.as_field) in request
548    pub fn include_as_field(mut self) -> Self {
549        self.numeric_field |= IpDataField::AsField as u32;
550        self
551    }
552
553    /// Include [`asname`](struct.IpData.html#structfield.asname) in request
554    pub fn include_asname(mut self) -> Self {
555        self.numeric_field |= IpDataField::Asname as u32;
556        self
557    }
558
559    /// Include [`reverse`](struct.IpData.html#structfield.reverse) in request
560    pub fn include_reverse(mut self) -> Self {
561        self.numeric_field |= IpDataField::Reverse as u32;
562        self
563    }
564
565    /// Include [`mobile`](struct.IpData.html#structfield.mobile) in request
566    pub fn include_mobile(mut self) -> Self {
567        self.numeric_field |= IpDataField::Mobile as u32;
568        self
569    }
570
571    /// Include [`proxy`](struct.IpData.html#structfield.proxy) in request
572    pub fn include_proxy(mut self) -> Self {
573        self.numeric_field |= IpDataField::Proxy as u32;
574        self
575    }
576
577    /// Include [`hosting`](struct.IpData.html#structfield.hosting) in request
578    pub fn include_hosting(mut self) -> Self {
579        self.numeric_field |= IpDataField::Hosting as u32;
580        self
581    }
582
583    /// Include [`query`](struct.IpData.html#structfield.query) in request
584    pub fn include_query(mut self) -> Self {
585        self.numeric_field |= IpDataField::Query as u32;
586        self
587    }
588
589    /// Exclude [`continent`](struct.IpData.html#structfield.continent) from request
590    pub fn exclude_continent(mut self) -> Self {
591        self.numeric_field &= !(IpDataField::Continent as u32);
592        self
593    }
594
595    /// Exclude [`continent_code`](struct.IpData.html#structfield.continent_code) from request
596    pub fn exclude_continent_code(mut self) -> Self {
597        self.numeric_field &= !(IpDataField::ContinentCode as u32);
598        self
599    }
600
601    /// Exclude [`country`](struct.IpData.html#structfield.country) from request
602    pub fn exclude_country(mut self) -> Self {
603        self.numeric_field &= !(IpDataField::Country as u32);
604        self
605    }
606
607    /// Exclude [`country_code`](struct.IpData.html#structfield.country_code) from request
608    pub fn exclude_country_code(mut self) -> Self {
609        self.numeric_field &= !(IpDataField::CountryCode as u32);
610        self
611    }
612
613    /// Exclude [`region`](struct.IpData.html#structfield.region) from request
614    pub fn exclude_region(mut self) -> Self {
615        self.numeric_field &= !(IpDataField::Region as u32);
616        self
617    }
618
619    /// Exclude [`region_name`](struct.IpData.html#structfield.region_name) from request
620    pub fn exclude_region_name(mut self) -> Self {
621        self.numeric_field &= !(IpDataField::RegionName as u32);
622        self
623    }
624
625    /// Exclude [`city`](struct.IpData.html#structfield.city) from request
626    pub fn exclude_city(mut self) -> Self {
627        self.numeric_field &= !(IpDataField::City as u32);
628        self
629    }
630
631    /// Exclude [`district`](struct.IpData.html#structfield.district) from request
632    pub fn exclude_district(mut self) -> Self {
633        self.numeric_field &= !(IpDataField::District as u32);
634        self
635    }
636
637    /// Exclude [`zip`](struct.IpData.html#structfield.zip) from request
638    pub fn exclude_zip(mut self) -> Self {
639        self.numeric_field &= !(IpDataField::Zip as u32);
640        self
641    }
642
643    /// Exclude [`lat`](struct.IpData.html#structfield.lat) from request
644    pub fn exclude_lat(mut self) -> Self {
645        self.numeric_field &= !(IpDataField::Lat as u32);
646        self
647    }
648
649    /// Exclude [`lon`](struct.IpData.html#structfield.lon) from request
650    pub fn exclude_lon(mut self) -> Self {
651        self.numeric_field &= !(IpDataField::Lon as u32);
652        self
653    }
654
655    /// Exclude [`timezone`](struct.IpData.html#structfield.timezone) from request
656    pub fn exclude_timezone(mut self) -> Self {
657        self.numeric_field &= !(IpDataField::Timezone as u32);
658        self
659    }
660
661    /// Exclude [`offset`](struct.IpData.html#structfield.offset) from request
662    pub fn exclude_offset(mut self) -> Self {
663        self.numeric_field &= !(IpDataField::Offset as u32);
664        self
665    }
666
667    /// Exclude [`currency`](struct.IpData.html#structfield.currency) from request
668    pub fn exclude_currency(mut self) -> Self {
669        self.numeric_field &= !(IpDataField::Currency as u32);
670        self
671    }
672
673    /// Exclude [`isp`](struct.IpData.html#structfield.isp) from request
674    pub fn exclude_isp(mut self) -> Self {
675        self.numeric_field &= !(IpDataField::Isp as u32);
676        self
677    }
678
679    /// Exclude [`org`](struct.IpData.html#structfield.org) from request
680    pub fn exclude_org(mut self) -> Self {
681        self.numeric_field &= !(IpDataField::Org as u32);
682        self
683    }
684
685    /// Exclude [`as_field`](struct.IpData.html#structfield.as_field) from request
686    pub fn exclude_as_field(mut self) -> Self {
687        self.numeric_field &= !(IpDataField::AsField as u32);
688        self
689    }
690
691    /// Exclude [`asname`](struct.IpData.html#structfield.asname) from request
692    pub fn exclude_asname(mut self) -> Self {
693        self.numeric_field &= !(IpDataField::Asname as u32);
694        self
695    }
696
697    /// Exclude [`reverse`](struct.IpData.html#structfield.reverse) from request
698    pub fn exclude_reverse(mut self) -> Self {
699        self.numeric_field &= !(IpDataField::Reverse as u32);
700        self
701    }
702
703    /// Exclude [`mobile`](struct.IpData.html#structfield.mobile) from request
704    pub fn exclude_mobile(mut self) -> Self {
705        self.numeric_field &= !(IpDataField::Mobile as u32);
706        self
707    }
708
709    /// Exclude [`proxy`](struct.IpData.html#structfield.proxy) from request
710    pub fn exclude_proxy(mut self) -> Self {
711        self.numeric_field &= !(IpDataField::Proxy as u32);
712        self
713    }
714
715    /// Exclude [`hosting`](struct.IpData.html#structfield.hosting) from request
716    pub fn exclude_hosting(mut self) -> Self {
717        self.numeric_field &= !(IpDataField::Hosting as u32);
718        self
719    }
720
721    /// Exclude [`query`](struct.IpData.html#structfield.query) from request
722    pub fn exclude_query(mut self) -> Self {
723        self.numeric_field &= !(IpDataField::Query as u32);
724        self
725    }
726
727    /// Set custom language for [`IpData`]
728    pub fn set_language(mut self, language: IpApiLanguage) -> Self {
729        self.language = language;
730        self
731    }
732}
733
734/// Create an empty config to create your own from scratch
735pub const fn generate_empty_config() -> IpApiConfig {
736    IpApiConfig {
737        numeric_field: IpDataField::Message as u32,
738        language: IpApiLanguage::En,
739    }
740}
741
742/// Generate minimum config that includes only important fields
743pub 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
756/// Generate maximum config that includes all fields
757pub 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}