rspotify_model/
lib.rs

1//! All Spotify API endpoint response objects. Please refer to the endpoints
2//! where they are used for a link to their reference in the Spotify API
3//! documentation.
4pub mod album;
5pub mod artist;
6pub mod audio;
7pub mod auth;
8pub mod category;
9pub mod context;
10pub(crate) mod custom_serde;
11pub mod device;
12pub mod enums;
13pub mod error;
14pub mod idtypes;
15pub mod image;
16pub mod offset;
17pub mod page;
18pub mod playing;
19pub mod playlist;
20pub mod recommend;
21pub mod search;
22pub mod show;
23pub mod track;
24pub mod user;
25
26pub use {
27    album::*, artist::*, audio::*, auth::*, category::*, context::*, device::*, enums::*, error::*,
28    idtypes::*, image::*, offset::*, page::*, playing::*, playlist::*, recommend::*, search::*,
29    show::*, track::*, user::*,
30};
31
32use serde::{Deserialize, Serialize};
33
34/// Followers object
35#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
36pub struct Followers {
37    // This field will always set to null, as the Web API does not support it at the moment.
38    // pub href: Option<String>,
39    pub total: u32,
40}
41
42/// A full track object or a full episode object
43#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
44#[serde(untagged)]
45pub enum PlayableItem {
46    Track(track::FullTrack),
47    Episode(show::FullEpisode),
48    // The fallback variant to store the raw JSON for anything that doesn't parse
49    // see https://github.com/ramsayleung/rspotify/issues/525 for
50    // detail
51    Unknown(serde_json::Value),
52}
53
54impl PlayableItem {
55    /// Check if this is an unknown/malformed item that couldn't be parsed as Track or Episode.
56    ///
57    /// Returns `true` if the item was captured as raw JSON due to schema mismatch.
58    pub fn is_unknown(&self) -> bool {
59        matches!(self, PlayableItem::Unknown(_))
60    }
61
62    /// Utility to get the ID from either variant in the enum.
63    ///
64    /// Note that if it's a track and if it's local, it may not have an ID, in
65    /// which case this function will return `None`.
66    #[must_use]
67    pub fn id(&self) -> Option<PlayableId<'_>> {
68        match self {
69            PlayableItem::Track(t) => t.id.as_ref().map(|t| PlayableId::Track(t.as_ref())),
70            PlayableItem::Episode(e) => Some(PlayableId::Episode(e.id.as_ref())),
71            PlayableItem::Unknown(value) => {
72                let id_str = value.get("id")?.as_str()?;
73                if let Some(type_str) = value.get("type").and_then(|v| v.as_str()) {
74                    match type_str {
75                        "episode" => Some(PlayableId::Episode(EpisodeId::from_id(id_str).ok()?)),
76                        _ => Some(PlayableId::Track(TrackId::from_id(id_str).ok()?)),
77                    }
78                } else {
79                    // Default to track if type is unclear
80                    Some(PlayableId::Track(TrackId::from_id(id_str).ok()?))
81                }
82            }
83        }
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn test_get_id() {
93        // Assert artist
94        let artist_id = "spotify:artist:2WX2uTcsvV5OnS0inACecP";
95        let id = ArtistId::from_id_or_uri(artist_id).unwrap();
96        assert_eq!("2WX2uTcsvV5OnS0inACecP", id.id());
97
98        // Assert album
99        let album_id_a = "spotify/album/2WX2uTcsvV5OnS0inACecP";
100        assert_eq!(
101            "2WX2uTcsvV5OnS0inACecP",
102            AlbumId::from_id_or_uri(album_id_a).unwrap().id()
103        );
104
105        // Mismatch type
106        assert_eq!(
107            Err(IdError::InvalidType),
108            ArtistId::from_id_or_uri(album_id_a)
109        );
110
111        // Could not split
112        let artist_id_c = "spotify-album-2WX2uTcsvV5OnS0inACecP";
113        assert_eq!(
114            Err(IdError::InvalidId),
115            ArtistId::from_id_or_uri(artist_id_c)
116        );
117
118        let playlist_id = "spotify:playlist:59ZbFPES4DQwEjBpWHzrtC";
119        assert_eq!(
120            "59ZbFPES4DQwEjBpWHzrtC",
121            PlaylistId::from_id_or_uri(playlist_id).unwrap().id()
122        );
123    }
124
125    #[test]
126    fn test_get_uri() {
127        let track_id1 = "spotify:track:4iV5W9uYEdYUVa79Axb7Rh";
128        let track_id2 = "1301WleyT98MSxVHPZCA6M";
129        let id1 = TrackId::from_id_or_uri(track_id1).unwrap();
130        let id2 = TrackId::from_id_or_uri(track_id2).unwrap();
131        assert_eq!(track_id1, &id1.uri());
132        assert_eq!("spotify:track:1301WleyT98MSxVHPZCA6M", &id2.uri());
133    }
134}