lastfm_client/api/user/weekly/
charts.rs1use std::sync::Arc;
2
3use crate::api::constants::{
4 BASE_URL, METHOD_WEEKLY_ALBUM_CHART, METHOD_WEEKLY_ARTIST_CHART, METHOD_WEEKLY_CHART_LIST,
5 METHOD_WEEKLY_TRACK_CHART,
6};
7use crate::api::user_params;
8use crate::client::HttpClient;
9use crate::config::Config;
10use crate::error::Result;
11use crate::types::{
12 WeeklyAlbum, WeeklyAlbumChartResponse, WeeklyArtist, WeeklyArtistChartResponse,
13 WeeklyChartListResponse, WeeklyChartRange, WeeklyTrack, WeeklyTrackChartResponse,
14};
15use crate::url_builder::Url;
16
17#[derive(Debug)]
21pub struct WeeklyChartListRequestBuilder {
22 http: Arc<dyn HttpClient>,
23 config: Arc<Config>,
24 username: String,
25}
26
27impl WeeklyChartListRequestBuilder {
28 pub(crate) fn new(http: Arc<dyn HttpClient>, config: Arc<Config>, username: String) -> Self {
29 Self {
30 http,
31 config,
32 username,
33 }
34 }
35
36 pub async fn fetch(self) -> Result<Vec<WeeklyChartRange>> {
41 let params = user_params(
42 METHOD_WEEKLY_CHART_LIST,
43 &self.username,
44 self.config.api_key(),
45 );
46 let url = Url::new(BASE_URL).add_args(params).build();
47 let value = self.http.get(&url).await?;
48 let response: WeeklyChartListResponse = serde_json::from_value(value)?;
49
50 Ok(Vec::from(response))
51 }
52}
53
54#[derive(Debug)]
58pub struct WeeklyTrackChartRequestBuilder {
59 http: Arc<dyn HttpClient>,
60 config: Arc<Config>,
61 username: String,
62 from: Option<u32>,
63 to: Option<u32>,
64}
65
66impl WeeklyTrackChartRequestBuilder {
67 pub(crate) fn new(http: Arc<dyn HttpClient>, config: Arc<Config>, username: String) -> Self {
68 Self {
69 http,
70 config,
71 username,
72 from: None,
73 to: None,
74 }
75 }
76
77 #[must_use]
82 pub const fn from(mut self, timestamp: u32) -> Self {
83 self.from = Some(timestamp);
84 self
85 }
86
87 #[must_use]
91 pub const fn to(mut self, timestamp: u32) -> Self {
92 self.to = Some(timestamp);
93 self
94 }
95
96 #[must_use]
98 pub const fn range(self, range: &WeeklyChartRange) -> Self {
99 self.from(range.from).to(range.to)
100 }
101
102 pub async fn fetch(self) -> Result<Vec<WeeklyTrack>> {
107 let mut params = user_params(
108 METHOD_WEEKLY_TRACK_CHART,
109 &self.username,
110 self.config.api_key(),
111 );
112
113 if let Some(from) = self.from {
114 params.insert("from".to_string(), from.to_string());
115 }
116
117 if let Some(to) = self.to {
118 params.insert("to".to_string(), to.to_string());
119 }
120
121 let url = Url::new(BASE_URL).add_args(params).build();
122 let value = self.http.get(&url).await?;
123 let response: WeeklyTrackChartResponse = serde_json::from_value(value)?;
124
125 Ok(Vec::from(response))
126 }
127}
128
129#[derive(Debug)]
133pub struct WeeklyArtistChartRequestBuilder {
134 http: Arc<dyn HttpClient>,
135 config: Arc<Config>,
136 username: String,
137 from: Option<u32>,
138 to: Option<u32>,
139}
140
141impl WeeklyArtistChartRequestBuilder {
142 pub(crate) fn new(http: Arc<dyn HttpClient>, config: Arc<Config>, username: String) -> Self {
143 Self {
144 http,
145 config,
146 username,
147 from: None,
148 to: None,
149 }
150 }
151
152 #[must_use]
154 pub const fn from(mut self, timestamp: u32) -> Self {
155 self.from = Some(timestamp);
156 self
157 }
158
159 #[must_use]
161 pub const fn to(mut self, timestamp: u32) -> Self {
162 self.to = Some(timestamp);
163 self
164 }
165
166 #[must_use]
168 pub const fn range(self, range: &WeeklyChartRange) -> Self {
169 self.from(range.from).to(range.to)
170 }
171
172 pub async fn fetch(self) -> Result<Vec<WeeklyArtist>> {
177 let mut params = user_params(
178 METHOD_WEEKLY_ARTIST_CHART,
179 &self.username,
180 self.config.api_key(),
181 );
182
183 if let Some(from) = self.from {
184 params.insert("from".to_string(), from.to_string());
185 }
186
187 if let Some(to) = self.to {
188 params.insert("to".to_string(), to.to_string());
189 }
190
191 let url = Url::new(BASE_URL).add_args(params).build();
192 let value = self.http.get(&url).await?;
193 let response: WeeklyArtistChartResponse = serde_json::from_value(value)?;
194
195 Ok(Vec::from(response))
196 }
197}
198
199#[derive(Debug)]
203pub struct WeeklyAlbumChartRequestBuilder {
204 http: Arc<dyn HttpClient>,
205 config: Arc<Config>,
206 username: String,
207 from: Option<u32>,
208 to: Option<u32>,
209}
210
211impl WeeklyAlbumChartRequestBuilder {
212 pub(crate) fn new(http: Arc<dyn HttpClient>, config: Arc<Config>, username: String) -> Self {
213 Self {
214 http,
215 config,
216 username,
217 from: None,
218 to: None,
219 }
220 }
221
222 #[must_use]
224 pub const fn from(mut self, timestamp: u32) -> Self {
225 self.from = Some(timestamp);
226 self
227 }
228
229 #[must_use]
231 pub const fn to(mut self, timestamp: u32) -> Self {
232 self.to = Some(timestamp);
233 self
234 }
235
236 #[must_use]
238 pub const fn range(self, range: &WeeklyChartRange) -> Self {
239 self.from(range.from).to(range.to)
240 }
241
242 pub async fn fetch(self) -> Result<Vec<WeeklyAlbum>> {
247 let mut params = user_params(
248 METHOD_WEEKLY_ALBUM_CHART,
249 &self.username,
250 self.config.api_key(),
251 );
252
253 if let Some(from) = self.from {
254 params.insert("from".to_string(), from.to_string());
255 }
256
257 if let Some(to) = self.to {
258 params.insert("to".to_string(), to.to_string());
259 }
260
261 let url = Url::new(BASE_URL).add_args(params).build();
262 let value = self.http.get(&url).await?;
263 let response: WeeklyAlbumChartResponse = serde_json::from_value(value)?;
264
265 Ok(Vec::from(response))
266 }
267}
268
269#[cfg(test)]
270#[allow(clippy::unwrap_used)]
271mod tests {
272 use super::*;
273 use crate::client::MockClient;
274 use crate::config::ConfigBuilder;
275 use serde_json::json;
276 use std::sync::Arc;
277
278 fn http_config(
279 method: &str,
280 response: serde_json::Value,
281 ) -> (Arc<dyn HttpClient>, Arc<Config>) {
282 let config = Arc::new(ConfigBuilder::new().api_key("test_key").build().unwrap());
283 let mock = Arc::new(MockClient::new().with_response(method, response));
284 (mock, config)
285 }
286
287 #[tokio::test]
288 async fn test_fetch_chart_list() {
289 let (http, config) = http_config(
290 "user.getweeklychartlist",
291 json!({
292 "weeklychartlist": {
293 "@attr": { "user": "testuser" },
294 "chart": [
295 { "from": "1108296000", "to": "1108900800" },
296 { "from": "1108900800", "to": "1109505600" }
297 ]
298 }
299 }),
300 );
301
302 let ranges = WeeklyChartListRequestBuilder::new(http, config, "testuser".to_string())
303 .fetch()
304 .await
305 .unwrap();
306 assert_eq!(ranges.len(), 2);
307 assert_eq!(ranges[0].from, 1_108_296_000);
308 assert_eq!(ranges[0].to, 1_108_900_800);
309 }
310
311 #[tokio::test]
312 async fn test_fetch_weekly_track_chart() {
313 let (http, config) = http_config(
314 "user.getweeklytrackchart",
315 json!({
316 "weeklytrackchart": {
317 "@attr": { "user": "testuser", "from": "1108296000", "to": "1108900800" },
318 "track": [
319 {
320 "name": "Test Track",
321 "mbid": "",
322 "url": "https://www.last.fm/music/Artist/_/Test+Track",
323 "playcount": "5",
324 "artist": { "#text": "Test Artist", "mbid": "" },
325 "@attr": { "rank": "1" }
326 }
327 ]
328 }
329 }),
330 );
331
332 let tracks = WeeklyTrackChartRequestBuilder::new(http, config, "testuser".to_string())
333 .fetch()
334 .await
335 .unwrap();
336 assert_eq!(tracks.len(), 1);
337 assert_eq!(tracks[0].name, "Test Track");
338 assert_eq!(tracks[0].playcount, 5);
339 assert_eq!(tracks[0].artist_name, "Test Artist");
340 assert_eq!(tracks[0].rank, 1);
341 }
342
343 #[tokio::test]
344 async fn test_fetch_weekly_artist_chart() {
345 let (http, config) = http_config(
346 "user.getweeklyartistchart",
347 json!({
348 "weeklyartistchart": {
349 "@attr": { "user": "testuser", "from": "1108296000", "to": "1108900800" },
350 "artist": [
351 {
352 "name": "Test Artist",
353 "mbid": "",
354 "url": "https://www.last.fm/music/Test+Artist",
355 "playcount": "10",
356 "@attr": { "rank": "1" }
357 }
358 ]
359 }
360 }),
361 );
362
363 let artists = WeeklyArtistChartRequestBuilder::new(http, config, "testuser".to_string())
364 .fetch()
365 .await
366 .unwrap();
367 assert_eq!(artists.len(), 1);
368 assert_eq!(artists[0].name, "Test Artist");
369 assert_eq!(artists[0].playcount, 10);
370 assert_eq!(artists[0].rank, 1);
371 }
372
373 #[tokio::test]
374 async fn test_fetch_weekly_album_chart() {
375 let (http, config) = http_config(
376 "user.getweeklyalbumchart",
377 json!({
378 "weeklyalbumchart": {
379 "@attr": { "user": "testuser", "from": "1108296000", "to": "1108900800" },
380 "album": [
381 {
382 "name": "Test Album",
383 "mbid": "",
384 "url": "https://www.last.fm/music/Artist/Test+Album",
385 "playcount": "8",
386 "artist": { "#text": "Test Artist", "mbid": "" },
387 "@attr": { "rank": "1" }
388 }
389 ]
390 }
391 }),
392 );
393
394 let albums = WeeklyAlbumChartRequestBuilder::new(http, config, "testuser".to_string())
395 .fetch()
396 .await
397 .unwrap();
398 assert_eq!(albums.len(), 1);
399 assert_eq!(albums[0].name, "Test Album");
400 assert_eq!(albums[0].playcount, 8);
401 assert_eq!(albums[0].artist_name, "Test Artist");
402 assert_eq!(albums[0].rank, 1);
403 }
404
405 #[test]
406 fn test_range_builder() {
407 let range = WeeklyChartRange {
408 from: 1_108_296_000,
409 to: 1_108_900_800,
410 };
411 let config = Arc::new(ConfigBuilder::new().api_key("test_key").build().unwrap());
412 let mock = Arc::new(MockClient::new());
413 let builder =
414 WeeklyTrackChartRequestBuilder::new(mock, config, "testuser".to_string()).range(&range);
415 assert_eq!(builder.from, Some(1_108_296_000));
416 assert_eq!(builder.to, Some(1_108_900_800));
417 }
418}