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 main;
|
||||
pub mod precipitation;
|
||||
pub mod response;
|
||||
pub mod sys;
|
||||
pub mod weather;
|
||||
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