Better error handling
[With input from AI in VSCode]
This commit is contained in:
@@ -14,3 +14,4 @@ chrono = "0.4.41"
|
|||||||
chrono-tz = "0.10.3"
|
chrono-tz = "0.10.3"
|
||||||
iana-time-zone = "0.1.63"
|
iana-time-zone = "0.1.63"
|
||||||
clap = { version = "4.5.1", features = ["derive"] }
|
clap = { version = "4.5.1", features = ["derive"] }
|
||||||
|
thiserror = "1.0"
|
||||||
|
@@ -3,11 +3,19 @@ use curl::easy::{Easy, List};
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use thiserror::Error;
|
||||||
|
use std::num::ParseFloatError;
|
||||||
|
|
||||||
use crate::common;
|
use crate::common;
|
||||||
use crate::common::UpdateResult;
|
use crate::common::UpdateResult;
|
||||||
use crate::bar_modules;
|
use crate::bar_modules;
|
||||||
|
|
||||||
|
/// Number of cycles between weather updates
|
||||||
|
const UPDATE_INTERVAL: u32 = 300;
|
||||||
|
|
||||||
|
/// Base URL for the National Weather Service API
|
||||||
|
const WEATHER_API_BASE: &str = "https://api.weather.gov";
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct UnibarModuleWeather {
|
pub struct UnibarModuleWeather {
|
||||||
opts: common::AppOptions,
|
opts: common::AppOptions,
|
||||||
@@ -15,7 +23,27 @@ pub struct UnibarModuleWeather {
|
|||||||
update_cnt: u32,
|
update_cnt: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum WeatherError {
|
||||||
|
#[error("Invalid temperature format: {0}")]
|
||||||
|
ParseError(String),
|
||||||
|
#[error("API response missing temperature field: {0}")]
|
||||||
|
ApiResponseStrErr(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ParseFloatError> for WeatherError {
|
||||||
|
fn from(err: ParseFloatError) -> Self {
|
||||||
|
WeatherError::ParseError(err.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl UnibarModuleWeather {
|
impl UnibarModuleWeather {
|
||||||
|
const FAHRENHEIT_MULTIPLIER: f64 = 9.0 / 5.0;
|
||||||
|
const FAHRENHEIT_OFFSET: f64 = 32.0;
|
||||||
|
|
||||||
|
fn celsius_to_fahrenheit(celsius: f64) -> f64 {
|
||||||
|
celsius * Self::FAHRENHEIT_MULTIPLIER + Self::FAHRENHEIT_OFFSET
|
||||||
|
}
|
||||||
|
|
||||||
// --------------------
|
// --------------------
|
||||||
pub fn new(o :common::AppOptions) -> Self {
|
pub fn new(o :common::AppOptions) -> Self {
|
||||||
@@ -27,23 +55,27 @@ impl UnibarModuleWeather {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --------------------
|
// --------------------
|
||||||
fn get_current_temperature(&self) -> f32 {
|
fn get_current_temperature(&self) -> Result<f64, WeatherError> {
|
||||||
let deg_c :f32;
|
let deg_c = match &self.weather_info {
|
||||||
|
serde_json::Value::Null => 0.0,
|
||||||
match self.weather_info {
|
|
||||||
serde_json::Value::Null => {
|
|
||||||
deg_c = 0.0;
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
deg_c = self.weather_info["features"][0]["properties"]["temperature"]["value"]
|
// Safely navigate the JSON structure
|
||||||
.to_string().parse().unwrap();
|
self.weather_info
|
||||||
|
.get("features")
|
||||||
|
.and_then(|f| f.get(0))
|
||||||
|
.and_then(|f| f.get("properties"))
|
||||||
|
.and_then(|p| p.get("temperature"))
|
||||||
|
.and_then(|t| t.get("value"))
|
||||||
|
.and_then(|v| v.as_f64())
|
||||||
|
.ok_or(WeatherError::ApiResponseStrErr("as_str likely failed".to_string()))?
|
||||||
|
// .parse()?
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
match self.opts.weather_units {
|
Ok(match self.opts.weather_units {
|
||||||
common::TemperatureUnits::Metric => return deg_c,
|
common::TemperatureUnits::Metric => deg_c,
|
||||||
common::TemperatureUnits::Imperial => return (deg_c * 9.0 / 5.0) + 32.0,
|
common::TemperatureUnits::Imperial => Self::celsius_to_fahrenheit(deg_c),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------
|
// --------------------
|
||||||
@@ -100,7 +132,8 @@ impl UnibarModuleWeather {
|
|||||||
let mut url_string = Vec::new();
|
let mut url_string = Vec::new();
|
||||||
let mut curl_ret = Vec::new();
|
let mut curl_ret = Vec::new();
|
||||||
|
|
||||||
url_string.push("https://api.weather.gov/icons".to_owned());
|
url_string.push(WEATHER_API_BASE.to_owned());
|
||||||
|
url_string.push("/icons".to_owned());
|
||||||
|
|
||||||
curl.url(url_string.concat().as_str()).unwrap();
|
curl.url(url_string.concat().as_str()).unwrap();
|
||||||
{
|
{
|
||||||
@@ -171,7 +204,7 @@ impl bar_modules::BarModuleActions for UnibarModuleWeather {
|
|||||||
// Weather update every 60 cycles
|
// Weather update every 60 cycles
|
||||||
fn should_update(&mut self) -> UpdateResult {
|
fn should_update(&mut self) -> UpdateResult {
|
||||||
if self.update_cnt == 0 {
|
if self.update_cnt == 0 {
|
||||||
self.update_cnt = 300;
|
self.update_cnt = UPDATE_INTERVAL;
|
||||||
return UpdateResult::Update;
|
return UpdateResult::Update;
|
||||||
} else {
|
} else {
|
||||||
self.update_cnt -= 1;
|
self.update_cnt -= 1;
|
||||||
@@ -192,7 +225,8 @@ impl bar_modules::BarModuleActions for UnibarModuleWeather {
|
|||||||
let mut curl_ret = Vec::new();
|
let mut curl_ret = Vec::new();
|
||||||
let mut curl_err = false;
|
let mut curl_err = false;
|
||||||
|
|
||||||
url_string.push("https://api.weather.gov/stations/".to_owned());
|
url_string.push(WEATHER_API_BASE.to_owned());
|
||||||
|
url_string.push("/stations/".to_owned());
|
||||||
url_string.push(self.opts.weather_station.to_owned());
|
url_string.push(self.opts.weather_station.to_owned());
|
||||||
url_string.push("/observations?limit=1".to_owned());
|
url_string.push("/observations?limit=1".to_owned());
|
||||||
|
|
||||||
@@ -233,8 +267,8 @@ impl bar_modules::BarModuleActions for UnibarModuleWeather {
|
|||||||
|
|
||||||
// --------------------
|
// --------------------
|
||||||
fn get_content(&self) -> String {
|
fn get_content(&self) -> String {
|
||||||
let temperature_value :f32 = self.get_current_temperature();
|
let temperature_value :f64 = self.get_current_temperature().expect("Temperature query");
|
||||||
let temperature_unit :String = self.get_temperature_unit();
|
let temperature_unit :String = self.get_temperature_unit();
|
||||||
// let temperature_icon :String = self.get_icon(v.clone());
|
// let temperature_icon :String = self.get_icon(v.clone());
|
||||||
|
|
||||||
return format!("{:.0}{}", temperature_value, temperature_unit);
|
return format!("{:.0}{}", temperature_value, temperature_unit);
|
||||||
@@ -251,6 +285,9 @@ impl bar_modules::BarModuleActions for UnibarModuleWeather {
|
|||||||
// "icon": "https://api.weather.gov/icons/land/night/ovc?size=medium",
|
// "icon": "https://api.weather.gov/icons/land/night/ovc?size=medium",
|
||||||
let re = Regex::new(r"(\w+)\?size").unwrap();
|
let re = Regex::new(r"(\w+)\?size").unwrap();
|
||||||
let json_val = self.weather_info["features"][0]["properties"]["icon"].to_string();
|
let json_val = self.weather_info["features"][0]["properties"]["icon"].to_string();
|
||||||
|
if self.opts.debug {
|
||||||
|
println!("-----> weather_data - {:#?}", self.weather_info["features"][0]["properties"]);
|
||||||
|
}
|
||||||
match self.weather_info["features"][0]["properties"]["icon"] {
|
match self.weather_info["features"][0]["properties"]["icon"] {
|
||||||
serde_json::Value::Null => {
|
serde_json::Value::Null => {
|
||||||
return "⁈".to_string();
|
return "⁈".to_string();
|
||||||
|
@@ -3,34 +3,34 @@ use crate::common::UpdateResult;
|
|||||||
// --------------------
|
// --------------------
|
||||||
/// All Bar modules must implement the actions
|
/// All Bar modules must implement the actions
|
||||||
pub trait BarModuleActions {
|
pub trait BarModuleActions {
|
||||||
/// Return String with name of the module
|
/// Returns the name of the module
|
||||||
fn get_name(&self) -> String;
|
fn get_name(&self) -> String;
|
||||||
|
|
||||||
/// Do necessary clean up before starting new fetch
|
/// Cleans up any state before refreshing data
|
||||||
fn clear(&mut self);
|
fn clear(&mut self);
|
||||||
|
|
||||||
/// Do necessary processing to generate data for this bar module
|
/// Fetches and processes new data for the module
|
||||||
fn generate_data(&mut self);
|
fn generate_data(&mut self);
|
||||||
|
|
||||||
/// Return String content to be displayed in the bar
|
/// Returns the formatted content to display in the bar
|
||||||
fn get_content(&self) -> String;
|
fn get_content(&self) -> String;
|
||||||
|
|
||||||
/// Return a Unicode icon to display before content in the bar.
|
/// Returns a Unicode icon representing the module's current state
|
||||||
/// This icon may differ based on content of the data
|
|
||||||
fn get_icon(&self) -> String;
|
fn get_icon(&self) -> String;
|
||||||
|
|
||||||
/// Returns UpdateResult depending on whether the module
|
/// Determines if the module should update its data
|
||||||
/// should be updated during the current update cycle
|
/// Default implementation always returns Update
|
||||||
fn should_update(&mut self) -> UpdateResult {
|
fn should_update(&mut self) -> UpdateResult {
|
||||||
return UpdateResult::Update;
|
return UpdateResult::Update;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait BarModuleDebug {
|
pub trait BarModuleDebug {
|
||||||
/// Print debug information at the end
|
/// Prints debug information about the module
|
||||||
fn post_debug(&self);
|
fn post_debug(&self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Module declarations
|
||||||
pub mod bar_module_weather;
|
pub mod bar_module_weather;
|
||||||
pub mod bar_module_music;
|
pub mod bar_module_music;
|
||||||
pub mod bar_module_network;
|
pub mod bar_module_network;
|
||||||
|
Reference in New Issue
Block a user