Skip to main content

lastfm_client/api/user/top/
artists.rs

1use crate::api::builder_ext::{FetchAndSave, LimitBuilder};
2use crate::api::constants::METHOD_TOP_ARTISTS;
3use crate::api::fetch_utils::{Period, ProgressCallback, ResourceContainer, fetch};
4use crate::client::HttpClient;
5use crate::config::Config;
6use crate::error::Result;
7use crate::types::{TopArtist, TrackLimit, TrackList, UserTopArtists};
8use crate::url_builder::QueryParams;
9
10use serde::de::DeserializeOwned;
11use std::fmt;
12use std::sync::Arc;
13
14/// Builder for top artists requests
15pub struct TopArtistsRequestBuilder {
16    http: Arc<dyn HttpClient>,
17    config: Arc<Config>,
18    username: String,
19    limit: Option<u32>,
20    period: Option<Period>,
21    progress_callback: Option<ProgressCallback>,
22}
23
24impl fmt::Debug for TopArtistsRequestBuilder {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        f.debug_struct("TopArtistsRequestBuilder")
27            .field("username", &self.username)
28            .field("limit", &self.limit)
29            .field("period", &self.period)
30            .finish_non_exhaustive()
31    }
32}
33
34impl TopArtistsRequestBuilder {
35    pub(crate) fn new(http: Arc<dyn HttpClient>, config: Arc<Config>, username: String) -> Self {
36        Self {
37            http,
38            config,
39            username,
40            limit: None,
41            period: None,
42            progress_callback: None,
43        }
44    }
45
46    /// Register a progress callback invoked with `(fetched, total)` after each batch.
47    #[must_use]
48    pub fn on_progress(mut self, callback: impl Fn(u32, u32) + Send + Sync + 'static) -> Self {
49        self.progress_callback = Some(Arc::new(callback));
50        self
51    }
52
53    /// Display a terminal progress bar while fetching (requires `progress` feature).
54    #[cfg(feature = "progress")]
55    #[must_use]
56    pub fn with_progress(self) -> Self {
57        self.on_progress(crate::api::progress::make_progress_callback())
58    }
59
60    /// Set the time period for top artists
61    ///
62    /// # Arguments
63    /// * `period` - The time range to calculate top artists over. Use `Period::Overall` for all-time,
64    ///   `Period::Week` for last 7 days, `Period::Month` for last 30 days, etc.
65    ///   If not set, defaults to the Last.fm API's default behavior (typically overall).
66    #[must_use]
67    pub const fn period(mut self, period: Period) -> Self {
68        self.period = Some(period);
69        self
70    }
71
72    /// Fetch the artists
73    ///
74    /// # Errors
75    /// Returns an error if the HTTP request fails or the response cannot be parsed.
76    pub async fn fetch(self) -> Result<TrackList<TopArtist>> {
77        let mut params = QueryParams::new();
78
79        if let Some(period) = self.period {
80            params.insert("period".to_string(), period.as_api_str().to_string());
81        }
82
83        let limit = self
84            .limit
85            .map_or(TrackLimit::Unlimited, TrackLimit::Limited);
86
87        self.fetch_artists::<UserTopArtists>(limit, params)
88            .await
89            .map(TrackList::from)
90    }
91
92    async fn fetch_artists<T>(
93        &self,
94        limit: TrackLimit,
95        additional_params: QueryParams,
96    ) -> Result<Vec<TopArtist>>
97    where
98        T: DeserializeOwned + ResourceContainer<ItemType = TopArtist>,
99    {
100        fetch::<TopArtist, T>(
101            self.http.clone(),
102            self.config.clone(),
103            self.username.clone(),
104            METHOD_TOP_ARTISTS,
105            limit,
106            additional_params,
107            self.progress_callback.as_ref(),
108        )
109        .await
110    }
111}
112
113impl LimitBuilder for TopArtistsRequestBuilder {
114    fn limit_mut(&mut self) -> &mut Option<u32> {
115        &mut self.limit
116    }
117}
118
119impl FetchAndSave for TopArtistsRequestBuilder {
120    type Item = TopArtist;
121
122    fn resource_label() -> &'static str {
123        "top artists"
124    }
125
126    async fn do_fetch(self) -> crate::error::Result<Vec<Self::Item>> {
127        Ok(Vec::from(self.fetch().await?))
128    }
129}
130
131impl ResourceContainer for UserTopArtists {
132    type ItemType = TopArtist;
133
134    fn total(&self) -> u32 {
135        self.topartists.attr.total
136    }
137
138    fn items(self) -> Vec<Self::ItemType> {
139        self.topartists.artist
140    }
141}