Skip to main content

spotify_rs/model/
search.rs

1use std::{fmt::Display, str::FromStr};
2
3use serde::Deserialize;
4
5use super::{
6    album::SimplifiedAlbum,
7    artist::Artist,
8    audiobook::SimplifiedAudiobook,
9    playlist::SimplifiedPlaylist,
10    show::{SimplifiedEpisode, SimplifiedShow},
11    track::Track,
12    Page,
13};
14
15/// Represents a search query builder.
16#[derive(Clone, Debug, Default)]
17pub struct SearchQuery {
18    query: String,
19    album: Option<String>,
20    artist: Option<String>,
21    track: Option<String>,
22    year: Option<String>,
23    irsc: Option<String>,
24    genre: Option<String>,
25    upc: Option<String>,
26    hipster: bool,
27    new: bool,
28}
29
30impl SearchQuery {
31    pub fn from_query(query: impl Into<String>) -> Self {
32        Self {
33            query: query.into(),
34            ..Default::default()
35        }
36    }
37
38    pub fn album(mut self, album: impl Into<String>) -> Self {
39        self.album = Some(album.into());
40        self
41    }
42
43    pub fn artist(mut self, artist: impl Into<String>) -> Self {
44        self.artist = Some(artist.into());
45        self
46    }
47
48    pub fn track(mut self, track: impl Into<String>) -> Self {
49        self.track = Some(track.into());
50        self
51    }
52
53    pub fn year(mut self, year: u32) -> Self {
54        self.year = Some(year.to_string());
55        self
56    }
57
58    pub fn years(mut self, start_year: u32, end_year: u32) -> Self {
59        self.year = Some(format!("{start_year}-{end_year}"));
60        self
61    }
62
63    pub fn irsc(mut self, irsc: impl Into<String>) -> Self {
64        self.irsc = Some(irsc.into());
65        self
66    }
67
68    pub fn genre(mut self, genre: impl Into<String>) -> Self {
69        self.genre = Some(genre.into());
70        self
71    }
72
73    pub fn upc(mut self, upc: impl Into<String>) -> Self {
74        self.upc = Some(upc.into());
75        self
76    }
77
78    pub fn hipster(mut self, hipster: bool) -> Self {
79        self.hipster = hipster;
80        self
81    }
82
83    pub fn new(mut self, new: bool) -> Self {
84        self.new = new;
85        self
86    }
87}
88
89impl<T> From<T> for SearchQuery
90where
91    T: Into<String>,
92{
93    fn from(value: T) -> Self {
94        Self::from_query(value)
95    }
96}
97
98// To format the query.
99impl Display for SearchQuery {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        write!(f, "{}", self.query)?;
102
103        if let Some(album) = &self.album {
104            write!(f, " album:{album}")?;
105        }
106
107        if let Some(artist) = &self.artist {
108            write!(f, " artist:{artist}")?;
109        }
110
111        if let Some(track) = &self.track {
112            write!(f, " track:{track}")?;
113        }
114
115        if let Some(year) = &self.year {
116            write!(f, " year:{year}")?;
117        }
118
119        if let Some(irsc) = &self.irsc {
120            write!(f, " irsc:{irsc}")?;
121        }
122
123        if let Some(genre) = &self.genre {
124            write!(f, " genre:{genre}")?;
125        }
126
127        if let Some(upc) = &self.upc {
128            write!(f, " upc:{upc}")?;
129        }
130
131        if self.hipster {
132            write!(f, " tag:hipster")?;
133        }
134
135        if self.new {
136            write!(f, " tag:new")?;
137        }
138
139        Ok(())
140    }
141}
142
143/// The results of a search.
144///
145/// Note: audiobooks are only available within the US, Canada, the UK, Ireland,
146/// New Zealand and Australia.
147#[derive(Clone, Debug, Deserialize, PartialEq)]
148pub struct SearchResults {
149    /// The track results.
150    pub tracks: Option<Page<Track>>,
151    /// The artist results.
152    pub artists: Option<Page<Artist>>,
153    /// The album results.
154    pub albums: Option<Page<SimplifiedAlbum>>,
155    /// The playlist results.
156    pub playlists: Option<Page<SimplifiedPlaylist>>,
157    /// The show results.
158    pub shows: Option<Page<SimplifiedShow>>,
159    /// The episode results.
160    pub episodes: Option<Page<SimplifiedEpisode>>,
161    /// The audiobook results.
162    pub audiobooks: Option<Page<SimplifiedAudiobook>>,
163}
164
165/// An item type to search for.
166///
167/// You can either use [all](Self::all()) to get a list of all types of items,
168/// or construct a list yourself to use in your search.
169#[derive(Clone, Debug)]
170pub enum Item {
171    /// Album type.
172    Album,
173    /// Artist type.
174    Artist,
175    /// Playlist type.
176    Playlist,
177    /// Track type.
178    Track,
179    /// Show type.
180    Show,
181    /// Episode type.
182    Episode,
183    /// Audiobook type.
184    Audiobook,
185}
186
187impl Item {
188    /// Returns a list of all the types of item to use in a search.
189    pub fn all() -> &'static [Self; 7] {
190        &[
191            Self::Album,
192            Self::Artist,
193            Self::Playlist,
194            Self::Track,
195            Self::Show,
196            Self::Episode,
197            Self::Audiobook,
198        ]
199    }
200}
201
202impl FromStr for Item {
203    type Err = crate::Error;
204
205    fn from_str(s: &str) -> Result<Self, Self::Err> {
206        match s.to_lowercase().as_ref() {
207            "album" => Ok(Self::Album),
208            "artist" => Ok(Self::Artist),
209            "playlist" => Ok(Self::Playlist),
210            "track" => Ok(Self::Track),
211            "show" => Ok(Self::Show),
212            "episode" => Ok(Self::Episode),
213            "audiobook" => Ok(Self::Audiobook),
214            _ => Err(crate::Error::Parse { description: format!("Failed to parse search item: {s} is not a valid item. Expected one of: *album, artist, playlist, track, show, episode, audiobook*.") }),
215        }
216    }
217}
218
219impl AsRef<str> for Item {
220    fn as_ref(&self) -> &str {
221        match self {
222            Item::Album => "album",
223            Item::Artist => "artist",
224            Item::Playlist => "playlist",
225            Item::Track => "track",
226            Item::Show => "show",
227            Item::Episode => "episode",
228            Item::Audiobook => "audiobook",
229        }
230    }
231}