Add module src/free_api_v25/current/response.rs

- Main struct in this module
This commit is contained in:
Candifloss 2025-10-10 01:09:29 +05:30
parent 3a6ddc199c
commit d24bc23ca3
2 changed files with 292 additions and 0 deletions

View File

@ -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::*;

View 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}",
)
}
}