feat(api): enhance error handling with serde_path_to_error integration
Some checks failed
build / checks-build (push) Has been cancelled
build / codecov (push) Has been cancelled
docs / docs (push) Has been cancelled
build / checks-matrix (push) Has been cancelled

This commit is contained in:
uttarayan21
2025-10-08 15:35:41 +05:30
parent c18e92ff01
commit 160ca86da5
4 changed files with 51 additions and 13 deletions

12
Cargo.lock generated
View File

@@ -1453,6 +1453,17 @@ dependencies = [
"serde_core", "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]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@@ -2278,6 +2289,7 @@ dependencies = [
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
"serde_path_to_error",
"thiserror", "thiserror",
"tokio", "tokio",
"tracing", "tracing",

View File

@@ -32,3 +32,4 @@ chrono = { version = "0.4", features = ["serde"] }
# Async utilities # Async utilities
futures = "0.3" futures = "0.3"
urlencoding = "2.1.3" urlencoding = "2.1.3"
serde_path_to_error = "0.1.20"

View File

@@ -7,8 +7,8 @@ use thiserror::Error;
pub enum ApiError { pub enum ApiError {
#[error("HTTP request failed: {0}")] #[error("HTTP request failed: {0}")]
Request(#[from] reqwest::Error), Request(#[from] reqwest::Error),
#[error("Serialization error: {0}")] #[error("Deserialization error: {0}")]
Serialization(#[from] serde_json::Error), Deserialization(#[from] serde_path_to_error::Error<serde_json::Error>),
#[error("API error: {message}")] #[error("API error: {message}")]
Api { message: String }, Api { message: String },
} }
@@ -47,7 +47,31 @@ impl SonarrClient {
} }
let text = response.text().await?; 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<T: for<'de> Deserialize<'de>>(&self, endpoint: &str) -> Result<T> {
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<T: Serialize, R: for<'de> Deserialize<'de>>( async fn post<T: Serialize, R: for<'de> Deserialize<'de>>(
@@ -71,7 +95,8 @@ impl SonarrClient {
} }
let text = response.text().await?; 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<SystemStatus> { pub async fn get_system_status(&self) -> Result<SystemStatus> {
@@ -147,7 +172,7 @@ impl SonarrClient {
} }
pub async fn search_series(&self, term: &str) -> Result<Vec<Series>> { pub async fn search_series(&self, term: &str) -> Result<Vec<Series>> {
self.get(&format!( self.get_debug(&format!(
"/series/lookup?term={}", "/series/lookup?term={}",
urlencoding::encode(term) urlencoding::encode(term)
)) ))
@@ -199,7 +224,7 @@ pub struct SystemStatus {
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Series { pub struct Series {
pub id: u32, pub id: Option<u32>,
pub title: Option<String>, pub title: Option<String>,
pub alternate_titles: Option<Vec<AlternateTitle>>, pub alternate_titles: Option<Vec<AlternateTitle>>,
pub sort_title: Option<String>, pub sort_title: Option<String>,
@@ -215,14 +240,14 @@ pub struct Series {
pub original_language: Option<Language>, pub original_language: Option<Language>,
pub remote_poster: Option<String>, pub remote_poster: Option<String>,
pub seasons: Option<Vec<Season>>, pub seasons: Option<Vec<Season>>,
pub year: i32, pub year: u32,
pub path: Option<String>, pub path: Option<String>,
pub quality_profile_id: u32, pub quality_profile_id: u32,
pub season_folder: bool, pub season_folder: bool,
pub monitored: bool, pub monitored: bool,
pub monitor_new_items: String, pub monitor_new_items: String,
pub use_scene_numbering: bool, pub use_scene_numbering: bool,
pub runtime: i32, pub runtime: u32,
pub tvdb_id: u32, pub tvdb_id: u32,
pub tv_rage_id: u32, pub tv_rage_id: u32,
pub tv_maze_id: u32, pub tv_maze_id: u32,
@@ -501,8 +526,8 @@ pub struct HistoryItem {
pub quality_cutoff_not_met: bool, pub quality_cutoff_not_met: bool,
pub date: chrono::DateTime<chrono::Utc>, pub date: chrono::DateTime<chrono::Utc>,
pub download_id: Option<String>, pub download_id: Option<String>,
pub event_type: String, pub event_type: Option<String>,
pub data: Option<HashMap<String, String>>, pub data: Option<HashMap<String, Option<String>>>,
pub episode: Option<Episode>, pub episode: Option<Episode>,
pub series: Option<Series>, pub series: Option<Series>,
} }
@@ -521,7 +546,7 @@ pub struct EpisodePagingResource {
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct HealthResource { pub struct HealthResource {
pub id: u32, pub id: Option<i32>,
pub source: Option<String>, pub source: Option<String>,
#[serde(rename = "type")] #[serde(rename = "type")]
pub health_type: String, pub health_type: String,

View File

@@ -901,7 +901,7 @@ fn render_history_tab(f: &mut Frame, area: Rect, app: &App) {
.as_ref() .as_ref()
.map(|e| format!("S{:02}E{:02}", e.season_number, e.episode_number)) .map(|e| format!("S{:02}E{:02}", e.season_number, e.episode_number))
.unwrap_or_default(); .unwrap_or_default();
let event = &h.event_type; let event = h.event_type.as_deref().unwrap_or("Unknown");
let quality = h let quality = h
.quality .quality
.as_ref() .as_ref()
@@ -912,7 +912,7 @@ fn render_history_tab(f: &mut Frame, area: Rect, app: &App) {
Cell::from(date), Cell::from(date),
Cell::from(series), Cell::from(series),
Cell::from(episode), Cell::from(episode),
Cell::from(event.as_str()), Cell::from(event),
Cell::from(quality), Cell::from(quality),
]) ])
}) })