spotify_rs/lib.rs
1// #![warn(missing_docs)]
2//! spotify-rs is a Rust wrapper for the Spotify API. It has full API coverage
3//! and supports all the authorisation flows (except for the implicit grant flow).
4//!
5//! # Getting Started
6//! First, you'll need to
7//! [create an app](https://developer.spotify.com/documentation/web-api/tutorials/getting-started#create-an-app)
8//! on Spotify's [developer dashboard](https://developer.spotify.com/dashboard).
9//!
10//! There you will need to set a redirect URL.
11//! You'll need to get the client ID, and possibly the client secret and redirect URL,
12//! depending on the authorisation flow you're going to use.
13//!
14//! There are two concepts: authenticating - that is, "logging the app in", using your
15//! client ID and secret - and authorisation - which means having a user grant your app
16//! access to their account.
17//!
18//! Depending on your chosen auth flow, there is either one step or two required to get you
19//! up and running.
20//!
21//! # Authorisation
22//! You will need to set your scopes, redirect the user to a URL returned by spotify-rs, which will
23//! redirect them *again* to your app's *redirect URL*, which will contain a code that allows
24//! your app to be authorised.
25//!
26//! spotify-rs supports 3 of the 4 OAuth2 authorisation flows the API makes available:
27//! the authorisation code flow, authorisation code with PKCE flow and the client credentials flow.
28//!
29//! The [implicit grant flow](https://developer.spotify.com/documentation/web-api/tutorials/implicit-flow)
30//! is not supported for 2 reasons:
31//! - it returns the access token in the URL, which is insecure and leaves your app vulnerable to all kinds of attacks;
32//! - it doesn't support refreshing the access token.
33//!
34//! The auth flow you should use depends on the use case:
35//! - the authorisation code flow is recommended for long-running applications
36//! where you can safely store the client secret (e.g. web and mobile apps)
37//! - the authorisation code with PKCE flow is recommended for long-running applications
38//! where you *can't* safely store the client secret (e.g. desktop apps and single page web apps)
39//! - the client credentials flow doesn't include authorisation, thus letting you only access public information
40//!
41//! Below is an example for each auth flow:
42//! ## Authorisation Code Flow
43//! ```no_run
44//! use spotify_rs::{AuthCodeClient, RedirectUrl};
45//!
46//! #[tokio::main]
47//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
48//! // Your application scopes
49//! let scopes = vec![
50//! "user-read-private",
51//! "playlist-modify-public",
52//! "playlist-modify-private",
53//! "playlist-read-private",
54//! "playlist-read-collaborative",
55//! ];
56//!
57//! // This should match the redirect URL you set in your app's settings
58//! // (on the Spotify API dashboard)
59//! let redirect_uri = RedirectUrl::new("your_redirect_url".to_owned())?;;
60//!
61//! // Whether or not to automatically refresh the token when it expires.
62//! let auto_refresh = true;
63//!
64//! // You will need to redirect the user to this URL.
65//! let (client, url) = AuthCodeClient::new(
66//! "client_id",
67//! "client_secret",
68//! scopes,
69//! redirect_uri,
70//! auto_refresh,
71//! );
72//!
73//! // After the user was redirected to `url`, they will be redirected *again*, to
74//! // your `redirect_uri`, with the "auth_code" and "csrf_state" parameters in the URL.
75//! // You will need to get those parameters from the URL.
76//!
77//! // Finally, you will be able to authenticate the client.
78//! let spotify = client.authenticate("auth_code", "csrf_state").await?;
79//!
80//! // Get an album with the specified ID.
81//! let album = spotify_rs::album("album_id").get(&spotify).await?;
82//! println!("The name of the album is: {}", album.name);
83//!
84//! // The `album` method returns a builder with optional parameters you can set
85//! // For example, this sets the market to "GB".
86//! let album_gb = spotify_rs::album("album_id")
87//! .market("GB")
88//! .get(&spotify)
89//! .await?;
90//! println!("The popularity of the album is {}", album_gb.popularity);
91//!
92//! // This gets 5 playlists of the user that authorised the app
93//! // (it requires the playlist-read-private scope).
94//! let user_playlists = spotify_rs::current_user_playlists()
95//! .limit(5)
96//! .get(&spotify)
97//! .await?;
98//! let result_count = user_playlists.items.len();
99//! println!("The API returned {} playlists.", result_count);
100//!
101//! Ok(())
102//! }
103//! ```
104//! The Authorisation Code Flow with PKCE is the same, except you would need to use
105//! [`AuthCodePkceClient`] instead of [`AuthCodeClient`].
106//!
107//! A of available scopes can be found [here](https://developer.spotify.com/documentation/web-api/concepts/scopes).
108//!
109//! The auth code and CSRF token can be obtained by parsing the URL the user was
110//! redirected to (the redirect URL, as set in the API dashboard and when creating the client).
111//!
112//! Please note that the redirect URL you pass to [`authenticate`] *must* match
113//! the redirect URL you set in the Spotify API developer dashboard.
114//!
115//! That could be achieved by simply having the user copy and paste the URL into
116//! your app, or, for example, by having a server listening at your `redirect_url`
117//! and sending the auth code and CSRF token to the main app when the user is
118//! redirected to said URL.
119//!
120//! For examples, check out the examples directory.
121//!
122//! ## Client Credentials Flow
123//! ```no_run
124//! use spotify_rs::{ClientCredsClient, RedirectUrl};
125//!
126//! #[tokio::main]
127//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
128//! let spotify = ClientCredsClient::authenticate("client_id", "client_secret").await?;
129//!
130//! // Get an album with the specified ID.
131//! let album = spotify_rs::album("album_id").get(&spotify).await?;
132//! println!("The name of the album is: {}", album.name);
133//!
134//! // The `album` method returns a builder with optional parameters you can set
135//! // For example, this sets the market to "GB".
136//! let album_gb = spotify_rs::album("album_id")
137//! .market("GB")
138//! .get(&spotify)
139//! .await?;
140//! println!("The popularity of the album is {}", album_gb.popularity);
141//!
142//! Ok(())
143//! }
144//! ```
145//! This flow doesn't require anything besides the client credentials,
146//! but you cannot access any user information.
147//!
148//! # Automatic Token Refreshing
149//! If `auto_refresh` is set to `true` when creating the client, on every request
150//! the client will check if the token is about to expire. If the token is close
151//! to expiring, it will refresh the token for you.
152//!
153//! *Note: this means that if the token has expired, the `RwLock` holding the [`Token`]*
154//! *will be acquired in order to change the token.*
155//!
156//! If you disable this feature, you'll have to refresh the token yourself using [`request_refresh_token`].
157//!
158//! [`Token`]: auth::Token
159//! [`AuthCodeFlow`]: auth::AuthCodeFlow
160//! [`AuthCodePkceFlow`]: auth::AuthCodePkceFlow
161//! [`request_refresh_token`]: client::Client::request_refresh_token()
162//! [`authenticate`]: client::Client::authenticate()
163
164mod auth;
165/// Struct and methods for constructing and authenticating [`Clients`](crate::client::Client).
166pub mod client;
167/// Functions and builders for all the Spotify endpoints.
168pub mod endpoint;
169mod error;
170/// Mappings of objects received from the Spotify API.
171pub mod model;
172
173use client::Body;
174use serde::{Deserialize, Deserializer};
175
176pub use auth::{
177 AuthCodeFlow, AuthCodePkceFlow, AuthFlow, ClientCredsFlow, Token, Unauthenticated, UnknownFlow,
178};
179pub use client::{AuthCodeClient, AuthCodePkceClient, ClientCredsClient};
180pub use error::{Error, Result as SpotifyResult};
181pub use oauth2::RedirectUrl;
182
183#[doc(hidden)]
184pub use endpoint::{
185 album::{
186 album, album_tracks, albums, check_saved_albums, new_releases, remove_saved_albums,
187 save_albums, saved_albums,
188 },
189 artist::{artist_albums, artist_top_tracks, get_artist, get_artists, get_related_artists},
190 audiobook::{
191 audiobook, audiobook_chapters, audiobooks, chapter, chapters, check_saved_audiobooks,
192 remove_saved_audiobooks, save_audiobooks, saved_audiobooks,
193 },
194 category::{browse_categories, browse_category},
195 genres::get_genre_seeds,
196 markets::get_available_markets,
197 player::{
198 add_item_to_queue, get_available_devices, get_currently_playing_track, get_playback_state,
199 get_user_queue, pause_playback, recently_played_tracks, seek_to_position,
200 set_playback_volume, set_repeat_mode, skip_to_next, skip_to_previous, start_playback,
201 toggle_playback_shuffle, transfer_playback,
202 },
203 playlist::{
204 add_items_to_playlist, add_playlist_image, category_playlists, change_playlist_details,
205 create_playlist, current_user_playlists, featured_playlists, get_playlist_image, playlist,
206 playlist_items, remove_playlist_items, update_playlist_items, user_playlists,
207 },
208 search::search,
209 show::{
210 check_saved_episodes, check_saved_shows, episode, episodes, remove_saved_episodes,
211 remove_saved_shows, save_episodes, save_shows, saved_episodes, saved_shows, show,
212 show_episodes, shows,
213 },
214 track::{
215 check_saved_tracks, get_track_audio_analysis, get_track_audio_features,
216 get_tracks_audio_features, recommendations, remove_saved_tracks, save_tracks, saved_tracks,
217 track, tracks,
218 },
219 user::{
220 check_if_current_user_follow_playlist, check_if_user_follows_artists,
221 check_if_user_follows_users, current_user_top_artists, current_user_top_tracks,
222 follow_artists, follow_playlist, follow_users, followed_artists, get_current_user_profile,
223 get_user, unfollow_artists, unfollow_playlist, unfollow_users,
224 },
225};
226
227// Function meant to create a URL query list from &[T].
228pub(crate) fn query_list<T: AsRef<str>>(list: &[T]) -> String {
229 list.iter()
230 .map(|i| i.as_ref())
231 .collect::<Vec<&str>>()
232 .join(",")
233}
234
235// Function meant to create a request body list from &[T].
236pub(crate) fn body_list<T: AsRef<str>>(name: &str, list: &[T]) -> Body<serde_json::Value> {
237 let list: Vec<&str> = list.iter().map(|i| i.as_ref()).collect();
238 Body::Json(serde_json::json!({ name: list }))
239}
240
241/// Represents an empty API response.
242pub struct Nil;
243
244// Used to deserialize an empty API response.
245// This is also necessary for when Spotify API endpoints that should
246// normally return empty responses, instead return (useless) non-JSON responses.
247impl<'de> Deserialize<'de> for Nil {
248 fn deserialize<D>(_: D) -> Result<Self, D::Error>
249 where
250 D: Deserializer<'de>,
251 {
252 Ok(Nil)
253 }
254}