fix(album-awards): accept LegacyAwardDto's awardId/awardedAt field names

Root cause of 'Discoteca Ideal Qobuz not clickable': /album/get
returns awards as LegacyAwardDto (per a20/c.java + LegacyAlbumDto
in the Android decompilation) whose id field is named awardId, not
id. Our AlbumAward struct only looked for 'id', so every award from
/album/get deserialized with id=None and the click fell back to the
catalog resolver — which was itself incomplete.

Adds serde aliases 'awardId' / 'award_id' for the id field and
'awardedAt' for awarded_at. Applies to both qbz-models (V2) and the
src-tauri legacy mirror.

Endpoint coverage:
- /discover/index  → {id, name, awarded_at}       (primary match)
- /album/get       → {awardId, name, awardedAt, …} (alias match)
- /artist/page     → {id, name, awarded_at}       (primary match)

cc
This commit is contained in:
vicrodh committed 2026-04-14 16:02:08 -06:00
1 parent 776e7846cd
commit c4c4bdf244
2 files changed
+33 -9

No files matched your search

+20 -6
View File
@@ -849,18 +849,32 @@ pub struct PageArtistAward {
} }
/// Award attached to an album. Shape is intentionally lenient because /// Award attached to an album. Shape is intentionally lenient because
/// Qobuz returns different field types across endpoints: /// Qobuz uses three different embedded shapes across endpoints:
/// - `/discover/index` — id=int, name=string, awarded_at="YYYY-MM-DD" /// - `/discover/index` — {id: int, name, awarded_at: "YYYY-MM-DD"}
/// - `/album/get` — id=string, awarded_at may be Unix epoch int /// - `/album/get` — LegacyAwardDto {awardId: string, name,
/// publicationId, publicationName, awardSlug,
/// awardedAt: long, …}
/// - `/artist/page` — PageArtistAward {id: int, name, awarded_at}
/// id is emitted as String downstream so the frontend has a single /// id is emitted as String downstream so the frontend has a single
/// type to carry into /award/page and /award/getAlbums. /// type to carry into /award/page and /award/getAlbums. The `alias`
/// list covers the LegacyAwardDto field name the web app never sees
/// but the mobile API uses.
#[derive(Debug, Clone, Serialize, Deserialize, Default)] #[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AlbumAward { pub struct AlbumAward {
#[serde(default, deserialize_with = "deserialize_award_id")] #[serde(
default,
alias = "awardId",
alias = "award_id",
deserialize_with = "deserialize_award_id"
)]
pub id: Option<String>, pub id: Option<String>,
#[serde(default)] #[serde(default)]
pub name: String, pub name: String,
#[serde(default, deserialize_with = "deserialize_award_awarded_at")] #[serde(
default,
alias = "awardedAt",
deserialize_with = "deserialize_award_awarded_at"
)]
pub awarded_at: Option<String>, pub awarded_at: Option<String>,
} }
+13 -3
View File
@@ -969,14 +969,24 @@ pub struct PageArtistAward {
} }
/// Tolerant award struct used in Album responses where Qobuz is /// Tolerant award struct used in Album responses where Qobuz is
/// inconsistent about field types. id is emitted as String downstream. /// inconsistent about field types. Aliases cover LegacyAwardDto's
/// awardId / awardedAt field names used in /album/get responses.
#[derive(Debug, Clone, Serialize, Deserialize, Default)] #[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AlbumAward { pub struct AlbumAward {
#[serde(default, deserialize_with = "deserialize_album_award_id")] #[serde(
default,
alias = "awardId",
alias = "award_id",
deserialize_with = "deserialize_album_award_id"
)]
pub id: Option<String>, pub id: Option<String>,
#[serde(default)] #[serde(default)]
pub name: String, pub name: String,
#[serde(default, deserialize_with = "deserialize_album_awarded_at")] #[serde(
default,
alias = "awardedAt",
deserialize_with = "deserialize_album_awarded_at"
)]
pub awarded_at: Option<String>, pub awarded_at: Option<String>,
} }