From 90d673f14d37f88cb22125ffda51fdd5128bec2a Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 19 Jun 2021 12:21:52 +0200 Subject: [PATCH 1/8] update tests --- tests/test_oauth2.rs | 138 +++++++++++++++------------------- tests/test_with_credential.rs | 21 ++---- tests/test_with_oauth.rs | 38 ++++------ 3 files changed, 83 insertions(+), 114 deletions(-) diff --git a/tests/test_oauth2.rs b/tests/test_oauth2.rs index ba1dbaed..bcaeedbf 100644 --- a/tests/test_oauth2.rs +++ b/tests/test_oauth2.rs @@ -3,9 +3,9 @@ mod common; use chrono::prelude::*; use chrono::Duration; use maybe_async::maybe_async; -use rspotify::client::SpotifyBuilder; -use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder, Token, TokenBuilder}; -use rspotify::scopes; +use rspotify::{ + prelude::*, scopes, AuthCodeSpotify, ClientCredsSpotify, Config, Credentials, OAuth, Token, +}; use std::{collections::HashMap, fs, io::Read, path::PathBuf, thread::sleep}; use url::Url; @@ -13,24 +13,15 @@ use common::maybe_async_test; #[test] fn test_get_authorize_url() { - let oauth = OAuthBuilder::default() - .state("fdsafdsfa") - .redirect_uri("localhost") - .scope(scopes!("playlist-read-private")) - .build() - .unwrap(); - - let creds = CredentialsBuilder::default() - .id("this-is-my-client-id") - .secret("this-is-my-client-secret") - .build() - .unwrap(); - - let spotify = SpotifyBuilder::default() - .credentials(creds) - .oauth(oauth) - .build() - .unwrap(); + let oauth = OAuth { + state: "fdsafdsfa".to_owned(), + redirect_uri: "localhost".to_owned(), + scope: scopes!("playlist-read-private"), + ..Default::default() + }; + let creds = Credentials::new("this-is-my-client-id", "this-is-my-client-secret"); + + let spotify = AuthCodeSpotify::new(creds, oauth); let authorize_url = spotify.get_authorize_url(false).unwrap(); let hash_query: HashMap<_, _> = Url::parse(&authorize_url) @@ -49,41 +40,32 @@ fn test_get_authorize_url() { #[maybe_async] #[maybe_async_test] async fn test_read_token_cache() { - let now: DateTime = Utc::now(); + let now = Utc::now(); let scope = scopes!("playlist-read-private", "playlist-read-collaborative"); - let tok = TokenBuilder::default() - .access_token("test-access_token") - .expires_in(Duration::seconds(3600)) - .expires_at(now) - .scope(scope.clone()) - .refresh_token("...") - .build() - .unwrap(); - - let predefined_spotify = SpotifyBuilder::default() - .token(tok.clone()) - .cache_path(PathBuf::from(".test_read_token_cache.json")) - .build() - .unwrap(); + let tok = Token { + access_token: "test-access_token".to_owned(), + expires_in: Duration::seconds(3600), + expires_at: Some(now), + scope: scope.clone(), + refresh_token: Some("...".to_owned()), + }; + + let config = Config { + token_cached: true, + cache_path: PathBuf::from(".test_read_token_cache.json"), + ..Default::default() + }; + let mut predefined_spotify = ClientCredsSpotify::from_token(tok.clone()); + predefined_spotify.config = config.clone(); // write token data to cache_path predefined_spotify.write_token_cache().unwrap(); - assert!(predefined_spotify.cache_path.exists()); - - let oauth_scope = scopes!("playlist-read-private"); - let oauth = OAuthBuilder::default() - .state("fdasfasfdasd") - .redirect_uri("http://localhost:8000") - .scope(oauth_scope) - .build() - .unwrap(); - - let mut spotify = SpotifyBuilder::default() - .oauth(oauth) - .cache_path(PathBuf::from(".test_read_token_cache.json")) - .build() - .unwrap(); + assert!(predefined_spotify.config.cache_path.exists()); + + let mut spotify = ClientCredsSpotify::default(); + spotify.config = config; + // read token from cache file let tok_from_file = spotify.read_token_cache().await.unwrap(); assert_eq!(tok_from_file.scope, scope); @@ -92,33 +74,34 @@ async fn test_read_token_cache() { assert_eq!(tok_from_file.expires_at.unwrap(), now); // delete cache file in the end - fs::remove_file(&spotify.cache_path).unwrap(); + fs::remove_file(&spotify.config.cache_path).unwrap(); } #[test] fn test_write_token() { - let now: DateTime = Utc::now(); + let now = Utc::now(); let scope = scopes!("playlist-read-private", "playlist-read-collaborative"); - let tok = TokenBuilder::default() - .access_token("test-access_token") - .expires_in(Duration::seconds(3600)) - .expires_at(now) - .scope(scope.clone()) - .refresh_token("...") - .build() - .unwrap(); - - let spotify = SpotifyBuilder::default() - .token(tok.clone()) - .cache_path(PathBuf::from(".test_write_token_cache.json")) - .build() - .unwrap(); + let tok = Token { + access_token: "test-access_token".to_owned(), + expires_in: Duration::seconds(3600), + expires_at: Some(now), + scope: scope.clone(), + refresh_token: Some("...".to_owned()), + }; + + let config = Config { + token_cached: true, + cache_path: PathBuf::from(".test_write_token_cache.json"), + ..Default::default() + }; + let mut spotify = ClientCredsSpotify::from_token(tok.clone()); + spotify.config = config; let tok_str = serde_json::to_string(&tok).unwrap(); spotify.write_token_cache().unwrap(); - let mut file = fs::File::open(&spotify.cache_path).unwrap(); + let mut file = fs::File::open(&spotify.config.cache_path).unwrap(); let mut tok_str_file = String::new(); file.read_to_string(&mut tok_str_file).unwrap(); @@ -129,21 +112,20 @@ fn test_write_token() { assert_eq!(tok_from_file.expires_at.unwrap(), now); // delete cache file in the end - fs::remove_file(&spotify.cache_path).unwrap(); + fs::remove_file(&spotify.config.cache_path).unwrap(); } #[test] fn test_token_is_expired() { let scope = scopes!("playlist-read-private", "playlist-read-collaborative"); - let tok = TokenBuilder::default() - .access_token("test-access_token") - .expires_in(Duration::seconds(1)) - .expires_at(Utc::now()) - .scope(scope) - .refresh_token("...") - .build() - .unwrap(); + let tok = Token { + scope, + access_token: "test-access_token".to_owned(), + expires_in: Duration::seconds(1), + expires_at: Some(Utc::now()), + refresh_token: Some("...".to_owned()), + }; assert!(!tok.is_expired()); sleep(std::time::Duration::from_secs(2)); assert!(tok.is_expired()); @@ -151,7 +133,7 @@ fn test_token_is_expired() { #[test] fn test_parse_response_code() { - let spotify = SpotifyBuilder::default().build().unwrap(); + let spotify = AuthCodeSpotify::default(); let url = "http://localhost:8888/callback"; let code = spotify.parse_response_code(url); diff --git a/tests/test_with_credential.rs b/tests/test_with_credential.rs index 81ec48af..dfb3afa1 100644 --- a/tests/test_with_credential.rs +++ b/tests/test_with_credential.rs @@ -1,19 +1,19 @@ mod common; use common::maybe_async_test; -use rspotify::oauth2::CredentialsBuilder; use rspotify::{ - client::{Spotify, SpotifyBuilder}, model::{AlbumType, Country, Id, Market}, + prelude::*, + ClientCredsSpotify, Credentials, }; use maybe_async::maybe_async; /// Generating a new basic client for the requests. #[maybe_async] -pub async fn creds_client() -> Spotify { +pub async fn creds_client() -> ClientCredsSpotify { // The credentials must be available in the environment. - let creds = CredentialsBuilder::from_env().build().unwrap_or_else(|_| { + let creds = Credentials::from_env().unwrap_or_else(|| { panic!( "No credentials configured. Make sure that either the `env-file` \ feature is enabled, or that the required environment variables are \ @@ -21,13 +21,8 @@ pub async fn creds_client() -> Spotify { ) }); - let mut spotify = SpotifyBuilder::default() - .credentials(creds) - .build() - .unwrap(); - - spotify.request_client_token().await.unwrap(); - + let mut spotify = ClientCredsSpotify::new(creds); + spotify.request_token().await.unwrap(); spotify } @@ -208,7 +203,7 @@ mod test_pagination { #[test] fn test_pagination_sync() { let mut client = creds_client(); - client.pagination_chunks = 2; + client.config.pagination_chunks = 2; let album = Id::from_uri(ALBUM).unwrap(); let names = client @@ -226,7 +221,7 @@ mod test_pagination { use futures_util::StreamExt; let mut client = creds_client().await; - client.pagination_chunks = 2; + client.config.pagination_chunks = 2; let album = Id::from_uri(ALBUM).unwrap(); let names = client diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index 6a10236c..412dd7bd 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -17,15 +17,13 @@ mod common; use common::maybe_async_test; -use rspotify::model::offset::Offset; -use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder, TokenBuilder}; use rspotify::{ - client::{Spotify, SpotifyBuilder}, model::{ - Country, EpisodeId, Id, Market, RepeatState, SearchType, ShowId, TimeRange, TrackId, - TrackPositions, + Country, EpisodeId, Id, Market, Offset, RepeatState, SearchType, ShowId, TimeRange, + TrackId, TrackPositions, }, - scopes, + prelude::*, + scopes, AuthCodeSpotify, Credentials, OAuth, Token, }; use chrono::prelude::*; @@ -35,18 +33,18 @@ use std::env; /// Generating a new OAuth client for the requests. #[maybe_async] -pub async fn oauth_client() -> Spotify { +pub async fn oauth_client() -> AuthCodeSpotify { if let Ok(access_token) = env::var("RSPOTIFY_ACCESS_TOKEN") { - let tok = TokenBuilder::default() - .access_token(access_token) - .build() - .unwrap(); + let tok = Token { + access_token, + ..Default::default() + }; - SpotifyBuilder::default().token(tok).build().unwrap() + AuthCodeSpotify::from_token(tok) } else if let Ok(refresh_token) = env::var("RSPOTIFY_REFRESH_TOKEN") { // The credentials must be available in the environment. Enable // `env-file` in order to read them from an `.env` file. - let creds = CredentialsBuilder::from_env().build().unwrap_or_else(|_| { + let creds = Credentials::from_env().unwrap_or_else(|| { panic!( "No credentials configured. Make sure that either the \ `env-file` feature is enabled, or that the required \ @@ -75,16 +73,10 @@ pub async fn oauth_client() -> Spotify { "ugc-image-upload" ); // Using every possible scope - let oauth = OAuthBuilder::from_env().scope(scope).build().unwrap(); - - let mut spotify = SpotifyBuilder::default() - .credentials(creds) - .oauth(oauth) - .build() - .unwrap(); - - spotify.refresh_user_token(&refresh_token).await.unwrap(); + let oauth = OAuth::from_env(scope).unwrap(); + let mut spotify = AuthCodeSpotify::new(creds, oauth); + spotify.refresh_token(&refresh_token).await.unwrap(); spotify } else { panic!( @@ -133,7 +125,7 @@ async fn test_category_playlists() { async fn test_current_playback() { oauth_client() .await - .current_playback::<&[_]>(None, None) + .current_playback(None, None::<&[_]>) .await .unwrap(); } From 9b2cbbbb6e080afd831d113e064cc0dfa78bd88d Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 19 Jun 2021 12:22:03 +0200 Subject: [PATCH 2/8] update examples --- examples/auth_code.rs | 55 +++++++++ examples/auth_code_pkce.rs | 47 +++++++ examples/{album.rs => client_creds.rs} | 24 ++-- examples/current_user_recently_played.rs | 51 -------- examples/oauth_tokens.rs | 19 ++- examples/pagination_async.rs | 42 +------ examples/pagination_manual.rs | 42 +------ examples/pagination_sync.rs | 42 +------ examples/track.rs | 42 ------- examples/tracks.rs | 42 ------- examples/ureq/device.rs | 42 +------ examples/ureq/me.rs | 42 +------ examples/ureq/search.rs | 64 +++------- examples/ureq/seek_track.rs | 42 +------ examples/webapp/src/main.rs | 149 +++++++++++------------ examples/with_refresh_token.rs | 37 ++---- 16 files changed, 254 insertions(+), 528 deletions(-) create mode 100644 examples/auth_code.rs create mode 100644 examples/auth_code_pkce.rs rename examples/{album.rs => client_creds.rs} (62%) delete mode 100644 examples/current_user_recently_played.rs delete mode 100644 examples/track.rs delete mode 100644 examples/tracks.rs diff --git a/examples/auth_code.rs b/examples/auth_code.rs new file mode 100644 index 00000000..a54443ae --- /dev/null +++ b/examples/auth_code.rs @@ -0,0 +1,55 @@ +use rspotify::{ + model::{AdditionalType, Country, Market}, + prelude::*, + scopes, AuthCodeSpotify, Credentials, OAuth, +}; + +#[tokio::main] +async fn main() { + // You can use any logger for debugging. + env_logger::init(); + + // Set RSPOTIFY_CLIENT_ID, RSPOTIFY_CLIENT_SECRET and + // RSPOTIFY_REDIRECT_URI in an .env file or export them manually: + // + // export RSPOTIFY_CLIENT_ID="your client_id" + // export RSPOTIFY_CLIENT_SECRET="secret" + // + // These will then be read with `from_env`. + // + // Otherwise, set client_id and client_secret explictly: + // + // ``` + // let creds = Credentials { + // id: "this-is-my-client-id".to_string(), + // secret: "this-is-my-client-secret".to_string() + // }; + // ``` + let creds = Credentials::from_env().unwrap(); + + // Or set the redirect_uri explictly: + // + // ``` + // let oauth = OAuth { + // redirect_uri: "http://localhost:8888/callback".to_string(), + // scope: scopes!("user-read-recently-played"), + // ..Default::default(), + // }; + // ``` + let oauth = OAuth::from_env(scopes!("user-read-currently-playing")).unwrap(); + + let mut spotify = AuthCodeSpotify::new(creds, oauth); + + // Obtaining the access token + let url = spotify.get_authorize_url(false).unwrap(); + spotify.prompt_for_token(&url).await.unwrap(); + + // Running the requests + let market = Market::Country(Country::Spain); + let additional_types = [AdditionalType::Episode]; + let artists = spotify + .current_playing(Some(&market), Some(&additional_types)) + .await; + + println!("Response: {:?}", artists); +} diff --git a/examples/auth_code_pkce.rs b/examples/auth_code_pkce.rs new file mode 100644 index 00000000..c5e572e0 --- /dev/null +++ b/examples/auth_code_pkce.rs @@ -0,0 +1,47 @@ +use rspotify::{prelude::*, scopes, AuthCodePkceSpotify, Credentials, OAuth}; + +#[tokio::main] +async fn main() { + // You can use any logger for debugging. + env_logger::init(); + + // Set RSPOTIFY_CLIENT_ID, RSPOTIFY_CLIENT_SECRET and + // RSPOTIFY_REDIRECT_URI in an .env file or export them manually: + // + // export RSPOTIFY_CLIENT_ID="your client_id" + // export RSPOTIFY_CLIENT_SECRET="secret" + // + // These will then be read with `from_env`. + // + // Otherwise, set client_id and client_secret explictly: + // + // ``` + // let creds = Credentials { + // id: "this-is-my-client-id".to_string(), + // secret: "this-is-my-client-secret".to_string() + // }; + // ``` + let creds = Credentials::from_env().unwrap(); + + // Or set the redirect_uri explictly: + // + // ``` + // let oauth = OAuth { + // redirect_uri: "http://localhost:8888/callback".to_string(), + // scope: scopes!("user-read-recently-played"), + // ..Default::default(), + // }; + // ``` + let oauth = OAuth::from_env(scopes!("user-read-recently-played")).unwrap(); + + let mut spotify = AuthCodePkceSpotify::new(creds, oauth); + + // Obtaining the access token + let url = spotify.get_authorize_url().unwrap(); + spotify.prompt_for_token(&url).await.unwrap(); + + // Running the requests + let history = spotify.current_playback(None, None::>).await; + + println!("Response: {:?}", history); +} diff --git a/examples/album.rs b/examples/client_creds.rs similarity index 62% rename from examples/album.rs rename to examples/client_creds.rs index f2213185..d54faaac 100644 --- a/examples/album.rs +++ b/examples/client_creds.rs @@ -1,6 +1,4 @@ -use rspotify::client::SpotifyBuilder; -use rspotify::model::Id; -use rspotify::oauth2::CredentialsBuilder; +use rspotify::{model::Id, prelude::*, ClientCredsSpotify, Credentials}; #[tokio::main] async fn main() { @@ -17,22 +15,20 @@ async fn main() { // // Otherwise, set client_id and client_secret explictly: // - // let creds = CredentialsBuilder::default() - // .client_id("this-is-my-client-id") - // .client_secret("this-is-my-client-secret") - // .build() - // .unwrap(); - let creds = CredentialsBuilder::from_env().build().unwrap(); + // ``` + // let creds = Credentials { + // id: "this-is-my-client-id".to_string(), + // secret: "this-is-my-client-secret".to_string() + // }; + // ``` + let creds = Credentials::from_env().unwrap(); - let mut spotify = SpotifyBuilder::default() - .credentials(creds) - .build() - .unwrap(); + let mut spotify = ClientCredsSpotify::new(creds); // Obtaining the access token. Requires to be mutable because the internal // token will be modified. We don't need OAuth for this specific endpoint, // so `...` is used instead of `prompt_for_user_token`. - spotify.request_client_token().await.unwrap(); + spotify.request_token().await.unwrap(); // Running the requests let birdy_uri = Id::from_uri("spotify:album:0sNOF9WDwhWunNAHPD3Baj").unwrap(); diff --git a/examples/current_user_recently_played.rs b/examples/current_user_recently_played.rs deleted file mode 100644 index d68b0eb1..00000000 --- a/examples/current_user_recently_played.rs +++ /dev/null @@ -1,51 +0,0 @@ -use rspotify::client::SpotifyBuilder; -use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder}; -use rspotify::scopes; - -#[tokio::main] -async fn main() { - // You can use any logger for debugging. - env_logger::init(); - - // Set RSPOTIFY_CLIENT_ID, RSPOTIFY_CLIENT_SECRET and - // RSPOTIFY_REDIRECT_URI in an .env file or export them manually: - // - // export RSPOTIFY_CLIENT_ID="your client_id" - // export RSPOTIFY_CLIENT_SECRET="secret" - // - // These will then be read with `from_env`. - // - // Otherwise, set client_id and client_secret explictly: - // - // let creds = CredentialsBuilder::default() - // .client_id("this-is-my-client-id") - // .client_secret("this-is-my-client-secret") - // .build() - // .unwrap(); - let creds = CredentialsBuilder::from_env().build().unwrap(); - - // Or set the redirect_uri explictly: - // - // let oauth = OAuthBuilder::default() - // .redirect_uri("http://localhost:8888/callback") - // .build() - // .unwrap(); - let oauth = OAuthBuilder::from_env() - .scope(scopes!("user-read-recently-played")) - .build() - .unwrap(); - - let mut spotify = SpotifyBuilder::default() - .credentials(creds) - .oauth(oauth) - .build() - .unwrap(); - - // Obtaining the access token - spotify.prompt_for_user_token().await.unwrap(); - - // Running the requests - let history = spotify.current_user_recently_played(Some(10)).await; - - println!("Response: {:?}", history); -} diff --git a/examples/oauth_tokens.rs b/examples/oauth_tokens.rs index de127938..6a1baea3 100644 --- a/examples/oauth_tokens.rs +++ b/examples/oauth_tokens.rs @@ -5,9 +5,7 @@ //! an .env file or export them manually as environmental variables for this to //! work. -use rspotify::client::SpotifyBuilder; -use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder}; -use rspotify::scopes; +use rspotify::{prelude::*, scopes, AuthCodeSpotify, Credentials, OAuth}; #[tokio::main] async fn main() { @@ -16,10 +14,10 @@ async fn main() { // The credentials must be available in the environment. Enable // `env-file` in order to read them from an `.env` file. - let creds = CredentialsBuilder::from_env().build().unwrap(); + let creds = Credentials::from_env().unwrap(); // Using every possible scope - let scope = scopes!( + let scopes = scopes!( "user-read-email", "user-read-private", "user-top-read", @@ -38,15 +36,12 @@ async fn main() { "playlist-modify-private", "ugc-image-upload" ); - let oauth = OAuthBuilder::from_env().scope(scope).build().unwrap(); + let oauth = OAuth::from_env(scopes).unwrap(); - let mut spotify = SpotifyBuilder::default() - .credentials(creds) - .oauth(oauth) - .build() - .unwrap(); + let mut spotify = AuthCodeSpotify::new(creds, oauth); - spotify.prompt_for_user_token().await.unwrap(); + let url = spotify.get_authorize_url(false).unwrap(); + spotify.prompt_for_token(&url).await.unwrap(); let token = spotify.token.as_ref().unwrap(); println!("Access token: {}", &token.access_token); diff --git a/examples/pagination_async.rs b/examples/pagination_async.rs index 874ce60c..ac01bbbe 100644 --- a/examples/pagination_async.rs +++ b/examples/pagination_async.rs @@ -8,51 +8,21 @@ use futures::stream::TryStreamExt; use futures_util::pin_mut; -use rspotify::client::SpotifyBuilder; -use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder}; -use rspotify::scopes; +use rspotify::{prelude::*, scopes, AuthCodeSpotify, Credentials, OAuth}; #[tokio::main] async fn main() { // You can use any logger for debugging. env_logger::init(); - // Set RSPOTIFY_CLIENT_ID, RSPOTIFY_CLIENT_SECRET and - // RSPOTIFY_REDIRECT_URI in an .env file or export them manually: - // - // export RSPOTIFY_CLIENT_ID="your client_id" - // export RSPOTIFY_CLIENT_SECRET="secret" - // - // These will then be read with `from_env`. - // - // Otherwise, set client_id and client_secret explictly: - // - // let creds = CredentialsBuilder::default() - // .client_id("this-is-my-client-id") - // .client_secret("this-is-my-client-secret") - // .build() - // .unwrap(); - let creds = CredentialsBuilder::from_env().build().unwrap(); + let creds = Credentials::from_env().unwrap(); + let oauth = OAuth::from_env(scopes!("user-library-read")).unwrap(); - // Or set the redirect_uri explictly: - // - // let oauth = OAuthBuilder::default() - // .redirect_uri("http://localhost:8888/callback") - // .build() - // .unwrap(); - let oauth = OAuthBuilder::from_env() - .scope(scopes!("user-library-read")) - .build() - .unwrap(); - - let mut spotify = SpotifyBuilder::default() - .credentials(creds) - .oauth(oauth) - .build() - .unwrap(); + let mut spotify = AuthCodeSpotify::new(creds, oauth); // Obtaining the access token - spotify.prompt_for_user_token().await.unwrap(); + let url = spotify.get_authorize_url(false).unwrap(); + spotify.prompt_for_token(&url).await.unwrap(); // Executing the futures sequentially let stream = spotify.current_user_saved_tracks(); diff --git a/examples/pagination_manual.rs b/examples/pagination_manual.rs index 80b338fd..a8c6ab0d 100644 --- a/examples/pagination_manual.rs +++ b/examples/pagination_manual.rs @@ -1,51 +1,21 @@ //! This example shows how manual pagination works. It's what the raw API //! returns, but harder to use than an iterator or stream. -use rspotify::client::SpotifyBuilder; -use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder}; -use rspotify::scopes; +use rspotify::{prelude::*, scopes, AuthCodeSpotify, Credentials, OAuth}; #[tokio::main] async fn main() { // You can use any logger for debugging. env_logger::init(); - // Set RSPOTIFY_CLIENT_ID, RSPOTIFY_CLIENT_SECRET and - // RSPOTIFY_REDIRECT_URI in an .env file or export them manually: - // - // export RSPOTIFY_CLIENT_ID="your client_id" - // export RSPOTIFY_CLIENT_SECRET="secret" - // - // These will then be read with `from_env`. - // - // Otherwise, set client_id and client_secret explictly: - // - // let creds = CredentialsBuilder::default() - // .client_id("this-is-my-client-id") - // .client_secret("this-is-my-client-secret") - // .build() - // .unwrap(); - let creds = CredentialsBuilder::from_env().build().unwrap(); + let creds = Credentials::from_env().unwrap(); + let oauth = OAuth::from_env(scopes!("user-library-read")).unwrap(); - // Or set the redirect_uri explictly: - // - // let oauth = OAuthBuilder::default() - // .redirect_uri("http://localhost:8888/callback") - // .build() - // .unwrap(); - let oauth = OAuthBuilder::from_env() - .scope(scopes!("user-library-read")) - .build() - .unwrap(); - - let mut spotify = SpotifyBuilder::default() - .credentials(creds) - .oauth(oauth) - .build() - .unwrap(); + let mut spotify = AuthCodeSpotify::new(creds, oauth); // Obtaining the access token - spotify.prompt_for_user_token().await.unwrap(); + let url = spotify.get_authorize_url(false).unwrap(); + spotify.prompt_for_token(&url).await.unwrap(); // Manual pagination. You may choose the number of items returned per // iteration. diff --git a/examples/pagination_sync.rs b/examples/pagination_sync.rs index eb127588..2dd8600e 100644 --- a/examples/pagination_sync.rs +++ b/examples/pagination_sync.rs @@ -9,50 +9,20 @@ //! } //! ``` -use rspotify::client::SpotifyBuilder; -use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder}; -use rspotify::scopes; +use rspotify::{prelude::*, scopes, AuthCodeSpotify, Credentials, OAuth}; fn main() { // You can use any logger for debugging. env_logger::init(); - // Set RSPOTIFY_CLIENT_ID, RSPOTIFY_CLIENT_SECRET and - // RSPOTIFY_REDIRECT_URI in an .env file or export them manually: - // - // export RSPOTIFY_CLIENT_ID="your client_id" - // export RSPOTIFY_CLIENT_SECRET="secret" - // - // These will then be read with `from_env`. - // - // Otherwise, set client_id and client_secret explictly: - // - // let creds = CredentialsBuilder::default() - // .client_id("this-is-my-client-id") - // .client_secret("this-is-my-client-secret") - // .build() - // .unwrap(); - let creds = CredentialsBuilder::from_env().build().unwrap(); + let creds = Credentials::from_env().unwrap(); + let oauth = OAuth::from_env(scopes!("user-library-read")).unwrap(); - // Or set the redirect_uri explictly: - // - // let oauth = OAuthBuilder::default() - // .redirect_uri("http://localhost:8888/callback") - // .build() - // .unwrap(); - let oauth = OAuthBuilder::from_env() - .scope(scopes!("user-library-read")) - .build() - .unwrap(); - - let mut spotify = SpotifyBuilder::default() - .credentials(creds) - .oauth(oauth) - .build() - .unwrap(); + let mut spotify = AuthCodeSpotify::new(creds, oauth); // Obtaining the access token - spotify.prompt_for_user_token().unwrap(); + let url = spotify.get_authorize_url(false).unwrap(); + spotify.prompt_for_token(&url).unwrap(); // Typical iteration, no extra boilerplate needed. let stream = spotify.current_user_saved_tracks(); diff --git a/examples/track.rs b/examples/track.rs deleted file mode 100644 index 66446771..00000000 --- a/examples/track.rs +++ /dev/null @@ -1,42 +0,0 @@ -use rspotify::client::SpotifyBuilder; -use rspotify::model::Id; -use rspotify::oauth2::CredentialsBuilder; - -#[tokio::main] -async fn main() { - // You can use any logger for debugging. - env_logger::init(); - - // Set RSPOTIFY_CLIENT_ID, RSPOTIFY_CLIENT_SECRET and - // RSPOTIFY_REDIRECT_URI in an .env file or export them manually: - // - // export RSPOTIFY_CLIENT_ID="your client_id" - // export RSPOTIFY_CLIENT_SECRET="secret" - // - // These will then be read with `from_env`. - // - // Otherwise, set client_id and client_secret explictly: - // - // let creds = CredentialsBuilder::default() - // .client_id("this-is-my-client-id") - // .client_secret("this-is-my-client-secret") - // .build() - // .unwrap(); - let creds = CredentialsBuilder::from_env().build().unwrap(); - - let mut spotify = SpotifyBuilder::default() - .credentials(creds) - .build() - .unwrap(); - - // Obtaining the access token. Requires to be mutable because the internal - // token will be modified. We don't need OAuth for this specific endpoint, - // so `...` is used instead of `prompt_for_user_token`. - spotify.request_client_token().await.unwrap(); - - // Running the requests - let birdy_uri = Id::from_uri("spotify:track:6rqhFgbbKwnb9MLmUQDhG6").unwrap(); - let track = spotify.track(birdy_uri).await; - - println!("Response: {:#?}", track); -} diff --git a/examples/tracks.rs b/examples/tracks.rs deleted file mode 100644 index 7549ecd7..00000000 --- a/examples/tracks.rs +++ /dev/null @@ -1,42 +0,0 @@ -use rspotify::client::SpotifyBuilder; -use rspotify::model::Id; -use rspotify::oauth2::CredentialsBuilder; - -#[tokio::main] -async fn main() { - // You can use any logger for debugging. - env_logger::init(); - - // Set RSPOTIFY_CLIENT_ID, RSPOTIFY_CLIENT_SECRET and - // RSPOTIFY_REDIRECT_URI in an .env file or export them manually: - // - // export RSPOTIFY_CLIENT_ID="your client_id" - // export RSPOTIFY_CLIENT_SECRET="secret" - // - // These will then be read with `from_env`. - // - // Otherwise, set client_id and client_secret explictly: - // - // let creds = CredentialsBuilder::default() - // .client_id("this-is-my-client-id") - // .client_secret("this-is-my-client-secret") - // .build() - // .unwrap(); - let creds = CredentialsBuilder::from_env().build().unwrap(); - - let mut spotify = SpotifyBuilder::default() - .credentials(creds) - .build() - .unwrap(); - - // Obtaining the access token. Requires to be mutable because the internal - // token will be modified. We don't need OAuth for this specific endpoint, - // so `...` is used instead of `prompt_for_user_token`. - spotify.request_client_token().await.unwrap(); - - let birdy_uri1 = Id::from_uri("spotify:track:3n3Ppam7vgaVa1iaRUc9Lp").unwrap(); - let birdy_uri2 = Id::from_uri("spotify:track:3twNvmDtFQtAd5gMKedhLD").unwrap(); - let track_uris = vec![birdy_uri1, birdy_uri2]; - let tracks = spotify.tracks(track_uris, None).await; - println!("Response: {:?}", tracks); -} diff --git a/examples/ureq/device.rs b/examples/ureq/device.rs index 4154394f..f4c66dd0 100644 --- a/examples/ureq/device.rs +++ b/examples/ureq/device.rs @@ -1,47 +1,17 @@ -use rspotify::client::SpotifyBuilder; -use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder}; -use rspotify::scopes; +use rspotify::{prelude::*, scopes, AuthCodeSpotify, Credentials, OAuth}; fn main() { // You can use any logger for debugging. env_logger::init(); - // Set RSPOTIFY_CLIENT_ID, RSPOTIFY_CLIENT_SECRET and - // RSPOTIFY_REDIRECT_URI in an .env file or export them manually: - // - // export RSPOTIFY_CLIENT_ID="your client_id" - // export RSPOTIFY_CLIENT_SECRET="secret" - // - // These will then be read with `from_env`. - // - // Otherwise, set client_id and client_secret explictly: - // - // let creds = CredentialsBuilder::default() - // .client_id("this-is-my-client-id") - // .client_secret("this-is-my-client-secret") - // .build() - // .unwrap(); - let creds = CredentialsBuilder::from_env().build().unwrap(); + let creds = Credentials::from_env().unwrap(); + let oauth = OAuth::from_env(scopes!("user-read-playback-state")).unwrap(); - // Or set the redirect_uri explictly: - // - // let oauth = OAuthBuilder::default() - // .redirect_uri("http://localhost:8888/callback") - // .build() - // .unwrap(); - let oauth = OAuthBuilder::from_env() - .scope(scopes!("user-read-playback-state")) - .build() - .unwrap(); - - let mut spotify = SpotifyBuilder::default() - .credentials(creds) - .oauth(oauth) - .build() - .unwrap(); + let mut spotify = AuthCodeSpotify::new(creds, oauth); // Obtaining the access token - spotify.prompt_for_user_token().unwrap(); + let url = spotify.get_authorize_url(false).unwrap(); + spotify.prompt_for_token(&url).unwrap(); let devices = spotify.device(); diff --git a/examples/ureq/me.rs b/examples/ureq/me.rs index e089c2fd..9258589b 100644 --- a/examples/ureq/me.rs +++ b/examples/ureq/me.rs @@ -1,47 +1,17 @@ -use rspotify::client::SpotifyBuilder; -use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder}; -use rspotify::scopes; +use rspotify::{prelude::*, scopes, AuthCodeSpotify, Credentials, OAuth}; fn main() { // You can use any logger for debugging. env_logger::init(); - // Set RSPOTIFY_CLIENT_ID, RSPOTIFY_CLIENT_SECRET and - // RSPOTIFY_REDIRECT_URI in an .env file or export them manually: - // - // export RSPOTIFY_CLIENT_ID="your client_id" - // export RSPOTIFY_CLIENT_SECRET="secret" - // - // These will then be read with `from_env`. - // - // Otherwise, set client_id and client_secret explictly: - // - // let creds = CredentialsBuilder::default() - // .client_id("this-is-my-client-id") - // .client_secret("this-is-my-client-secret") - // .build() - // .unwrap(); - let creds = CredentialsBuilder::from_env().build().unwrap(); + let creds = Credentials::from_env().unwrap(); + let oauth = OAuth::from_env(scopes!("user-read-playback-state")).unwrap(); - // Or set the redirect_uri explictly: - // - // let oauth = OAuthBuilder::default() - // .redirect_uri("http://localhost:8888/callback") - // .build() - // .unwrap(); - let oauth = OAuthBuilder::from_env() - .scope(scopes!("user-read-playback-state")) - .build() - .unwrap(); - - let mut spotify = SpotifyBuilder::default() - .credentials(creds) - .oauth(oauth) - .build() - .unwrap(); + let mut spotify = AuthCodeSpotify::new(creds, oauth); // Obtaining the access token - spotify.prompt_for_user_token().unwrap(); + let url = spotify.get_authorize_url(false).unwrap(); + spotify.prompt_for_token(&url).unwrap(); let user = spotify.me(); println!("Request: {:?}", user); diff --git a/examples/ureq/search.rs b/examples/ureq/search.rs index 2a601877..caab1fae 100644 --- a/examples/ureq/search.rs +++ b/examples/ureq/search.rs @@ -1,51 +1,21 @@ -use rspotify::client::SpotifyBuilder; -use rspotify::model::{Country, Market, SearchType}; -use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder}; -use rspotify::scopes; +use rspotify::{ + model::{Country, Market, SearchType}, + prelude::*, + ClientCredsSpotify, Credentials, +}; fn main() { // You can use any logger for debugging. env_logger::init(); - // Set RSPOTIFY_CLIENT_ID, RSPOTIFY_CLIENT_SECRET and - // RSPOTIFY_REDIRECT_URI in an .env file or export them manually: - // - // export RSPOTIFY_CLIENT_ID="your client_id" - // export RSPOTIFY_CLIENT_SECRET="secret" - // - // These will then be read with `from_env`. - // - // Otherwise, set client_id and client_secret explictly: - // - // let creds = CredentialsBuilder::default() - // .client_id("this-is-my-client-id") - // .client_secret("this-is-my-client-secret") - // .build() - // .unwrap(); - let creds = CredentialsBuilder::from_env().build().unwrap(); - - // Or set the redirect_uri explictly: - // - // let oauth = OAuthBuilder::default() - // .redirect_uri("http://localhost:8888/callback") - // .build() - // .unwrap(); - let oauth = OAuthBuilder::from_env() - .scope(scopes!("user-read-playback-state")) - .build() - .unwrap(); - - let mut spotify = SpotifyBuilder::default() - .credentials(creds) - .oauth(oauth) - .build() - .unwrap(); + let creds = Credentials::from_env().unwrap(); + let mut spotify = ClientCredsSpotify::new(creds); // Obtaining the access token - spotify.request_client_token().unwrap(); + spotify.request_token().unwrap(); let album_query = "album:arrival artist:abba"; - let result = spotify.search(album_query, SearchType::Album, None, None, Some(10), None); + let result = spotify.search(album_query, &SearchType::Album, None, None, Some(10), None); match result { Ok(album) => println!("searched album:{:?}", album), Err(err) => println!("search error!{:?}", err), @@ -54,8 +24,8 @@ fn main() { let artist_query = "tania bowra"; let result = spotify.search( artist_query, - SearchType::Artist, - Some(Market::Country(Country::UnitedStates)), + &SearchType::Artist, + Some(&Market::Country(Country::UnitedStates)), None, Some(10), None, @@ -68,8 +38,8 @@ fn main() { let playlist_query = "\"doom metal\""; let result = spotify.search( playlist_query, - SearchType::Playlist, - Some(Market::Country(Country::UnitedStates)), + &SearchType::Playlist, + Some(&Market::Country(Country::UnitedStates)), None, Some(10), None, @@ -82,8 +52,8 @@ fn main() { let track_query = "abba"; let result = spotify.search( track_query, - SearchType::Track, - Some(Market::Country(Country::UnitedStates)), + &SearchType::Track, + Some(&Market::Country(Country::UnitedStates)), None, Some(10), None, @@ -94,7 +64,7 @@ fn main() { } let show_query = "love"; - let result = spotify.search(show_query, SearchType::Show, None, None, Some(10), None); + let result = spotify.search(show_query, &SearchType::Show, None, None, Some(10), None); match result { Ok(show) => println!("searched show:{:?}", show), Err(err) => println!("search error!{:?}", err), @@ -103,7 +73,7 @@ fn main() { let episode_query = "love"; let result = spotify.search( episode_query, - SearchType::Episode, + &SearchType::Episode, None, None, Some(10), diff --git a/examples/ureq/seek_track.rs b/examples/ureq/seek_track.rs index 5194ddc5..a4a98ae1 100644 --- a/examples/ureq/seek_track.rs +++ b/examples/ureq/seek_track.rs @@ -1,47 +1,17 @@ -use rspotify::client::SpotifyBuilder; -use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder}; -use rspotify::scopes; +use rspotify::{prelude::*, scopes, AuthCodeSpotify, Credentials, OAuth}; fn main() { // You can use any logger for debugging. env_logger::init(); - // Set RSPOTIFY_CLIENT_ID, RSPOTIFY_CLIENT_SECRET and - // RSPOTIFY_REDIRECT_URI in an .env file or export them manually: - // - // export RSPOTIFY_CLIENT_ID="your client_id" - // export RSPOTIFY_CLIENT_SECRET="secret" - // - // These will then be read with `from_env`. - // - // Otherwise, set client_id and client_secret explictly: - // - // let creds = CredentialsBuilder::default() - // .client_id("this-is-my-client-id") - // .client_secret("this-is-my-client-secret") - // .build() - // .unwrap(); - let creds = CredentialsBuilder::from_env().build().unwrap(); + let creds = Credentials::from_env().unwrap(); + let oauth = OAuth::from_env(scopes!("user-read-playback-state")).unwrap(); - // Or set the redirect_uri explictly: - // - // let oauth = OAuthBuilder::default() - // .redirect_uri("http://localhost:8888/callback") - // .build() - // .unwrap(); - let oauth = OAuthBuilder::from_env() - .scope(scopes!("user-read-playback-state")) - .build() - .unwrap(); - - let mut spotify = SpotifyBuilder::default() - .credentials(creds) - .oauth(oauth) - .build() - .unwrap(); + let mut spotify = AuthCodeSpotify::new(creds, oauth); // Obtaining the access token - spotify.prompt_for_user_token().unwrap(); + let url = spotify.get_authorize_url(false).unwrap(); + spotify.prompt_for_token(&url).unwrap(); match spotify.seek_track(25000, None) { Ok(_) => println!("change to previous playback successful"), diff --git a/examples/webapp/src/main.rs b/examples/webapp/src/main.rs index 79553de8..742c1e68 100644 --- a/examples/webapp/src/main.rs +++ b/examples/webapp/src/main.rs @@ -1,7 +1,7 @@ //! In this example, the token is saved into a cache file. If you are building a //! real-world web app, you should store it in a database instead. In that case -//! you can use `Spotify::request_user_token_without_cache` and -//! `Spotify::refresh_user_token_without_cache` to avoid creating cache files. +//! you can disable `token_cached` in the `Config` struct passed to the client +//! when initializing it to avoid using cache files. #![feature(proc_macro_hygiene, decl_macro)] @@ -14,13 +14,11 @@ use rocket::response::Redirect; use rocket_contrib::json; use rocket_contrib::json::JsonValue; use rocket_contrib::templates::Template; -use rspotify::client::{ClientError, SpotifyBuilder}; -use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder, TokenBuilder}; -use rspotify::scopes; +use rspotify::{scopes, AuthCodeSpotify, OAuth, Credentials, Config, prelude::*, Token}; use std::fs; use std::{ - collections::{HashMap, HashSet}, + collections::HashMap, env, path::PathBuf, }; @@ -47,9 +45,18 @@ fn generate_random_uuid(length: usize) -> String { .collect() } +fn get_cache_path(cookies: &Cookies) -> PathBuf { + let project_dir_path = env::current_dir().unwrap(); + let mut cache_path = project_dir_path; + cache_path.push(CACHE_PATH); + cache_path.push(cookies.get("uuid").unwrap().value()); + + cache_path +} + fn create_cache_path_if_absent(cookies: &Cookies) -> PathBuf { - let (exist, cache_path) = check_cache_path_exists(cookies); - if !exist { + let cache_path = get_cache_path(cookies); + if !cache_path.exists() { let mut path = cache_path.clone(); path.pop(); fs::create_dir_all(path).unwrap(); @@ -58,54 +65,49 @@ fn create_cache_path_if_absent(cookies: &Cookies) -> PathBuf { } fn remove_cache_path(mut cookies: Cookies) { - let (exist, cache_path) = check_cache_path_exists(&cookies); - if exist { + let cache_path = get_cache_path(&cookies); + if cache_path.exists() { fs::remove_file(cache_path).unwrap() } cookies.remove(Cookie::named("uuid")) } -fn check_cache_path_exists(cookies: &Cookies) -> (bool, PathBuf) { - let project_dir_path = env::current_dir().unwrap(); - let mut cache_path = project_dir_path; - cache_path.push(CACHE_PATH); - cache_path.push(cookies.get("uuid").unwrap().value()); - (cache_path.exists(), cache_path) +fn check_cache_path_exists(cookies: &Cookies) -> bool { + let cache_path = get_cache_path(cookies); + cache_path.exists() } -fn init_spotify() -> SpotifyBuilder { +fn init_spotify(cookies: &Cookies) -> AuthCodeSpotify { + let config = Config { + token_cached: true, + cache_path: create_cache_path_if_absent(cookies), + ..Default::default() + }; + // Please notice that protocol of redirect_uri, make sure it's http // (or https). It will fail if you mix them up. - let scope = scopes!("user-read-currently-playing", "playlist-modify-private"); - let oauth = OAuthBuilder::default() - .redirect_uri("http://localhost:8000/callback") - .scope(scope) - .build() - .unwrap(); + let oauth = OAuth { + scope: scopes!("user-read-currently-playing", "playlist-modify-private"), + redirect_uri: "http://localhost:8000/callback".to_owned(), + ..Default::default() + }; // Replacing client_id and client_secret with yours. - let creds = CredentialsBuilder::default() - .id("e1dce60f1e274e20861ce5d96142a4d3") - .secret("0e4e03b9be8d465d87fc32857a4b5aa3") - .build() - .unwrap(); - - SpotifyBuilder::default() - .credentials(creds) - .oauth(oauth) - .clone() + let creds = Credentials::new( + "e1dce60f1e274e20861ce5d96142a4d3", + "0e4e03b9be8d465d87fc32857a4b5aa3" + ); + + AuthCodeSpotify::with_config(creds, oauth, config) } #[get("/callback?")] fn callback(cookies: Cookies, code: String) -> AppResponse { - let mut spotify = init_spotify(); - let mut spotify = spotify - .cache_path(create_cache_path_if_absent(&cookies)) - .build() - .unwrap(); - return match spotify.request_user_token(code.as_str()) { + let mut spotify = init_spotify(&cookies); + + match spotify.request_token(&code) { Ok(_) => { - println!("request user token successful"); + println!("Request user token successful"); AppResponse::Redirect(Redirect::to("/")) } Err(err) => { @@ -114,33 +116,28 @@ fn callback(cookies: Cookies, code: String) -> AppResponse { context.insert("err_msg", "Failed to get token!"); AppResponse::Template(Template::render("error", context)) } - }; + } } #[get("/")] fn index(mut cookies: Cookies) -> AppResponse { - let mut spotify_builder = init_spotify(); - let check_exists = |c| { - let (exists, _) = check_cache_path_exists(c); - exists - }; + let mut context = HashMap::new(); // The user is authenticated if their cookie is set and a cache exists for // them. - let authenticated = cookies.get("uuid").is_some() && check_exists(&cookies); - let spotify = if authenticated { - let (_, cache_path) = check_cache_path_exists(&cookies); - let token = TokenBuilder::from_cache(cache_path).build().unwrap(); - spotify_builder.token(token).build().unwrap() - } else { + let authenticated = cookies.get("uuid").is_some() && check_cache_path_exists(&cookies); + if !authenticated { cookies.add(Cookie::new("uuid", generate_random_uuid(64))); - spotify_builder - .cache_path(create_cache_path_if_absent(&cookies)) - .build() - .unwrap() - }; - let mut context = HashMap::new(); + let spotify = init_spotify(&cookies); + let auth_url = spotify.get_authorize_url(true).unwrap(); + context.insert("auth_url", auth_url); + return AppResponse::Template(Template::render("authorize", context)); + } + + let cache_path = get_cache_path(&cookies); + let token = Token::from_cache(cache_path).unwrap(); + let spotify = AuthCodeSpotify::from_token(token); match spotify.me() { Ok(user_info) => { context.insert( @@ -151,14 +148,7 @@ fn index(mut cookies: Cookies) -> AppResponse { ); AppResponse::Template(Template::render("index", context.clone())) } - Err(ClientError::InvalidAuth(msg)) => { - println!("InvalidAuth msg {:?}", msg); - let auth_url = spotify.get_authorize_url(true).unwrap(); - context.insert("auth_url", auth_url); - AppResponse::Template(Template::render("authorize", context)) - } Err(err) => { - let mut context = HashMap::new(); context.insert("err_msg", format!("Failed for {}!", err)); AppResponse::Template(Template::render("error", context)) } @@ -173,30 +163,33 @@ fn sign_out(cookies: Cookies) -> AppResponse { #[get("/playlists")] fn playlist(cookies: Cookies) -> AppResponse { - let mut spotify = init_spotify(); - let (exist, cache_path) = check_cache_path_exists(&cookies); - if !exist { + let mut spotify = init_spotify(&cookies); + if !spotify.config.cache_path.exists() { return AppResponse::Redirect(Redirect::to("/")); } - let token = TokenBuilder::from_cache(cache_path).build().unwrap(); - let spotify = spotify.token(token).build().unwrap(); - match spotify.current_user_playlists(Some(20), Some(0)) { - Ok(playlists) => AppResponse::Json(json!(playlists)), - Err(_) => AppResponse::Redirect(Redirect::to("/")), + let token = spotify.read_token_cache().unwrap(); + spotify.token = Some(token); + let playlists = spotify.current_user_playlists() + .take(50) + .filter_map(Result::ok) + .collect::>(); + + if playlists.is_empty() { + return AppResponse::Redirect(Redirect::to("/")); } + + AppResponse::Json(json!(playlists)) } #[get("/me")] fn me(cookies: Cookies) -> AppResponse { - let mut spotify = init_spotify(); - let (exist, cache_path) = check_cache_path_exists(&cookies); - if !exist { + let mut spotify = init_spotify(&cookies); + if !spotify.config.cache_path.exists() { return AppResponse::Redirect(Redirect::to("/")); } - let token = TokenBuilder::from_cache(cache_path).build().unwrap(); - let spotify = spotify.token(token).build().unwrap(); + spotify.token = Some(spotify.read_token_cache().unwrap()); match spotify.me() { Ok(user_info) => AppResponse::Json(json!(user_info)), Err(_) => AppResponse::Redirect(Redirect::to("/")), diff --git a/examples/with_refresh_token.rs b/examples/with_refresh_token.rs index 98e45e81..b4ce8fee 100644 --- a/examples/with_refresh_token.rs +++ b/examples/with_refresh_token.rs @@ -15,14 +15,11 @@ //! tokens](https://github.com/felix-hilden/tekore/issues/86), so in the case of //! Spotify it doesn't seem to revoke them at all. -use rspotify::client::{Spotify, SpotifyBuilder}; -use rspotify::model::Id; -use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder}; -use rspotify::scopes; +use rspotify::{model::Id, prelude::*, scopes, AuthCodeSpotify, Credentials, OAuth}; // Sample request that will follow some artists, print the user's // followed artists, and then unfollow the artists. -async fn do_things(spotify: Spotify) { +async fn do_things(spotify: AuthCodeSpotify) { let artists = vec![ Id::from_id("3RGLhK1IP9jnYFH4BRFJBS").unwrap(), // The Clash Id::from_id("0yNLKJebCb8Aueb54LYya3").unwrap(), // New Order @@ -57,20 +54,16 @@ async fn main() { env_logger::init(); // The default credentials from the `.env` file will be used by default. - let creds = CredentialsBuilder::from_env().build().unwrap(); - let scope = scopes!("user-follow-read user-follow-modify"); - let oauth = OAuthBuilder::from_env().scope(scope).build().unwrap(); - let mut spotify = SpotifyBuilder::default() - .credentials(creds.clone()) - .oauth(oauth.clone()) - .build() - .unwrap(); + let creds = Credentials::from_env().unwrap(); + let oauth = OAuth::from_env(scopes!("user-follow-read user-follow-modify")).unwrap(); + let mut spotify = AuthCodeSpotify::new(creds.clone(), oauth.clone()); // In the first session of the application we authenticate and obtain the // refresh token. We can also do some requests here. println!(">>> Session one, obtaining refresh token and running some requests:"); + let url = spotify.get_authorize_url(false).unwrap(); spotify - .prompt_for_user_token_without_cache() + .prompt_for_token(&url) .await .expect("couldn't authenticate successfully"); let refresh_token = spotify @@ -86,14 +79,10 @@ async fn main() { // At a different time, the refresh token can be used to refresh an access // token directly and run requests: println!(">>> Session two, running some requests:"); - let mut spotify = SpotifyBuilder::default() - .credentials(creds.clone()) - .oauth(oauth.clone()) - .build() - .unwrap(); + let mut spotify = AuthCodeSpotify::new(creds.clone(), oauth.clone()); // No `prompt_for_user_token_without_cache` needed. spotify - .refresh_user_token(&refresh_token) + .refresh_token(&refresh_token) .await .expect("couldn't refresh user token"); do_things(spotify).await; @@ -101,13 +90,9 @@ async fn main() { // This process can now be repeated multiple times by using only the // refresh token that was obtained at the beginning. println!(">>> Session three, running some requests:"); - let mut spotify = SpotifyBuilder::default() - .credentials(creds.clone()) - .oauth(oauth.clone()) - .build() - .unwrap(); + let mut spotify = AuthCodeSpotify::new(creds, oauth); spotify - .refresh_user_token(&refresh_token) + .refresh_token(&refresh_token) .await .expect("couldn't refresh user token"); do_things(spotify).await; From f8ab71850c8722d64cc0d9b5c9b6b8214d57a42e Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 19 Jun 2021 12:22:13 +0200 Subject: [PATCH 3/8] update CI --- .github/workflows/ci.yml | 36 +++++++----------------------------- .travis.yml | 25 ------------------------- 2 files changed, 7 insertions(+), 54 deletions(-) delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 843ed8b2..68b528c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,8 +43,8 @@ jobs: - arm-unknown-linux-gnueabihf - armv7-unknown-linux-gnueabihf client: - - client-ureq,ureq-rustls-tls - - client-reqwest,reqwest-rustls-tls + - rspotify/cli,rspotify/env-file,rspotify/client-ureq,rspotify/ureq-rustls-tls,rspotify-http/client-ureq,rspotify-http/ureq-rustls-tls + - rspotify/cli,rspotify/env-file,rspotify/client-reqwest,rspotify/reqwest-rustls-tls,rspotify-http/client-reqwest,rspotify-http/reqwest-rustls-tls steps: - name: Checkout sources uses: actions/checkout@v2 @@ -64,7 +64,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: build - args: --workspace --target ${{ matrix.target }} --no-default-features --features=cli,env-file,${{ matrix.client }} + args: -p rspotify -p rspotify-http -p rspotify-model -p rspotify-macros --no-default-features --features=${{ matrix.client }} --target ${{ matrix.target }} test-client: name: Test and Lint for each Client @@ -72,8 +72,8 @@ jobs: strategy: matrix: client: - - client-ureq,ureq-rustls-tls - - client-reqwest,reqwest-rustls-tls + - rspotify/cli,rspotify/env-file,rspotify/client-ureq,rspotify/ureq-rustls-tls,rspotify-http/client-ureq,rspotify-http/ureq-rustls-tls + - rspotify/cli,rspotify/env-file,rspotify/client-reqwest,rspotify/reqwest-rustls-tls,rspotify-http/client-reqwest,rspotify-http/reqwest-rustls-tls steps: - name: Checkout sources uses: actions/checkout@v2 @@ -90,32 +90,10 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - args: --workspace --no-default-features --features=cli,env-file,${{ matrix.client }} -- -D warnings + args: -p rspotify -p rspotify-http -p rspotify-model -p rspotify-macros --no-default-features --features=${{ matrix.client }} -- -D warnings - name: Run cargo test uses: actions-rs/cargo@v1 with: command: test - args: --no-default-features --features=env-file,${{ matrix.client }} - - # The rest of the crates don't need to be tested with multiple feature - # combinations. - test-crates: - name: Simple Tests for Crates - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Install stable toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - - name: Run cargo test - uses: actions-rs/cargo@v1 - with: - command: test - args: -p rspotify-macros -p rspotify-model + args: -p rspotify -p rspotify-http -p rspotify-model -p rspotify-macros --no-default-features --features=${{ matrix.client }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e3a90395..00000000 --- a/.travis.yml +++ /dev/null @@ -1,25 +0,0 @@ -language: rust -rust: - - stable - - beta - - nightly -matrix: - allow_failures: - - rust: nightly - -script: - - cargo test - - cargo run --example artists_albums - - cargo run --example track - - cargo run --example artist_related_artists - - cargo run --example tracks - - cargo run --example artist_top_tracks - - cargo run --example user - - cargo run --example artists - - cargo run --example albums - - cargo run --example audios_features - - cargo run --example audio_analysis - - cargo run --example album_tracks - - cargo run --example audio_features - - cargo run --example artist - - cargo run --example album From 210cd1d459ed2f07ffef7a265728a42e2a452cd3 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 19 Jun 2021 13:57:44 +0200 Subject: [PATCH 4/8] Added an upgrade guide --- CHANGELOG.md | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38846ee7..4d689f0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,91 @@ ## 0.11 (unreleased) -This release contains *lots* of breaking changes. These were necessary to continue Rspotify's development, and no more versions like this should happen again. Lots of internal code was rewritten to make Rspotify more flexible, performant and easier to use. Sorry for the inconvenience! + +This release contains *lots* of breaking changes. These were necessary to continue Rspotify's development, and this shouldn't happen again. From now on we'll work on getting closer to the first stable release. Lots of internal code was rewritten to make Rspotify more flexible, performant and easier to use. Sorry for the inconvenience! If we missed any change or there's something you'd like to discuss about this version, please open a new issue and let us know. +### Upgrade guide + +This guide should make it easier to upgrade your code, rather than checking out the changelog line by line. The most important changes are: + +* Support for **multiple HTTP clients**. Instead of using `rspotify::blocking` for synchronous access, you just need to configure the `ureq-client` feature and its TLS (learn more in the docs). +* No need for the builder pattern anymore: `Spotify` has been split up into **multiple clients depending on the authentication process** you want to follow. This means that you'll be required to `use rspotify::prelude::*` in order to access the traits with the endpoints. + * [Client Credentials Flow](https://developer.spotify.com/documentation/general/guides/authorization-guide/#client-credentials-flow): see `ClientCredsSpotify`. + * [Authorization Code Flow](https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow): see `AuthCodeSpotify`. + * [Authorization Code Flow with Proof Key for Code Exchange (PKCE)](https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow-with-proof-key-for-code-exchange-pkce): see `AuthCodePkceSpotify`. This is new! You might be interested in using PKCE for your app. + * [Implicit Grant Flow](https://developer.spotify.com/documentation/general/guides/authorization-guide/#implicit-grant-flow): unimplemented, as Rspotify has not been tested on a browser yet. If you'd like support for it, let us know in an issue! +* There's now support for (both sync and async) **automatic pagination** as well! Make sure you upgrade to these after checking out the [`pagination_async.rs`](https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/pagination_async.rs) and [`pagination_sync.rs`](https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/pagination_sync.rs) examples. +* We've **renamed** a few structs and endpoints. The new names are quite similar, so the Rust compiler should suggest you what to change after an error. The only one you might not notice is the **environmental variables**: they're now `RSPOTIFY_CLIENT_ID`, `RSPOTIFY_CLIENT_SECRET` and `RSPOTIFY_REDIRECT_URI` to avoid collisions with other libraries. + +Now to a quick example: here's how you *used to* query the current user saved tracks: + +```rust +extern crate rspotify; + +use rspotify::blocking::client::Spotify; +use rspotify::blocking::oauth2::{SpotifyClientCredentials, SpotifyOAuth}; +use rspotify::blocking::util::get_token; + +fn main() { + let mut oauth = SpotifyOAuth::default().scope("user-library-read").build(); // Turns out this reads from the environment variables! + let token_info = get_token(&mut oauth).unwrap(); // How does it get the token? Why is it not in the client if it makes a request? + + let client_credential = SpotifyClientCredentials::default() // This also accesses the environment variables with no warning. + .token_info(token_info) + .build(); // So verbose... + + let spotify = Spotify::default() // So verbose and easy to mess up... What auth flow is this again? + .client_credentials_manager(client_credential) + .build(); + let tracks = spotify.current_user_saved_tracks(10, 0); // Iterating is hard + println!("{:?}", tracks); +} +``` + +And here's how you do it now: + +```rust +use rspotify::{prelude::*, scopes, AuthCodeSpotify, Credentials, OAuth}; + +fn main() { + let oauth = OAuth::from_env(scopes!("user-library-read")).unwrap(); // Concise & explicit with `from_env` + let creds = Credentials::from_env().unwrap(); // Same, concise & explicit + + let mut spotify = AuthCodeSpotify::new(creds, oauth); // Simpler initialization + + let url = spotify.get_authorize_url(false).unwrap(); // More flexible, lets us implement PKCE + spotify.prompt_for_token(&url).unwrap(); // Explicit: the token is obtained by interacting with the user + + let stream = spotify.current_user_saved_tracks(); + println!("Items:"); + for item in stream { // Easy iteration instead of manual pagination + println!("* {}", item.unwrap().track.name); + } +} +``` + +Hopefully this will convince you that the new breaking changes are good; you'll find the new interface easier to read, more intuitive and less error prone. + +Here are a few examples of upgrades: + +| Name | Old | New | +|----------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------| +| [Sync] device | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/blocking/device.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/ureq/device.rs | +| [Sync] me | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/blocking/me.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/ureq/me.rs | +| [Sync] search | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/blocking/search_track.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/ureq/search.rs | +| [Sync] seek_track | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/blocking/seek_track.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/ureq/seek_track.rs | +| [Sync] current_user_saved_tracks | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/blocking/current_user_saved_tracks.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/pagination_sync.rs | +| [Async] current_user_saved_tracks | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/current_user_saved_tracks.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/pagination_async.rs | +| [Async] current_user_saved_tracks (manually) | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/current_user_saved_tracks.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/pagination_manual.rs | +| [Async] current_playing | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/current_playing.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/auth_code.rs | +| [Async] current_playback | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/current_playback.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/auth_code_pkce.rs | +| [Async] album | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/album.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/client_creds.rs | +| [Async] webapp with Rocket | https://github.com/ramsayleung/rspotify/tree/4c1c3366630a8b2b37668a17878b746108c93fd0/examples/webapp | https://github.com/ramsayleung/rspotify/tree/auth-rewrite-part4/examples/webapp | + +More in the [`examples` directory](https://github.com/ramsayleung/rspotify/tree/master/examples)! + +### Full changelog + - Rewritten documentation in hopes that it's easier to get started with Rspotify. - Reduced the number of examples. Instead of having an example for each endpoint, which is repetitive and unhelpful for newcomers, some real-life examples are now included. If you'd like to add your own example, please do! ([#113](https://github.com/ramsayleung/rspotify/pull/113)) - Rspotify now uses macros internally to make the endpoints as concise as possible and nice to read. From 029fe17f9c1672b291c65f60b293a3f9e24e627d Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 19 Jun 2021 14:01:42 +0200 Subject: [PATCH 5/8] Prettier table --- CHANGELOG.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d689f0c..256ba317 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,17 +70,17 @@ Here are a few examples of upgrades: | Name | Old | New | |----------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------| -| [Sync] device | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/blocking/device.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/ureq/device.rs | -| [Sync] me | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/blocking/me.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/ureq/me.rs | -| [Sync] search | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/blocking/search_track.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/ureq/search.rs | -| [Sync] seek_track | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/blocking/seek_track.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/ureq/seek_track.rs | -| [Sync] current_user_saved_tracks | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/blocking/current_user_saved_tracks.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/pagination_sync.rs | -| [Async] current_user_saved_tracks | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/current_user_saved_tracks.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/pagination_async.rs | -| [Async] current_user_saved_tracks (manually) | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/current_user_saved_tracks.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/pagination_manual.rs | -| [Async] current_playing | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/current_playing.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/auth_code.rs | -| [Async] current_playback | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/current_playback.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/auth_code_pkce.rs | -| [Async] album | https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/album.rs | https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/client_creds.rs | -| [Async] webapp with Rocket | https://github.com/ramsayleung/rspotify/tree/4c1c3366630a8b2b37668a17878b746108c93fd0/examples/webapp | https://github.com/ramsayleung/rspotify/tree/auth-rewrite-part4/examples/webapp | +| [Sync] device | [`examples/blocking/device.rs`](https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/blocking/device.rs) | [`examples/ureq/device.rs`](https://github.com/ramsayleung/rspotify/blob/master/examples/ureq/device.rs) | +| [Sync] me | [`examples/blocking/me.rs`](https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/blocking/me.rs) | [`examples/ureq/me.rs`](https://github.com/ramsayleung/rspotify/blob/master/examples/ureq/me.rs) | +| [Sync] search | [`examples/blocking/search_track.rs`](https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/blocking/search_track.rs) | [`examples/ureq/search.rs`](https://github.com/ramsayleung/rspotify/blob/master/examples/ureq/search.rs) | +| [Sync] seek_track | [`examples/blocking/seek_track.rs`](https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/blocking/seek_track.rs) | [`examples/ureq/seek_track.rs`](https://github.com/ramsayleung/rspotify/blob/master/examples/ureq/seek_track.rs) | +| [Sync] current_user_saved_tracks | [`examples/blocking/current_user_saved_tracks.rs`](https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/blocking/current_user_saved_tracks.rs) | [`examples/pagination_sync.rs`](https://github.com/ramsayleung/rspotify/blob/master/examples/pagination_sync.rs) | +| [Async] current_user_saved_tracks | [`examples/current_user_saved_tracks.rs`](https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/current_user_saved_tracks.rs) | [`examples/pagination_async.rs`](https://github.com/ramsayleung/rspotify/blob/master/examples/pagination_async.rs) | +| [Async] current_user_saved_tracks (manually) | [`examples/current_user_saved_tracks.rs`](https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/current_user_saved_tracks.rs) | [`examples/pagination_manual.rs`](https://github.com/ramsayleung/rspotify/blob/master/examples/pagination_manual.rs) | +| [Async] current_playing | [`examples/current_playing.rs`](https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/current_playing.rs) | [`examples/auth_code.rs`](https://github.com/ramsayleung/rspotify/blob/master/examples/auth_code.rs) | +| [Async] current_playback | [`examples/current_playback.rs`](https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/current_playback.rs) | [`examples/auth_code_pkce.rs`](https://github.com/ramsayleung/rspotify/blob/master/examples/auth_code_pkce.rs) | +| [Async] album | [`examples/album.rs`](https://github.com/ramsayleung/rspotify/blob/22a995a061dffbce9f5069fd603e266d7ed3a252/examples/album.rs) | [`examples/client_creds.rs`](https://github.com/ramsayleung/rspotify/blob/master/examples/client_creds.rs) | +| [Async] webapp with Rocket | [`examples/webapp`](https://github.com/ramsayleung/rspotify/tree/4c1c3366630a8b2b37668a17878b746108c93fd0/examples/webapp) | [`examples/webapp`](https://github.com/ramsayleung/rspotify/tree/master/examples/webapp) | More in the [`examples` directory](https://github.com/ramsayleung/rspotify/tree/master/examples)! From a5693084d0354156dfc7488b6194ff3ff1a1a57a Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 19 Jun 2021 14:11:43 +0200 Subject: [PATCH 6/8] Some fixes to the changelog --- CHANGELOG.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 256ba317..5903e75f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,8 +14,11 @@ This guide should make it easier to upgrade your code, rather than checking out * [Authorization Code Flow](https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow): see `AuthCodeSpotify`. * [Authorization Code Flow with Proof Key for Code Exchange (PKCE)](https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow-with-proof-key-for-code-exchange-pkce): see `AuthCodePkceSpotify`. This is new! You might be interested in using PKCE for your app. * [Implicit Grant Flow](https://developer.spotify.com/documentation/general/guides/authorization-guide/#implicit-grant-flow): unimplemented, as Rspotify has not been tested on a browser yet. If you'd like support for it, let us know in an issue! -* There's now support for (both sync and async) **automatic pagination** as well! Make sure you upgrade to these after checking out the [`pagination_async.rs`](https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/pagination_async.rs) and [`pagination_sync.rs`](https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/pagination_sync.rs) examples. +* There's now support for (both sync and async) **automatic pagination** as well! Make sure you upgrade to these after checking out the [`pagination_async.rs`](https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/pagination_async.rs) and [`pagination_sync.rs`](https://github.com/ramsayleung/rspotify/blob/auth-rewrite-part4/examples/pagination_sync.rs) examples. You can use the `_manual`-suffixed endpoints for the previous pagination style. * We've **renamed** a few structs and endpoints. The new names are quite similar, so the Rust compiler should suggest you what to change after an error. The only one you might not notice is the **environmental variables**: they're now `RSPOTIFY_CLIENT_ID`, `RSPOTIFY_CLIENT_SECRET` and `RSPOTIFY_REDIRECT_URI` to avoid collisions with other libraries. +* We always use **`Option`** for optional parameters now. This means that you might have to add `Some(...)` to some of your parameters. We were using both `Into>` and `Option` but decided that either of these would be best as long as it's *consistent*. `Option` has less magic, so we went for that one. +* The core library has been split up with **features**. If you need `dotenv` just activate `env-file`, and if you need CLI functionality (`prompt_for_token` and similars), activate `cli`. +* We use **custom errors** now instead of the `failure` crate. Now to a quick example: here's how you *used to* query the current user saved tracks: @@ -176,7 +179,6 @@ More in the [`examples` directory](https://github.com/ramsayleung/rspotify/tree/ - `TokenBuilder` and `OAuthBuilder` will only read from environment variables when `from_env` is used, instead of `default`. - `dotenv` support is now optional. You can enable it with the `env-file` feature to have the same behavior as before ([#108](https://github.com/ramsayleung/rspotify/issues/108)). It may be used with `from_env` as well. - Renamed environmental variables to `RSPOTIFY_CLIENT_ID`, `RSPOTIFY_CLIENT_SECRET` and `RSPOTIFY_REDIRECT_URI` to avoid name collisions with other libraries that use OAuth2 ([#118](https://github.com/ramsayleung/rspotify/issues/118)). -- All fallible calls in the client return a `ClientResult` rather than using `failure`, which is equivalent to a `Result`. - A real builder pattern is used now. For example, `Token` is constructed now with `TokenBuilder::default().access_token("...").build().unwrap()`. This has been applied to `Spotify`, `OAuth`, `Token` and `Credentials` ([#129](https://github.com/ramsayleung/rspotify/pull/129)). - The `blocking` module has been removed, since Rspotify is able to use multiple HTTP clients now. `reqwest` and `ureq` are currently supported, meaning that you can still use blocking code by enabling the `client-ureq` feature and a TLS like `ureq-rustls-tls`. Read the docs for more information ([#129](https://github.com/ramsayleung/rspotify/pull/129)). - The authentication process has been completely rewritten in order to make it more performant and robust. Please read the docs to learn more about how that works now ([#129](https://github.com/ramsayleung/rspotify/pull/129)). These are the main changes: @@ -192,7 +194,7 @@ More in the [`examples` directory](https://github.com/ramsayleung/rspotify/tree/ + `Spotify::prompt_for_user_token[_without_cache]` + The `ClientError::Cli` variant, for whenever user interaction goes wrong - Fix typo in `user_playlist_remove_specific_occurrenes_of_tracks`, now it's `user_playlist_remove_specific_occurrences_of_tracks`. -- ([#123](https://github.com/ramsayleung/rspotify/pull/123))All fallible calls in the client return a `ClientError` rather than using `failure`. +- ([#123](https://github.com/ramsayleung/rspotify/pull/123)) All fallible calls in the client return a `ClientError` rather than using `failure`. - ([#161](https://github.com/ramsayleung/rspotify/pull/161)) Endpoints taking `Vec/&[String]` as parameter have changed to `impl IntoIterator>`. + The endpoints which changes parameter from `Vec` to `impl IntoIterator>`: - `albums` @@ -233,7 +235,7 @@ More in the [`examples` directory](https://github.com/ramsayleung/rspotify/tree/ - ([#128](https://github.com/ramsayleung/rspotify/pull/128)) Refactor all enum files with `strum`, reduced boilerplate code. + All enums don't have a method named `as_str()` anymore, by leveraging `strum`, it's easy to convert strings to enum variants based on their name, with method `to_string()`. - Fix typo in `transfer_playback`: `device_id` to `device_ids`. -- ([#145](https://github.com/ramsayleung/rspotify/pull/145))Refactor models to make it easier to use: +- ([#145](https://github.com/ramsayleung/rspotify/pull/145)) Refactor models to make it easier to use: + Changed type of `track` in `PlayHistory` to `FullTrack` ([#139](https://github.com/ramsayleung/rspotify/pull/139)). + Rename model `CurrentlyPlaybackContext` to `CurrentPlaybackContext` + Change `copyrights` from `Vec>` to `Vec` @@ -253,7 +255,7 @@ More in the [`examples` directory](https://github.com/ramsayleung/rspotify/tree/ + Replace `Actions::disallows` with a `Vec` by removing all entires whose value is false, which will result in a simpler API + Replace `{FullAlbum, SimplifiedEpisode, FullEpisode}::release_date_precision` from `String` to `DatePrecision` enum, makes it easier to use. + Id and URI parameters are type-safe now everywhere, `Id` and `IdBuf` types for ids/URIs added (non-owning and owning structs). -- ([#157](https://github.com/ramsayleung/rspotify/pull/157))Keep polishing models to make it easier to use: +- ([#157](https://github.com/ramsayleung/rspotify/pull/157)) Keep polishing models to make it easier to use: + Constrain visibility of `FullArtists` struct with `pub (in crate)`, make `artists` and `artist_related_artists` endpoints return a `Vec` instead. + Constrain visibility of `FullTracks` struct with `pub (in crate)`, make `tracks` and `artist_top_tracks` endpoints return a `Vec` instead. + Constrain visibility of `AudioFeaturesPayload` struct with `pub (in crate)`, make `tracks_features` endpoints return a `Vec` instead. @@ -285,7 +287,7 @@ More in the [`examples` directory](https://github.com/ramsayleung/rspotify/tree/ + Change `OAuth.scope` from `String` to `HashSet`. + Change `SimplifiedPlaylist::tracks` from `HashMap` to `PlaylistTracksRef` - ([#194](https://github.com/ramsayleung/rspotify/pull/194)) Rename `PlayingItem` to `PlayableItem`, `PlaylistItem::track` type changed to `Option`, so playlists can contain episodes as well -- ([#197](https://github.com/ramsayleung/rspotify/pull/197)) Makeing acronym lowercase +- ([#197](https://github.com/ramsayleung/rspotify/pull/197)) Making acronyms lowercase: + Rename `ClientError::ParseJSON` to `ClientError::ParseJson` + Rename `ClientError::ParseURL` to `ClientError::ParseUrl` + Rename `ClientError::IO` to `ClientError::Io` From 918c6ac6505aeffe6aa1940ab1bf943f7055e77b Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 19 Jun 2021 14:19:09 +0200 Subject: [PATCH 7/8] Update changelog after this PR --- CHANGELOG.md | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5903e75f..109b80ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -170,6 +170,7 @@ More in the [`examples` directory](https://github.com/ramsayleung/rspotify/tree/ - ([#188](https://github.com/ramsayleung/rspotify/pull/188)) Replace html links with intra-documentation links - ([#189](https://github.com/ramsayleung/rspotify/pull/189)) Add `scopes!` macro to generate scope for `Token` from string literal - Rspotify has now been split up into independent crates, so that it can be used without the client. See `rspotify-macros` and `rspotify-model`. +- ([#128](https://github.com/ramsayleung/rspotify/pull/128)) Reexport `model` module to allow user to write `rspotify::model::FullAlbum` instead of `rspotify::model::album::FullAlbum`. **Breaking changes:** - ([#202](https://github.com/ramsayleung/rspotify/pull/202)) Rspotify now consistently uses `Option` for optional parameters. Those generic over `Into>` have been changed, which makes calling endpoints a bit ugiler but more consistent and simpler. @@ -179,20 +180,8 @@ More in the [`examples` directory](https://github.com/ramsayleung/rspotify/tree/ - `TokenBuilder` and `OAuthBuilder` will only read from environment variables when `from_env` is used, instead of `default`. - `dotenv` support is now optional. You can enable it with the `env-file` feature to have the same behavior as before ([#108](https://github.com/ramsayleung/rspotify/issues/108)). It may be used with `from_env` as well. - Renamed environmental variables to `RSPOTIFY_CLIENT_ID`, `RSPOTIFY_CLIENT_SECRET` and `RSPOTIFY_REDIRECT_URI` to avoid name collisions with other libraries that use OAuth2 ([#118](https://github.com/ramsayleung/rspotify/issues/118)). -- A real builder pattern is used now. For example, `Token` is constructed now with `TokenBuilder::default().access_token("...").build().unwrap()`. This has been applied to `Spotify`, `OAuth`, `Token` and `Credentials` ([#129](https://github.com/ramsayleung/rspotify/pull/129)). - The `blocking` module has been removed, since Rspotify is able to use multiple HTTP clients now. `reqwest` and `ureq` are currently supported, meaning that you can still use blocking code by enabling the `client-ureq` feature and a TLS like `ureq-rustls-tls`. Read the docs for more information ([#129](https://github.com/ramsayleung/rspotify/pull/129)). -- The authentication process has been completely rewritten in order to make it more performant and robust. Please read the docs to learn more about how that works now ([#129](https://github.com/ramsayleung/rspotify/pull/129)). These are the main changes: - + `TokenInfo::get_cached_token` is now `TokenBuilder::from_cache` and `Spotify::read_token_cache` (using the internal cache path). Instead of panicking, the resulting `TokenBuilder` may be empty (and `build` will fail). - + `Spotify::save_token_info` is now `Token::write_cache` and `Spotify::write_token_cache`. The latter uses the client's set cache path for the write. These functions also now return `ClientResult` instead of panicking. - + `Spotify::is_token_expired` is now `Token::is_expired`. - + `SpotifyOAuth2::get_authorize_url` is now `Spotify::get_authorize_url`, and it returns `ClientResult` instead of panicking. - + `SpotifyOAuth2::refresh_access_token[_without_cache]` are now `Spotify::refresh_user_token[_with_cache]`. It returns `ClientResult<()>`, and the resulting token will be saved internally instead of returned. - + `SpotifyOAuth2::request_client_token[_without_cache]` are now `Spotify::request_client_token[_with_cache]`. It returns `ClientResult<()>`, and the resulting token will be saved internally instead of returned. - + `SpotifyOAuth2::get_access_token[_without_cache]` are now `Spotify::request_user_token[_with_cache]`. It returns `ClientResult<()>`, and the resulting token will be saved internally instead of returned. - + `get_token[_without_cache]` is now `Spotify::prompt_for_user_token[_without_cache]`. It returns `ClientResult<()>`, and the resulting token will be saved internally instead of returned. -- CLI-exclusive functions and are now optional under the `cli` feature: - + `Spotify::prompt_for_user_token[_without_cache]` - + The `ClientError::Cli` variant, for whenever user interaction goes wrong +- The `Spotify` client has been split up by authorization flows (`ClientCredsSpotify`, `AuthCodeSpotify`, `AuthCodePkceSpotify`), which allows us to remove the builder pattern. The authentication process has been rewritten. ([#216](https://github.com/ramsayleung/rspotify/pull/216)). - Fix typo in `user_playlist_remove_specific_occurrenes_of_tracks`, now it's `user_playlist_remove_specific_occurrences_of_tracks`. - ([#123](https://github.com/ramsayleung/rspotify/pull/123)) All fallible calls in the client return a `ClientError` rather than using `failure`. - ([#161](https://github.com/ramsayleung/rspotify/pull/161)) Endpoints taking `Vec/&[String]` as parameter have changed to `impl IntoIterator>`. @@ -230,10 +219,9 @@ More in the [`examples` directory](https://github.com/ramsayleung/rspotify/tree/ + `audio_analysis` -> `track_analysis` + `audio_features` -> `track_features` + `audios_features` -> `tracks_features` -- ([#128](https://github.com/ramsayleung/rspotify/pull/128)) Reexport `model` module to allow user to write `rspotify::model::FullAlbum` instead of `rspotify::model::album::FullAlbum`. - ([#128](https://github.com/ramsayleung/rspotify/pull/128)) Split single `senum.rs` file into a separate module named `enums` (which is more appropriate compared with `senum`) with three files `country.rs`, `types.rs`, `misc.rs`, and move `enums` module into `model` module, which should be part of the `model` module, check [enums mod.rs file](src/model/enums/mod.rs) for details. - ([#128](https://github.com/ramsayleung/rspotify/pull/128)) Refactor all enum files with `strum`, reduced boilerplate code. - + All enums don't have a method named `as_str()` anymore, by leveraging `strum`, it's easy to convert strings to enum variants based on their name, with method `to_string()`. + + All enums don't have a method named `as_str()` anymore, by leveraging `strum`, it's easy to convert strings to enum variants based on their name, with method `as_ref()`. - Fix typo in `transfer_playback`: `device_id` to `device_ids`. - ([#145](https://github.com/ramsayleung/rspotify/pull/145)) Refactor models to make it easier to use: + Changed type of `track` in `PlayHistory` to `FullTrack` ([#139](https://github.com/ramsayleung/rspotify/pull/139)). From b526ea07df598d9e55f665853142e6477d440c06 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 19 Jun 2021 15:01:41 +0200 Subject: [PATCH 8/8] using maybe_async::test --- tests/common/mod.rs | 5 - tests/test_oauth2.rs | 8 +- tests/test_with_credential.rs | 51 ++++------ tests/test_with_oauth.rs | 171 +++++++++++----------------------- 4 files changed, 73 insertions(+), 162 deletions(-) delete mode 100644 tests/common/mod.rs diff --git a/tests/common/mod.rs b/tests/common/mod.rs deleted file mode 100644 index 1a3d6cb3..00000000 --- a/tests/common/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[cfg(feature = "__sync")] -pub use test as maybe_async_test; - -#[cfg(feature = "__async")] -pub use tokio::test as maybe_async_test; diff --git a/tests/test_oauth2.rs b/tests/test_oauth2.rs index bcaeedbf..75356202 100644 --- a/tests/test_oauth2.rs +++ b/tests/test_oauth2.rs @@ -1,16 +1,11 @@ -mod common; - use chrono::prelude::*; use chrono::Duration; -use maybe_async::maybe_async; use rspotify::{ prelude::*, scopes, AuthCodeSpotify, ClientCredsSpotify, Config, Credentials, OAuth, Token, }; use std::{collections::HashMap, fs, io::Read, path::PathBuf, thread::sleep}; use url::Url; -use common::maybe_async_test; - #[test] fn test_get_authorize_url() { let oauth = OAuth { @@ -37,8 +32,7 @@ fn test_get_authorize_url() { assert_eq!(hash_query.get("state").unwrap(), "fdsafdsfa"); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] async fn test_read_token_cache() { let now = Utc::now(); let scope = scopes!("playlist-read-private", "playlist-read-collaborative"); diff --git a/tests/test_with_credential.rs b/tests/test_with_credential.rs index dfb3afa1..fa7706ad 100644 --- a/tests/test_with_credential.rs +++ b/tests/test_with_credential.rs @@ -1,6 +1,3 @@ -mod common; - -use common::maybe_async_test; use rspotify::{ model::{AlbumType, Country, Id, Market}, prelude::*, @@ -26,15 +23,13 @@ pub async fn creds_client() -> ClientCredsSpotify { spotify } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] async fn test_album() { let birdy_uri = Id::from_uri("spotify:album:0sNOF9WDwhWunNAHPD3Baj").unwrap(); creds_client().await.album(birdy_uri).await.unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] async fn test_albums() { let birdy_uri1 = Id::from_uri("spotify:album:41MnTivkwTO3UUJ8DrqEJJ").unwrap(); let birdy_uri2 = Id::from_uri("spotify:album:6JWc4iAiJ9FjyK0B59ABb4").unwrap(); @@ -43,8 +38,7 @@ async fn test_albums() { creds_client().await.albums(track_uris).await.unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] async fn test_album_tracks() { let birdy_uri = Id::from_uri("spotify:album:6akEvsycLGftJxYudPjmqK").unwrap(); creds_client() @@ -54,8 +48,7 @@ async fn test_album_tracks() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] async fn test_artist_related_artists() { let birdy_uri = Id::from_uri("spotify:artist:43ZHCT0cAZBISjO8DG9PnE").unwrap(); creds_client() @@ -65,15 +58,13 @@ async fn test_artist_related_artists() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] async fn test_artist() { let birdy_uri = Id::from_uri("spotify:artist:2WX2uTcsvV5OnS0inACecP").unwrap(); creds_client().await.artist(birdy_uri).await.unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] async fn test_artists_albums() { let birdy_uri = Id::from_uri("spotify:artist:2WX2uTcsvV5OnS0inACecP").unwrap(); creds_client() @@ -89,8 +80,7 @@ async fn test_artists_albums() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] async fn test_artists() { let birdy_uri1 = Id::from_uri("spotify:artist:0oSGxfWSnnOXhD2fKuz2Gy").unwrap(); let birdy_uri2 = Id::from_uri("spotify:artist:3dBVyJ7JuOMt4GE9607Qin").unwrap(); @@ -98,8 +88,7 @@ async fn test_artists() { creds_client().await.artists(artist_uris).await.unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] async fn test_artist_top_tracks() { let birdy_uri = Id::from_uri("spotify:artist:2WX2uTcsvV5OnS0inACecP").unwrap(); creds_client() @@ -109,22 +98,19 @@ async fn test_artist_top_tracks() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] async fn test_audio_analysis() { let track = Id::from_id("06AKEBrKUckW0KREUWRnvT").unwrap(); creds_client().await.track_analysis(track).await.unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] async fn test_audio_features() { let track = Id::from_uri("spotify:track:06AKEBrKUckW0KREUWRnvT").unwrap(); creds_client().await.track_features(track).await.unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] async fn test_audios_features() { let mut tracks_ids = vec![]; let track_id1 = Id::from_uri("spotify:track:4JpKVNYnVcJ8tuMKjAj50A").unwrap(); @@ -138,22 +124,19 @@ async fn test_audios_features() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] async fn test_user() { let birdy_uri = Id::from_id("tuggareutangranser").unwrap(); creds_client().await.user(birdy_uri).await.unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] async fn test_track() { let birdy_uri = Id::from_uri("spotify:track:6rqhFgbbKwnb9MLmUQDhG6").unwrap(); creds_client().await.track(birdy_uri).await.unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] async fn test_tracks() { let birdy_uri1 = Id::from_uri("spotify:track:3n3Ppam7vgaVa1iaRUc9Lp").unwrap(); let birdy_uri2 = Id::from_uri("spotify:track:3twNvmDtFQtAd5gMKedhLD").unwrap(); @@ -161,8 +144,7 @@ async fn test_tracks() { creds_client().await.tracks(track_uris, None).await.unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] async fn test_existing_playlist() { creds_client() .await @@ -171,8 +153,7 @@ async fn test_existing_playlist() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] async fn test_fake_playlist() { let playlist = creds_client() .await diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index 412dd7bd..e8beffe9 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -14,9 +14,6 @@ //! (these tokens must have been generated for all available scopes, see //! the `oauth_tokens` example). -mod common; - -use common::maybe_async_test; use rspotify::{ model::{ Country, EpisodeId, Id, Market, Offset, RepeatState, SearchType, ShowId, TimeRange, @@ -87,8 +84,7 @@ pub async fn oauth_client() -> AuthCodeSpotify { } } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_categories() { oauth_client() @@ -103,8 +99,7 @@ async fn test_categories() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_category_playlists() { oauth_client() @@ -119,8 +114,7 @@ async fn test_category_playlists() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_current_playback() { oauth_client() @@ -130,8 +124,7 @@ async fn test_current_playback() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_current_playing() { oauth_client() @@ -141,8 +134,7 @@ async fn test_current_playing() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_current_user_followed_artists() { oauth_client() @@ -152,8 +144,7 @@ async fn test_current_user_followed_artists() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_current_user_playing_track() { oauth_client() @@ -163,8 +154,7 @@ async fn test_current_user_playing_track() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_current_user_playlists() { oauth_client() @@ -174,8 +164,7 @@ async fn test_current_user_playlists() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_current_user_recently_played() { oauth_client() @@ -185,8 +174,7 @@ async fn test_current_user_recently_played() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_current_user_saved_albums_add() { let mut album_ids = vec![]; @@ -201,8 +189,7 @@ async fn test_current_user_saved_albums_add() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_current_user_saved_albums_delete() { let mut album_ids = vec![]; @@ -217,8 +204,7 @@ async fn test_current_user_saved_albums_delete() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_current_user_saved_albums() { oauth_client() @@ -228,8 +214,7 @@ async fn test_current_user_saved_albums() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_current_user_saved_tracks_add() { let mut tracks_ids = vec![]; @@ -244,8 +229,7 @@ async fn test_current_user_saved_tracks_add() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_current_user_saved_tracks_contains() { let mut tracks_ids = vec![]; @@ -260,8 +244,7 @@ async fn test_current_user_saved_tracks_contains() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_current_user_saved_tracks_delete() { let mut tracks_ids = vec![]; @@ -276,8 +259,7 @@ async fn test_current_user_saved_tracks_delete() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_current_user_saved_tracks() { oauth_client() @@ -287,8 +269,7 @@ async fn test_current_user_saved_tracks() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_current_user_top_artists() { oauth_client() @@ -298,8 +279,7 @@ async fn test_current_user_top_artists() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_current_user_top_tracks() { oauth_client() @@ -309,15 +289,13 @@ async fn test_current_user_top_tracks() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_device() { oauth_client().await.device().await.unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_featured_playlists() { let now: DateTime = Utc::now(); @@ -328,15 +306,13 @@ async fn test_featured_playlists() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_me() { oauth_client().await.me().await.unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_new_releases() { oauth_client() @@ -346,8 +322,7 @@ async fn test_new_releases() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_new_releases_with_from_token() { oauth_client() @@ -357,8 +332,7 @@ async fn test_new_releases_with_from_token() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_next_playback() { let device_id = "74ASZWbe4lXaubB36ztrGX"; @@ -369,8 +343,7 @@ async fn test_next_playback() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_pause_playback() { let device_id = "74ASZWbe4lXaubB36ztrGX"; @@ -381,8 +354,7 @@ async fn test_pause_playback() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_previous_playback() { let device_id = "74ASZWbe4lXaubB36ztrGX"; @@ -393,8 +365,7 @@ async fn test_previous_playback() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_recommendations() { let mut payload = Map::new(); @@ -416,8 +387,7 @@ async fn test_recommendations() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_repeat() { oauth_client() @@ -427,8 +397,7 @@ async fn test_repeat() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_search_album() { let query = "album:arrival artist:abba"; @@ -439,8 +408,7 @@ async fn test_search_album() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_search_artist() { let query = "tania bowra"; @@ -458,8 +426,7 @@ async fn test_search_artist() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_search_playlist() { let query = "\"doom metal\""; @@ -477,8 +444,7 @@ async fn test_search_playlist() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_search_track() { let query = "abba"; @@ -496,22 +462,19 @@ async fn test_search_track() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_seek_track() { oauth_client().await.seek_track(25000, None).await.unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_shuffle() { oauth_client().await.shuffle(true, None).await.unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_start_playback() { let device_id = "74ASZWbe4lXaubB36ztrGX"; @@ -523,8 +486,7 @@ async fn test_start_playback() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_transfer_playback() { let device_id = "74ASZWbe4lXaubB36ztrGX"; @@ -535,8 +497,7 @@ async fn test_transfer_playback() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_user_follow_artist() { let mut artists = vec![]; @@ -551,8 +512,7 @@ async fn test_user_follow_artist() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_user_unfollow_artist() { let mut artists = vec![]; @@ -567,8 +527,7 @@ async fn test_user_unfollow_artist() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_user_follow_users() { let mut users = vec![]; @@ -577,8 +536,7 @@ async fn test_user_follow_users() { oauth_client().await.user_follow_users(users).await.unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_user_unfollow_users() { let mut users = vec![]; @@ -591,8 +549,7 @@ async fn test_user_unfollow_users() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_playlist_add_tracks() { let playlist_id = Id::from_id("5jAOgWXCBKuinsGiZxjDQ5").unwrap(); @@ -608,8 +565,7 @@ async fn test_playlist_add_tracks() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_playlist_change_detail() { let playlist_id = "5jAOgWXCBKuinsGiZxjDQ5"; @@ -621,8 +577,7 @@ async fn test_playlist_change_detail() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_playlist_check_follow() { let playlist_id = Id::from_id("2v3iNvBX8Ay1Gt2uXtUKUT").unwrap(); @@ -638,8 +593,7 @@ async fn test_playlist_check_follow() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_user_playlist_create() { let user_id = Id::from_id("2257tjys2e2u2ygfke42niy2q").unwrap(); @@ -651,8 +605,7 @@ async fn test_user_playlist_create() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_playlist_follow_playlist() { let playlist_id = Id::from_id("2v3iNvBX8Ay1Gt2uXtUKUT").unwrap(); @@ -663,8 +616,7 @@ async fn test_playlist_follow_playlist() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_playlist_recorder_tracks() { let uris = Some(vec![EpisodeId::from_id("0lbiy3LKzIY2fnyjioC11p").unwrap()]); @@ -686,8 +638,7 @@ async fn test_playlist_recorder_tracks() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_playlist_remove_all_occurrences_of_tracks() { let playlist_id = Id::from_id("5jAOgWXCBKuinsGiZxjDQ5").unwrap(); @@ -703,8 +654,7 @@ async fn test_playlist_remove_all_occurrences_of_tracks() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_playlist_remove_specific_occurrences_of_tracks() { let playlist_id = Id::from_id("5jAOgWXCBKuinsGiZxjDQ5").unwrap(); @@ -724,8 +674,7 @@ async fn test_playlist_remove_specific_occurrences_of_tracks() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_playlist_replace_tracks() { let playlist_id = Id::from_id("5jAOgWXCBKuinsGiZxjDQ5").unwrap(); @@ -741,8 +690,7 @@ async fn test_playlist_replace_tracks() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_user_playlist() { let user_id = Id::from_id("spotify").unwrap(); @@ -754,8 +702,7 @@ async fn test_user_playlist() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_user_playlists() { let user_id = Id::from_id("2257tjys2e2u2ygfke42niy2q").unwrap(); @@ -766,8 +713,7 @@ async fn test_user_playlists() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_playlist_tracks() { let playlist_id = Id::from_uri("spotify:playlist:59ZbFPES4DQwEjBpWHzrtC").unwrap(); @@ -778,8 +724,7 @@ async fn test_playlist_tracks() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_playlist_unfollow() { let playlist_id = "65V6djkcVRyOStLd8nza8E"; @@ -790,15 +735,13 @@ async fn test_playlist_unfollow() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_volume() { oauth_client().await.volume(78, None).await.unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_add_queue() { let birdy_uri = TrackId::from_uri("spotify:track:6rqhFgbbKwnb9MLmUQDhG6").unwrap(); @@ -809,8 +752,7 @@ async fn test_add_queue() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_get_several_shows() { oauth_client() @@ -826,8 +768,7 @@ async fn test_get_several_shows() { .unwrap(); } -#[maybe_async] -#[maybe_async_test] +#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_get_several_episodes() { oauth_client()