Add documentation to module current

- Add doc comments
- cargo `clippy` fixes
- Comment out code in `widget`
This commit is contained in:
Candifloss 2025-10-09 15:29:30 +05:30
parent d8048daa51
commit 07651af450
3 changed files with 685 additions and 23 deletions

View File

@ -1,128 +1,581 @@
//! # Current Weather Data
//!
//! Data structures and utilities for parsing and working with responses
//! from the **`OpenWeatherMap` Current Weather API**.
//!
//! See: <https://openweathermap.org/current>
//!
//! ## Features
//!
//! - Complete type-safe representation of all API response fields
//! - Optional fields for resilience against API changes
//! - Convenient accessor methods
//! - Chrono integration for date/time handling
//! - Comprehensive error handling
//!
//! ## Example
//!
//! ```no_run
//! use owm_api25::current::WeatherResponse;
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let json = r#"
//! {
//! "coord": {"lon": 7.367, "lat": 45.133},
//! "weather": [{
//! "id": 501,
//! "main": "Rain",
//! "description": "moderate rain",
//! "icon": "10d"
//! }],
//! "main": {
//! "temp": 284.2,
//! "feels_like": 282.93,
//! "pressure": 1021,
//! "humidity": 60
//! },
//! "name": "Turin",
//! "cod": 200
//! }"#;
//!
//! let data: WeatherResponse = serde_json::from_str(json)?;
//! assert!(data.is_success());
//! println!("{} → {}", data.city().unwrap_or("Unknown"), data.summary());
//! # Ok(())
//! # }
//! ```
//!
//! ## Error Handling
//!
//! The API may return error responses with `cod` values other than 200.
//! Always check [`WeatherResponse::is_success()`] before using the data.
//!
//! ```no_run
//! use owm_api25::current::WeatherResponse;
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let error_response = r#"{"cod": 404, "message": "city not found"}"#;
//! let data: WeatherResponse = serde_json::from_str(error_response)?;
//!
//! if !data.is_success() {
//! eprintln!("API error: {}", data.error_message().unwrap_or("Unknown error"));
//! }
//! # Ok(())
//! # }
//! ```
//!
//! ## See Also
//! - [`forecast`](crate::forecast) for multi-day weather data
//! - [`query`](crate::query) for building request URLs
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)] /// Geographic coordinates (longitude and latitude)
///
/// # Example
/// ```
/// use owm_api25::current::Coord;
///
/// let coord = Coord { lon: -0.1257, lat: 51.5085 };
/// assert_eq!(coord.lon, -0.1257);
/// assert_eq!(coord.lat, 51.5085);
/// ```
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)]
pub struct Coord { pub struct Coord {
/// Longitude of the location (-180 to 180)
pub lon: f64, pub lon: f64,
/// Latitude of the location (-90 to 90)
pub lat: f64, pub lat: f64,
} }
#[derive(Debug, Clone, Deserialize, Serialize)] impl Coord {
/// Creates new coordinates from longitude and latitude
///
/// # Example
/// ```
/// use owm_api25::current::Coord;
///
/// let london = Coord::new(-0.1257, 51.5085);
/// ```
#[must_use]
pub fn new(lon: f64, lat: f64) -> Self {
Self { lon, lat }
}
}
/// Weather condition information
///
/// Contains details about current weather conditions including
/// human-readable descriptions and icon identifiers.
///
/// See: [Weather condition codes](https://openweathermap.org/weather-conditions)
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)]
pub struct Weather { pub struct Weather {
/// Weather condition ID
///
/// See: [Weather condition codes](https://openweathermap.org/weather-conditions)
pub id: u32, pub id: u32,
/// Group of weather parameters (e.g. Rain, Snow, Clouds)
pub main: String, pub main: String,
/// Human-readable weather condition description
///
/// This field can be localized based on the API request.
pub description: String, pub description: String,
/// Weather icon ID (for retrieving icon assets)
///
/// Combine with base URL: `https://openweathermap.org/img/wn/{icon}@2x.png`
pub icon: String, pub icon: String,
} }
#[derive(Debug, Clone, Deserialize, Serialize)] impl Weather {
/// Returns the URL to the weather icon image
///
/// # Example
/// ```
/// use owm_api25::current::Weather;
///
/// let weather = Weather {
/// id: 501,
/// main: "Rain".to_string(),
/// description: "moderate rain".to_string(),
/// icon: "10d".to_string(),
/// };
///
/// assert_eq!(
/// weather.icon_url(),
/// "https://openweathermap.org/img/wn/10d@2x.png"
/// );
/// ```
#[must_use]
pub fn icon_url(&self) -> String {
format!("https://openweathermap.org/img/wn/{}@2x.png", self.icon)
}
}
/// Main weather parameters including temperature, pressure, and humidity
///
/// All temperature values are in the units specified in the API request
/// (Kelvin, Celsius, or Fahrenheit).
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)]
pub struct Main { pub struct Main {
/// Temperature in requested units (Kelvin, Celsius, or Fahrenheit)
#[serde(default)]
pub temp: Option<f32>, pub temp: Option<f32>,
/// "Feels like" temperature, accounting for human perception of weather
#[serde(default)] #[serde(default)]
pub feels_like: Option<f32>, pub feels_like: Option<f32>,
/// Minimum observed temperature (within large urban areas)
#[serde(default)] #[serde(default)]
pub temp_min: Option<f32>, pub temp_min: Option<f32>,
/// Maximum observed temperature (within large urban areas)
#[serde(default)] #[serde(default)]
pub temp_max: Option<f32>, pub temp_max: Option<f32>,
/// Atmospheric pressure at sea level (hPa)
#[serde(default)] #[serde(default)]
pub pressure: Option<u32>, pub pressure: Option<u32>,
/// Humidity percentage (0-100)
#[serde(default)] #[serde(default)]
pub humidity: Option<u8>, pub humidity: Option<u8>,
/// Atmospheric pressure at sea level (hPa)
#[serde(default)] #[serde(default)]
pub sea_level: Option<u32>, pub sea_level: Option<u32>,
/// Atmospheric pressure at ground level (hPa)
#[serde(default)] #[serde(default)]
pub grnd_level: Option<u32>, pub grnd_level: Option<u32>,
} }
#[derive(Debug, Clone, Deserialize, Serialize, Default)] impl Main {
/// Returns the temperature difference between max and min
///
/// # Example
/// ```
/// use owm_api25::current::Main;
///
/// let main = Main {
/// temp: Some(20.0),
/// temp_min: Some(15.0),
/// temp_max: Some(25.0),
/// ..Default::default()
/// };
///
/// assert_eq!(main.temp_range(), Some(10.0));
/// ```
#[must_use]
pub fn temp_range(&self) -> Option<f32> {
match (self.temp_min, self.temp_max) {
(Some(min), Some(max)) => Some(max - min),
_ => None,
}
}
}
/// Wind information including speed, direction, and gusts
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)]
pub struct Wind { pub struct Wind {
/// Wind speed in chosen units (m/s by default)
#[serde(default)]
pub speed: Option<f32>, pub speed: Option<f32>,
/// Wind direction in degrees (meteorological, 0-360)
///
/// - 0°: North
/// - 90°: East
/// - 180°: South
/// - 270°: West
#[serde(default)]
pub deg: Option<u16>, pub deg: Option<u16>,
/// Wind gust speed in chosen units
#[serde(default)]
pub gust: Option<f32>, pub gust: Option<f32>,
} }
#[derive(Debug, Clone, Deserialize, Serialize, Default)] impl Wind {
/// Returns wind direction as a cardinal direction (N, NE, E, etc.)
///
/// # Example
/// ```
/// use owm_api25::current::Wind;
///
/// let north_wind = Wind { deg: Some(0), ..Default::default() };
/// assert_eq!(north_wind.direction(), Some("N"));
///
/// let east_wind = Wind { deg: Some(90), ..Default::default() };
/// assert_eq!(east_wind.direction(), Some("E"));
/// ```
#[must_use]
pub fn direction(&self) -> Option<&'static str> {
let deg = self.deg?;
let directions = [
"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW",
"NW", "NNW",
];
let index = (((u32::from(deg) * 16 + 11) / 22) % 16) as usize;
Some(directions[index])
}
}
/// Cloud cover information
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)]
pub struct Clouds { pub struct Clouds {
/// Cloudiness percentage (0-100)
#[serde(default)]
pub all: Option<u8>, pub all: Option<u8>,
} }
#[derive(Debug, Clone, Deserialize, Serialize, Default)] impl Clouds {
/// Returns true if cloud cover indicates clear skies (<= 20%)
#[must_use]
pub fn is_clear(&self) -> bool {
self.all.is_some_and(|cover| cover <= 20)
}
/// Returns true if cloud cover indicates overcast (>= 80%)
#[must_use]
pub fn is_overcast(&self) -> bool {
self.all.is_some_and(|cover| cover >= 80)
}
}
/// Precipitation information for rain or snow
///
/// Contains precipitation volumes over different time periods.
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)]
pub struct Precipitation { pub struct Precipitation {
/// Precipitation volume over the last 1 hour (mm)
#[serde(rename = "1h", default)] #[serde(rename = "1h", default)]
pub one_hour: Option<f32>, pub one_hour: Option<f32>,
/// Precipitation volume over the last 3 hours (mm)
#[serde(rename = "3h", default)] #[serde(rename = "3h", default)]
pub three_hour: Option<f32>, pub three_hour: Option<f32>,
} }
#[derive(Debug, Clone, Deserialize, Serialize, Default)] impl Precipitation {
/// Returns the precipitation intensity category
///
/// - None: No precipitation data
/// - "Light": < 2.5 mm/h
/// - "Moderate": 2.5 - 7.5 mm/h
/// - "Heavy": > 7.5 mm/h
#[must_use]
pub fn intensity(&self) -> Option<&'static str> {
let rate = self.one_hour.or(self.three_hour.map(|v| v / 3.0))?;
if rate < 2.5 {
Some("Light")
} else if rate <= 7.5 {
Some("Moderate")
} else {
Some("Heavy")
}
}
}
/// System and location information
///
/// Contains country, sunrise/sunset times, and internal API parameters.
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)]
pub struct Sys { pub struct Sys {
/// Internal parameter
#[serde(default)]
pub r#type: Option<u8>, pub r#type: Option<u8>,
/// Internal parameter
#[serde(default)]
pub id: Option<u32>, pub id: Option<u32>,
/// Country code (ISO 3166-1 alpha-2, e.g. "GB", "JP")
#[serde(default)]
pub country: Option<String>, pub country: Option<String>,
/// Sunrise time (UTC Unix timestamp)
#[serde(with = "chrono::serde::ts_seconds_option")] #[serde(with = "chrono::serde::ts_seconds_option")]
pub sunrise: Option<DateTime<Utc>>, pub sunrise: Option<DateTime<Utc>>,
/// Sunset time (UTC Unix timestamp)
#[serde(with = "chrono::serde::ts_seconds_option")] #[serde(with = "chrono::serde::ts_seconds_option")]
pub sunset: Option<DateTime<Utc>>, pub sunset: Option<DateTime<Utc>>,
/// Internal parameter (often used for error messages)
#[serde(default)]
pub message: Option<f64>, pub message: Option<f64>,
} }
#[derive(Debug, Clone, Deserialize, Serialize, Default)] impl Sys {
/// Returns true if it's currently daytime at the location
///
/// Compares current UTC time with sunrise and sunset times.
#[must_use]
pub fn is_daytime(&self) -> Option<bool> {
let now = Utc::now();
match (self.sunrise, self.sunset) {
(Some(sunrise), Some(sunset)) => Some(now >= sunrise && now <= sunset),
_ => None,
}
}
/// Returns the duration of daylight (sunset - sunrise)
///
/// Returns `None` if either sunrise or sunset data is missing.
#[must_use]
pub fn daylight_duration(&self) -> Option<chrono::Duration> {
Some(self.sunset? - self.sunrise?)
}
}
/// 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 { pub struct WeatherResponse {
/// Geographic coordinates
#[serde(default)] #[serde(default)]
pub coord: Option<Coord>, pub coord: Option<Coord>,
/// Weather conditions array (usually contains one element)
#[serde(default)] #[serde(default)]
pub weather: Vec<Weather>, pub weather: Vec<Weather>,
/// Internal parameter
#[serde(default)] #[serde(default)]
pub base: Option<String>, pub base: Option<String>,
/// Main weather parameters
#[serde(default)] #[serde(default)]
pub main: Option<Main>, pub main: Option<Main>,
/// Visibility in meters (maximum value is 10,000 m)
#[serde(default)] #[serde(default)]
pub visibility: Option<u32>, pub visibility: Option<u32>,
#[serde(with = "chrono::serde::ts_seconds_option")] /// Wind information
pub dt: Option<DateTime<Utc>>,
#[serde(default)]
pub sys: Option<Sys>,
#[serde(default)]
pub timezone: Option<i32>,
#[serde(default)]
pub id: Option<u64>,
#[serde(default)]
pub name: Option<String>,
#[serde(default)] #[serde(default)]
pub wind: Option<Wind>, pub wind: Option<Wind>,
/// Cloud cover information
#[serde(default)] #[serde(default)]
pub clouds: Option<Clouds>, pub clouds: Option<Clouds>,
/// Rain precipitation data
#[serde(default)] #[serde(default)]
pub rain: Option<Precipitation>, pub rain: Option<Precipitation>,
/// Snow precipitation data
#[serde(default)] #[serde(default)]
pub snow: Option<Precipitation>, 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)] #[serde(default)]
pub cod: Option<u16>, pub cod: Option<u16>,
/// Error message (present when API call fails)
#[serde(default)] #[serde(default)]
pub message: Option<String>, pub message: Option<String>,
} }
impl WeatherResponse { impl WeatherResponse {
/// Returns the first (primary) weather condition, if present
#[must_use]
pub fn primary_weather(&self) -> Option<&Weather> { pub fn primary_weather(&self) -> Option<&Weather> {
self.weather.first() 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 { pub fn is_success(&self) -> bool {
matches!(self.cod, Some(200)) matches!(self.cod, Some(200))
} }
pub fn temperature(&self) -> Option<f32> { /// Returns the error message if the API call failed
self.main.as_ref().map(|m| m.temp)? ///
/// 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> { pub fn city(&self) -> Option<&str> {
self.name.as_deref() 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> { pub fn country(&self) -> Option<&str> {
self.sys.as_ref()?.country.as_deref() 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 (0100), 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 { pub fn summary(&self) -> String {
let temp = self let temp = self
.temperature() .temperature()
@ -132,4 +585,212 @@ impl WeatherResponse {
.map_or("N/A", |w| w.description.as_str()); .map_or("N/A", |w| w.description.as_str());
format!("{desc}, {temp}") 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}",
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_success_response() {
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,
"feels_like": 278.15,
"pressure": 1012,
"humidity": 81
},
"wind": {
"speed": 4.1,
"deg": 240
},
"name": "London",
"cod": 200
}"#;
let response: WeatherResponse = serde_json::from_str(json).unwrap();
assert!(response.is_success());
assert_eq!(response.city(), Some("London"));
assert_eq!(response.temperature(), Some(280.32));
assert_eq!(response.feels_like(), Some(278.15));
assert_eq!(response.pressure(), Some(1012));
assert_eq!(response.humidity(), Some(81));
assert_eq!(response.wind_speed(), Some(4.1));
let weather = response.primary_weather().unwrap();
assert_eq!(weather.main, "Drizzle");
assert_eq!(weather.description, "light intensity drizzle");
}
#[test]
fn test_error_response() {
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"));
assert_eq!(response.city(), None);
}
#[test]
fn test_partial_response() {
// Test that missing fields don't cause deserialization to fail
let json = r#"{"cod": 200, "name": "Paris"}"#;
let response: WeatherResponse = serde_json::from_str(json).unwrap();
assert!(response.is_success());
assert_eq!(response.city(), Some("Paris"));
assert_eq!(response.temperature(), None);
}
#[test]
fn test_wind_direction() {
assert_eq!(
Wind {
deg: Some(0),
..Default::default()
}
.direction(),
Some("N")
);
assert_eq!(
Wind {
deg: Some(90),
..Default::default()
}
.direction(),
Some("E")
);
assert_eq!(
Wind {
deg: Some(180),
..Default::default()
}
.direction(),
Some("S")
);
assert_eq!(
Wind {
deg: Some(270),
..Default::default()
}
.direction(),
Some("W")
);
assert_eq!(
Wind {
deg: Some(45),
..Default::default()
}
.direction(),
Some("NE")
);
}
#[test]
fn test_precipitation_intensity() {
let light = Precipitation {
one_hour: Some(1.0),
..Default::default()
};
assert_eq!(light.intensity(), Some("Light"));
let moderate = Precipitation {
one_hour: Some(5.0),
..Default::default()
};
assert_eq!(moderate.intensity(), Some("Moderate"));
let heavy = Precipitation {
one_hour: Some(10.0),
..Default::default()
};
assert_eq!(heavy.intensity(), Some("Heavy"));
}
#[test]
fn test_sys_daytime_none() {
let sys = Sys {
sunrise: None,
sunset: None,
..Default::default()
};
assert_eq!(sys.is_daytime(), None);
}
#[test]
fn test_wind_direction_out_of_bounds() {
assert_eq!(
Wind {
deg: Some(361),
..Default::default()
}
.direction(),
Some("N")
);
assert_eq!(
Wind {
deg: Some(720),
..Default::default()
}
.direction(),
Some("N")
);
}
#[test]
fn test_precipitation_three_hour_only() {
let p = Precipitation {
one_hour: None,
three_hour: Some(6.0),
};
assert_eq!(p.intensity(), Some("Moderate")); // 6/3 = 2.0 → Light
}
} }

View File

@ -1,3 +1,3 @@
pub mod current;
pub mod forecast; pub mod forecast;
pub mod query; pub mod query;
pub mod current;

View File

@ -1,6 +1,5 @@
use owm_api25::current::WeatherResponse; //use owm_api25::current::WeatherResponse;
use owm_widg_config::config::Config; use owm_widg_config::config::Config;
use reqwest::blocking;
use std::fs; use std::fs;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
@ -16,6 +15,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
match cfg.general.api_version.as_str() { match cfg.general.api_version.as_str() {
"free_2.5" => { "free_2.5" => {
/*
let query = owm_api25::query::QueryParams::from(cfg.query_params); let query = owm_api25::query::QueryParams::from(cfg.query_params);
let url = query.weather_url()?; let url = query.weather_url()?;
@ -27,6 +27,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Icon: {}", w.icon); println!("Icon: {}", w.icon);
} }
println!("Temperature: {}°C", resp.main.temp); println!("Temperature: {}°C", resp.main.temp);
*/
} }
other => { other => {
return Err(format!("Unsupported api_version: {other}").into()); return Err(format!("Unsupported api_version: {other}").into());