fjordgard

A desktop clock application
Log | Files | Refs | README | LICENSE

lib.rs (3957B)


      1 use std::fmt::Debug;
      2 
      3 use reqwest::Client;
      4 
      5 pub use error::Error;
      6 use error::Result;
      7 use model::*;
      8 use serde::{Serialize, de::DeserializeOwned};
      9 
     10 mod error;
     11 pub mod model;
     12 
     13 #[cfg(not(target_arch = "wasm32"))]
     14 const USER_AGENT: &str = concat!("fjordgard/", env!("CARGO_PKG_VERSION"));
     15 const GEOCODING_API_HOST: &str = "geocoding-api.open-meteo.com";
     16 const FORECASTING_API_HOST: &str = "api.open-meteo.com";
     17 
     18 pub struct MeteoClient {
     19     api_key: Option<String>,
     20     client: Client,
     21 }
     22 
     23 impl MeteoClient {
     24     pub fn new(api_key: Option<&str>) -> Result<Self> {
     25         #[cfg(not(target_arch = "wasm32"))]
     26         let client = Client::builder().user_agent(USER_AGENT).build()?;
     27         #[cfg(target_arch = "wasm32")]
     28         let client = Client::new();
     29 
     30         Ok(Self {
     31             api_key: api_key.map(|k| k.to_string()),
     32             client,
     33         })
     34     }
     35 
     36     async fn request<O1: Serialize, O2: Serialize, T: DeserializeOwned + Debug>(
     37         &self,
     38         url: &str,
     39         route: &str,
     40         opt1: Option<O1>,
     41         opt2: Option<O2>,
     42     ) -> Result<T> {
     43         let prefix = if self.api_key.is_some() {
     44             "customer-"
     45         } else {
     46             ""
     47         };
     48 
     49         let mut req = self.client.get(format!("https://{prefix}{url}/v1/{route}"));
     50 
     51         if let Some(ref key) = self.api_key {
     52             req = req.query(&[("apikey", key)])
     53         };
     54 
     55         if let Some(ref opt) = opt1 {
     56             req = req.query(opt)
     57         };
     58 
     59         if let Some(ref opt) = opt2 {
     60             req = req.query(opt)
     61         };
     62 
     63         let resp: MeteoResponse = req.send().await?.json().await?;
     64 
     65         match resp {
     66             MeteoResponse::Error { reason } => Err(Error::Meteo(reason)),
     67             MeteoResponse::Success(v) => match serde_json::from_value(v) {
     68                 Ok(o) => Ok(o),
     69                 Err(e) => Err(Error::SerdeJson(e)),
     70             },
     71         }
     72     }
     73 
     74     /// Endpoint: `/search`
     75     pub async fn geocode(&self, name: &str, opt: Option<GeocodeOptions>) -> Result<Vec<Location>> {
     76         let resp: GeocodeResponse = self
     77             .request(GEOCODING_API_HOST, "search", Some(&[("name", name)]), opt)
     78             .await?;
     79 
     80         Ok(resp.results)
     81     }
     82 
     83     /// Endpoint: `/forecast`
     84     pub async fn forecast_single(
     85         &self,
     86         latitude: f64,
     87         longitude: f64,
     88         opt: Option<ForecastOptions>,
     89     ) -> Result<Forecast> {
     90         self.request(
     91             FORECASTING_API_HOST,
     92             "forecast",
     93             Some(&[("latitude", latitude), ("longitude", longitude)]),
     94             opt,
     95         )
     96         .await
     97     }
     98 }
     99 
    100 #[cfg(test)]
    101 mod tests {
    102     use super::*;
    103 
    104     async fn get_london(client: &MeteoClient) -> Location {
    105         let res = client
    106             .geocode("London, United Kingdom", None)
    107             .await
    108             .unwrap();
    109 
    110         res.first().unwrap().clone()
    111     }
    112 
    113     #[tokio::test]
    114     async fn geocode() {
    115         let client = MeteoClient::new(None).unwrap();
    116         let london = get_london(&client).await;
    117 
    118         assert_eq!(london.timezone, "Europe/London");
    119         assert_eq!(london.admin1, Some("England".to_string()));
    120         assert_eq!(london.country, "United Kingdom");
    121     }
    122 
    123     #[tokio::test]
    124     async fn forecast_single() {
    125         let client = MeteoClient::new(None).unwrap();
    126         let london = get_london(&client).await;
    127 
    128         client
    129             .forecast_single(
    130                 london.latitude,
    131                 london.longitude,
    132                 Some(ForecastOptions {
    133                     current: Some(vec![CurrentVariable::Temperature2m]),
    134                     daily: Some(vec![DailyVariable::Temperature2mMean]),
    135                     hourly: Some(vec![
    136                         HourlyVariable::Temperature2m,
    137                         HourlyVariable::TemperaturePressureLevel(1000),
    138                     ]),
    139                     ..Default::default()
    140                 }),
    141             )
    142             .await
    143             .unwrap();
    144     }
    145 }