openweather_cli/
query.rs

1use crate::config::{Config, GeometryMode};
2
3#[derive(Debug)]
4pub enum Geometry {
5    Location { lat: f64, lon: f64 },
6    City { city: String, country_code: String },
7}
8
9impl Geometry {
10    pub async fn new(mode: &GeometryMode) -> anyhow::Result<Self> {
11        let url = url::Url::parse("http://ip-api.com/json/")?;
12        let text = reqwest::get(url).await?.text().await?;
13        let json: serde_json::Value = serde_json::from_str(text.as_str())?;
14        log::info!("[geo json]: {:?}", json);
15        fn field_access<T>(
16            json: &serde_json::Value,
17            field: &'static str,
18            f: impl FnOnce(&serde_json::Value) -> Option<T>,
19        ) -> anyhow::Result<T> {
20            json.get(field)
21                .and_then(f)
22                .ok_or_else(|| anyhow::anyhow!(format!("error getting {} from ip-api", field)))
23        }
24        match mode {
25            GeometryMode::Location => {
26                let field = |field: &'static str| -> anyhow::Result<f64> {
27                    field_access(&json, field, |x| x.as_f64())
28                };
29                let lat = field("lat")?;
30                let lon = field("lon")?;
31                Ok(Geometry::Location { lat, lon })
32            }
33            GeometryMode::City => {
34                let field = |field: &'static str| -> anyhow::Result<String> {
35                    field_access(&json, field, |x| x.as_str().map(ToString::to_string))
36                };
37                let city = field("city")?;
38                let country_code = field("countryCode")?;
39                Ok(Geometry::City { city, country_code })
40            }
41        }
42    }
43}
44
45pub struct Weather {
46    pub body: String,
47}
48
49impl Weather {
50    pub async fn new(config: Config, geo: Geometry) -> anyhow::Result<Self> {
51        let mut url_str = format!("https://api.openweathermap.org/data");
52        match geo {
53            Geometry::Location { lat, lon } => {
54                url_str += &format!("/2.5/onecall");
55                url_str += &format!("?appid={}", config.api_key);
56                url_str += &format!("&lat={}", lat);
57                url_str += &format!("&lon={}", lon);
58            }
59            Geometry::City { city, country_code } => {
60                url_str += &format!("/2.5/weather");
61                url_str += &format!("?appid={}", config.api_key);
62                url_str += &format!("&q={},{}", city, country_code);
63            }
64        }
65        url_str += &format!("&units={}", "metric");
66        let mut exclude = Vec::new();
67        if !config.minutely {
68            exclude.push("minutely");
69        }
70        if !config.hourly {
71            exclude.push("hourly");
72        }
73        if !config.daily {
74            exclude.push("daily");
75        }
76        if exclude.len() > 0 {
77            url_str += &format!("&exclude={}", exclude.join(","));
78        }
79        log::info!("[weather url]: {}", url_str);
80        let url = url::Url::parse(&url_str)?;
81        let body = reqwest::get(url).await?.text().await?;
82        Ok(Self { body })
83    }
84}