[GH-ISSUE #149] The way to reduce wrapper object. #52

Closed
opened 2026-02-27 20:22:50 +03:00 by kerem · 1 comment
Owner

Originally created by @ramsayleung on GitHub (Nov 7, 2020).
Original GitHub issue: https://github.com/ramsayleung/rspotify/issues/149

There are several wrapper objects in the model module, which tried to deserialize endpoint response to a decent struct.

─> rg "TODO:" src/model
track.rs
60:// TODO: Reduce wrapper object to `Vec<FullTrack>`

artist.rs
43:// TODO: Reduce this wrapper object to `Vec<FullArtist>`
52:// TODO: Reduce this wrapper object to `CursorBasedPage<FullArtist>`

album.rs
69:// TODO: Reduce this wrapper object to `Vec<FullAlbum>`
78:// TODO: Reduce this wrapper object to `Page<SimplifiedAlbum>`

show.rs
43:// TODO: Reduce this wrapper object to `Vec<SimplifiedShow>`

category.rs
19:// TODO: Reduce this wrapper object to `Page<Category>`

audio.rs
33:// TODO: Reduce this wrapper object to `Vec<AudioFeatures>`

device.rs
22:// TODO: Reduce this wrapper object to `Vec<Device>`

For example, the response of albums endpoint is something like this:

{
  "albums": [`FullAlbum Object`]
}

instead of a pure JSON array containing a list of FullAlbum object:

[`FullAlbum Object`]

Thus, in order to deserialize the Vec<FullAlbum> from HTTP response, I built a wrapper struct named FullTracks to handle this deserialization :

/// Full Albums wrapped by Vec object
///
/// [Reference](https://developer.spotify.com/documentation/web-api/reference/albums/get-several-albums/)
// TODO: Reduce this wrapper object to `Vec<FullAlbum>`
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct FullAlbums {
    pub albums: Vec<FullAlbum>,
}

Yes, it works well, but makes the API more verbose to use, you have to introduce more additional structs into your application, and the wrapper objects actually are some temporary object, so it's time to reduce this wrapper object, to make it more easier to use.

After investigating into the serde crate for a while, it seems there is no built-in way to strip the outer albums field out. Then another solution comes to my mind that we keep the wrapper object, but inside the function scope, makes it no need to export to developers:

 pub async fn albums<'a>(
        &self,
        album_ids: impl IntoIterator<Item = &'a str>,
    ) -> ClientResult<Vec<FullAlbum>> {
        let mut ids: Vec<String> = vec![];
        for album_id in album_ids {
            ids.push(self.get_id(Type::Album, album_id));
        }
        let url = format!("albums/?ids={}", ids.join(","));
        let result = self.get(&url, None, &Query::new()).await?;
		// defined inside the function, which will not export to user
		#[derive(Deserialize)]
		struct TemporaryFullAlbums{
			pub albums: Vec<FullAlbum>
		}
        let full_albums: TemporaryFullAlbums = serde_json::from_str(&result).map_err(Into::into);
		full_albums.albums
    }

I'd like to tag @Koxiaet again, who opened an issue #127 with a very opinionated list of improvements and mentioned the benefit and necessity to reduce these wrapper object; I'm sure they can help with this as well :)

Originally created by @ramsayleung on GitHub (Nov 7, 2020). Original GitHub issue: https://github.com/ramsayleung/rspotify/issues/149 There are several wrapper objects in the model module, which tried to deserialize endpoint response to a decent struct. ```sh ─> rg "TODO:" src/model track.rs 60:// TODO: Reduce wrapper object to `Vec<FullTrack>` artist.rs 43:// TODO: Reduce this wrapper object to `Vec<FullArtist>` 52:// TODO: Reduce this wrapper object to `CursorBasedPage<FullArtist>` album.rs 69:// TODO: Reduce this wrapper object to `Vec<FullAlbum>` 78:// TODO: Reduce this wrapper object to `Page<SimplifiedAlbum>` show.rs 43:// TODO: Reduce this wrapper object to `Vec<SimplifiedShow>` category.rs 19:// TODO: Reduce this wrapper object to `Page<Category>` audio.rs 33:// TODO: Reduce this wrapper object to `Vec<AudioFeatures>` device.rs 22:// TODO: Reduce this wrapper object to `Vec<Device>` ``` For example, the response of [`albums`](https://developer.spotify.com/web-api/get-several-albums/) endpoint is something like this: ```json { "albums": [`FullAlbum Object`] } ``` instead of a pure JSON array containing a list of `FullAlbum` object: ```json [`FullAlbum Object`] ``` Thus, in order to deserialize the `Vec<FullAlbum>` from HTTP response, I built a wrapper struct named `FullTracks` to handle this deserialization : ```rust /// Full Albums wrapped by Vec object /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/albums/get-several-albums/) // TODO: Reduce this wrapper object to `Vec<FullAlbum>` #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct FullAlbums { pub albums: Vec<FullAlbum>, } ``` Yes, it works well, but makes the API more verbose to use, you have to introduce more additional structs into your application, and the wrapper objects actually are some temporary object, so it's time to reduce this wrapper object, to make it more easier to use. After investigating into the `serde` crate for a while, it seems there is no built-in way to strip the outer `albums` field out. Then another solution comes to my mind that we keep the wrapper object, but inside the function scope, makes it no need to export to developers: ```rust pub async fn albums<'a>( &self, album_ids: impl IntoIterator<Item = &'a str>, ) -> ClientResult<Vec<FullAlbum>> { let mut ids: Vec<String> = vec![]; for album_id in album_ids { ids.push(self.get_id(Type::Album, album_id)); } let url = format!("albums/?ids={}", ids.join(",")); let result = self.get(&url, None, &Query::new()).await?; // defined inside the function, which will not export to user #[derive(Deserialize)] struct TemporaryFullAlbums{ pub albums: Vec<FullAlbum> } let full_albums: TemporaryFullAlbums = serde_json::from_str(&result).map_err(Into::into); full_albums.albums } ``` I'd like to tag @Koxiaet again, who opened an issue #127 with a very opinionated list of improvements and mentioned the benefit and necessity to reduce these wrapper object; I'm sure they can help with this as well :)
kerem 2026-02-27 20:22:50 +03:00
Author
Owner

@Kestrer commented on GitHub (Nov 7, 2020):

This sounds like a good idea! Thanks so much for all the improvements to rspotify :)

<!-- gh-comment-id:723398290 --> @Kestrer commented on GitHub (Nov 7, 2020): This sounds like a good idea! Thanks so much for all the improvements to rspotify :)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/rspotify#52
No description provided.