Skip to main content

rspotify_model/
playlist.rs

1//! All kinds of playlists objects
2
3use crate::{Followers, Image, Page, PlayableItem, PlaylistId, PublicUser};
4use chrono::prelude::*;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// Playlist result object
9#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
10pub struct PlaylistResult {
11    pub snapshot_id: String,
12}
13
14/// Playlist Track Reference Object
15#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
16pub struct PlaylistTracksRef {
17    pub href: String,
18    pub total: u32,
19}
20
21fn deserialize_null_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
22where
23    T: Default + serde::Deserialize<'de>,
24    D: serde::Deserializer<'de>,
25{
26    Ok(Option::deserialize(deserializer)?.unwrap_or_default())
27}
28
29#[derive(Deserialize)]
30struct SimplifiedPlaylistShadow {
31    pub collaborative: bool,
32    pub external_urls: HashMap<String, String>,
33    pub href: String,
34    pub id: PlaylistId<'static>,
35    #[serde(deserialize_with = "deserialize_null_default")]
36    pub images: Vec<Image>,
37    pub name: String,
38    pub owner: PublicUser,
39    pub public: Option<bool>,
40    pub snapshot_id: String,
41    #[serde(default)]
42    pub items: Option<PlaylistTracksRef>,
43    #[serde(default)]
44    pub tracks: Option<PlaylistTracksRef>,
45}
46
47#[allow(deprecated)]
48impl From<SimplifiedPlaylistShadow> for SimplifiedPlaylist {
49    fn from(shadow: SimplifiedPlaylistShadow) -> Self {
50        let items = shadow
51            .items
52            .or(shadow.tracks)
53            .expect("missing items/tracks");
54        Self {
55            collaborative: shadow.collaborative,
56            external_urls: shadow.external_urls,
57            href: shadow.href,
58            id: shadow.id,
59            images: shadow.images,
60            name: shadow.name,
61            owner: shadow.owner,
62            public: shadow.public,
63            snapshot_id: shadow.snapshot_id,
64            tracks: items.clone(),
65            items,
66        }
67    }
68}
69
70/// Simplified playlist object
71///
72/// Note: The `tracks` field was renamed to `items` by Spotify. This struct
73/// uses an internal "shadow" struct for deserialization to ensure that both
74/// fields are correctly populated regardless of whether Spotify sends the old
75/// or new key. This maintains compatibility before, during and after the
76/// February 2026 API migration.
77#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
78#[serde(from = "SimplifiedPlaylistShadow")]
79pub struct SimplifiedPlaylist {
80    pub collaborative: bool,
81    pub external_urls: HashMap<String, String>,
82    pub href: String,
83    pub id: PlaylistId<'static>,
84    #[serde(deserialize_with = "deserialize_null_default")]
85    pub images: Vec<Image>,
86    pub name: String,
87    pub owner: PublicUser,
88    pub public: Option<bool>,
89    pub snapshot_id: String,
90    /// Note: This field is kept for compatibility before, during and after
91    /// Spotify's February 2026 API migration. It is synced with `items`
92    /// during deserialization.
93    #[deprecated(
94        since = "0.16.0",
95        note = "Renamed to `items` by Spotify. Use `items` instead. See https://github.com/ramsayleung/rspotify/issues/550"
96    )]
97    pub tracks: PlaylistTracksRef,
98    pub items: PlaylistTracksRef,
99}
100
101#[derive(Deserialize)]
102struct FullPlaylistShadow {
103    pub collaborative: bool,
104    pub description: Option<String>,
105    pub external_urls: HashMap<String, String>,
106    pub followers: Followers,
107    pub href: String,
108    pub id: PlaylistId<'static>,
109    #[serde(deserialize_with = "deserialize_null_default")]
110    pub images: Vec<Image>,
111    pub name: String,
112    pub owner: PublicUser,
113    pub public: Option<bool>,
114    pub snapshot_id: String,
115    #[serde(default)]
116    pub items: Option<Page<PlaylistItem>>,
117    #[serde(default)]
118    pub tracks: Option<Page<PlaylistItem>>,
119}
120
121#[allow(deprecated)]
122impl From<FullPlaylistShadow> for FullPlaylist {
123    fn from(shadow: FullPlaylistShadow) -> Self {
124        let items = shadow
125            .items
126            .or(shadow.tracks)
127            .expect("missing items/tracks");
128        Self {
129            collaborative: shadow.collaborative,
130            description: shadow.description,
131            external_urls: shadow.external_urls,
132            followers: shadow.followers,
133            href: shadow.href,
134            id: shadow.id,
135            images: shadow.images,
136            name: shadow.name,
137            owner: shadow.owner,
138            public: shadow.public,
139            snapshot_id: shadow.snapshot_id,
140            tracks: items.clone(),
141            items,
142        }
143    }
144}
145
146/// Full playlist object
147///
148/// Note: The `tracks` field was renamed to `items` by Spotify. This struct
149/// uses an internal "shadow" struct for deserialization to ensure that both
150/// fields are correctly populated regardless of whether Spotify sends the old
151/// or new key. This maintains compatibility before, during and after the
152/// February 2026 API migration.
153#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
154#[serde(from = "FullPlaylistShadow")]
155pub struct FullPlaylist {
156    pub collaborative: bool,
157    pub description: Option<String>,
158    pub external_urls: HashMap<String, String>,
159    pub followers: Followers,
160    pub href: String,
161    pub id: PlaylistId<'static>,
162    #[serde(deserialize_with = "deserialize_null_default")]
163    pub images: Vec<Image>,
164    pub name: String,
165    pub owner: PublicUser,
166    pub public: Option<bool>,
167    pub snapshot_id: String,
168    /// Note: This field is kept for compatibility before, during and after
169    /// Spotify's February 2026 API migration. It is synced with `items`
170    /// during deserialization.
171    #[deprecated(
172        since = "0.16.0",
173        note = "Renamed to `items` by Spotify. Use `items` instead. See https://github.com/ramsayleung/rspotify/issues/550"
174    )]
175    pub tracks: Page<PlaylistItem>,
176    pub items: Page<PlaylistItem>,
177}
178
179#[derive(Deserialize)]
180struct PlaylistItemShadow {
181    pub added_at: Option<DateTime<Utc>>,
182    pub added_by: Option<PublicUser>,
183    pub is_local: bool,
184    #[serde(default)]
185    pub item: Option<PlayableItem>,
186    #[serde(default)]
187    pub track: Option<PlayableItem>,
188}
189
190#[allow(deprecated)]
191impl From<PlaylistItemShadow> for PlaylistItem {
192    fn from(shadow: PlaylistItemShadow) -> Self {
193        let item = shadow.item.or(shadow.track);
194        Self {
195            added_at: shadow.added_at,
196            added_by: shadow.added_by,
197            is_local: shadow.is_local,
198            track: item.clone(),
199            item,
200        }
201    }
202}
203
204/// Playlist track object
205///
206/// Note: The `track` field was renamed to `item` by Spotify. This struct
207/// uses an internal "shadow" struct for deserialization to ensure that both
208/// fields are correctly populated regardless of whether Spotify sends the old
209/// or new key. This maintains compatibility before, during and after the
210/// February 2026 API migration.
211#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
212#[serde(from = "PlaylistItemShadow")]
213pub struct PlaylistItem {
214    pub added_at: Option<DateTime<Utc>>,
215    pub added_by: Option<PublicUser>,
216    pub is_local: bool,
217    /// Note: This field is kept for compatibility before, during and after
218    /// Spotify's February 2026 API migration. It is synced with `item`
219    /// during deserialization.
220    #[deprecated(
221        since = "0.16.0",
222        note = "Renamed to `item` by Spotify. Use `item` instead. See https://github.com/ramsayleung/rspotify/issues/550"
223    )]
224    pub track: Option<PlayableItem>,
225    pub item: Option<PlayableItem>,
226}
227
228/// Featured playlists object
229#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
230pub struct FeaturedPlaylists {
231    pub message: String,
232    pub playlists: Page<SimplifiedPlaylist>,
233}
234
235/// Intermediate category playlists object wrapped by `Page`
236#[derive(Deserialize)]
237pub struct CategoryPlaylists {
238    pub playlists: Page<SimplifiedPlaylist>,
239}