use crate::client::http_client;
const FEED_URL_PREFIX: &str = "https://api.waqi.info/feed/geo:";
pub(crate) async fn fetch_headline_aqi(
latitude: f64,
longitude: f64,
token: &str,
) -> Option<i32> {
if token.trim().is_empty() {
return None;
}
let url = format!(
"{FEED_URL_PREFIX}{latitude};{longitude}/?token={}",
urlencoding::encode(token)
);
let body = http_client()
.ok()?
.get(&url)
.send()
.await
.map_err(|e| tracing::debug!("aqicn request failed: {e}"))
.ok()?
.text()
.await
.map_err(|e| tracing::debug!("aqicn response body failed: {e}"))
.ok()?;
extract_aqi(&body)
}
fn extract_aqi(json_text: &str) -> Option<i32> {
let value: serde_json::Value = serde_json::from_str(json_text)
.map_err(|e| tracing::debug!("aqicn response parse failed: {e}"))
.ok()?;
let status = value.get("status")?.as_str()?;
if status != "ok" {
tracing::debug!("aqicn returned non-ok status: {status}");
return None;
}
value.get("data")?.get("aqi")?.as_i64().map(|v| v as i32)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extracts_aqi_from_ok_response() {
let json = r#"{
"status": "ok",
"data": {
"aqi": 52,
"iaqi": { "pm25": { "v": 22 } },
"city": { "name": "Seoul" }
}
}"#;
assert_eq!(extract_aqi(json), Some(52));
}
#[test]
fn returns_none_on_error_status() {
let json = r#"{"status":"error","data":"Invalid key"}"#;
assert_eq!(extract_aqi(json), None);
}
#[test]
fn returns_none_when_aqi_missing() {
let json = r#"{"status":"ok","data":{"iaqi":{}}}"#;
assert_eq!(extract_aqi(json), None);
}
#[test]
fn returns_none_on_malformed_json() {
assert_eq!(extract_aqi("not json"), None);
assert_eq!(extract_aqi(""), None);
}
#[test]
fn returns_none_when_status_field_absent() {
assert_eq!(extract_aqi(r#"{"data":{"aqi":42}}"#), None);
}
}