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}