use serde::Deserialize;
use crate::client::http_client;
use crate::error::Result;
#[derive(Debug, Clone)]
pub struct PollenData {
pub alder: f32,
pub birch: f32,
pub grass: f32,
pub mugwort: f32,
pub olive: f32,
pub ragweed: f32,
}
pub async fn fetch_pollen(latitude: f64, longitude: f64) -> Result<Option<PollenData>> {
let url = format!(
"https://air-quality-api.open-meteo.com/v1/air-quality?latitude={}&longitude={}¤t=alder_pollen,birch_pollen,grass_pollen,mugwort_pollen,olive_pollen,ragweed_pollen&timezone=auto",
latitude, longitude
);
let response = http_client()?.get(&url).send().await?.error_for_status()?;
let data: PollenResponse = response.json().await?;
Ok(from_current(&data.current))
}
fn from_current(current: &PollenCurrent) -> Option<PollenData> {
let any_value = current.alder_pollen.is_some()
|| current.birch_pollen.is_some()
|| current.grass_pollen.is_some()
|| current.mugwort_pollen.is_some()
|| current.olive_pollen.is_some()
|| current.ragweed_pollen.is_some();
if !any_value {
return None;
}
Some(PollenData {
alder: current.alder_pollen.unwrap_or(0.0),
birch: current.birch_pollen.unwrap_or(0.0),
grass: current.grass_pollen.unwrap_or(0.0),
mugwort: current.mugwort_pollen.unwrap_or(0.0),
olive: current.olive_pollen.unwrap_or(0.0),
ragweed: current.ragweed_pollen.unwrap_or(0.0),
})
}
#[derive(Debug, Deserialize)]
struct PollenResponse {
current: PollenCurrent,
}
#[derive(Debug, Deserialize)]
struct PollenCurrent {
alder_pollen: Option<f32>,
birch_pollen: Option<f32>,
grass_pollen: Option<f32>,
mugwort_pollen: Option<f32>,
olive_pollen: Option<f32>,
ragweed_pollen: Option<f32>,
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(json_text: &str) -> Option<PollenData> {
let resp: PollenResponse = serde_json::from_str(json_text).unwrap();
from_current(&resp.current)
}
#[test]
fn returns_some_when_covered_with_active_species() {
let json = r#"{
"current": {
"alder_pollen": 0.0,
"birch_pollen": 0.1,
"grass_pollen": 3.5,
"mugwort_pollen": 0.0,
"olive_pollen": 0.0,
"ragweed_pollen": 0.0
}
}"#;
let data = parse(json).unwrap();
assert_eq!(data.birch, 0.1);
assert_eq!(data.grass, 3.5);
assert_eq!(data.alder, 0.0);
}
#[test]
fn returns_some_when_covered_with_all_zero_species() {
let json = r#"{
"current": {
"alder_pollen": 0.0,
"birch_pollen": 0.0,
"grass_pollen": 0.0,
"mugwort_pollen": 0.0,
"olive_pollen": 0.0,
"ragweed_pollen": 0.0
}
}"#;
let data = parse(json).unwrap();
assert_eq!(data.grass, 0.0);
}
#[test]
fn returns_none_when_all_six_null() {
let json = r#"{
"current": {
"alder_pollen": null,
"birch_pollen": null,
"grass_pollen": null,
"mugwort_pollen": null,
"olive_pollen": null,
"ragweed_pollen": null
}
}"#;
assert!(parse(json).is_none());
}
#[test]
fn zero_fills_when_some_species_null_but_others_present() {
let json = r#"{
"current": {
"alder_pollen": null,
"birch_pollen": null,
"grass_pollen": 5.0,
"mugwort_pollen": null,
"olive_pollen": null,
"ragweed_pollen": null
}
}"#;
let data = parse(json).unwrap();
assert_eq!(data.grass, 5.0);
assert_eq!(data.alder, 0.0);
}
}