feat(api): enhance error handling with serde_path_to_error integration
This commit is contained in:
12
Cargo.lock
generated
12
Cargo.lock
generated
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
47
src/api.rs
47
src/api.rs
@@ -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,
|
||||||
|
|||||||
@@ -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),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user