pub mod openweathermap { use kelikatti_api::*; use serde::Deserialize; #[derive(Deserialize)] struct MainData { temp: Option, feels_like: Option, humidity: Option, } #[derive(Deserialize)] struct WeatherData { main: String, } #[derive(Deserialize)] struct WindData { speed: Option, deg: Option, gust: Option, } #[derive(Deserialize)] struct CloudsData { all: Option, } #[derive(Deserialize)] struct RainData { #[serde(rename = "1h")] one_hour: Option, #[serde(rename = "3h")] three_hour: Option, } #[derive(Deserialize)] struct SnowData { #[serde(rename = "1h")] one_hour: Option, #[serde(rename = "3h")] three_hour: Option, } #[derive(Deserialize)] struct CurrentSysData { sunrise: Option, sunset: Option, } #[derive(Deserialize)] struct WeatherResponse { main: MainData, weather: Vec, wind: WindData, clouds: CloudsData, timezone: Option, rain: Option, snow: Option, sys: CurrentSysData, } impl From for WeatherDTO { fn from(value: WeatherResponse) -> Self { Self { sunrise_utc_ms: value.sys.sunrise.map(|seconds| seconds * 1000), sunset_utc_ms: value.sys.sunset.map(|seconds| seconds * 1000), timezone_shift_ms: value.timezone.map(|shift| shift * 1000), temperature: value.main.temp, feels_like: value.main.feels_like, wind_speed: value.wind.speed, gust_speed: value.wind.gust, wind_dir: value.wind.deg, humidity: value.main.humidity.map(|percentage| percentage / 100.0), rain_rate: value.rain.and_then(|rate| { rate.one_hour.or(rate .three_hour .map(|three_hour_cumulative| three_hour_cumulative / 3.0)) }), snow_rate: value.snow.and_then(|rate| { rate.one_hour.or(rate .three_hour .map(|three_hour_cumulative| three_hour_cumulative / 3.0)) }), cloudiness: value.clouds.all.map(|percentage| percentage / 100.0), conditions: value .weather .iter() .map(|weather| weather.main.to_lowercase()) .collect(), } } } #[derive(Deserialize)] struct ForecastSysData { pod: String, } #[derive(Deserialize)] struct ForecastDatapoint { dt: u64, main: MainData, weather: Vec, wind: WindData, clouds: CloudsData, pop: Option, rain: Option, snow: Option, sys: ForecastSysData, } impl From for ForecastDatapointDTO { fn from(value: ForecastDatapoint) -> Self { Self { timestamp_utc_ms: value.dt * 1000, is_day: value.sys.pod == "d", temperature: value.main.temp, feels_like: value.main.feels_like, wind_speed: value.wind.speed, gust_speed: value.wind.gust, wind_dir: value.wind.deg, humidity: value.main.humidity.map(|percentage| percentage / 100.0), precipitation_chance: value.pop, rain_rate: value.rain.and_then(|rate| { rate.one_hour.or(rate .three_hour .map(|three_hour_cumulative| three_hour_cumulative / 3.0)) }), snow_rate: value.snow.and_then(|rate| { rate.one_hour.or(rate .three_hour .map(|three_hour_cumulative| three_hour_cumulative / 3.0)) }), cloudiness: value.clouds.all.map(|percentage| percentage / 100.0), conditions: value .weather .iter() .map(|weather| weather.main.to_lowercase()) .collect(), } } } #[derive(Deserialize)] struct ForecastResponse { list: Vec, } impl From for ForecastDTO { fn from(value: ForecastResponse) -> Self { Self { list: value .list .into_iter() .map(ForecastDatapointDTO::from) .collect(), } } } const GEO_POS: Coordinates = Coordinates { lat: 60.20076, lon: 24.80352, }; const UNIT: &str = "metric"; const API_HOST: &str = "https://api.openweathermap.org"; const API_KEY: &str = ""; fn new_client() -> reqwest::Client { reqwest::Client::new() } pub async fn fetch_weather() -> reqwest::Result { let url = format!("{API_HOST}/data/2.5/weather"); let response = new_client() .get(url) .query(&[ ("lat", GEO_POS.lat.to_string()), ("lon", GEO_POS.lon.to_string()), ("units", UNIT.to_string()), ("appid", API_KEY.to_string()), ]) .send() .await? .json::() .await?; Ok(WeatherDTO::from(response)) } pub async fn fetch_forecast() -> reqwest::Result { let url = format!("{API_HOST}/data/2.5/forecast"); let response = new_client() .get(url) .query(&[ ("lat", GEO_POS.lat.to_string()), ("lon", GEO_POS.lon.to_string()), ("units", UNIT.to_string()), ("appid", API_KEY.to_string()), ]) .send() .await? .json::() .await?; Ok(ForecastDTO::from(response)) } }