Skip to main content

lastfm_client/types/
artists.rs

1use std::cmp::Ordering;
2use std::fmt;
3
4use crate::types::{BaseResponse, RankAttr, TrackImage};
5use serde::{Deserialize, Serialize};
6
7use crate::types::utils::{bool_from_str, u32_from_str};
8
9/// An artist from a user's top artists, ranked by play count
10///
11/// Retrieved from the `user.gettopartists` API endpoint
12#[derive(Serialize, Deserialize, Debug, Clone)]
13#[non_exhaustive]
14pub struct TopArtist {
15    /// Artist name
16    pub name: String,
17    /// `MusicBrainz` artist identifier (may be empty string)
18    pub mbid: String,
19    /// Last.fm URL for this artist
20    pub url: String,
21    /// Total number of times this artist has been played
22    #[serde(deserialize_with = "u32_from_str")]
23    pub playcount: u32,
24    /// Whether the artist is streamable on Last.fm
25    #[serde(deserialize_with = "bool_from_str")]
26    pub streamable: bool,
27    /// Artist images in various sizes
28    pub image: Vec<TrackImage>,
29    /// Rank attributes (position in top artists)
30    #[serde(rename = "@attr")]
31    pub attr: RankAttr,
32}
33
34impl fmt::Display for TopArtist {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        write!(
37            f,
38            "#{} - {} ({} plays)",
39            self.attr.rank, self.name, self.playcount
40        )
41    }
42}
43
44impl PartialEq for TopArtist {
45    fn eq(&self, other: &Self) -> bool {
46        self.playcount == other.playcount
47    }
48}
49
50impl Eq for TopArtist {}
51
52impl PartialOrd for TopArtist {
53    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
54        Some(self.cmp(other))
55    }
56}
57
58impl Ord for TopArtist {
59    fn cmp(&self, other: &Self) -> Ordering {
60        self.playcount.cmp(&other.playcount)
61    }
62}
63
64// SQLITE EXPORT ==============================================================
65
66#[cfg(feature = "sqlite")]
67impl crate::sqlite::SqliteExportable for TopArtist {
68    fn table_name() -> &'static str {
69        "top_artists"
70    }
71
72    fn create_table_sql() -> &'static str {
73        "CREATE TABLE IF NOT EXISTS top_artists (
74            id        INTEGER PRIMARY KEY AUTOINCREMENT,
75            name      TEXT    NOT NULL,
76            mbid      TEXT    NOT NULL,
77            url       TEXT    NOT NULL,
78            playcount INTEGER NOT NULL,
79            rank      INTEGER NOT NULL
80        )"
81    }
82
83    fn insert_sql() -> &'static str {
84        "INSERT INTO top_artists (name, mbid, url, playcount, rank)
85         VALUES (?1, ?2, ?3, ?4, ?5)"
86    }
87
88    fn bind_and_execute(&self, stmt: &mut rusqlite::Statement<'_>) -> rusqlite::Result<usize> {
89        let rank: u32 = self.attr.rank.parse().unwrap_or_default();
90        stmt.execute(rusqlite::params![
91            self.name,
92            self.mbid,
93            self.url,
94            self.playcount,
95            rank,
96        ])
97    }
98}
99
100/// Top artists response wrapper
101#[derive(Serialize, Deserialize, Debug, Clone)]
102#[non_exhaustive]
103pub struct TopArtists {
104    /// List of top artists
105    pub artist: Vec<TopArtist>,
106    /// Response metadata
107    #[serde(rename = "@attr")]
108    pub attr: BaseResponse,
109}
110
111/// Top-level top artists API response
112#[derive(Serialize, Deserialize, Debug, Clone)]
113#[non_exhaustive]
114pub struct UserTopArtists {
115    /// Top artists data
116    pub topartists: TopArtists,
117}