1use serde::{Deserialize, Serialize};
4
5use super::common::{ExternalUrls, Followers, Image};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct UserPublic {
10 pub display_name: Option<String>,
12 pub external_urls: Option<ExternalUrls>,
14 pub followers: Option<Followers>,
16 pub href: Option<String>,
18 pub id: String,
20 pub images: Option<Vec<Image>>,
22 #[serde(rename = "type")]
24 pub item_type: String,
25 pub uri: String,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct UserPrivate {
32 pub country: Option<String>,
34 pub display_name: Option<String>,
36 pub email: Option<String>,
38 pub explicit_content: Option<ExplicitContent>,
40 pub external_urls: Option<ExternalUrls>,
42 pub followers: Option<Followers>,
44 pub href: Option<String>,
46 pub id: String,
48 pub images: Option<Vec<Image>>,
50 pub product: Option<String>,
52 #[serde(rename = "type")]
54 pub item_type: String,
55 pub uri: String,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct ExplicitContent {
62 pub filter_enabled: Option<bool>,
64 pub filter_locked: Option<bool>,
66}
67
68impl UserPrivate {
69 pub fn is_premium(&self) -> bool {
71 self.product.as_deref() == Some("premium")
72 }
73
74 pub fn image_url(&self) -> Option<&str> {
76 self.images
77 .as_ref()
78 .and_then(|imgs| imgs.first())
79 .map(|img| img.url.as_str())
80 }
81}
82
83impl UserPublic {
84 pub fn image_url(&self) -> Option<&str> {
86 self.images
87 .as_ref()
88 .and_then(|imgs| imgs.first())
89 .map(|img| img.url.as_str())
90 }
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct TopTracksResponse {
96 pub href: Option<String>,
98 pub limit: Option<u32>,
100 pub next: Option<String>,
102 pub offset: Option<u32>,
104 pub previous: Option<String>,
106 pub total: Option<u32>,
108 pub items: Vec<super::track::Track>,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct TopArtistsResponse {
115 pub href: Option<String>,
117 pub limit: Option<u32>,
119 pub next: Option<String>,
121 pub offset: Option<u32>,
123 pub previous: Option<String>,
125 pub total: Option<u32>,
127 pub items: Vec<super::artist::Artist>,
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use serde_json::json;
135
136 #[test]
137 fn user_public_deserializes() {
138 let json = json!({
139 "id": "user123",
140 "type": "user",
141 "uri": "spotify:user:user123",
142 "display_name": "John Doe"
143 });
144 let user: UserPublic = serde_json::from_value(json).unwrap();
145 assert_eq!(user.id, "user123");
146 assert_eq!(user.display_name, Some("John Doe".to_string()));
147 }
148
149 #[test]
150 fn user_public_image_url() {
151 let json = json!({
152 "id": "user123",
153 "type": "user",
154 "uri": "spotify:user:user123",
155 "images": [{"url": "https://profile.jpg", "height": 300, "width": 300}]
156 });
157 let user: UserPublic = serde_json::from_value(json).unwrap();
158 assert_eq!(user.image_url(), Some("https://profile.jpg"));
159 }
160
161 #[test]
162 fn user_public_image_url_none_when_empty() {
163 let json = json!({
164 "id": "user123",
165 "type": "user",
166 "uri": "spotify:user:user123"
167 });
168 let user: UserPublic = serde_json::from_value(json).unwrap();
169 assert!(user.image_url().is_none());
170 }
171
172 #[test]
173 fn user_private_deserializes() {
174 let json = json!({
175 "id": "user123",
176 "type": "user",
177 "uri": "spotify:user:user123",
178 "email": "user@example.com",
179 "product": "premium",
180 "country": "US"
181 });
182 let user: UserPrivate = serde_json::from_value(json).unwrap();
183 assert_eq!(user.id, "user123");
184 assert_eq!(user.email, Some("user@example.com".to_string()));
185 assert!(user.is_premium());
186 }
187
188 #[test]
189 fn user_private_is_premium_false_for_free() {
190 let json = json!({
191 "id": "user123",
192 "type": "user",
193 "uri": "spotify:user:user123",
194 "product": "free"
195 });
196 let user: UserPrivate = serde_json::from_value(json).unwrap();
197 assert!(!user.is_premium());
198 }
199
200 #[test]
201 fn user_private_is_premium_false_when_none() {
202 let json = json!({
203 "id": "user123",
204 "type": "user",
205 "uri": "spotify:user:user123"
206 });
207 let user: UserPrivate = serde_json::from_value(json).unwrap();
208 assert!(!user.is_premium());
209 }
210
211 #[test]
212 fn explicit_content_deserializes() {
213 let json = json!({
214 "filter_enabled": true,
215 "filter_locked": false
216 });
217 let explicit: ExplicitContent = serde_json::from_value(json).unwrap();
218 assert_eq!(explicit.filter_enabled, Some(true));
219 assert_eq!(explicit.filter_locked, Some(false));
220 }
221
222 #[test]
223 fn top_tracks_response_deserializes() {
224 let json = json!({
225 "items": [],
226 "total": 50,
227 "limit": 20,
228 "offset": 0
229 });
230 let resp: TopTracksResponse = serde_json::from_value(json).unwrap();
231 assert!(resp.items.is_empty());
232 assert_eq!(resp.total, Some(50));
233 }
234
235 #[test]
236 fn top_artists_response_deserializes() {
237 let json = json!({
238 "items": [],
239 "total": 50,
240 "limit": 20,
241 "offset": 0
242 });
243 let resp: TopArtistsResponse = serde_json::from_value(json).unwrap();
244 assert!(resp.items.is_empty());
245 assert_eq!(resp.total, Some(50));
246 }
247
248 #[test]
249 fn user_private_image_url_returns_first() {
250 let json = json!({
251 "id": "user123",
252 "type": "user",
253 "uri": "spotify:user:user123",
254 "images": [
255 {"url": "https://first.jpg", "height": 640, "width": 640},
256 {"url": "https://second.jpg", "height": 300, "width": 300}
257 ]
258 });
259 let user: UserPrivate = serde_json::from_value(json).unwrap();
260 assert_eq!(user.image_url(), Some("https://first.jpg"));
261 }
262
263 #[test]
264 fn user_private_image_url_none_when_empty() {
265 let json = json!({
266 "id": "user123",
267 "type": "user",
268 "uri": "spotify:user:user123"
269 });
270 let user: UserPrivate = serde_json::from_value(json).unwrap();
271 assert!(user.image_url().is_none());
272 }
273}