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// SQLITE LOAD ================================================================
101
102#[cfg(feature = "sqlite")]
103impl crate::sqlite::SqliteLoadable for TopArtist {
104    fn select_sql() -> &'static str {
105        // Columns: name(0) mbid(1) url(2) playcount(3) rank(4)
106        "SELECT name, mbid, url, playcount, rank
107         FROM top_artists
108         ORDER BY rank ASC"
109    }
110
111    fn from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<Self> {
112        Ok(Self {
113            name: row.get(0)?,
114            mbid: row.get(1)?,
115            url: row.get(2)?,
116            playcount: row.get(3)?,
117            attr: crate::types::RankAttr {
118                rank: row.get::<_, u32>(4)?.to_string(),
119            },
120            // Fields not stored in the schema — reconstructed with defaults.
121            streamable: false,
122            image: vec![],
123        })
124    }
125}
126
127/// Top artists response wrapper
128#[derive(Serialize, Deserialize, Debug, Clone)]
129#[non_exhaustive]
130pub struct TopArtists {
131    /// List of top artists
132    pub artist: Vec<TopArtist>,
133    /// Response metadata
134    #[serde(rename = "@attr")]
135    pub attr: BaseResponse,
136}
137
138/// Top-level top artists API response
139#[derive(Serialize, Deserialize, Debug, Clone)]
140#[non_exhaustive]
141pub struct UserTopArtists {
142    /// Top artists data
143    pub topartists: TopArtists,
144}