fix(album): accept awarded_at as integer or string on PageArtistAward

/album/get returns award.awarded_at as a Unix timestamp integer while
/discover/index returns it as a YYYY-MM-DD string — our struct only
accepted String, so /album/get started failing with
'invalid type: integer `…`, expected a string' as soon as awards were
added to Album in Phase 2. Every 'Load album' call broke.

Adds a custom deserializer that accepts both shapes and normalizes to
String downstream. Applied in both qbz-models (V2) and src-tauri legacy
api::models for symmetry.

cc
This commit is contained in:
vicrodh committed 2026-04-14 11:28:19 -06:00
1 parent 7bf9972846
commit f0b4499dab
2 files changed
+74

No files matched your search

+58
View File
@@ -467,6 +467,47 @@ pub struct LabelPageGenericList {
pub items: Option<Vec<serde_json::Value>>,
}
// ============ Award Page Types (/award/page) ============
/// Magazine/publisher behind a press award.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AwardMagazine {
pub id: Option<String>,
pub name: Option<String>,
pub image: Option<String>,
}
/// Top-level response from /award/page.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AwardPageData {
pub id: String,
pub name: String,
#[serde(default)]
pub image: Option<String>,
#[serde(default)]
pub awarded_at: Option<String>,
#[serde(default)]
pub magazine: Option<AwardMagazine>,
/// Categorized containers of award-winning releases (matches
/// Android's `releases: List<GenericContainerDto<AlbumDto>>`).
#[serde(default)]
pub releases: Option<Vec<AwardPageContainer>>,
#[serde(default)]
pub playlists: Option<AwardPageGenericList>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AwardPageContainer {
pub id: Option<String>,
pub data: Option<AwardPageGenericList>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AwardPageGenericList {
pub has_more: Option<bool>,
pub items: Option<Vec<serde_json::Value>>,
}
/// Response from /label/explore
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LabelExploreResponse {
@@ -785,9 +826,26 @@ pub struct PageArtistRights {
pub struct PageArtistAward {
pub id: u64,
pub name: String,
/// Qobuz is inconsistent — /discover/index returns this as a
/// "YYYY-MM-DD" string, /album/get returns a Unix timestamp integer.
/// Accept both and emit a string downstream.
#[serde(default, deserialize_with = "deserialize_awarded_at")]
pub awarded_at: Option<String>,
}
fn deserialize_awarded_at<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = Option::<serde_json::Value>::deserialize(deserializer)?;
Ok(match value {
Some(serde_json::Value::String(s)) => Some(s),
Some(serde_json::Value::Number(n)) => Some(n.to_string()),
Some(serde_json::Value::Null) | None => None,
Some(_) => None,
})
}
/// Track from /artist/page
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PageArtistTrack {