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}