kelikatti/kelikatti-api/src/api.rs

210 lines
6.2 KiB
Rust

pub mod openweathermap {
use kelikatti_api::*;
use serde::Deserialize;
#[derive(Deserialize)]
struct MainData {
temp: Option<f32>,
feels_like: Option<f32>,
humidity: Option<f32>,
}
#[derive(Deserialize)]
struct WeatherData {
main: String,
}
#[derive(Deserialize)]
struct WindData {
speed: Option<f32>,
deg: Option<f32>,
gust: Option<f32>,
}
#[derive(Deserialize)]
struct CloudsData {
all: Option<f32>,
}
#[derive(Deserialize)]
struct RainData {
#[serde(rename = "1h")]
one_hour: Option<f32>,
#[serde(rename = "3h")]
three_hour: Option<f32>,
}
#[derive(Deserialize)]
struct SnowData {
#[serde(rename = "1h")]
one_hour: Option<f32>,
#[serde(rename = "3h")]
three_hour: Option<f32>,
}
#[derive(Deserialize)]
struct CurrentSysData {
sunrise: Option<u64>,
sunset: Option<u64>,
}
#[derive(Deserialize)]
struct WeatherResponse {
main: MainData,
weather: Vec<WeatherData>,
wind: WindData,
clouds: CloudsData,
timezone: Option<i64>,
rain: Option<RainData>,
snow: Option<SnowData>,
sys: CurrentSysData,
}
impl From<WeatherResponse> 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<WeatherData>,
wind: WindData,
clouds: CloudsData,
pop: Option<f32>,
rain: Option<RainData>,
snow: Option<SnowData>,
sys: ForecastSysData,
}
impl From<ForecastDatapoint> 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<ForecastDatapoint>,
}
impl From<ForecastResponse> 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<WeatherDTO> {
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::<WeatherResponse>()
.await?;
Ok(WeatherDTO::from(response))
}
pub async fn fetch_forecast() -> reqwest::Result<ForecastDTO> {
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::<ForecastResponse>()
.await?;
Ok(ForecastDTO::from(response))
}
}