Add documentation to query.rs

- Documentation comments
- Tests
This commit is contained in:
Candifloss 2025-10-09 16:26:34 +05:30
parent 66f04c5cc9
commit 808325c534

View File

@ -1,11 +1,45 @@
//! Query construction for `OpenWeatherMap` API v2.5
//!
//! Provides types and utilities for building request URLs for the
//! `/weather` and `/forecast` endpoints.
//!
//! ## Examples
//!
//! ```no_run
//! use owm_api25::query::{QueryParams, Units, WEATHER_URL};
//!
//! let query = QueryParams {
//! api_key: "MY_KEY".into(),
//! city_name: Some("London".into()),
//! ..Default::default()
//! };
//!
//! let url = query.weather_url().unwrap();
//! assert!(url.contains("q=London"));
//! ```
use std::fmt; use std::fmt;
/// Base URLs for `OpenWeatherMap` API v2.5
pub const BASE_URL: &str = "https://api.openweathermap.org/data/2.5"; pub const BASE_URL: &str = "https://api.openweathermap.org/data/2.5";
pub const WEATHER_URL: &str = "https://api.openweathermap.org/data/2.5/weather"; pub const WEATHER_URL: &str = "https://api.openweathermap.org/data/2.5/weather";
pub const FORECAST_URL: &str = "https://api.openweathermap.org/data/2.5/forecast"; pub const FORECAST_URL: &str = "https://api.openweathermap.org/data/2.5/forecast";
/// Units of measurement for temperature and wind speed /// Units of measurement for temperature and wind speed.
#[derive(Debug)] ///
/// - **Standard**: Kelvin (temperature), m/s (wind)
/// - **Metric**: Celsius, m/s
/// - **Imperial**: Fahrenheit, miles/hour
///
/// See: <https://openweathermap.org/current#data>
///
/// # Example
/// ```
/// use owm_api25::query::Units;
/// assert_eq!(Units::Metric.to_string(), "metric");
/// ```
#[derive(Debug, Clone, Copy)]
pub enum Units { pub enum Units {
Standard, Standard,
Metric, Metric,
@ -22,16 +56,43 @@ impl fmt::Display for Units {
} }
} }
/// Query parameters for `OpenWeatherMap` Free API v2.5 endpoints /// Query parameters for `OpenWeatherMap` API.
#[derive(Debug)] ///
/// **Note:** One of `city_id`, `city_name`, `(lat, lon)` pair, or `zip` **must** be provided,
/// otherwise URL building will fail.
#[derive(Debug, Clone)]
pub struct QueryParams { pub struct QueryParams {
/// Your `OpenWeatherMap` API key
pub api_key: String, pub api_key: String,
/// City ID (optional)
///
/// You can make an API call by city ID to get an unambiguous result for your city.
/// The list of city IDs (`city.list.json.gz`) can be downloaded [here](http://bulk.openweathermap.org/sample/).
pub city_id: Option<String>, pub city_id: Option<String>,
/// City name (optional)
///
/// You can call by city name, or city name + state code + country code.
/// Searching by state is only available for locations in the USA.
/// See: <https://openweathermap.org/current#name>
pub city_name: Option<String>, pub city_name: Option<String>,
/// Latitude (optional, must be used with `lon`)
pub lat: Option<f32>, pub lat: Option<f32>,
/// Longitude (optional, must be used with `lat`)
pub lon: Option<f32>, pub lon: Option<f32>,
/// Zip code (optional)
///
/// Format: `"94040,us"`. If the country code is not specified, it defaults to USA.
pub zip: Option<String>, pub zip: Option<String>,
/// Units for temperature and wind (default `"Standard"`)
pub units: Units, pub units: Units,
/// Language code for descriptions (default `"en"`)
pub lang: String, pub lang: String,
} }
@ -41,7 +102,23 @@ impl QueryParams {
/// # Errors /// # Errors
/// ///
/// Returns an `Err` if no valid location is specified /// Returns an `Err` if no valid location is specified
/// (e.g., `city_id`, `city_name`, `lat`/`lon`, or `zip` are all `None`). /// (`city_id`, `city_name`, `lat`/`lon`, or `zip` are all None).
///
/// # Example
///
/// ```
/// use owm_api25::query::{QueryParams, Units, WEATHER_URL};
///
/// let query = QueryParams {
/// api_key: "MY_API_KEY".to_string(),
/// city_name: Some("London".to_string()),
/// ..Default::default()
/// };
///
/// let url = query.weather_url().unwrap();
/// assert!(url.contains("q=London"));
/// assert!(url.contains("appid=MY_API_KEY"));
/// ```
pub fn weather_url(&self) -> Result<String, String> { pub fn weather_url(&self) -> Result<String, String> {
self.build_url(WEATHER_URL) self.build_url(WEATHER_URL)
} }
@ -50,12 +127,17 @@ impl QueryParams {
/// ///
/// # Errors /// # Errors
/// ///
/// Returns an `Err` if no valid location is specified /// Returns an `Err` if no valid location is specified.
/// (e.g., `city_id`, `city_name`, `lat`/`lon`, or `zip` are all `None`). /// See `weather_url`.
pub fn forecast_url(&self) -> Result<String, String> { pub fn forecast_url(&self) -> Result<String, String> {
self.build_url(FORECAST_URL) self.build_url(FORECAST_URL)
} }
/// Internal helper to build query URL.
///
/// # Errors
///
/// Returns an `Err` if no valid location is specified (`city_id`, `city_name`, `lat`/`lon`, or `zip`).
fn build_url(&self, base: &str) -> Result<String, String> { fn build_url(&self, base: &str) -> Result<String, String> {
let mut params = vec![ let mut params = vec![
format!("appid={}", self.api_key), format!("appid={}", self.api_key),
@ -79,3 +161,93 @@ impl QueryParams {
Ok(format!("{base}?{}", params.join("&"))) Ok(format!("{base}?{}", params.join("&")))
} }
} }
/// Default values for query parameters
impl Default for QueryParams {
fn default() -> Self {
Self {
api_key: String::new(),
city_id: None,
city_name: None,
lat: None,
lon: None,
zip: None,
units: Units::Standard,
lang: "en".to_string(),
}
}
}
// Tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_weather_url_city_name() {
let query = QueryParams {
api_key: "KEY".into(),
city_name: Some("Paris".into()),
..Default::default()
};
let url = query.weather_url().unwrap();
assert!(url.contains("q=Paris"));
assert!(url.contains("appid=KEY"));
assert!(url.starts_with(WEATHER_URL));
}
#[test]
fn test_weather_url_city_id() {
let query = QueryParams {
api_key: "KEY".into(),
city_id: Some("123".into()),
..Default::default()
};
let url = query.weather_url().unwrap();
assert!(url.contains("id=123"));
}
#[test]
fn test_weather_url_lat_lon() {
let query = QueryParams {
api_key: "KEY".into(),
lat: Some(10.0),
lon: Some(20.0),
..Default::default()
};
let url = query.weather_url().unwrap();
assert!(url.contains("lat=10") && url.contains("lon=20"));
}
#[test]
fn test_weather_url_zip() {
let query = QueryParams {
api_key: "KEY".into(),
zip: Some("94040,us".into()),
..Default::default()
};
let url = query.weather_url().unwrap();
assert!(url.contains("zip=94040,us"));
}
#[test]
fn test_weather_url_error() {
let query = QueryParams {
api_key: "KEY".into(),
..Default::default()
};
assert!(query.weather_url().is_err());
}
#[test]
fn test_forecast_url() {
let query = QueryParams {
api_key: "KEY".into(),
city_name: Some("Berlin".into()),
..Default::default()
};
let url = query.forecast_url().unwrap();
assert!(url.contains("q=Berlin"));
assert!(url.starts_with(FORECAST_URL));
}
}