spotify_cli/types/
album.rs

1//! Album types from Spotify API.
2
3use serde::{Deserialize, Serialize};
4
5use super::artist::ArtistSimplified;
6use super::common::{Copyright, ExternalIds, ExternalUrls, Image, Paginated, Restrictions};
7use super::track::TrackSimplified;
8
9/// Album type enumeration.
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
11#[serde(rename_all = "snake_case")]
12pub enum AlbumType {
13    Album,
14    Single,
15    Compilation,
16}
17
18/// Simplified album object (used in nested contexts).
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct AlbumSimplified {
21    /// Album type.
22    pub album_type: Option<String>,
23    /// Total number of tracks.
24    pub total_tracks: Option<u32>,
25    /// Markets where the album is available.
26    pub available_markets: Option<Vec<String>>,
27    /// External URLs.
28    pub external_urls: Option<ExternalUrls>,
29    /// Spotify URL.
30    pub href: Option<String>,
31    /// Spotify ID.
32    pub id: String,
33    /// Album cover images.
34    pub images: Option<Vec<Image>>,
35    /// Album name.
36    pub name: String,
37    /// Release date (YYYY, YYYY-MM, or YYYY-MM-DD).
38    pub release_date: Option<String>,
39    /// Release date precision.
40    pub release_date_precision: Option<String>,
41    /// Restrictions if any.
42    pub restrictions: Option<Restrictions>,
43    /// Object type (always "album").
44    #[serde(rename = "type")]
45    pub item_type: String,
46    /// Spotify URI.
47    pub uri: String,
48    /// Artists on the album.
49    pub artists: Option<Vec<ArtistSimplified>>,
50}
51
52/// Full album object.
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct Album {
55    /// Album type.
56    pub album_type: Option<String>,
57    /// Total number of tracks.
58    pub total_tracks: Option<u32>,
59    /// Markets where the album is available.
60    pub available_markets: Option<Vec<String>>,
61    /// External URLs.
62    pub external_urls: Option<ExternalUrls>,
63    /// Spotify URL.
64    pub href: Option<String>,
65    /// Spotify ID.
66    pub id: String,
67    /// Album cover images.
68    pub images: Option<Vec<Image>>,
69    /// Album name.
70    pub name: String,
71    /// Release date.
72    pub release_date: Option<String>,
73    /// Release date precision.
74    pub release_date_precision: Option<String>,
75    /// Restrictions if any.
76    pub restrictions: Option<Restrictions>,
77    /// Object type.
78    #[serde(rename = "type")]
79    pub item_type: String,
80    /// Spotify URI.
81    pub uri: String,
82    /// Artists on the album.
83    pub artists: Option<Vec<ArtistSimplified>>,
84    /// Album tracks (paginated).
85    pub tracks: Option<Paginated<TrackSimplified>>,
86    /// Copyright information.
87    pub copyrights: Option<Vec<Copyright>>,
88    /// External IDs.
89    pub external_ids: Option<ExternalIds>,
90    /// Genres (may be empty).
91    pub genres: Option<Vec<String>>,
92    /// Label name.
93    pub label: Option<String>,
94    /// Popularity score (0-100).
95    pub popularity: Option<u32>,
96}
97
98impl Album {
99    /// Get the largest image URL if available.
100    pub fn image_url(&self) -> Option<&str> {
101        self.images
102            .as_ref()
103            .and_then(|imgs| imgs.first())
104            .map(|img| img.url.as_str())
105    }
106
107    /// Get the primary artist name.
108    pub fn artist_name(&self) -> Option<&str> {
109        self.artists
110            .as_ref()
111            .and_then(|artists| artists.first())
112            .map(|a| a.name.as_str())
113    }
114
115    /// Get release year.
116    pub fn release_year(&self) -> Option<&str> {
117        self.release_date.as_ref().map(|d| &d[..4])
118    }
119}
120
121impl AlbumSimplified {
122    /// Get the largest image URL if available.
123    pub fn image_url(&self) -> Option<&str> {
124        self.images
125            .as_ref()
126            .and_then(|imgs| imgs.first())
127            .map(|img| img.url.as_str())
128    }
129
130    /// Get the primary artist name.
131    pub fn artist_name(&self) -> Option<&str> {
132        self.artists
133            .as_ref()
134            .and_then(|artists| artists.first())
135            .map(|a| a.name.as_str())
136    }
137}
138
139/// Saved album (wraps album with added_at timestamp).
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct SavedAlbum {
142    /// When the album was saved.
143    pub added_at: String,
144    /// The album.
145    pub album: Album,
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use serde_json::json;
152
153    #[test]
154    fn album_type_deserializes() {
155        let json = json!("album");
156        let album_type: AlbumType = serde_json::from_value(json).unwrap();
157        assert_eq!(album_type, AlbumType::Album);
158
159        let json = json!("single");
160        let album_type: AlbumType = serde_json::from_value(json).unwrap();
161        assert_eq!(album_type, AlbumType::Single);
162
163        let json = json!("compilation");
164        let album_type: AlbumType = serde_json::from_value(json).unwrap();
165        assert_eq!(album_type, AlbumType::Compilation);
166    }
167
168    #[test]
169    fn album_simplified_deserializes() {
170        let json = json!({
171            "id": "album123",
172            "name": "Test Album",
173            "type": "album",
174            "uri": "spotify:album:album123",
175            "album_type": "album",
176            "total_tracks": 12,
177            "release_date": "2024-01-15"
178        });
179        let album: AlbumSimplified = serde_json::from_value(json).unwrap();
180        assert_eq!(album.id, "album123");
181        assert_eq!(album.name, "Test Album");
182        assert_eq!(album.total_tracks, Some(12));
183    }
184
185    #[test]
186    fn album_simplified_image_url() {
187        let json = json!({
188            "id": "album123",
189            "name": "Test Album",
190            "type": "album",
191            "uri": "spotify:album:album123",
192            "images": [{"url": "https://cover.jpg", "height": 640, "width": 640}]
193        });
194        let album: AlbumSimplified = serde_json::from_value(json).unwrap();
195        assert_eq!(album.image_url(), Some("https://cover.jpg"));
196    }
197
198    #[test]
199    fn album_simplified_image_url_none() {
200        let json = json!({
201            "id": "album123",
202            "name": "Test Album",
203            "type": "album",
204            "uri": "spotify:album:album123"
205        });
206        let album: AlbumSimplified = serde_json::from_value(json).unwrap();
207        assert!(album.image_url().is_none());
208    }
209
210    #[test]
211    fn album_simplified_artist_name() {
212        let json = json!({
213            "id": "album123",
214            "name": "Test Album",
215            "type": "album",
216            "uri": "spotify:album:album123",
217            "artists": [{"id": "artist1", "name": "Test Artist", "type": "artist", "uri": "spotify:artist:artist1"}]
218        });
219        let album: AlbumSimplified = serde_json::from_value(json).unwrap();
220        assert_eq!(album.artist_name(), Some("Test Artist"));
221    }
222
223    #[test]
224    fn album_simplified_artist_name_none() {
225        let json = json!({
226            "id": "album123",
227            "name": "Test Album",
228            "type": "album",
229            "uri": "spotify:album:album123"
230        });
231        let album: AlbumSimplified = serde_json::from_value(json).unwrap();
232        assert!(album.artist_name().is_none());
233    }
234
235    #[test]
236    fn album_full_deserializes() {
237        let json = json!({
238            "id": "album123",
239            "name": "Test Album",
240            "type": "album",
241            "uri": "spotify:album:album123",
242            "album_type": "album",
243            "total_tracks": 12,
244            "release_date": "2024-01-15",
245            "popularity": 75,
246            "label": "Test Label",
247            "genres": ["rock", "alternative"]
248        });
249        let album: Album = serde_json::from_value(json).unwrap();
250        assert_eq!(album.id, "album123");
251        assert_eq!(album.popularity, Some(75));
252        assert_eq!(album.label, Some("Test Label".to_string()));
253    }
254
255    #[test]
256    fn album_image_url() {
257        let json = json!({
258            "id": "album123",
259            "name": "Test Album",
260            "type": "album",
261            "uri": "spotify:album:album123",
262            "images": [{"url": "https://large.jpg", "height": 640, "width": 640}]
263        });
264        let album: Album = serde_json::from_value(json).unwrap();
265        assert_eq!(album.image_url(), Some("https://large.jpg"));
266    }
267
268    #[test]
269    fn album_artist_name() {
270        let json = json!({
271            "id": "album123",
272            "name": "Test Album",
273            "type": "album",
274            "uri": "spotify:album:album123",
275            "artists": [{"id": "artist1", "name": "Primary Artist", "type": "artist", "uri": "spotify:artist:artist1"}]
276        });
277        let album: Album = serde_json::from_value(json).unwrap();
278        assert_eq!(album.artist_name(), Some("Primary Artist"));
279    }
280
281    #[test]
282    fn album_release_year() {
283        let json = json!({
284            "id": "album123",
285            "name": "Test Album",
286            "type": "album",
287            "uri": "spotify:album:album123",
288            "release_date": "2024-01-15"
289        });
290        let album: Album = serde_json::from_value(json).unwrap();
291        assert_eq!(album.release_year(), Some("2024"));
292    }
293
294    #[test]
295    fn album_release_year_none() {
296        let json = json!({
297            "id": "album123",
298            "name": "Test Album",
299            "type": "album",
300            "uri": "spotify:album:album123"
301        });
302        let album: Album = serde_json::from_value(json).unwrap();
303        assert!(album.release_year().is_none());
304    }
305
306    #[test]
307    fn saved_album_deserializes() {
308        let json = json!({
309            "added_at": "2024-01-15T10:30:00Z",
310            "album": {
311                "id": "album123",
312                "name": "Test Album",
313                "type": "album",
314                "uri": "spotify:album:album123"
315            }
316        });
317        let saved: SavedAlbum = serde_json::from_value(json).unwrap();
318        assert_eq!(saved.added_at, "2024-01-15T10:30:00Z");
319        assert_eq!(saved.album.id, "album123");
320    }
321}