spotify_cli/cli/commands/
resource.rs

1use crate::endpoints::albums::get_album;
2use crate::endpoints::artists::{
3    get_artist, get_artist_top_tracks, get_artists_albums, get_artists_related_artists,
4};
5use crate::endpoints::tracks::get_track;
6use crate::io::output::{ErrorKind, Response};
7use crate::types::{Album, Artist, ArtistTopTracksResponse, RelatedArtistsResponse, Track};
8
9use super::{extract_id, now_playing, with_client};
10
11/// What information to retrieve for an artist
12#[derive(Debug, Clone, Copy, Default)]
13pub enum ArtistView {
14    /// Basic artist details (default)
15    #[default]
16    Details,
17    /// Artist's top tracks
18    TopTracks,
19    /// Artist's albums
20    Albums,
21    /// Related artists
22    Related,
23}
24
25/// Query parameters for artist info
26#[derive(Debug, Clone)]
27pub struct ArtistQuery {
28    /// Artist ID or URL (None = now playing artist)
29    pub id: Option<String>,
30    /// Only output the ID
31    pub id_only: bool,
32    /// What view/information to retrieve
33    pub view: ArtistView,
34    /// Market for top tracks (ISO country code)
35    pub market: String,
36    /// Number of results for albums
37    pub limit: u8,
38    /// Offset for album pagination
39    pub offset: u32,
40}
41
42impl ArtistQuery {
43    /// Create a new query for artist details
44    pub fn new() -> Self {
45        Self {
46            id: None,
47            id_only: false,
48            view: ArtistView::Details,
49            market: "US".to_string(),
50            limit: 20,
51            offset: 0,
52        }
53    }
54
55    /// Set the artist ID
56    pub fn with_id(mut self, id: Option<String>) -> Self {
57        self.id = id;
58        self
59    }
60
61    /// Set id_only flag
62    pub fn id_only(mut self, id_only: bool) -> Self {
63        self.id_only = id_only;
64        self
65    }
66
67    /// Set the view type
68    pub fn view(mut self, view: ArtistView) -> Self {
69        self.view = view;
70        self
71    }
72
73    /// Set the market
74    pub fn market(mut self, market: String) -> Self {
75        self.market = market;
76        self
77    }
78
79    /// Set pagination
80    pub fn paginate(mut self, limit: u8, offset: u32) -> Self {
81        self.limit = limit;
82        self.offset = offset;
83        self
84    }
85}
86
87impl Default for ArtistQuery {
88    fn default() -> Self {
89        Self::new()
90    }
91}
92
93/// Get track info - defaults to now playing if no ID provided
94pub async fn info_track(id: Option<&str>, id_only: bool) -> Response {
95    with_client(|client| async move {
96        let track_id = match id {
97            Some(id) => extract_id(id),
98            None => match now_playing::get_track_id(&client).await {
99                Ok(id) => id,
100                Err(e) => return e,
101            },
102        };
103
104        if id_only {
105            return Response::success(200, &track_id);
106        }
107
108        match get_track::get_track(&client, &track_id).await {
109            Ok(Some(payload)) => {
110                // Validate response structure and extract info for message
111                match serde_json::from_value::<Track>(payload.clone()) {
112                    Ok(track) => {
113                        let msg = format!("{} - {}", track.name, track.artist_names());
114                        Response::success_with_payload(200, msg, payload)
115                    }
116                    Err(_) => Response::success_with_payload(200, "Track details", payload),
117                }
118            }
119            Ok(None) => Response::err(404, "Track not found", ErrorKind::NotFound),
120            Err(e) => Response::from_http_error(&e, "Failed to get track"),
121        }
122    })
123    .await
124}
125
126/// Get album info - defaults to now playing album if no ID provided
127pub async fn info_album(id: Option<&str>, id_only: bool) -> Response {
128    with_client(|client| async move {
129        let album_id = match id {
130            Some(id) => extract_id(id),
131            None => match now_playing::get_album_id(&client).await {
132                Ok(id) => id,
133                Err(e) => return e,
134            },
135        };
136
137        if id_only {
138            return Response::success(200, &album_id);
139        }
140
141        match get_album::get_album(&client, &album_id).await {
142            Ok(Some(payload)) => {
143                // Validate response structure and extract info for message
144                match serde_json::from_value::<Album>(payload.clone()) {
145                    Ok(album) => {
146                        let artist = album.artist_name().unwrap_or("Unknown Artist");
147                        let msg = format!("{} - {}", album.name, artist);
148                        Response::success_with_payload(200, msg, payload)
149                    }
150                    Err(_) => Response::success_with_payload(200, "Album details", payload),
151                }
152            }
153            Ok(None) => Response::err(404, "Album not found", ErrorKind::NotFound),
154            Err(e) => Response::from_http_error(&e, "Failed to get album"),
155        }
156    })
157    .await
158}
159
160/// Get artist info - defaults to now playing artist if no ID provided
161pub async fn info_artist(query: ArtistQuery) -> Response {
162    let ArtistQuery {
163        id,
164        id_only,
165        view,
166        market,
167        limit,
168        offset,
169    } = query;
170
171    with_client(|client| async move {
172        let artist_id = match id.as_deref() {
173            Some(id) => extract_id(id),
174            None => match now_playing::get_artist_id(&client).await {
175                Ok(id) => id,
176                Err(e) => return e,
177            },
178        };
179
180        if id_only {
181            return Response::success(200, &artist_id);
182        }
183
184        match view {
185            ArtistView::TopTracks => {
186                match get_artist_top_tracks::get_artist_top_tracks(
187                    &client,
188                    &artist_id,
189                    Some(&market),
190                )
191                .await
192                {
193                    Ok(Some(payload)) => {
194                        match serde_json::from_value::<ArtistTopTracksResponse>(payload.clone()) {
195                            Ok(resp) => {
196                                let count = resp.tracks.len();
197                                Response::success_with_payload(
198                                    200,
199                                    format!("Top {} tracks", count),
200                                    payload,
201                                )
202                            }
203                            Err(_) => Response::success_with_payload(200, "Top tracks", payload),
204                        }
205                    }
206                    Ok(None) => Response::success_with_payload(
207                        200,
208                        "No top tracks",
209                        serde_json::json!({ "tracks": [] }),
210                    ),
211                    Err(e) => Response::from_http_error(&e, "Failed to get top tracks"),
212                }
213            }
214            ArtistView::Albums => {
215                match get_artists_albums::get_artists_albums(
216                    &client,
217                    &artist_id,
218                    Some(limit),
219                    Some(offset),
220                )
221                .await
222                {
223                    Ok(Some(payload)) => {
224                        Response::success_with_payload(200, "Artist albums", payload)
225                    }
226                    Ok(None) => Response::success_with_payload(
227                        200,
228                        "No albums",
229                        serde_json::json!({ "items": [] }),
230                    ),
231                    Err(e) => Response::from_http_error(&e, "Failed to get artist albums"),
232                }
233            }
234            ArtistView::Related => {
235                match get_artists_related_artists::get_artists_related_artists(&client, &artist_id)
236                    .await
237                {
238                    Ok(Some(payload)) => {
239                        match serde_json::from_value::<RelatedArtistsResponse>(payload.clone()) {
240                            Ok(resp) => {
241                                let count = resp.artists.len();
242                                Response::success_with_payload(
243                                    200,
244                                    format!("{} related artists", count),
245                                    payload,
246                                )
247                            }
248                            Err(_) => {
249                                Response::success_with_payload(200, "Related artists", payload)
250                            }
251                        }
252                    }
253                    Ok(None) => Response::success_with_payload(
254                        200,
255                        "No related artists",
256                        serde_json::json!({ "artists": [] }),
257                    ),
258                    Err(e) => Response::from_http_error(&e, "Failed to get related artists"),
259                }
260            }
261            ArtistView::Details => match get_artist::get_artist(&client, &artist_id).await {
262                Ok(Some(payload)) => match serde_json::from_value::<Artist>(payload.clone()) {
263                    Ok(artist) => Response::success_with_payload(200, artist.name.clone(), payload),
264                    Err(_) => Response::success_with_payload(200, "Artist details", payload),
265                },
266                Ok(None) => Response::err(404, "Artist not found", ErrorKind::NotFound),
267                Err(e) => Response::from_http_error(&e, "Failed to get artist"),
268            },
269        }
270    })
271    .await
272}