diff --git a/CHANGELOG.md b/CHANGELOG.md index b9438d37..1bf368ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 0.11.7 (Unreleased) - ([#375](https://github.com/ramsayleung/rspotify/pull/375)) We now use `chrono::Duration` in more places for consistency and usability: `start_uris_playback`, `start_context_playback`, `rspotify_model::Offset`, `resume_playback`, `seek_track`. Some of these fields have been renamed from `position_ms` to `position`. +- ((#356)[https://github.com/ramsayleung/rspotify/pull/356]) We now support custom authentication base URLs. `Config::prefix` has been renamed to `Config::api_base_url`, and we've introduced `Config::auth_base_url`. ## 0.11.6 (2022.12.14) diff --git a/doc/uml/trait_hierarchy.plantuml b/doc/uml/trait_hierarchy.plantuml index 3006f254..9c6e0017 100644 --- a/doc/uml/trait_hierarchy.plantuml +++ b/doc/uml/trait_hierarchy.plantuml @@ -9,7 +9,8 @@ abstract class BaseClient { --- ClientResult<()> auto_reauth() ClientResult<()> refresh_token() - String endpoint_url() + String api_url() + String auth_url() ClientResult auth_headers() ClientResult<()> write_token_cache() ClientResult fetch_access_token() @@ -55,4 +56,4 @@ class ClientCredsSpotify implements BaseClient{ ClientResult> refetch_token() --- } -@enduml \ No newline at end of file +@enduml diff --git a/src/auth_code.rs b/src/auth_code.rs index fae538cc..c60f7dc7 100644 --- a/src/auth_code.rs +++ b/src/auth_code.rs @@ -202,7 +202,8 @@ impl AuthCodeSpotify { payload.insert(params::SHOW_DIALOG, "true"); } - let parsed = Url::parse_with_params(auth_urls::AUTHORIZE, payload)?; + let request_url = self.auth_url(auth_urls::AUTHORIZE); + let parsed = Url::parse_with_params(&request_url, payload)?; Ok(parsed.into()) } } diff --git a/src/auth_code_pkce.rs b/src/auth_code_pkce.rs index 35fc1b22..f883b111 100644 --- a/src/auth_code_pkce.rs +++ b/src/auth_code_pkce.rs @@ -204,7 +204,8 @@ impl AuthCodePkceSpotify { payload.insert(params::STATE, &self.oauth.state); payload.insert(params::SCOPE, &scopes); - let parsed = Url::parse_with_params(auth_urls::AUTHORIZE, payload)?; + let request_url = self.auth_url(auth_urls::AUTHORIZE); + let parsed = Url::parse_with_params(&request_url, payload)?; Ok(parsed.into()) } } diff --git a/src/clients/base.rs b/src/clients/base.rs index dc9f3262..6455fe6a 100644 --- a/src/clients/base.rs +++ b/src/clients/base.rs @@ -35,15 +35,22 @@ where /// be mutable (the token is accessed to from every endpoint). fn get_token(&self) -> Arc>>; - /// If it's a relative URL like "me", the prefix is appended to it. - /// Otherwise, the same URL is returned. - fn endpoint_url(&self, url: &str) -> String { - // Using the client's prefix in case it's a relative route. - if url.starts_with("http") { - url.to_string() - } else { - self.get_config().prefix.clone() + url + /// Returns the absolute URL for an endpoint in the API. + fn api_url(&self, url: &str) -> String { + let mut base = self.get_config().api_base_url.clone(); + if !base.ends_with('/') { + base.push('/'); + } + base + url + } + + /// Returns the absolute URL for an authentication step in the API. + fn auth_url(&self, url: &str) -> String { + let mut base = self.get_config().auth_base_url.clone(); + if !base.ends_with('/') { + base.push('/'); } + base + url } /// Refetch the current access token given a refresh token. @@ -100,109 +107,64 @@ where .auth_headers() } - // HTTP-related methods for the Spotify client. It wraps the basic HTTP - // client with features needed of higher level. - // - // The Spotify client has two different wrappers to perform requests: - // - // * Basic wrappers: `get`, `post`, `put`, `delete`, `post_form`. These only - // append the configured Spotify API URL to the relative URL provided so - // that it's not forgotten. They're used in the authentication process to - // request an access token and similars. - // * Endpoint wrappers: `endpoint_get`, `endpoint_post`, `endpoint_put`, - // `endpoint_delete`. These append the authentication headers for endpoint - // requests to reduce the code needed for endpoints and make them as - // concise as possible. + // HTTP-related methods for the Spotify client. They wrap up the basic HTTP + // client with its specific usage for endpoints or authentication. + /// Convenience method to send GET requests related to an endpoint in the + /// API. #[doc(hidden)] #[inline] - async fn get( - &self, - url: &str, - headers: Option<&Headers>, - payload: &Query<'_>, - ) -> ClientResult { - let url = self.endpoint_url(url); - Ok(self.get_http().get(&url, headers, payload).await?) + async fn api_get(&self, url: &str, payload: &Query<'_>) -> ClientResult { + let url = self.api_url(url); + let headers = self.auth_headers().await; + Ok(self.get_http().get(&url, Some(&headers), payload).await?) } + /// Convenience method to send POST requests related to an endpoint in the + /// API. #[doc(hidden)] #[inline] - async fn post( - &self, - url: &str, - headers: Option<&Headers>, - payload: &Value, - ) -> ClientResult { - let url = self.endpoint_url(url); - Ok(self.get_http().post(&url, headers, payload).await?) + async fn api_post(&self, url: &str, payload: &Value) -> ClientResult { + let url = self.api_url(url); + let headers = self.auth_headers().await; + Ok(self.get_http().post(&url, Some(&headers), payload).await?) } + /// Convenience method to send PUT requests related to an endpoint in the + /// API. #[doc(hidden)] #[inline] - async fn post_form( - &self, - url: &str, - headers: Option<&Headers>, - payload: &Form<'_>, - ) -> ClientResult { - let url = self.endpoint_url(url); - Ok(self.get_http().post_form(&url, headers, payload).await?) + async fn api_put(&self, url: &str, payload: &Value) -> ClientResult { + let url = self.api_url(url); + let headers = self.auth_headers().await; + Ok(self.get_http().put(&url, Some(&headers), payload).await?) } + /// Convenience method to send DELETE requests related to an endpoint in the + /// API. #[doc(hidden)] #[inline] - async fn put( - &self, - url: &str, - headers: Option<&Headers>, - payload: &Value, - ) -> ClientResult { - let url = self.endpoint_url(url); - Ok(self.get_http().put(&url, headers, payload).await?) + async fn api_delete(&self, url: &str, payload: &Value) -> ClientResult { + let url = self.api_url(url); + let headers = self.auth_headers().await; + Ok(self + .get_http() + .delete(&url, Some(&headers), payload) + .await?) } + /// Convenience method to send POST requests related to the authentication + /// process. #[doc(hidden)] #[inline] - async fn delete( + async fn auth_post( &self, url: &str, headers: Option<&Headers>, - payload: &Value, + payload: &Form<'_>, ) -> ClientResult { - let url = self.endpoint_url(url); - Ok(self.get_http().delete(&url, headers, payload).await?) - } - - // The wrappers for the endpoints, which also includes the required - // autentication. - - #[doc(hidden)] - #[inline] - async fn endpoint_get(&self, url: &str, payload: &Query<'_>) -> ClientResult { - let headers = self.auth_headers().await; - self.get(url, Some(&headers), payload).await - } - - #[doc(hidden)] - #[inline] - async fn endpoint_post(&self, url: &str, payload: &Value) -> ClientResult { - let headers = self.auth_headers().await; - self.post(url, Some(&headers), payload).await - } - - #[doc(hidden)] - #[inline] - async fn endpoint_put(&self, url: &str, payload: &Value) -> ClientResult { - let headers = self.auth_headers().await; - self.put(url, Some(&headers), payload).await - } - - #[doc(hidden)] - #[inline] - async fn endpoint_delete(&self, url: &str, payload: &Value) -> ClientResult { - let headers = self.auth_headers().await; - self.delete(url, Some(&headers), payload).await + let url = self.auth_url(url); + Ok(self.get_http().post_form(&url, headers, payload).await?) } /// Updates the cache file at the internal cache path. @@ -230,7 +192,7 @@ where payload: &Form<'_>, headers: Option<&Headers>, ) -> ClientResult { - let response = self.post_form(auth_urls::TOKEN, headers, payload).await?; + let response = self.auth_post(auth_urls::TOKEN, headers, payload).await?; let mut tok = serde_json::from_str::(&response)?; tok.expires_at = Utc::now().checked_add_signed(tok.expires_in); @@ -245,7 +207,7 @@ where /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/get-track) async fn track(&self, track_id: TrackId<'_>) -> ClientResult { let url = format!("tracks/{}", track_id.id()); - let result = self.endpoint_get(&url, &Query::new()).await?; + let result = self.api_get(&url, &Query::new()).await?; convert_result(&result) } @@ -265,7 +227,7 @@ where let params = build_map([("market", market.map(Into::into))]); let url = format!("tracks/?ids={ids}"); - let result = self.endpoint_get(&url, ¶ms).await?; + let result = self.api_get(&url, ¶ms).await?; convert_result::(&result).map(|x| x.tracks) } @@ -277,7 +239,7 @@ where /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/get-an-artist) async fn artist(&self, artist_id: ArtistId<'_>) -> ClientResult { let url = format!("artists/{}", artist_id.id()); - let result = self.endpoint_get(&url, &Query::new()).await?; + let result = self.api_get(&url, &Query::new()).await?; convert_result(&result) } @@ -293,7 +255,7 @@ where ) -> ClientResult> { let ids = join_ids(artist_ids); let url = format!("artists/?ids={ids}"); - let result = self.endpoint_get(&url, &Query::new()).await?; + let result = self.api_get(&url, &Query::new()).await?; convert_result::(&result).map(|x| x.artists) } @@ -351,7 +313,7 @@ where ]); let url = format!("artists/{}/albums", artist_id.id()); - let result = self.endpoint_get(&url, ¶ms).await?; + let result = self.api_get(&url, ¶ms).await?; convert_result(&result) } @@ -371,7 +333,7 @@ where let params = build_map([("market", Some(market.into()))]); let url = format!("artists/{}/top-tracks", artist_id.id()); - let result = self.endpoint_get(&url, ¶ms).await?; + let result = self.api_get(&url, ¶ms).await?; convert_result::(&result).map(|x| x.tracks) } @@ -388,7 +350,7 @@ where artist_id: ArtistId<'_>, ) -> ClientResult> { let url = format!("artists/{}/related-artists", artist_id.id()); - let result = self.endpoint_get(&url, &Query::new()).await?; + let result = self.api_get(&url, &Query::new()).await?; convert_result::(&result).map(|x| x.artists) } @@ -401,7 +363,7 @@ where async fn album(&self, album_id: AlbumId<'_>) -> ClientResult { let url = format!("albums/{}", album_id.id()); - let result = self.endpoint_get(&url, &Query::new()).await?; + let result = self.api_get(&url, &Query::new()).await?; convert_result(&result) } @@ -417,7 +379,7 @@ where ) -> ClientResult> { let ids = join_ids(album_ids); let url = format!("albums/?ids={ids}"); - let result = self.endpoint_get(&url, &Query::new()).await?; + let result = self.api_get(&url, &Query::new()).await?; convert_result::(&result).map(|x| x.albums) } @@ -456,7 +418,7 @@ where ("offset", offset.as_deref()), ]); - let result = self.endpoint_get("search", ¶ms).await?; + let result = self.api_get("search", ¶ms).await?; convert_result(&result) } @@ -496,7 +458,7 @@ where let params = build_map([("limit", limit.as_deref()), ("offset", offset.as_deref())]); let url = format!("albums/{}/tracks", album_id.id()); - let result = self.endpoint_get(&url, ¶ms).await?; + let result = self.api_get(&url, ¶ms).await?; convert_result(&result) } @@ -508,7 +470,7 @@ where /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/get-users-profile) async fn user(&self, user_id: UserId<'_>) -> ClientResult { let url = format!("users/{}", user_id.id()); - let result = self.endpoint_get(&url, &Query::new()).await?; + let result = self.api_get(&url, &Query::new()).await?; convert_result(&result) } @@ -528,7 +490,7 @@ where let params = build_map([("fields", fields), ("market", market.map(Into::into))]); let url = format!("playlists/{}", playlist_id.id()); - let result = self.endpoint_get(&url, ¶ms).await?; + let result = self.api_get(&url, ¶ms).await?; convert_result(&result) } @@ -552,7 +514,7 @@ where Some(playlist_id) => format!("users/{}/playlists/{}", user_id.id(), playlist_id.id()), None => format!("users/{}/starred", user_id.id()), }; - let result = self.endpoint_get(&url, ¶ms).await?; + let result = self.api_get(&url, ¶ms).await?; convert_result(&result) } @@ -578,7 +540,7 @@ where playlist_id.id(), user_ids.iter().map(Id::id).collect::>().join(","), ); - let result = self.endpoint_get(&url, &Query::new()).await?; + let result = self.api_get(&url, &Query::new()).await?; convert_result(&result) } @@ -595,7 +557,7 @@ where let params = build_map([("market", market.map(Into::into))]); let url = format!("shows/{}", id.id()); - let result = self.endpoint_get(&url, ¶ms).await?; + let result = self.api_get(&url, ¶ms).await?; convert_result(&result) } @@ -615,7 +577,7 @@ where let ids = join_ids(ids); let params = build_map([("ids", Some(&ids)), ("market", market.map(Into::into))]); - let result = self.endpoint_get("shows", ¶ms).await?; + let result = self.api_get("shows", ¶ms).await?; convert_result::(&result).map(|x| x.shows) } @@ -665,7 +627,7 @@ where ]); let url = format!("shows/{}/episodes", id.id()); - let result = self.endpoint_get(&url, ¶ms).await?; + let result = self.api_get(&url, ¶ms).await?; convert_result(&result) } @@ -686,7 +648,7 @@ where let url = format!("episodes/{}", id.id()); let params = build_map([("market", market.map(Into::into))]); - let result = self.endpoint_get(&url, ¶ms).await?; + let result = self.api_get(&url, ¶ms).await?; convert_result(&result) } @@ -705,7 +667,7 @@ where let ids = join_ids(ids); let params = build_map([("ids", Some(&ids)), ("market", market.map(Into::into))]); - let result = self.endpoint_get("episodes", ¶ms).await?; + let result = self.api_get("episodes", ¶ms).await?; convert_result::(&result).map(|x| x.episodes) } @@ -717,7 +679,7 @@ where /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/get-audio-features) async fn track_features(&self, track_id: TrackId<'_>) -> ClientResult { let url = format!("audio-features/{}", track_id.id()); - let result = self.endpoint_get(&url, &Query::new()).await?; + let result = self.api_get(&url, &Query::new()).await?; convert_result(&result) } @@ -733,7 +695,7 @@ where ) -> ClientResult>> { let url = format!("audio-features/?ids={}", join_ids(track_ids)); - let result = self.endpoint_get(&url, &Query::new()).await?; + let result = self.api_get(&url, &Query::new()).await?; if result.is_empty() { Ok(None) } else { @@ -750,7 +712,7 @@ where /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/get-audio-analysis) async fn track_analysis(&self, track_id: TrackId<'_>) -> ClientResult { let url = format!("audio-analysis/{}", track_id.id()); - let result = self.endpoint_get(&url, &Query::new()).await?; + let result = self.api_get(&url, &Query::new()).await?; convert_result(&result) } @@ -796,7 +758,7 @@ where ("limit", limit.as_deref()), ("offset", offset.as_deref()), ]); - let result = self.endpoint_get("browse/categories", ¶ms).await?; + let result = self.api_get("browse/categories", ¶ms).await?; convert_result::(&result).map(|x| x.categories) } @@ -844,7 +806,7 @@ where ]); let url = format!("browse/categories/{category_id}/playlists"); - let result = self.endpoint_get(&url, ¶ms).await?; + let result = self.api_get(&url, ¶ms).await?; convert_result::(&result).map(|x| x.playlists) } @@ -884,9 +846,7 @@ where ("offset", offset.as_deref()), ]); - let result = self - .endpoint_get("browse/featured-playlists", ¶ms) - .await?; + let result = self.api_get("browse/featured-playlists", ¶ms).await?; convert_result(&result) } @@ -928,7 +888,7 @@ where ("offset", offset.as_deref()), ]); - let result = self.endpoint_get("browse/new-releases", ¶ms).await?; + let result = self.api_get("browse/new-releases", ¶ms).await?; convert_result::(&result).map(|x| x.albums) } @@ -983,7 +943,7 @@ where // And finally adding all of them to the payload params.extend(borrowed_attributes); - let result = self.endpoint_get("recommendations", ¶ms).await?; + let result = self.api_get("recommendations", ¶ms).await?; convert_result(&result) } @@ -1040,7 +1000,7 @@ where ]); let url = format!("playlists/{}/tracks", playlist_id.id()); - let result = self.endpoint_get(&url, ¶ms).await?; + let result = self.api_get(&url, ¶ms).await?; convert_result(&result) } @@ -1080,7 +1040,7 @@ where let params = build_map([("limit", limit.as_deref()), ("offset", offset.as_deref())]); let url = format!("users/{}/playlists", user_id.id()); - let result = self.endpoint_get(&url, ¶ms).await?; + let result = self.api_get(&url, ¶ms).await?; convert_result(&result) } } diff --git a/src/clients/mod.rs b/src/clients/mod.rs index c2f3e3ea..65117abc 100644 --- a/src/clients/mod.rs +++ b/src/clients/mod.rs @@ -32,7 +32,7 @@ pub(crate) fn append_device_id(path: &str, device_id: Option<&str>) -> String { #[cfg(test)] mod test { use super::*; - use crate::{model::Token, scopes, ClientCredsSpotify}; + use crate::{model::Token, scopes, ClientCredsSpotify, Config}; use chrono::{prelude::*, Duration}; #[test] @@ -55,19 +55,58 @@ mod test { } #[test] - fn test_endpoint_url() { - let spotify = ClientCredsSpotify::default(); + fn test_api_url() { + let mut spotify = ClientCredsSpotify::default(); assert_eq!( - spotify.endpoint_url("me/player/play"), + spotify.api_url("me/player/play"), "https://api.spotify.com/v1/me/player/play" ); + + spotify.config = Config { + api_base_url: String::from("http://localhost:8080/api/v1/"), + ..Default::default() + }; assert_eq!( - spotify.endpoint_url("http://api.spotify.com/v1/me/player/play"), - "http://api.spotify.com/v1/me/player/play" + spotify.api_url("me/player/play"), + "http://localhost:8080/api/v1/me/player/play" ); + + // Also works without trailing character + spotify.config = Config { + api_base_url: String::from("http://localhost:8080/api/v1"), + ..Default::default() + }; assert_eq!( - spotify.endpoint_url("https://api.spotify.com/v1/me/player/play"), - "https://api.spotify.com/v1/me/player/play" + spotify.api_url("me/player/play"), + "http://localhost:8080/api/v1/me/player/play" + ); + } + + #[test] + fn test_auth_url() { + let mut spotify = ClientCredsSpotify::default(); + assert_eq!( + spotify.auth_url("api/token"), + "https://accounts.spotify.com/api/token" + ); + + spotify.config = Config { + auth_base_url: String::from("http://localhost:8080/accounts/"), + ..Default::default() + }; + assert_eq!( + spotify.auth_url("api/token"), + "http://localhost:8080/accounts/api/token" + ); + + // Also works without trailing character + spotify.config = Config { + auth_base_url: String::from("http://localhost:8080/accounts"), + ..Default::default() + }; + assert_eq!( + spotify.auth_url("api/token"), + "http://localhost:8080/accounts/api/token" ); } diff --git a/src/clients/oauth.rs b/src/clients/oauth.rs index 8304476b..80d74a4c 100644 --- a/src/clients/oauth.rs +++ b/src/clients/oauth.rs @@ -206,7 +206,7 @@ pub trait OAuthClient: BaseClient { let offset = offset.map(|s| s.to_string()); let params = build_map([("limit", limit.as_deref()), ("offset", offset.as_deref())]); - let result = self.endpoint_get("me/playlists", ¶ms).await?; + let result = self.api_get("me/playlists", ¶ms).await?; convert_result(&result) } @@ -243,7 +243,7 @@ pub trait OAuthClient: BaseClient { .build(); let url = format!("users/{}/playlists", user_id.id()); - let result = self.endpoint_post(&url, ¶ms).await?; + let result = self.api_post(&url, ¶ms).await?; convert_result(&result) } @@ -273,7 +273,7 @@ pub trait OAuthClient: BaseClient { .build(); let url = format!("playlists/{}", playlist_id.id()); - self.endpoint_put(&url, ¶ms).await + self.api_put(&url, ¶ms).await } /// Unfollows (deletes) a playlist for a user. @@ -284,7 +284,7 @@ pub trait OAuthClient: BaseClient { /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/unfollow-playlist) async fn playlist_unfollow(&self, playlist_id: PlaylistId<'_>) -> ClientResult<()> { let url = format!("playlists/{}/followers", playlist_id.id()); - self.endpoint_delete(&url, &json!({})).await?; + self.api_delete(&url, &json!({})).await?; Ok(()) } @@ -310,7 +310,7 @@ pub trait OAuthClient: BaseClient { .build(); let url = format!("playlists/{}/tracks", playlist_id.id()); - let result = self.endpoint_post(&url, ¶ms).await?; + let result = self.api_post(&url, ¶ms).await?; convert_result(&result) } @@ -331,7 +331,7 @@ pub trait OAuthClient: BaseClient { let params = JsonBuilder::new().required("uris", uris).build(); let url = format!("playlists/{}/tracks", playlist_id.id()); - self.endpoint_put(&url, ¶ms).await?; + self.api_put(&url, ¶ms).await?; Ok(()) } @@ -364,7 +364,7 @@ pub trait OAuthClient: BaseClient { .build(); let url = format!("playlists/{}/tracks", playlist_id.id()); - let result = self.endpoint_put(&url, ¶ms).await?; + let result = self.api_put(&url, ¶ms).await?; convert_result(&result) } @@ -397,7 +397,7 @@ pub trait OAuthClient: BaseClient { .build(); let url = format!("playlists/{}/tracks", playlist_id.id()); - let result = self.endpoint_delete(&url, ¶ms).await?; + let result = self.api_delete(&url, ¶ms).await?; convert_result(&result) } @@ -452,7 +452,7 @@ pub trait OAuthClient: BaseClient { .build(); let url = format!("playlists/{}/tracks", playlist_id.id()); - let result = self.endpoint_delete(&url, ¶ms).await?; + let result = self.api_delete(&url, ¶ms).await?; convert_result(&result) } @@ -471,7 +471,7 @@ pub trait OAuthClient: BaseClient { let params = JsonBuilder::new().optional("public", public).build(); - self.endpoint_put(&url, ¶ms).await?; + self.api_put(&url, ¶ms).await?; Ok(()) } @@ -481,7 +481,7 @@ pub trait OAuthClient: BaseClient { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/get-current-users-profile) async fn me(&self) -> ClientResult { - let result = self.endpoint_get("me/", &Query::new()).await?; + let result = self.api_get("me/", &Query::new()).await?; convert_result(&result) } @@ -498,7 +498,7 @@ pub trait OAuthClient: BaseClient { /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/get-the-users-currently-playing-track) async fn current_user_playing_item(&self) -> ClientResult> { let result = self - .endpoint_get("me/player/currently-playing", &Query::new()) + .api_get("me/player/currently-playing", &Query::new()) .await?; if result.is_empty() { Ok(None) @@ -546,7 +546,7 @@ pub trait OAuthClient: BaseClient { ("offset", offset.as_deref()), ]); - let result = self.endpoint_get("me/albums", ¶ms).await?; + let result = self.api_get("me/albums", ¶ms).await?; convert_result(&result) } @@ -589,7 +589,7 @@ pub trait OAuthClient: BaseClient { ("offset", offset.as_deref()), ]); - let result = self.endpoint_get("me/tracks", ¶ms).await?; + let result = self.api_get("me/tracks", ¶ms).await?; convert_result(&result) } @@ -612,7 +612,7 @@ pub trait OAuthClient: BaseClient { ("limit", limit.as_deref()), ]); - let result = self.endpoint_get("me/following", ¶ms).await?; + let result = self.api_get("me/following", ¶ms).await?; convert_result::(&result).map(|x| x.artists) } @@ -627,7 +627,7 @@ pub trait OAuthClient: BaseClient { track_ids: impl IntoIterator> + Send + 'a, ) -> ClientResult<()> { let url = format!("me/tracks/?ids={}", join_ids(track_ids)); - self.endpoint_delete(&url, &json!({})).await?; + self.api_delete(&url, &json!({})).await?; Ok(()) } @@ -644,7 +644,7 @@ pub trait OAuthClient: BaseClient { track_ids: impl IntoIterator> + Send + 'a, ) -> ClientResult> { let url = format!("me/tracks/contains/?ids={}", join_ids(track_ids)); - let result = self.endpoint_get(&url, &Query::new()).await?; + let result = self.api_get(&url, &Query::new()).await?; convert_result(&result) } @@ -659,7 +659,7 @@ pub trait OAuthClient: BaseClient { track_ids: impl IntoIterator> + Send + 'a, ) -> ClientResult<()> { let url = format!("me/tracks/?ids={}", join_ids(track_ids)); - self.endpoint_put(&url, &json!({})).await?; + self.api_put(&url, &json!({})).await?; Ok(()) } @@ -702,7 +702,7 @@ pub trait OAuthClient: BaseClient { ("offset", offset.as_deref()), ]); - let result = self.endpoint_get("me/top/artists", ¶ms).await?; + let result = self.api_get("me/top/artists", ¶ms).await?; convert_result(&result) } @@ -744,7 +744,7 @@ pub trait OAuthClient: BaseClient { ("offset", offset.as_deref()), ]); - let result = self.endpoint_get("me/top/tracks", ¶ms).await?; + let result = self.api_get("me/top/tracks", ¶ms).await?; convert_result(&result) } @@ -773,9 +773,7 @@ pub trait OAuthClient: BaseClient { params.insert(name, value); } - let result = self - .endpoint_get("me/player/recently-played", ¶ms) - .await?; + let result = self.api_get("me/player/recently-played", ¶ms).await?; convert_result(&result) } @@ -790,7 +788,7 @@ pub trait OAuthClient: BaseClient { album_ids: impl IntoIterator> + Send + 'a, ) -> ClientResult<()> { let url = format!("me/albums/?ids={}", join_ids(album_ids)); - self.endpoint_put(&url, &json!({})).await?; + self.api_put(&url, &json!({})).await?; Ok(()) } @@ -806,7 +804,7 @@ pub trait OAuthClient: BaseClient { album_ids: impl IntoIterator> + Send + 'a, ) -> ClientResult<()> { let url = format!("me/albums/?ids={}", join_ids(album_ids)); - self.endpoint_delete(&url, &json!({})).await?; + self.api_delete(&url, &json!({})).await?; Ok(()) } @@ -823,7 +821,7 @@ pub trait OAuthClient: BaseClient { album_ids: impl IntoIterator> + Send + 'a, ) -> ClientResult> { let url = format!("me/albums/contains/?ids={}", join_ids(album_ids)); - let result = self.endpoint_get(&url, &Query::new()).await?; + let result = self.api_get(&url, &Query::new()).await?; convert_result(&result) } @@ -838,7 +836,7 @@ pub trait OAuthClient: BaseClient { artist_ids: impl IntoIterator> + Send + 'a, ) -> ClientResult<()> { let url = format!("me/following?type=artist&ids={}", join_ids(artist_ids)); - self.endpoint_put(&url, &json!({})).await?; + self.api_put(&url, &json!({})).await?; Ok(()) } @@ -854,7 +852,7 @@ pub trait OAuthClient: BaseClient { artist_ids: impl IntoIterator> + Send + 'a, ) -> ClientResult<()> { let url = format!("me/following?type=artist&ids={}", join_ids(artist_ids)); - self.endpoint_delete(&url, &json!({})).await?; + self.api_delete(&url, &json!({})).await?; Ok(()) } @@ -874,7 +872,7 @@ pub trait OAuthClient: BaseClient { "me/following/contains?type=artist&ids={}", join_ids(artist_ids) ); - let result = self.endpoint_get(&url, &Query::new()).await?; + let result = self.api_get(&url, &Query::new()).await?; convert_result(&result) } @@ -889,7 +887,7 @@ pub trait OAuthClient: BaseClient { user_ids: impl IntoIterator> + Send + 'a, ) -> ClientResult<()> { let url = format!("me/following?type=user&ids={}", join_ids(user_ids)); - self.endpoint_put(&url, &json!({})).await?; + self.api_put(&url, &json!({})).await?; Ok(()) } @@ -905,7 +903,7 @@ pub trait OAuthClient: BaseClient { user_ids: impl IntoIterator> + Send + 'a, ) -> ClientResult<()> { let url = format!("me/following?type=user&ids={}", join_ids(user_ids)); - self.endpoint_delete(&url, &json!({})).await?; + self.api_delete(&url, &json!({})).await?; Ok(()) } @@ -914,9 +912,7 @@ pub trait OAuthClient: BaseClient { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/get-a-users-available-devices) async fn device(&self) -> ClientResult> { - let result = self - .endpoint_get("me/player/devices", &Query::new()) - .await?; + let result = self.api_get("me/player/devices", &Query::new()).await?; convert_result::(&result).map(|x| x.devices) } @@ -945,7 +941,7 @@ pub trait OAuthClient: BaseClient { ("additional_types", additional_types.as_deref()), ]); - let result = self.endpoint_get("me/player", ¶ms).await?; + let result = self.api_get("me/player", ¶ms).await?; if result.is_empty() { Ok(None) } else { @@ -978,9 +974,7 @@ pub trait OAuthClient: BaseClient { ("additional_types", additional_types.as_deref()), ]); - let result = self - .endpoint_get("me/player/currently-playing", ¶ms) - .await?; + let result = self.api_get("me/player/currently-playing", ¶ms).await?; if result.is_empty() { Ok(None) } else { @@ -993,7 +987,7 @@ pub trait OAuthClient: BaseClient { /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/get-queue) async fn current_user_queue(&self) -> ClientResult { let params = build_map([]); - let result = self.endpoint_get("me/player/queue", ¶ms).await?; + let result = self.api_get("me/player/queue", ¶ms).await?; convert_result(&result) } @@ -1013,7 +1007,7 @@ pub trait OAuthClient: BaseClient { .optional("play", play) .build(); - self.endpoint_put("me/player", ¶ms).await?; + self.api_put("me/player", ¶ms).await?; Ok(()) } @@ -1054,7 +1048,7 @@ pub trait OAuthClient: BaseClient { .build(); let url = append_device_id("me/player/play", device_id); - self.endpoint_put(&url, ¶ms).await?; + self.api_put(&url, ¶ms).await?; Ok(()) } @@ -1093,7 +1087,7 @@ pub trait OAuthClient: BaseClient { .build(); let url = append_device_id("me/player/play", device_id); - self.endpoint_put(&url, ¶ms).await?; + self.api_put(&url, ¶ms).await?; Ok(()) } @@ -1106,7 +1100,7 @@ pub trait OAuthClient: BaseClient { /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/pause-a-users-playback) async fn pause_playback(&self, device_id: Option<&str>) -> ClientResult<()> { let url = append_device_id("me/player/pause", device_id); - self.endpoint_put(&url, &json!({})).await?; + self.api_put(&url, &json!({})).await?; Ok(()) } @@ -1128,7 +1122,7 @@ pub trait OAuthClient: BaseClient { .build(); let url = append_device_id("me/player/play", device_id); - self.endpoint_put(&url, ¶ms).await?; + self.api_put(&url, ¶ms).await?; Ok(()) } @@ -1141,7 +1135,7 @@ pub trait OAuthClient: BaseClient { /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/skip-users-playback-to-next-track) async fn next_track(&self, device_id: Option<&str>) -> ClientResult<()> { let url = append_device_id("me/player/next", device_id); - self.endpoint_post(&url, &json!({})).await?; + self.api_post(&url, &json!({})).await?; Ok(()) } @@ -1154,7 +1148,7 @@ pub trait OAuthClient: BaseClient { /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/skip-users-playback-to-previous-track) async fn previous_track(&self, device_id: Option<&str>) -> ClientResult<()> { let url = append_device_id("me/player/previous", device_id); - self.endpoint_post(&url, &json!({})).await?; + self.api_post(&url, &json!({})).await?; Ok(()) } @@ -1175,7 +1169,7 @@ pub trait OAuthClient: BaseClient { &format!("me/player/seek?position_ms={}", position.num_milliseconds()), device_id, ); - self.endpoint_put(&url, &json!({})).await?; + self.api_put(&url, &json!({})).await?; Ok(()) } @@ -1192,7 +1186,7 @@ pub trait OAuthClient: BaseClient { &format!("me/player/repeat?state={}", <&str>::from(state)), device_id, ); - self.endpoint_put(&url, &json!({})).await?; + self.api_put(&url, &json!({})).await?; Ok(()) } @@ -1213,7 +1207,7 @@ pub trait OAuthClient: BaseClient { &format!("me/player/volume?volume_percent={volume_percent}"), device_id, ); - self.endpoint_put(&url, &json!({})).await?; + self.api_put(&url, &json!({})).await?; Ok(()) } @@ -1227,7 +1221,7 @@ pub trait OAuthClient: BaseClient { /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/toggle-shuffle-for-users-playback) async fn shuffle(&self, state: bool, device_id: Option<&str>) -> ClientResult<()> { let url = append_device_id(&format!("me/player/shuffle?state={state}"), device_id); - self.endpoint_put(&url, &json!({})).await?; + self.api_put(&url, &json!({})).await?; Ok(()) } @@ -1247,7 +1241,7 @@ pub trait OAuthClient: BaseClient { device_id: Option<&str>, ) -> ClientResult<()> { let url = append_device_id(&format!("me/player/queue?uri={}", item.uri()), device_id); - self.endpoint_post(&url, &json!({})).await?; + self.api_post(&url, &json!({})).await?; Ok(()) } @@ -1264,7 +1258,7 @@ pub trait OAuthClient: BaseClient { show_ids: impl IntoIterator> + Send + 'a, ) -> ClientResult<()> { let url = format!("me/shows/?ids={}", join_ids(show_ids)); - self.endpoint_put(&url, &json!({})).await?; + self.api_put(&url, &json!({})).await?; Ok(()) } @@ -1299,7 +1293,7 @@ pub trait OAuthClient: BaseClient { let offset = offset.map(|x| x.to_string()); let params = build_map([("limit", limit.as_deref()), ("offset", offset.as_deref())]); - let result = self.endpoint_get("me/shows", ¶ms).await?; + let result = self.api_get("me/shows", ¶ms).await?; convert_result(&result) } @@ -1315,7 +1309,7 @@ pub trait OAuthClient: BaseClient { ) -> ClientResult> { let ids = join_ids(ids); let params = build_map([("ids", Some(&ids))]); - let result = self.endpoint_get("me/shows/contains", ¶ms).await?; + let result = self.api_get("me/shows/contains", ¶ms).await?; convert_result(&result) } @@ -1336,7 +1330,7 @@ pub trait OAuthClient: BaseClient { let params = JsonBuilder::new() .optional("country", country.map(<&str>::from)) .build(); - self.endpoint_delete(&url, ¶ms).await?; + self.api_delete(&url, ¶ms).await?; Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 2297d145..54db7851 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -185,8 +185,8 @@ pub(crate) mod alphabets { } pub(crate) mod auth_urls { - pub const AUTHORIZE: &str = "https://accounts.spotify.com/authorize"; - pub const TOKEN: &str = "https://accounts.spotify.com/api/token"; + pub const AUTHORIZE: &str = "authorize"; + pub const TOKEN: &str = "api/token"; } /// Possible errors returned from the `rspotify` client. @@ -227,15 +227,19 @@ impl From for ClientError { pub type ClientResult = Result; -pub const DEFAULT_API_PREFIX: &str = "https://api.spotify.com/v1/"; +pub const DEFAULT_API_BASE_URL: &str = "https://api.spotify.com/v1/"; +pub const DEFAULT_AUTH_BASE_URL: &str = "https://accounts.spotify.com/"; pub const DEFAULT_CACHE_PATH: &str = ".spotify_token_cache.json"; pub const DEFAULT_PAGINATION_CHUNKS: u32 = 50; /// Struct to configure the Spotify client. #[derive(Debug, Clone)] pub struct Config { - /// The Spotify API prefix, [`DEFAULT_API_PREFIX`] by default. - pub prefix: String, + /// The Spotify API prefix, [`DEFAULT_API_BASE_URL`] by default. + pub api_base_url: String, + + /// The Spotify Authentication prefix, [`DEFAULT_AUTH_BASE_URL`] by default. + pub auth_base_url: String, /// The cache file path, in case it's used. By default it's /// [`DEFAULT_CACHE_PATH`] @@ -263,7 +267,8 @@ pub struct Config { impl Default for Config { fn default() -> Self { Self { - prefix: String::from(DEFAULT_API_PREFIX), + api_base_url: String::from(DEFAULT_API_BASE_URL), + auth_base_url: String::from(DEFAULT_AUTH_BASE_URL), cache_path: PathBuf::from(DEFAULT_CACHE_PATH), pagination_chunks: DEFAULT_PAGINATION_CHUNKS, token_cached: false,