Create Weather API 2.5 Library

- Move API-specific structs to library
- Move API-specific configuration to library
This commit is contained in:
Candifloss 2025-09-19 08:11:44 +05:30
parent a4034d6944
commit 1270a3e595
7 changed files with 135 additions and 77 deletions

View File

@ -1,12 +1,5 @@
[package]
name = "openweatherwidget"
version = "0.0.1"
edition = "2024"
[dependencies]
toml = "0.9.6"
dirs = "6.0.0"
serde = { version = "1.0.225", features = ["derive"] }
reqwest = {version = "0.12.23", features = ["blocking", "json"] }
serde_json = "1.0.145"
[workspace]
members = [
"owm_api25",
"widget",
]

10
owm_api25/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "owm_api25"
version = "0.0.1"
edition = "2024"
[dependencies]
toml = "0.9.6"
dirs = "6.0.0"
serde = { version = "1.0.225", features = ["derive"] }
serde_json = "1.0.145"

66
owm_api25/src/config.rs Normal file
View File

@ -0,0 +1,66 @@
use serde::Deserialize;
use std::fs;
#[derive(Debug, Deserialize)]
pub struct General {
pub api_key: String, // Required for API query. Get yours for free from https://openweathermap.org
pub city_id: Option<String>, // Any of these location parameters are required
pub city_name: Option<String>, // Find City ID or Name from https://openweathermap.org/find?
pub lat: Option<f32>, // Latitude and Longitude must be used together
pub lon: Option<f32>,
pub zip: Option<String>, // Zip code
#[serde(default = "default_units")]
pub units: String, // "metric", "imperial", "standard" (Default)
#[serde(default = "default_lang")]
pub lang: String, // Default: "en"
}
#[derive(Debug, Deserialize)]
pub struct Config {
pub general: General,
}
fn default_units() -> String {
"standard".into()
}
fn default_lang() -> String {
"en".into()
}
impl Config {
pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
let mut path = dirs::config_dir().ok_or("No config dir found")?;
path.push("candywidgets/openweathermap.toml");
let toml_str = fs::read_to_string(&path)
.map_err(|_| format!("Failed to read config: {}", path.display()))?;
Ok(toml::from_str(&toml_str)?)
}
pub fn build_url(&self) -> Result<String, String> {
let base = "https://api.openweathermap.org/data/2.5/weather";
let mut params = vec![
format!("appid={}", self.general.api_key),
format!("units={}", self.general.units),
format!("lang={}", self.general.lang),
"mode=json".to_string(),
];
if let Some(ref id) = self.general.city_id {
params.push(format!("id={id}"));
} else if let Some(ref name) = self.general.city_name {
params.push(format!("q={name}"));
} else if let (Some(lat), Some(lon)) = (self.general.lat, self.general.lon) {
params.push(format!("lat={lat}&lon={lon}"));
} else if let Some(ref zip) = self.general.zip {
params.push(format!("zip={zip}"));
} else {
return Err("No valid location field found in config".into());
}
Ok(format!("{}?{}", base, params.join("&")))
}
}

27
owm_api25/src/lib.rs Normal file
View File

@ -0,0 +1,27 @@
pub mod config;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct Weather {
pub main: String,
pub icon: String,
}
#[derive(Debug, Deserialize)]
pub struct Main {
pub temp: f32,
}
#[derive(Debug, Deserialize)]
pub struct Sys {
pub country: String,
}
#[derive(Debug, Deserialize)]
pub struct WeatherResponse {
pub name: String,
pub sys: Sys,
pub weather: Vec<Weather>,
pub main: Main,
}

View File

@ -1,65 +0,0 @@
use reqwest::blocking;
use serde::Deserialize;
use std::fs;
#[derive(Debug, Deserialize)]
struct General {
api_key: String,
city_id: String,
units: String,
}
#[derive(Debug, Deserialize)]
struct Config {
general: General,
}
#[derive(Debug, Deserialize)]
struct Weather {
main: String,
icon: String,
}
#[derive(Debug, Deserialize)]
struct Main {
temp: f32,
}
#[derive(Debug, Deserialize)]
struct Sys {
country: String,
}
#[derive(Debug, Deserialize)]
struct WeatherResponse {
name: String,
sys: Sys,
weather: Vec<Weather>,
main: Main,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut config_path = dirs::config_dir().ok_or("No config dir found")?;
config_path.push("candywidgets/openweathermap.toml");
let config_str = fs::read_to_string(&config_path)
.map_err(|_| format!("Failed to read config: {}", config_path.display()))?;
let config: Config = toml::from_str(&config_str)?;
let url = format!(
"https://api.openweathermap.org/data/2.5/weather?units={}&id={}&appid={}",
config.general.units, config.general.city_id, config.general.api_key
);
let resp = blocking::get(&url)?.json::<WeatherResponse>()?;
println!("City: {}, {}", resp.name, resp.sys.country);
if let Some(w) = resp.weather.first() {
println!("Weather: {}", w.main);
println!("Icon: {}", w.icon);
}
println!("Temperature: {}°C", resp.main.temp);
Ok(())
}

9
widget/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "openweatherwidget"
version = "0.0.1"
edition = "2024"
[dependencies]
owm_api25 = { path = "../owm_api25" }
reqwest = {version = "0.12.23", features = ["blocking", "json"] }

18
widget/src/main.rs Normal file
View File

@ -0,0 +1,18 @@
use owm_api25::{WeatherResponse, config::Config};
use reqwest::blocking;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Config::load()?;
let url = config.build_url()?;
let resp = blocking::get(&url)?.json::<WeatherResponse>()?;
println!("City: {}, {}", resp.name, resp.sys.country);
if let Some(w) = resp.weather.first() {
println!("Weather: {}", w.main);
println!("Icon: {}", w.icon);
}
println!("Temperature: {}°C", resp.main.temp);
Ok(())
}