Skip to main content

open_meteo_rs/
geocoding.rs

1use super::{client, errors};
2use serde::{Deserialize, Serialize};
3use std::error::Error;
4
5#[derive(Debug, Clone, Default)]
6pub struct Options {
7    pub name: Option<String>,
8    pub language: Option<String>,
9    pub count: Option<u16>,
10    pub apikey: Option<String>,
11}
12
13impl Options {
14    #[must_use]
15    pub fn with_name(mut self, name: String) -> Self {
16        self.name = Some(name);
17        self
18    }
19
20    #[must_use]
21    pub fn with_language(mut self, language: String) -> Self {
22        self.language = Some(language);
23        self
24    }
25
26    #[must_use]
27    pub fn with_count(mut self, count: u16) -> Self {
28        self.count = Some(count);
29        self
30    }
31
32    fn into_params(self) -> Vec<(String, String)> {
33        let mut params = Vec::new();
34
35        if let Some(v) = self.name {
36            params.push(("name".into(), v));
37        }
38
39        if let Some(v) = self.language {
40            params.push(("language".into(), v));
41        }
42
43        if let Some(v) = self.count {
44            params.push(("count".into(), v.to_string()));
45        }
46
47        if let Some(apikey) = self.apikey {
48            params.push(("apikey".into(), apikey.clone()));
49        }
50
51        params
52    }
53}
54
55#[derive(Debug, Serialize, Deserialize)]
56pub struct GeocodingResponse {
57    pub results: Option<Vec<GeocodingResult>>,
58    pub generationtime_ms: Option<f64>,
59}
60
61#[derive(Debug, Serialize, Deserialize)]
62pub struct GeocodingResult {
63    pub id: Option<i64>,
64    pub name: Option<String>,
65    pub latitude: Option<f64>,
66    pub longitude: Option<f64>,
67    pub elevation: Option<f64>,
68    pub feature_code: Option<String>,
69    pub country_code: Option<String>,
70    pub admin1_id: Option<i64>,
71    pub admin3_id: Option<i64>,
72    pub admin4_id: Option<i64>,
73    pub timezone: Option<String>,
74    pub population: Option<i64>,
75    pub postcodes: Option<Vec<String>>,
76    pub country_id: Option<i64>,
77    pub country: Option<String>,
78    pub admin1: Option<String>,
79    pub admin3: Option<String>,
80    pub admin4: Option<String>,
81    pub admin2_id: Option<i64>,
82    pub admin2: Option<String>,
83}
84
85impl client::Client {
86    /// Make a geocoding request.
87    ///
88    /// ### Errors
89    ///
90    /// Will return `Err` if api return an invaid response or in case of network error.
91    pub async fn geocoding(&self, opts: Options) -> Result<GeocodingResponse, Box<dyn Error>> {
92        let url = reqwest::Url::parse_with_params(&self.geocoding_endpoint, opts.into_params())?;
93        let res = self.http_client.get(url).send().await?;
94
95        if res.status().is_success() {
96            let res = res.json().await?;
97            return Ok(res);
98        }
99
100        Err(Box::new(errors::ClientError::InvalidResponseStatus {
101            status_code: res.status().as_u16(),
102            text: res.text().await.unwrap_or(String::new()),
103        }))
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[tokio::test]
112    async fn search() {
113        let clt = client::Client::new();
114        let opts = Options::default().with_name("Paris".into());
115        let res = clt.geocoding(opts).await.unwrap();
116
117        println!("{res:?}");
118
119        assert!(!res.results.unwrap().is_empty());
120    }
121}