feat(models): Track.streamable becomes Option<bool>, and absence stops meaning dead
`#[serde(default)]` on a bare bool means an absent key deserializes to FALSE, so a catalog Track from any endpoint that omits `streamable` came back marked unavailable. The models' own tests document this as a hazard rather than a bug (the "streamable split": PurchaseTrack defaults true, catalog Track defaults false, and the comment warns that a wrong default "inverts that whole screen's behaviour"). The inferred OpenAPI disagrees with the code and documents `default: true`. That was survivable only because nothing read the field. It stops being survivable the moment a frontend greys rows out and keeps them out of the queue: one terse endpoint would blank an entire view, silently. `None` now means the endpoint made no claim, and only `Some(false)` marks a track pulled. `is_streamable()` is the ONE place absence is interpreted, and it resolves to available: refusing to play a track because a response was terse is strictly worse than playing one that turns out to be dead, which the reactive skip path already handles. Album.streamable was already Option<bool> with a doc comment naming the same causes, so this makes Track consistent rather than novel. Every consumer in the workspace moves to the helper: both matchers, the ISRC validator, the daemon driver, the purchases service (which maps catalog Track into PurchaseTrack and therefore feeds the very screen the split protects), the radio pool, the mixtape resolver and the QConnect renderer. The replaced tests pin both directions of the new contract. Also here, because it lives in the same file as the matcher's streamable skip: `rank_candidates`, an additive public entry point over the existing weighted matcher (ISRC short-circuit, title/artist/album weights, duration bonus, stop-word normalisation, quality tie-break). `match_tracks` is untouched. cc
This commit is contained in:
13 files changed
+445
-24
No files matched your search
@@ -502,7 +502,9 @@ pub(crate) fn track_to_queue_track(track: &Track) -> QueueTrack {
|
||||
is_local: false,
|
||||
album_id,
|
||||
artist_id,
|
||||
streamable: track.streamable,
|
||||
// `QueueTrack.streamable` is a resolved `bool`; absence is interpreted
|
||||
// once, here, by `Track::is_streamable` (absent = available).
|
||||
streamable: track.is_streamable(),
|
||||
source: Some("qobuz".to_string()),
|
||||
parental_warning: track.parental_warning,
|
||||
source_item_id_hint: None,
|
||||
@@ -707,7 +709,11 @@ mod tests {
|
||||
hires: true,
|
||||
maximum_bit_depth: Some(24),
|
||||
maximum_sampling_rate: Some(96.0),
|
||||
streamable: true,
|
||||
// The catalog shape reports availability explicitly here — that is
|
||||
// what `/album/get` sends. `Some(true)`, not a bare `true`, since
|
||||
// the model distinguishes "the server said yes" from "the server
|
||||
// said nothing".
|
||||
streamable: Some(true),
|
||||
parental_warning: false,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user