Add module src/free_api_v25/current/response.rs
- Main struct in this module
This commit is contained in:
parent
3a6ddc199c
commit
d24bc23ca3
@ -21,6 +21,16 @@ pub mod clouds;
|
|||||||
pub mod coord;
|
pub mod coord;
|
||||||
pub mod main;
|
pub mod main;
|
||||||
pub mod precipitation;
|
pub mod precipitation;
|
||||||
|
pub mod response;
|
||||||
pub mod sys;
|
pub mod sys;
|
||||||
pub mod weather;
|
pub mod weather;
|
||||||
pub mod wind;
|
pub mod wind;
|
||||||
|
|
||||||
|
pub use clouds::*;
|
||||||
|
pub use coord::*;
|
||||||
|
pub use main::*;
|
||||||
|
pub use precipitation::*;
|
||||||
|
pub use response::*;
|
||||||
|
pub use sys::*;
|
||||||
|
pub use weather::*;
|
||||||
|
pub use wind::*;
|
||||||
|
282
src/free_api_v25/current/response.rs
Normal file
282
src/free_api_v25/current/response.rs
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::{Clouds, Coord, Main, Precipitation, Sys, Weather, Wind};
|
||||||
|
|
||||||
|
/// Full response structure for the **`OpenWeatherMap` Current Weather API**
|
||||||
|
///
|
||||||
|
/// Every field is optional to ensure resilience against missing or
|
||||||
|
/// undocumented fields in the live API responses.
|
||||||
|
///
|
||||||
|
/// ## Success vs Error Responses
|
||||||
|
///
|
||||||
|
/// - **Success**: `cod == 200`, weather data is populated
|
||||||
|
/// - **Error**: `cod != 200`, `message` contains error description
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ### Success Response
|
||||||
|
/// ```
|
||||||
|
/// use owm_api25::current::WeatherResponse;
|
||||||
|
///
|
||||||
|
/// let json = r#"{
|
||||||
|
/// "coord": {"lon": -0.1257, "lat": 51.5085},
|
||||||
|
/// "weather": [{
|
||||||
|
/// "id": 300,
|
||||||
|
/// "main": "Drizzle",
|
||||||
|
/// "description": "light intensity drizzle",
|
||||||
|
/// "icon": "09d"
|
||||||
|
/// }],
|
||||||
|
/// "main": {
|
||||||
|
/// "temp": 280.32,
|
||||||
|
/// "pressure": 1012,
|
||||||
|
/// "humidity": 81
|
||||||
|
/// },
|
||||||
|
/// "name": "London",
|
||||||
|
/// "cod": 200
|
||||||
|
/// }"#;
|
||||||
|
///
|
||||||
|
/// let response: WeatherResponse = serde_json::from_str(json).unwrap();
|
||||||
|
/// assert!(response.is_success());
|
||||||
|
/// assert_eq!(response.city(), Some("London"));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ### Error Response
|
||||||
|
/// ```
|
||||||
|
/// use owm_api25::current::WeatherResponse;
|
||||||
|
///
|
||||||
|
/// let json = r#"{
|
||||||
|
/// "cod": 404,
|
||||||
|
/// "message": "city not found"
|
||||||
|
/// }"#;
|
||||||
|
///
|
||||||
|
/// let response: WeatherResponse = serde_json::from_str(json).unwrap();
|
||||||
|
/// assert!(!response.is_success());
|
||||||
|
/// assert_eq!(response.error_message(), Some("city not found"));
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)]
|
||||||
|
pub struct WeatherResponse {
|
||||||
|
/// Geographic coordinates
|
||||||
|
#[serde(default)]
|
||||||
|
pub coord: Option<Coord>,
|
||||||
|
|
||||||
|
/// Weather conditions array (usually contains one element)
|
||||||
|
#[serde(default)]
|
||||||
|
pub weather: Vec<Weather>,
|
||||||
|
|
||||||
|
/// Internal parameter
|
||||||
|
#[serde(default)]
|
||||||
|
pub base: Option<String>,
|
||||||
|
|
||||||
|
/// Main weather parameters
|
||||||
|
#[serde(default)]
|
||||||
|
pub main: Option<Main>,
|
||||||
|
|
||||||
|
/// Visibility in meters (maximum value is 10,000 m)
|
||||||
|
#[serde(default)]
|
||||||
|
pub visibility: Option<u32>,
|
||||||
|
|
||||||
|
/// Wind information
|
||||||
|
#[serde(default)]
|
||||||
|
pub wind: Option<Wind>,
|
||||||
|
|
||||||
|
/// Cloud cover information
|
||||||
|
#[serde(default)]
|
||||||
|
pub clouds: Option<Clouds>,
|
||||||
|
|
||||||
|
/// Rain precipitation data
|
||||||
|
#[serde(default)]
|
||||||
|
pub rain: Option<Precipitation>,
|
||||||
|
|
||||||
|
/// Snow precipitation data
|
||||||
|
#[serde(default)]
|
||||||
|
pub snow: Option<Precipitation>,
|
||||||
|
|
||||||
|
/// Time of data calculation (UTC Unix timestamp)
|
||||||
|
#[serde(with = "chrono::serde::ts_seconds_option")]
|
||||||
|
pub dt: Option<DateTime<Utc>>,
|
||||||
|
|
||||||
|
/// System information (country, sunrise, sunset, etc.)
|
||||||
|
#[serde(default)]
|
||||||
|
pub sys: Option<Sys>,
|
||||||
|
|
||||||
|
/// Shift in seconds from UTC
|
||||||
|
#[serde(default)]
|
||||||
|
pub timezone: Option<i32>,
|
||||||
|
|
||||||
|
/// City ID
|
||||||
|
#[serde(default)]
|
||||||
|
pub id: Option<u64>,
|
||||||
|
|
||||||
|
/// City name
|
||||||
|
#[serde(default)]
|
||||||
|
pub name: Option<String>,
|
||||||
|
|
||||||
|
/// Internal parameter (HTTP-like status code)
|
||||||
|
///
|
||||||
|
/// - `200`: Success
|
||||||
|
/// - `4xx`: Client errors (e.g., 404 "city not found")
|
||||||
|
/// - `5xx`: Server errors
|
||||||
|
#[serde(default)]
|
||||||
|
pub cod: Option<u16>,
|
||||||
|
|
||||||
|
/// Error message (present when API call fails)
|
||||||
|
#[serde(default)]
|
||||||
|
pub message: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WeatherResponse {
|
||||||
|
/// Returns the first (primary) weather condition, if present
|
||||||
|
#[must_use]
|
||||||
|
pub fn primary_weather(&self) -> Option<&Weather> {
|
||||||
|
self.weather.first()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks whether the API response indicates success (`cod == 200`)
|
||||||
|
///
|
||||||
|
/// Always check this before accessing weather data fields.
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_success(&self) -> bool {
|
||||||
|
matches!(self.cod, Some(200))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the error message if the API call failed
|
||||||
|
///
|
||||||
|
/// Returns `None` for successful responses.
|
||||||
|
#[must_use]
|
||||||
|
pub fn error_message(&self) -> Option<&str> {
|
||||||
|
if self.is_success() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
self.message.as_deref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current temperature, if available
|
||||||
|
#[must_use]
|
||||||
|
pub fn temperature(&self) -> Option<f32> {
|
||||||
|
self.main.as_ref().and_then(|m| m.temp)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the "feels like" temperature, if available
|
||||||
|
#[must_use]
|
||||||
|
pub fn feels_like(&self) -> Option<f32> {
|
||||||
|
self.main.as_ref().and_then(|m| m.feels_like)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the city name
|
||||||
|
#[must_use]
|
||||||
|
pub fn city(&self) -> Option<&str> {
|
||||||
|
self.name.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the 2-letter country code (ISO 3166-1 alpha-2), if available
|
||||||
|
#[must_use]
|
||||||
|
pub fn country(&self) -> Option<&str> {
|
||||||
|
self.sys.as_ref()?.country.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the atmospheric pressure in hPa, if available
|
||||||
|
#[must_use]
|
||||||
|
pub fn pressure(&self) -> Option<u32> {
|
||||||
|
self.main.as_ref().and_then(|m| m.pressure)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the humidity percentage (0-100), if available
|
||||||
|
#[must_use]
|
||||||
|
pub fn humidity(&self) -> Option<u8> {
|
||||||
|
self.main.as_ref().and_then(|m| m.humidity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the wind speed, if available
|
||||||
|
#[must_use]
|
||||||
|
pub fn wind_speed(&self) -> Option<f32> {
|
||||||
|
self.wind.as_ref().and_then(|w| w.speed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the wind direction in degrees, if available
|
||||||
|
#[must_use]
|
||||||
|
pub fn wind_deg(&self) -> Option<u16> {
|
||||||
|
self.wind.as_ref().and_then(|w| w.deg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the cloudiness percentage (0-100), if available
|
||||||
|
#[must_use]
|
||||||
|
pub fn cloudiness(&self) -> Option<u8> {
|
||||||
|
self.clouds.as_ref().and_then(|c| c.all)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns precipitation intensity category (Rain or Snow)
|
||||||
|
#[must_use]
|
||||||
|
pub fn precipitation_intensity(&self) -> Option<&'static str> {
|
||||||
|
self.rain
|
||||||
|
.as_ref()
|
||||||
|
.and_then(Precipitation::intensity)
|
||||||
|
.or_else(|| self.snow.as_ref().and_then(Precipitation::intensity))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the visibility in meters, if available
|
||||||
|
#[must_use]
|
||||||
|
pub fn visibility(&self) -> Option<u32> {
|
||||||
|
self.visibility
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Produces a compact, human-readable weather summary
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// use owm_api25::current::WeatherResponse;
|
||||||
|
///
|
||||||
|
/// let json = r#"{
|
||||||
|
/// "weather": [{"description": "clear sky"}],
|
||||||
|
/// "main": {"temp": 293.15},
|
||||||
|
/// "cod": 200
|
||||||
|
/// }"#;
|
||||||
|
///
|
||||||
|
/// let response: WeatherResponse = serde_json::from_str(json).unwrap();
|
||||||
|
/// assert_eq!(response.summary(), "clear sky, 293.1°");
|
||||||
|
/// ```
|
||||||
|
#[must_use]
|
||||||
|
pub fn summary(&self) -> String {
|
||||||
|
let temp = self
|
||||||
|
.temperature()
|
||||||
|
.map_or("-".into(), |t| format!("{t:.1}°"));
|
||||||
|
let desc = self
|
||||||
|
.primary_weather()
|
||||||
|
.map_or("N/A", |w| w.description.as_str());
|
||||||
|
format!("{desc}, {temp}")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a detailed multi-line weather report
|
||||||
|
///
|
||||||
|
/// Includes temperature, conditions, wind, pressure, and humidity.
|
||||||
|
#[must_use]
|
||||||
|
pub fn detailed_report(&self) -> String {
|
||||||
|
let city = self.city().unwrap_or("Unknown location");
|
||||||
|
let temp = self
|
||||||
|
.temperature()
|
||||||
|
.map_or("?".to_string(), |t| format!("{t:.1}°"));
|
||||||
|
let feels_like = self
|
||||||
|
.feels_like()
|
||||||
|
.map_or("?".to_string(), |t| format!("{t:.1}°"));
|
||||||
|
let desc = self
|
||||||
|
.primary_weather()
|
||||||
|
.map_or("Unknown", |w| w.description.as_str());
|
||||||
|
let wind = self
|
||||||
|
.wind_speed()
|
||||||
|
.map_or("?".to_string(), |s| format!("{s:.1} m/s"));
|
||||||
|
let pressure = self
|
||||||
|
.pressure()
|
||||||
|
.map_or("?".to_string(), |p| format!("{p} hPa"));
|
||||||
|
let humidity = self.humidity().map_or("?".to_string(), |h| format!("{h}%"));
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"Weather in {city}:\n\
|
||||||
|
• Temperature: {temp} (feels like {feels_like})\n\
|
||||||
|
• Conditions: {desc}\n\
|
||||||
|
• Wind: {wind}\n\
|
||||||
|
• Pressure: {pressure}\n\
|
||||||
|
• Humidity: {humidity}",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user