i3status_rs/geolocator/
ipapi.rs1use super::*;
2
3const IP_API_URL: &str = "https://ipapi.co/json";
4
5#[derive(Deserialize, Debug, Default, Clone)]
7#[serde(deny_unknown_fields)]
8pub struct Config {}
9
10#[derive(Deserialize)]
11struct ApiResponse {
12 #[serde(flatten)]
13 data: Option<IpApiAddressInfo>,
14 #[serde(default)]
15 error: bool,
16 #[serde(default)]
17 reason: ApiError,
18}
19
20#[derive(Deserialize, Default)]
21#[serde(default)]
22struct IpApiAddressInfo {
23 ip: String,
24 version: String,
25 city: String,
26 region: String,
27 region_code: String,
28 country: String,
29 country_name: String,
30 country_code: String,
31 country_code_iso3: String,
32 country_capital: String,
33 country_tld: String,
34 continent_code: String,
35 in_eu: bool,
36 postal: Option<String>,
37 latitude: f64,
38 longitude: f64,
39 timezone: String,
40 utc_offset: String,
41 country_calling_code: String,
42 currency: String,
43 currency_name: String,
44 languages: String,
45 country_area: f64,
46 country_population: f64,
47 asn: String,
48 org: String,
49}
50
51impl From<IpApiAddressInfo> for IPAddressInfo {
52 fn from(val: IpApiAddressInfo) -> Self {
53 IPAddressInfo {
54 ip: val.ip,
55 version: Some(val.version),
56 city: val.city,
57 region: Some(val.region),
58 region_code: Some(val.region_code),
59 country: Some(val.country),
60 country_name: Some(val.country_name),
61 country_code: Some(val.country_code),
62 country_code_iso3: Some(val.country_code_iso3),
63 country_capital: Some(val.country_capital),
64 country_tld: Some(val.country_tld),
65 continent_code: Some(val.continent_code),
66 in_eu: Some(val.in_eu),
67 postal: val.postal,
68 latitude: val.latitude,
69 longitude: val.longitude,
70 timezone: Some(val.timezone),
71 utc_offset: Some(val.utc_offset),
72 country_calling_code: Some(val.country_calling_code),
73 currency: Some(val.currency),
74 currency_name: Some(val.currency_name),
75 languages: Some(val.languages),
76 country_area: Some(val.country_area),
77 country_population: Some(val.country_population),
78 asn: Some(val.asn),
79 org: Some(val.org),
80 }
81 }
82}
83
84#[derive(Deserialize, Default, Debug)]
85#[serde(transparent)]
86struct ApiError(Option<String>);
87
88impl fmt::Display for ApiError {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 f.write_str(self.0.as_deref().unwrap_or("Unknown Error"))
91 }
92}
93impl StdError for ApiError {}
94
95pub struct Ipapi;
96
97impl Ipapi {
98 pub fn name(&self) -> Cow<'static, str> {
99 Cow::Borrowed("ipapi.co")
100 }
101
102 pub async fn get_info(&self, client: &reqwest::Client) -> Result<IPAddressInfo> {
103 let response: ApiResponse = client
104 .get(IP_API_URL)
105 .send()
106 .await
107 .error("Failed during request for current location")?
108 .json()
109 .await
110 .error("Failed while parsing location API result")?;
111
112 if response.error {
113 Err(Error {
114 message: Some("ipapi.co error".into()),
115 cause: Some(Arc::new(response.reason)),
116 })
117 } else {
118 Ok(response
119 .data
120 .error("Failed while parsing location API result")?
121 .into())
122 }
123 }
124}