fix(album): use dedicated tolerant AlbumAward struct instead of mutating PageArtistAward

Previous attempt loosened PageArtistAward (id u64 -> Option<u64>,
awarded_at custom deserializer) to accept /album/get's inconsistent
award shape. That rippled into /artist/page consumers, which the user
rightly called out ('no eliminar lo que ya funcionaba, adaptar pues').

Restores PageArtistAward to its original shape (id: u64, name: String,
awarded_at: Option<String>) and introduces a parallel AlbumAward type
used only on DiscoverAlbum and Album. AlbumAward tolerates missing id,
accepts awarded_at as int or string, and never fails deserialization
of the parent payload. /artist/page keeps its strict contract.

cc
This commit is contained in:
vicrodh committed 2026-04-14 12:30:18 -06:00
1 parent fe3213ece5
commit ee2f413ec1
3 files changed
+37 -17

No files matched your search

+2 -1
View File
@@ -71,7 +71,8 @@ pub use types::{
LabelPageContainer,
LabelPageData,
LabelPageGenericList,
// Award page types
// Award types
AlbumAward,
AwardMagazine,
AwardPageContainer,
AwardPageData,
+20 -9
View File
@@ -267,7 +267,7 @@ pub struct Album {
pub goodies: Option<Vec<Goody>>,
/// Editorial awards (Qobuzissime, Album of the Week, press accolades).
#[serde(default)]
pub awards: Option<Vec<PageArtistAward>>,
pub awards: Option<Vec<AlbumAward>>,
}
/// A downloadable extra bundled with an album (e.g. PDF booklet)
@@ -678,7 +678,7 @@ pub struct DiscoverAlbum {
/// Editorial awards attached to the album. Id 88 = Qobuzissime,
/// id 151 = Qobuz Album of the Week (locale-stable).
#[serde(default)]
pub awards: Option<Vec<PageArtistAward>>,
pub awards: Option<Vec<AlbumAward>>,
}
/// Album image from discover endpoint
@@ -826,14 +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>
/// Award attached to an album. Shape is intentionally lenient because
/// Qobuz returns different field types across endpoints:
/// - `/discover/index` — id=int, name=string, awarded_at="YYYY-MM-DD"
/// - `/album/get` — id may be missing, awarded_at may be Unix epoch int
/// Keeps the parent Album struct from blowing up on any of those
/// variations while still giving the UI enough to render a ribbon.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AlbumAward {
#[serde(default)]
pub id: Option<u64>,
#[serde(default)]
pub name: String,
#[serde(default, deserialize_with = "deserialize_award_awarded_at")]
pub awarded_at: Option<String>,
}
fn deserialize_award_awarded_at<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: serde::Deserializer<'de>,
{
@@ -841,8 +853,7 @@ where
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,
_ => None,
})
}