From 160ca86da5b65609cfdddc0019c5298e299b0515 Mon Sep 17 00:00:00 2001 From: uttarayan21 Date: Wed, 8 Oct 2025 15:35:41 +0530 Subject: [PATCH] feat(api): enhance error handling with serde_path_to_error integration --- Cargo.lock | 12 ++++++++++++ Cargo.toml | 1 + src/api.rs | 47 ++++++++++++++++++++++++++++++++++++----------- src/tui.rs | 4 ++-- 4 files changed, 51 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4d82a8..a64f6e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1453,6 +1453,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2278,6 +2289,7 @@ dependencies = [ "reqwest", "serde", "serde_json", + "serde_path_to_error", "thiserror", "tokio", "tracing", diff --git a/Cargo.toml b/Cargo.toml index dced7d6..0849aed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,3 +32,4 @@ chrono = { version = "0.4", features = ["serde"] } # Async utilities futures = "0.3" urlencoding = "2.1.3" +serde_path_to_error = "0.1.20" diff --git a/src/api.rs b/src/api.rs index 4345ad8..9ac44a6 100644 --- a/src/api.rs +++ b/src/api.rs @@ -7,8 +7,8 @@ use thiserror::Error; pub enum ApiError { #[error("HTTP request failed: {0}")] Request(#[from] reqwest::Error), - #[error("Serialization error: {0}")] - Serialization(#[from] serde_json::Error), + #[error("Deserialization error: {0}")] + Deserialization(#[from] serde_path_to_error::Error), #[error("API error: {message}")] Api { message: String }, } @@ -47,7 +47,31 @@ impl SonarrClient { } let text = response.text().await?; - serde_json::from_str(&text).map_err(ApiError::from) + let deser = &mut serde_json::Deserializer::from_str(&text); + + serde_path_to_error::deserialize(deser).map_err(ApiError::from) + } + + async fn get_debug Deserialize<'de>>(&self, endpoint: &str) -> Result { + let url = format!("{}/api/v3{}", self.base_url, endpoint); + let response = self + .client + .get(&url) + .header("X-Api-Key", &self.api_key) + .send() + .await?; + + if !response.status().is_success() { + return Err(ApiError::Api { + message: format!("HTTP {}: {}", response.status(), response.text().await?), + }); + } + + let text = response.text().await?; + std::fs::write(endpoint.replace("/", "_"), &text); + let deser = &mut serde_json::Deserializer::from_str(&text); + + serde_path_to_error::deserialize(deser).map_err(ApiError::from) } async fn post Deserialize<'de>>( @@ -71,7 +95,8 @@ impl SonarrClient { } let text = response.text().await?; - serde_json::from_str(&text).map_err(ApiError::from) + let deser = &mut serde_json::Deserializer::from_str(&text); + serde_path_to_error::deserialize(deser).map_err(ApiError::from) } pub async fn get_system_status(&self) -> Result { @@ -147,7 +172,7 @@ impl SonarrClient { } pub async fn search_series(&self, term: &str) -> Result> { - self.get(&format!( + self.get_debug(&format!( "/series/lookup?term={}", urlencoding::encode(term) )) @@ -199,7 +224,7 @@ pub struct SystemStatus { #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Series { - pub id: u32, + pub id: Option, pub title: Option, pub alternate_titles: Option>, pub sort_title: Option, @@ -215,14 +240,14 @@ pub struct Series { pub original_language: Option, pub remote_poster: Option, pub seasons: Option>, - pub year: i32, + pub year: u32, pub path: Option, pub quality_profile_id: u32, pub season_folder: bool, pub monitored: bool, pub monitor_new_items: String, pub use_scene_numbering: bool, - pub runtime: i32, + pub runtime: u32, pub tvdb_id: u32, pub tv_rage_id: u32, pub tv_maze_id: u32, @@ -501,8 +526,8 @@ pub struct HistoryItem { pub quality_cutoff_not_met: bool, pub date: chrono::DateTime, pub download_id: Option, - pub event_type: String, - pub data: Option>, + pub event_type: Option, + pub data: Option>>, pub episode: Option, pub series: Option, } @@ -521,7 +546,7 @@ pub struct EpisodePagingResource { #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct HealthResource { - pub id: u32, + pub id: Option, pub source: Option, #[serde(rename = "type")] pub health_type: String, diff --git a/src/tui.rs b/src/tui.rs index 8d6d0d4..32a1f4e 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -901,7 +901,7 @@ fn render_history_tab(f: &mut Frame, area: Rect, app: &App) { .as_ref() .map(|e| format!("S{:02}E{:02}", e.season_number, e.episode_number)) .unwrap_or_default(); - let event = &h.event_type; + let event = h.event_type.as_deref().unwrap_or("Unknown"); let quality = h .quality .as_ref() @@ -912,7 +912,7 @@ fn render_history_tab(f: &mut Frame, area: Rect, app: &App) { Cell::from(date), Cell::from(series), Cell::from(episode), - Cell::from(event.as_str()), + Cell::from(event), Cell::from(quality), ]) })