diff --git a/rust_backend/crates/extensions/src/backend/provider_metadata.rs b/rust_backend/crates/extensions/src/backend/provider_metadata.rs index 98df916f..57c2bb2f 100644 --- a/rust_backend/crates/extensions/src/backend/provider_metadata.rs +++ b/rust_backend/crates/extensions/src/backend/provider_metadata.rs @@ -392,10 +392,17 @@ fn response(kind: &str, value: &Value, check: &Check<'_>) -> Result json!({"track":track(value, "", 0)}), - "album" => { - json!({"album_info":album(value, true),"track_list":tracks(array(value,"tracks"),cover,check)?}) - } + // Move normalized values into the envelope. json! serializes borrowed + // expressions, duplicating every field of an already-built track list. + "track" => Map::from_iter([("track".into(), track(value, "", 0))]).into(), + "album" => Map::from_iter([ + ("album_info".into(), album(value, true)), + ( + "track_list".into(), + tracks(array(value, "tracks"), cover, check)?, + ), + ]) + .into(), "playlist" => { let mut info = strings( value, @@ -413,7 +420,14 @@ fn response(kind: &str, value: &Value, check: &Check<'_>) -> Result { let cover = text(value, "image_url"); @@ -439,7 +453,7 @@ fn response(kind: &str, value: &Value, check: &Check<'_>) -> Result 0 { info.insert("listeners".into(), value["listeners"].clone()); } - let mut result = json!({"artist_info":info}); + let mut result = Value::Object(Map::from_iter([("artist_info".into(), info.into())])); for key in ["albums", "releases"] { let values = array(value, key); if key == "albums" || !values.is_empty() { diff --git a/rust_backend/crates/extensions/src/backend/provider_metadata/value_tests.rs b/rust_backend/crates/extensions/src/backend/provider_metadata/value_tests.rs index ecb718a0..c2b24427 100644 --- a/rust_backend/crates/extensions/src/backend/provider_metadata/value_tests.rs +++ b/rust_backend/crates/extensions/src/backend/provider_metadata/value_tests.rs @@ -49,6 +49,41 @@ fn fixture() -> (tempfile::TempDir, Backend) { (root, backend) } +#[test] +fn response_envelopes_preserve_json_and_cancellation() { + let value = json!({ + "id": "collection", "name": "音楽 🎵", "artists": "Artist Café", + "cover_url": "https://example.invalid/cover.jpg", + "tracks": [ + {"id": "first", "name": "One", "external_links": {"example": "link"}}, + {"id": "second", "name": "Two", "explicit": true, "track_number": 9}, + ], + }); + let check = || Ok(()); + let cover = text(&value, "cover_url"); + let expected = json!({ + "album_info": album(&value, true), + "track_list": tracks(array(&value, "tracks"), cover, &check).unwrap(), + }); + let actual = response("album", &value, &check).unwrap(); + assert_eq!(actual.to_string(), expected.to_string()); + let playlist = response("playlist", &value, &check).unwrap(); + assert_eq!(playlist["track_list"], expected["track_list"]); + assert_eq!(playlist["playlist_info"]["owner"]["name"], "Artist Café"); + assert_eq!(playlist["track_list"][0]["track_number"], 1); + assert_eq!(playlist["track_list"][1]["track_number"], 9); + assert_eq!( + response("track", &value, &check).unwrap().to_string(), + json!({"track": track(&value, "", 0)}).to_string(), + ); + for kind in ["album", "playlist", "artist", "track"] { + assert_eq!( + response(kind, &value, &|| Err("cancelled".into())), + Err(ResolverError::Cancelled("cancelled".into())), + ); + } +} + #[test] fn artist_metadata_preserves_logo_separately_from_portrait() { let (_root, backend) = fixture();