spotify_cli/types/
artist.rs1use serde::{Deserialize, Serialize};
4
5use super::common::{ExternalUrls, Followers, Image};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ArtistSimplified {
10 pub external_urls: Option<ExternalUrls>,
12 pub href: Option<String>,
14 pub id: String,
16 pub name: String,
18 #[serde(rename = "type")]
20 pub item_type: String,
21 pub uri: String,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct Artist {
28 pub external_urls: Option<ExternalUrls>,
30 pub followers: Option<Followers>,
32 pub genres: Option<Vec<String>>,
34 pub href: Option<String>,
36 pub id: String,
38 pub images: Option<Vec<Image>>,
40 pub name: String,
42 pub popularity: Option<u32>,
44 #[serde(rename = "type")]
46 pub item_type: String,
47 pub uri: String,
49}
50
51impl Artist {
52 pub fn image_url(&self) -> Option<&str> {
54 self.images
55 .as_ref()
56 .and_then(|imgs| imgs.first())
57 .map(|img| img.url.as_str())
58 }
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct ArtistTopTracksResponse {
64 pub tracks: Vec<super::track::Track>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct RelatedArtistsResponse {
71 pub artists: Vec<Artist>,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct FollowedArtistsResponse {
78 pub artists: FollowedArtistsCursored,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct FollowedArtistsCursored {
85 pub href: Option<String>,
87 pub limit: Option<u32>,
89 pub next: Option<String>,
91 pub cursors: Option<super::common::Cursors>,
93 pub total: Option<u32>,
95 pub items: Vec<Artist>,
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use serde_json::json;
103
104 #[test]
105 fn artist_simplified_deserializes() {
106 let json = json!({
107 "id": "abc123",
108 "name": "Test Artist",
109 "type": "artist",
110 "uri": "spotify:artist:abc123"
111 });
112 let artist: ArtistSimplified = serde_json::from_value(json).unwrap();
113 assert_eq!(artist.id, "abc123");
114 assert_eq!(artist.name, "Test Artist");
115 }
116
117 #[test]
118 fn artist_full_deserializes() {
119 let json = json!({
120 "id": "abc123",
121 "name": "Test Artist",
122 "type": "artist",
123 "uri": "spotify:artist:abc123",
124 "genres": ["rock", "alternative"],
125 "popularity": 75,
126 "followers": {"total": 1000000}
127 });
128 let artist: Artist = serde_json::from_value(json).unwrap();
129 assert_eq!(artist.id, "abc123");
130 assert_eq!(artist.popularity, Some(75));
131 assert_eq!(artist.genres.as_ref().unwrap().len(), 2);
132 }
133
134 #[test]
135 fn artist_image_url_returns_first_image() {
136 let json = json!({
137 "id": "abc123",
138 "name": "Test Artist",
139 "type": "artist",
140 "uri": "spotify:artist:abc123",
141 "images": [
142 {"url": "https://large.jpg", "height": 640, "width": 640},
143 {"url": "https://medium.jpg", "height": 300, "width": 300}
144 ]
145 });
146 let artist: Artist = serde_json::from_value(json).unwrap();
147 assert_eq!(artist.image_url(), Some("https://large.jpg"));
148 }
149
150 #[test]
151 fn artist_image_url_returns_none_when_no_images() {
152 let json = json!({
153 "id": "abc123",
154 "name": "Test Artist",
155 "type": "artist",
156 "uri": "spotify:artist:abc123"
157 });
158 let artist: Artist = serde_json::from_value(json).unwrap();
159 assert!(artist.image_url().is_none());
160 }
161
162 #[test]
163 fn artist_top_tracks_response_deserializes() {
164 let json = json!({
165 "tracks": []
166 });
167 let resp: ArtistTopTracksResponse = serde_json::from_value(json).unwrap();
168 assert!(resp.tracks.is_empty());
169 }
170
171 #[test]
172 fn related_artists_response_deserializes() {
173 let json = json!({
174 "artists": []
175 });
176 let resp: RelatedArtistsResponse = serde_json::from_value(json).unwrap();
177 assert!(resp.artists.is_empty());
178 }
179
180 #[test]
181 fn followed_artists_response_deserializes() {
182 let json = json!({
183 "artists": {
184 "items": [],
185 "limit": 20,
186 "total": 0
187 }
188 });
189 let resp: FollowedArtistsResponse = serde_json::from_value(json).unwrap();
190 assert!(resp.artists.items.is_empty());
191 }
192}