From 5e210004f8fda311751d5f845e47efc538d13981 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Mon, 19 Apr 2021 21:44:43 +0200 Subject: [PATCH 01/39] lots of unwrap_or removed --- CHANGELOG.md | 3 + src/client.rs | 212 ++++++++++++++++++++++++++++++++------------------ 2 files changed, 139 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cc3602a..524d4bdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -210,6 +210,9 @@ If we missed any change or there's something you'd like to discuss about this ve As a side effect, some methods now take references instead of values (so that they can be used multiple times when querying), and the parameters have been reordered so that the `limit` and `offset` are consistently the last two. The pagination chunk size can be configured with the `Spotify::pagination_chunks` field, which is set to 50 items by default. +- No default values are set from Rspotify now, they will be left to the Spotify API. +- []() Add a `collaborative` parameter to `user_playlist_create`. +- []() Add a `uris` parameter to `playlist_reorder_tracks`. ## 0.10 (2020/07/01) diff --git a/src/client.rs b/src/client.rs index fa8d6319..43fccae6 100644 --- a/src/client.rs +++ b/src/client.rs @@ -274,20 +274,20 @@ impl Spotify { offset: Option, ) -> ClientResult> { let mut params = Query::new(); + if let Some(ref album_type) = album_type { + params.insert("album_type", album_type.as_ref()); + } + if let Some(ref market) = market { + params.insert("market", market.as_ref()); + } let limit = limit.map(|x| x.to_string()); if let Some(ref limit) = limit { params.insert("limit", limit); } - if let Some(ref album_type) = album_type { - params.insert("album_type", album_type.as_ref()); - } let offset = offset.map(|x| x.to_string()); if let Some(ref offset) = offset { params.insert("offset", offset); } - if let Some(ref market) = market { - params.insert("market", market.as_ref()); - } let url = format!("artists/{}/albums", artist_id.id()); let result = self.endpoint_get(&url, ¶ms).await?; self.convert_result(&result) @@ -386,16 +386,12 @@ impl Spotify { &self, q: &str, _type: SearchType, - limit: L, - offset: O, market: Option, include_external: Option, + limit: L, + offset: O, ) -> ClientResult { let mut params = Query::with_capacity(4); - let limit = limit.into().unwrap_or(10).to_string(); - let offset = offset.into().unwrap_or(0).to_string(); - params.insert("limit", &limit); - params.insert("offset", &offset); params.insert("q", q); params.insert("type", _type.as_ref()); if let Some(ref market) = market { @@ -404,6 +400,14 @@ impl Spotify { if let Some(ref include_external) = include_external { params.insert("include_external", include_external.as_ref()); } + let limit = limit.into().map(|s| s.to_string()); + if let Some(ref limit) = limit { + params.insert("limit", &limit); + } + let offset = offset.into().map(|s| s.to_string()); + if let Some(ref offset) = offset { + params.insert("offset", &offset); + } let result = self.endpoint_get("search", ¶ms).await?; self.convert_result(&result) @@ -439,10 +443,15 @@ impl Spotify { offset: O, ) -> ClientResult> { let mut params = Query::with_capacity(2); - let limit = limit.into().unwrap_or(50).to_string(); - let offset = offset.into().unwrap_or(0).to_string(); - params.insert("limit", &limit); - params.insert("offset", &offset); + let limit = limit.into().map(|s| s.to_string()); + if let Some(ref limit) = limit { + params.insert("limit", &limit); + } + let offset = offset.into().map(|s| s.to_string()); + if let Some(ref offset) = offset { + params.insert("offset", &offset); + } + let url = format!("albums/{}/tracks", album_id.id()); let result = self.endpoint_get(&url, ¶ms).await?; self.convert_result(&result) @@ -513,10 +522,14 @@ impl Spotify { offset: O, ) -> ClientResult> { let mut params = Query::with_capacity(2); - let limit = limit.into().unwrap_or(50).to_string(); - let offset = offset.into().unwrap_or(0).to_string(); - params.insert("limit", &limit); - params.insert("offset", &offset); + let limit = limit.into().map(|s| s.to_string()); + if let Some(ref limit) = limit { + params.insert("limit", &limit); + } + let offset = offset.into().map(|s| s.to_string()); + if let Some(ref offset) = offset { + params.insert("offset", &offset); + } let result = self.endpoint_get("me/playlists", ¶ms).await?; self.convert_result(&result) @@ -552,10 +565,15 @@ impl Spotify { offset: O, ) -> ClientResult> { let mut params = Query::with_capacity(2); - let limit = limit.into().unwrap_or(50).to_string(); - let offset = offset.into().unwrap_or(0).to_string(); - params.insert("limit", &limit); - params.insert("offset", &offset); + let limit = limit.into().map(|s| s.to_string()); + if let Some(ref limit) = limit { + params.insert("limit", &limit); + } + let offset = offset.into().map(|s| s.to_string()); + if let Some(ref offset) = offset { + params.insert("offset", &offset); + } + let url = format!("users/{}/playlists", user_id.id()); let result = self.endpoint_get(&url, ¶ms).await?; self.convert_result(&result) @@ -632,16 +650,21 @@ impl Spotify { offset: O, ) -> ClientResult> { let mut params = Query::with_capacity(2); - let limit = limit.into().unwrap_or(50).to_string(); - let offset = offset.into().unwrap_or(0).to_string(); - params.insert("limit", &limit); - params.insert("offset", &offset); + let limit = limit.into().map(|s| s.to_string()); + if let Some(ref limit) = limit { + params.insert("limit", &limit); + } + let offset = offset.into().map(|s| s.to_string()); + if let Some(ref offset) = offset { + params.insert("offset", &offset); + } if let Some(ref market) = market { params.insert("market", market.as_ref()); } if let Some(fields) = fields { params.insert("fields", fields); } + let url = format!("playlists/{}/tracks", playlist_id.id()); let result = self.endpoint_get(&url, ¶ms).await?; self.convert_result(&result) @@ -654,6 +677,7 @@ impl Spotify { /// - name - the name of the playlist /// - public - is the created playlist public /// - description - the description of the playlist + /// - collaborative - if the playlist will be collaborative /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-create-playlist) #[maybe_async] @@ -662,15 +686,23 @@ impl Spotify { user_id: &UserId, name: &str, public: P, + collaborative: Option, description: D, ) -> ClientResult { - let public = public.into().unwrap_or(true); - let description = description.into().unwrap_or_else(|| "".to_owned()); - let params = json!({ + let mut params = json!({ "name": name, - "public": public, - "description": description }); + + if let Some(public) = public.into() { + json_insert!(params, "public", public); + } + if let Some(collaborative) = collaborative { + json_insert!(params, "collaborative", collaborative); + } + if let Some(description) = description.into() { + json_insert!(params, "description", description); + } + let url = format!("users/{}/playlists", user_id.id()); let result = self.endpoint_post(&url, ¶ms).await?; self.convert_result(&result) @@ -777,27 +809,41 @@ impl Spotify { /// /// Parameters: /// - playlist_id - the id of the playlist + /// - uris - a list of Spotify URIs to replace or clear /// - range_start - the position of the first track to be reordered + /// - insert_before - the position where the tracks should be inserted /// - range_length - optional the number of tracks to be reordered (default: /// 1) - /// - insert_before - the position where the tracks should be inserted /// - snapshot_id - optional playlist's snapshot ID /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-reorder-or-replace-playlists-tracks) #[maybe_async] - pub async fn playlist_reorder_tracks>>( + pub async fn playlist_reorder_tracks( &self, playlist_id: &PlaylistId, - range_start: i32, - range_length: R, - insert_before: i32, + uris: Option<&[&Id]>, + range_start: Option, + insert_before: Option, + range_length: Option, snapshot_id: Option, ) -> ClientResult { let mut params = json! ({ - "range_start": range_start, - "range_length": range_length.into().unwrap_or(1), - "insert_before": insert_before + "playlist_id": playlist_id }); + + let uris = uris.map(|u| u.iter().map(|id| id.uri()).collect::>()); + if let Some(uris) = uris { + json_insert!(params, "uris", uris); + } + if let Some(range_start) = range_start { + json_insert!(params, "range_start", range_start); + } + if let Some(insert_before) = insert_before { + json_insert!(params, "insert_before", insert_before); + } + if let Some(range_length) = range_length { + json_insert!(params, "range_length", range_length); + } if let Some(snapshot_id) = snapshot_id { json_insert!(params, "snapshot_id", snapshot_id); } @@ -904,19 +950,19 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-follow-playlist) #[maybe_async] - pub async fn playlist_follow>>( + pub async fn playlist_follow( &self, playlist_id: &PlaylistId, - public: P, + public: Option, ) -> ClientResult<()> { let url = format!("playlists/{}/followers", playlist_id.id()); - self.endpoint_put( - &url, - &json! ({ - "public": public.into().unwrap_or(true) - }), - ) + let mut params = json!({}); + if let Some(public) = public { + json_insert!(params, "public", public); + } + + self.endpoint_put(&url, ¶ms) .await?; Ok(()) @@ -1016,12 +1062,15 @@ impl Spotify { offset: O, ) -> ClientResult> { let mut params = Query::with_capacity(2); - // TODO: we should use the API's default value instead of - // `.unwrap_or(20)` and similars. - let limit = limit.into().unwrap_or(20).to_string(); - let offset = offset.into().unwrap_or(0).to_string(); - params.insert("limit", &limit); - params.insert("offset", &offset); + let limit = limit.into().map(|s| s.to_string()); + if let Some(ref limit) = limit { + params.insert("limit", &limit); + } + let offset = offset.into().map(|s| s.to_string()); + if let Some(ref offset) = offset { + params.insert("offset", &offset); + } + let result = self.endpoint_get("me/albums", ¶ms).await?; self.convert_result(&result) } @@ -1054,10 +1103,14 @@ impl Spotify { offset: O, ) -> ClientResult> { let mut params = Query::with_capacity(2); - let limit = limit.into().unwrap_or(20).to_string(); - let offset = offset.into().unwrap_or(0).to_string(); - params.insert("limit", &limit); - params.insert("offset", &offset); + let limit = limit.into().map(|s| s.to_string()); + if let Some(ref limit) = limit { + params.insert("limit", &limit); + } + let offset = offset.into().map(|s| s.to_string()); + if let Some(ref offset) = offset { + params.insert("offset", &offset); + } let result = self.endpoint_get("me/tracks", ¶ms).await?; self.convert_result(&result) } @@ -1065,23 +1118,25 @@ impl Spotify { /// Gets a list of the artists followed by the current authorized user. /// /// Parameters: - /// - limit - the number of tracks to return /// - after - the last artist ID retrieved from the previous request + /// - limit - the number of tracks to return /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-followed) #[maybe_async] pub async fn current_user_followed_artists>>( &self, - limit: L, after: Option, + limit: L, ) -> ClientResult> { let mut params = Query::with_capacity(2); - let limit = limit.into().unwrap_or(20).to_string(); - params.insert("limit", &limit); params.insert("type", Type::Artist.as_ref()); if let Some(ref after) = after { params.insert("after", &after); } + let limit = limit.into().map(|s| s.to_string()); + if let Some(ref limit) = limit { + params.insert("limit", &limit); + } let result = self.endpoint_get("me/following", ¶ms).await?; self.convert_result::(&result) @@ -1164,22 +1219,27 @@ impl Spotify { #[maybe_async] pub async fn current_user_top_artists_manual< 'a, - T: Into>, L: Into>, O: Into>, >( &'a self, - time_range: T, + time_range: Option<&'a TimeRange>, limit: L, offset: O, ) -> ClientResult> { let mut params = Query::with_capacity(3); - let limit = limit.into().unwrap_or(20).to_string(); - let offset = offset.into().unwrap_or(0).to_string(); - let time_range = time_range.into().unwrap_or(&TimeRange::MediumTerm); - params.insert("limit", &limit); - params.insert("offset", &offset); - params.insert("time_range", time_range.as_ref()); + if let Some(ref time_range) = time_range { + params.insert("time_range", time_range.as_ref()); + } + let limit = limit.into().map(|s| s.to_string()); + if let Some(ref limit) = limit { + params.insert("limit", &limit); + } + let offset = offset.into().map(|s| s.to_string()); + if let Some(ref offset) = offset { + params.insert("offset", &offset); + } + let result = self.endpoint_get(&"me/top/artists", ¶ms).await?; self.convert_result(&result) } @@ -1835,11 +1895,11 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-start-a-users-playback) #[maybe_async] - pub async fn start_context_playback( + pub async fn start_context_playback( &self, - context_uri: &Id, + context_uri: &Id, device_id: Option, - offset: Option>, + offset: Option>, position_ms: Option, ) -> ClientResult<()> { use super::model::Offset; @@ -2028,9 +2088,9 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-add-to-queue) #[maybe_async] - pub async fn add_item_to_queue( + pub async fn add_item_to_queue( &self, - item: &Id, + item: &Id, device_id: Option, ) -> ClientResult<()> { let url = self.append_device_id(&format!("me/player/queue?uri={}", item), device_id); From 3d183c6a5d69a4e0a2544064d88da51876332c8c Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Mon, 19 Apr 2021 22:41:59 +0200 Subject: [PATCH 02/39] initial macro --- src/client.rs | 96 +++++++++++++++++++-------------------------------- src/macros.rs | 35 +++++++++++++++++++ 2 files changed, 71 insertions(+), 60 deletions(-) diff --git a/src/client.rs b/src/client.rs index 43fccae6..6c1941b8 100644 --- a/src/client.rs +++ b/src/client.rs @@ -12,7 +12,7 @@ use thiserror::Error; use std::path::PathBuf; use super::http::{HTTPClient, Query}; -use super::json_insert; +use super::{json_params, map_params, json_insert}; use super::model::*; use super::oauth2::{Credentials, OAuth, Token}; use super::pagination::{paginate, Paginator}; @@ -193,11 +193,9 @@ impl Spotify { market: Option, ) -> ClientResult> { let ids = join_ids(track_ids); - - let mut params = Query::new(); - if let Some(ref market) = market { - params.insert("market", market.as_ref()); - } + let params = map_params! { + opt market => market.as_ref(), + }; let url = format!("tracks/?ids={}", ids); let result = self.endpoint_get(&url, ¶ms).await?; @@ -273,21 +271,15 @@ impl Spotify { limit: Option, offset: Option, ) -> ClientResult> { - let mut params = Query::new(); - if let Some(ref album_type) = album_type { - params.insert("album_type", album_type.as_ref()); - } - if let Some(ref market) = market { - params.insert("market", market.as_ref()); - } let limit = limit.map(|x| x.to_string()); - if let Some(ref limit) = limit { - params.insert("limit", limit); - } let offset = offset.map(|x| x.to_string()); - if let Some(ref offset) = offset { - params.insert("offset", offset); - } + let params = map_params! { + opt album_type => album_type.as_ref(), + opt market => market.as_ref(), + opt limit => limit.as_ref(), + opt offset => offset.as_ref(), + }; + let url = format!("artists/{}/albums", artist_id.id()); let result = self.endpoint_get(&url, ¶ms).await?; self.convert_result(&result) @@ -307,9 +299,9 @@ impl Spotify { artist_id: &ArtistId, market: Market, ) -> ClientResult> { - let mut params = Query::with_capacity(1); - - params.insert("market", market.as_ref()); + let params = map_params! { + req market => market.as_ref() + }; let url = format!("artists/{}/top-tracks", artist_id.id()); let result = self.endpoint_get(&url, ¶ms).await?; @@ -385,29 +377,22 @@ impl Spotify { pub async fn search>, O: Into>>( &self, q: &str, - _type: SearchType, + r#type: SearchType, market: Option, include_external: Option, limit: L, offset: O, ) -> ClientResult { - let mut params = Query::with_capacity(4); - params.insert("q", q); - params.insert("type", _type.as_ref()); - if let Some(ref market) = market { - params.insert("market", market.as_ref()); - } - if let Some(ref include_external) = include_external { - params.insert("include_external", include_external.as_ref()); - } let limit = limit.into().map(|s| s.to_string()); - if let Some(ref limit) = limit { - params.insert("limit", &limit); - } let offset = offset.into().map(|s| s.to_string()); - if let Some(ref offset) = offset { - params.insert("offset", &offset); - } + let params = map_params! { + req q => q, + req r#type => r#type.as_ref(), + opt market => market.as_ref(), + opt include_external => include_external.as_ref(), + opt limit => &limit, + opt offset => &offset, + }; let result = self.endpoint_get("search", ¶ms).await?; self.convert_result(&result) @@ -442,15 +427,12 @@ impl Spotify { limit: L, offset: O, ) -> ClientResult> { - let mut params = Query::with_capacity(2); let limit = limit.into().map(|s| s.to_string()); - if let Some(ref limit) = limit { - params.insert("limit", &limit); - } let offset = offset.into().map(|s| s.to_string()); - if let Some(ref offset) = offset { - params.insert("offset", &offset); - } + let params = map_params! { + opt limit => &limit, + opt offset => &offset, + }; let url = format!("albums/{}/tracks", album_id.id()); let result = self.endpoint_get(&url, ¶ms).await?; @@ -484,13 +466,10 @@ impl Spotify { fields: Option<&str>, market: Option, ) -> ClientResult { - let mut params = Query::new(); - if let Some(fields) = fields { - params.insert("fields", fields); - } - if let Some(ref market) = market { - params.insert("market", market.as_ref()); - } + let params = map_params! { + opt fields => fields, + opt market => market.as_ref(), + }; let url = format!("playlists/{}", playlist_id.id()); let result = self.endpoint_get(&url, ¶ms).await?; @@ -1622,14 +1601,11 @@ impl Spotify { limit: L, offset: O, ) -> ClientResult> { - let mut params = Query::with_capacity(2); - let limit = limit.into().unwrap_or(20).to_string(); - let offset = offset.into().unwrap_or(0).to_string(); - params.insert("limit", &limit); - params.insert("offset", &offset); - if let Some(ref market) = country { - params.insert("country", market.as_ref()); - } + let params = map_params! { + opt limit => &limit, + opt offset => &offset, + opt market => market.as_ref(), + }; let url = format!("browse/categories/{}/playlists", category_id); let result = self.endpoint_get(&url, ¶ms).await?; diff --git a/src/macros.rs b/src/macros.rs index c3dbb091..40ca3b59 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -45,6 +45,41 @@ macro_rules! json_insert { }; } +#[macro_export] +macro_rules! params_internal { + ($map:ident, req, $key:ident, $val:expr) => ( + $map.insert(stringify!($key), $val); + ); + ($map:ident, opt, $key:ident, $val:expr) => ( + if let Some(ref $key) = $key { + $map.insert(stringify!($key), $val); + } + ); +} + +/// TODO: use with_capacity? +#[macro_export] +macro_rules! map_params { + ( + $( + $kind:ident $key:ident => $val:expr + ),* $(,)? + ) => ({ + let mut params = crate::http::Query::new(); + $( + crate::params_internal!(params, $kind, $key, $val); + )* + params + }); +} + +#[macro_export] +macro_rules! json_params { + () => { + + } +} + #[cfg(test)] mod test { use crate::{json_insert, scopes}; From eee8ceb1f3e22fdf430330596a6add54575b0abf Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Mon, 19 Apr 2021 23:50:12 +0200 Subject: [PATCH 03/39] macros --- src/client.rs | 481 +++++++++++++++++++++++--------------------------- 1 file changed, 216 insertions(+), 265 deletions(-) diff --git a/src/client.rs b/src/client.rs index 6c1941b8..9d73ce1b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -12,7 +12,7 @@ use thiserror::Error; use std::path::PathBuf; use super::http::{HTTPClient, Query}; -use super::{json_params, map_params, json_insert}; +use super::{map_params, json_insert}; use super::model::*; use super::oauth2::{Credentials, OAuth, Token}; use super::pagination::{paginate, Paginator}; @@ -374,17 +374,17 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#category-search) #[maybe_async] - pub async fn search>, O: Into>>( + pub async fn search( &self, q: &str, r#type: SearchType, market: Option, include_external: Option, - limit: L, - offset: O, + limit: Option, + offset: Option, ) -> ClientResult { - let limit = limit.into().map(|s| s.to_string()); - let offset = offset.into().map(|s| s.to_string()); + let limit = limit.map(|s| s.to_string()); + let offset = offset.map(|s| s.to_string()); let params = map_params! { req q => q, req r#type => r#type.as_ref(), @@ -421,14 +421,14 @@ impl Spotify { /// The manually paginated version of [`Spotify::album_track`]. #[maybe_async] - pub async fn album_track_manual>, O: Into>>( + pub async fn album_track_manual( &self, album_id: &AlbumId, - limit: L, - offset: O, + limit: Option, + offset: Option, ) -> ClientResult> { - let limit = limit.into().map(|s| s.to_string()); - let offset = offset.into().map(|s| s.to_string()); + let limit = limit.map(|s| s.to_string()); + let offset = offset.map(|s| s.to_string()); let params = map_params! { opt limit => &limit, opt offset => &offset, @@ -495,20 +495,17 @@ impl Spotify { /// The manually paginated version of [`Spotify::current_user_playlists`]. #[maybe_async] - pub async fn current_user_playlists_manual>, O: Into>>( + pub async fn current_user_playlists_manual( &self, - limit: L, - offset: O, + limit: Option, + offset: Option, ) -> ClientResult> { - let mut params = Query::with_capacity(2); - let limit = limit.into().map(|s| s.to_string()); - if let Some(ref limit) = limit { - params.insert("limit", &limit); - } - let offset = offset.into().map(|s| s.to_string()); - if let Some(ref offset) = offset { - params.insert("offset", &offset); - } + let limit = limit.map(|s| s.to_string()); + let offset = offset.map(|s| s.to_string()); + let params = map_params! { + opt limit => &limit, + opt offset => &offset, + }; let result = self.endpoint_get("me/playlists", ¶ms).await?; self.convert_result(&result) @@ -537,21 +534,18 @@ impl Spotify { /// The manually paginated version of [`Spotify::user_playlists`]. #[maybe_async] - pub async fn user_playlists_manual>, O: Into>>( + pub async fn user_playlists_manual( &self, user_id: &UserId, - limit: L, - offset: O, + limit: Option, + offset: Option, ) -> ClientResult> { - let mut params = Query::with_capacity(2); - let limit = limit.into().map(|s| s.to_string()); - if let Some(ref limit) = limit { - params.insert("limit", &limit); - } - let offset = offset.into().map(|s| s.to_string()); - if let Some(ref offset) = offset { - params.insert("offset", &offset); - } + let limit = limit.map(|s| s.to_string()); + let offset = offset.map(|s| s.to_string()); + let params = map_params! { + opt limit => &limit, + opt offset => &offset, + }; let url = format!("users/{}/playlists", user_id.id()); let result = self.endpoint_get(&url, ¶ms).await?; @@ -573,10 +567,9 @@ impl Spotify { playlist_id: Option<&PlaylistId>, fields: Option<&str>, ) -> ClientResult { - let mut params = Query::new(); - if let Some(fields) = fields { - params.insert("fields", fields); - } + let params = map_params! { + opt fields => fields, + }; match playlist_id { Some(playlist_id) => { let url = format!("users/{}/playlists/{}", user_id.id(), playlist_id.id()); @@ -620,29 +613,22 @@ impl Spotify { /// The manually paginated version of [`Spotify::playlist_tracks`]. #[maybe_async] - pub async fn playlist_tracks_manual>, O: Into>>( + pub async fn playlist_tracks_manual( &self, playlist_id: &PlaylistId, fields: Option<&str>, market: Option<&Market>, - limit: L, - offset: O, + limit: Option, + offset: Option, ) -> ClientResult> { - let mut params = Query::with_capacity(2); - let limit = limit.into().map(|s| s.to_string()); - if let Some(ref limit) = limit { - params.insert("limit", &limit); - } - let offset = offset.into().map(|s| s.to_string()); - if let Some(ref offset) = offset { - params.insert("offset", &offset); - } - if let Some(ref market) = market { - params.insert("market", market.as_ref()); - } - if let Some(fields) = fields { - params.insert("fields", fields); - } + let limit = limit.map(|s| s.to_string()); + let offset = offset.map(|s| s.to_string()); + let params = map_params! { + opt limit => &limit, + opt offset => &offset, + opt market => market.as_ref(), + opt fields => fields, + }; let url = format!("playlists/{}/tracks", playlist_id.id()); let result = self.endpoint_get(&url, ¶ms).await?; @@ -1035,20 +1021,17 @@ impl Spotify { /// The manually paginated version of /// [`Spotify::current_user_saved_albums`]. #[maybe_async] - pub async fn current_user_saved_albums_manual>, O: Into>>( + pub async fn current_user_saved_albums_manual( &self, - limit: L, - offset: O, + limit: Option, + offset: Option, ) -> ClientResult> { - let mut params = Query::with_capacity(2); - let limit = limit.into().map(|s| s.to_string()); - if let Some(ref limit) = limit { - params.insert("limit", &limit); - } - let offset = offset.into().map(|s| s.to_string()); - if let Some(ref offset) = offset { - params.insert("offset", &offset); - } + let limit = limit.map(|s| s.to_string()); + let offset = offset.map(|s| s.to_string()); + let params = map_params! { + opt limit => &limit, + opt offset => &offset, + }; let result = self.endpoint_get("me/albums", ¶ms).await?; self.convert_result(&result) @@ -1068,7 +1051,7 @@ impl Spotify { /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-users-saved-tracks) pub fn current_user_saved_tracks(&self) -> impl Paginator> + '_ { paginate( - move |limit, offset| self.current_user_saved_tracks_manual(limit, offset), + move |limit, offset| self.current_user_saved_tracks_manual(Some(limit), Some(offset)), self.pagination_chunks, ) } @@ -1076,20 +1059,18 @@ impl Spotify { /// The manually paginated version of /// [`Spotify::current_user_saved_tracks`]. #[maybe_async] - pub async fn current_user_saved_tracks_manual>, O: Into>>( + pub async fn current_user_saved_tracks_manual( &self, - limit: L, - offset: O, + limit: Option, + offset: Option, ) -> ClientResult> { - let mut params = Query::with_capacity(2); - let limit = limit.into().map(|s| s.to_string()); - if let Some(ref limit) = limit { - params.insert("limit", &limit); - } - let offset = offset.into().map(|s| s.to_string()); - if let Some(ref offset) = offset { - params.insert("offset", &offset); - } + let limit = limit.map(|s| s.to_string()); + let offset = offset.map(|s| s.to_string()); + let params = map_params! { + opt limit => &limit, + opt offset => &offset, + }; + let result = self.endpoint_get("me/tracks", ¶ms).await?; self.convert_result(&result) } @@ -1102,20 +1083,17 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-followed) #[maybe_async] - pub async fn current_user_followed_artists>>( + pub async fn current_user_followed_artists( &self, after: Option, - limit: L, + limit: Option, ) -> ClientResult> { - let mut params = Query::with_capacity(2); - params.insert("type", Type::Artist.as_ref()); - if let Some(ref after) = after { - params.insert("after", &after); - } - let limit = limit.into().map(|s| s.to_string()); - if let Some(ref limit) = limit { - params.insert("limit", &limit); - } + let limit = limit.map(|s| s.to_string()); + let params = map_params! { + req r#type => Type::Artist.as_ref(), + opt after => &after, + opt limit => &limit, + }; let result = self.endpoint_get("me/following", ¶ms).await?; self.convert_result::(&result) @@ -1189,35 +1167,26 @@ impl Spotify { time_range: Option<&'a TimeRange>, ) -> impl Paginator> + 'a { paginate( - move |limit, offset| self.current_user_top_artists_manual(time_range, limit, offset), + move |limit, offset| self.current_user_top_artists_manual(time_range, Some(limit), Some(offset)), self.pagination_chunks, ) } /// The manually paginated version of [`Spotify::current_user_top_artists`]. #[maybe_async] - pub async fn current_user_top_artists_manual< - 'a, - L: Into>, - O: Into>, - >( - &'a self, - time_range: Option<&'a TimeRange>, - limit: L, - offset: O, + pub async fn current_user_top_artists_manual( + &self, + time_range: Option<&TimeRange>, + limit: Option, + offset: Option, ) -> ClientResult> { - let mut params = Query::with_capacity(3); - if let Some(ref time_range) = time_range { - params.insert("time_range", time_range.as_ref()); - } - let limit = limit.into().map(|s| s.to_string()); - if let Some(ref limit) = limit { - params.insert("limit", &limit); - } - let offset = offset.into().map(|s| s.to_string()); - if let Some(ref offset) = offset { - params.insert("offset", &offset); - } + let limit = limit.map(|s| s.to_string()); + let offset = offset.map(|s| s.to_string()); + let params = map_params! { + opt time_range => time_range.as_ref(), + opt limit => &limit, + opt offset => &offset, + }; let result = self.endpoint_get(&"me/top/artists", ¶ms).await?; self.convert_result(&result) @@ -1239,26 +1208,27 @@ impl Spotify { time_range: Option<&'a TimeRange>, ) -> impl Paginator> + 'a { paginate( - move |limit, offset| self.current_user_top_tracks_manual(time_range, limit, offset), + move |limit, offset| self.current_user_top_tracks_manual(time_range, Some(limit), Some(offset)), self.pagination_chunks, ) } /// The manually paginated version of [`Spotify::current_user_top_tracks`]. #[maybe_async] - pub async fn current_user_top_tracks_manual>, O: Into>>( + pub async fn current_user_top_tracks_manual( &self, time_range: Option<&TimeRange>, - limit: L, - offset: O, + limit: Option, + offset: Option, ) -> ClientResult> { - let mut params = Query::with_capacity(3); - let limit = limit.into().unwrap_or(20).to_string(); - let offset = offset.into().unwrap_or(0).to_string(); - let time_range = time_range.unwrap_or(&TimeRange::MediumTerm); - params.insert("limit", &limit); - params.insert("offset", &offset); - params.insert("time_range", time_range.as_ref()); + let limit = limit.map(|x| x.to_string()); + let offset = offset.map(|x| x.to_string()); + let params = map_params! { + opt time_range => time_range.as_ref(), + opt limit => &limit, + opt offset => &offset, + }; + let result = self.endpoint_get("me/top/tracks", ¶ms).await?; self.convert_result(&result) } @@ -1270,13 +1240,15 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-the-users-currently-playing-track) #[maybe_async] - pub async fn current_user_recently_played>>( + pub async fn current_user_recently_played( &self, - limit: L, + limit: Option, ) -> ClientResult> { - let mut params = Query::with_capacity(1); - let limit = limit.into().unwrap_or(50).to_string(); - params.insert("limit", &limit); + let limit = limit.map(|x| x.to_string()); + let params = map_params! { + opt limit => &limit, + }; + let result = self .endpoint_get("me/player/recently-played", ¶ms) .await?; @@ -1440,29 +1412,25 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-featured-playlists) #[maybe_async] - pub async fn featured_playlists>, O: Into>>( + pub async fn featured_playlists( &self, locale: Option, - country: Option, + market: Option, timestamp: Option>, - limit: L, - offset: O, + limit: Option, + offset: Option, ) -> ClientResult { - let mut params = Query::with_capacity(2); - let limit = limit.into().unwrap_or(20).to_string(); - let offset = offset.into().unwrap_or(0).to_string(); - params.insert("limit", &limit); - params.insert("offset", &offset); - if let Some(ref locale) = locale { - params.insert("locale", locale); - } - if let Some(ref market) = country { - params.insert("country", market.as_ref()); - } + let limit = limit.map(|x| x.to_string()); + let offset = offset.map(|x| x.to_string()); let timestamp = timestamp.map(|x| x.to_rfc3339()); - if let Some(ref timestamp) = timestamp { - params.insert("timestamp", timestamp); - } + let params = map_params! { + opt limit => &limit, + opt offset => &offset, + opt locale => locale, + opt market => market.as_ref(), + opt timestamp => timestamp, + }; + let result = self .endpoint_get("browse/featured-playlists", ¶ms) .await?; @@ -1484,30 +1452,29 @@ impl Spotify { /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-new-releases) pub fn new_releases<'a>( &'a self, - country: Option<&'a Market>, + market: Option<&'a Market>, ) -> impl Paginator> + 'a { paginate( - move |limit, offset| self.new_releases_manual(country, limit, offset), + move |limit, offset| self.new_releases_manual(market, Some(limit), Some(offset)), self.pagination_chunks, ) } /// The manually paginated version of [`Spotify::new_releases`]. #[maybe_async] - pub async fn new_releases_manual>, O: Into>>( + pub async fn new_releases_manual( &self, - country: Option<&Market>, - limit: L, - offset: O, + market: Option<&Market>, + limit: Option, + offset: Option, ) -> ClientResult> { - let mut params = Query::with_capacity(2); - let limit = limit.into().unwrap_or(20).to_string(); - let offset = offset.into().unwrap_or(0).to_string(); - params.insert("limit", &limit); - params.insert("offset", &offset); - if let Some(ref market) = country { - params.insert("country", market.as_ref()); - } + let limit = limit.map(|x| x.to_string()); + let offset = offset.map(|x| x.to_string()); + let params = map_params! { + opt limit => &limit, + opt offset => &offset, + opt market => market.as_ref(), + }; let result = self.endpoint_get("browse/new-releases", ¶ms).await?; self.convert_result::(&result) @@ -1532,34 +1499,31 @@ impl Spotify { pub fn categories<'a>( &'a self, locale: Option<&'a str>, - country: Option<&'a Market>, + market: Option<&'a Market>, ) -> impl Paginator> + 'a { paginate( - move |limit, offset| self.categories_manual(locale, country, limit, offset), + move |limit, offset| self.categories_manual(locale, market, Some(limit), Some(offset)), self.pagination_chunks, ) } /// The manually paginated version of [`Spotify::categories`]. #[maybe_async] - pub async fn categories_manual>, O: Into>>( + pub async fn categories_manual( &self, locale: Option<&str>, - country: Option<&Market>, - limit: L, - offset: O, + market: Option<&Market>, + limit: Option, + offset: Option, ) -> ClientResult> { - let mut params = Query::with_capacity(2); - let limit = limit.into().unwrap_or(20).to_string(); - let offset = offset.into().unwrap_or(0).to_string(); - params.insert("limit", &limit); - params.insert("offset", &offset); - if let Some(locale) = locale { - params.insert("locale", locale); - } - if let Some(ref market) = country { - params.insert("country", market.as_ref()); - } + let limit = limit.map(|x| x.to_string()); + let offset = offset.map(|x| x.to_string()); + let params = map_params! { + opt limit => &limit, + opt offset => &offset, + opt locale => locale, + opt market => market.as_ref(), + }; let result = self.endpoint_get("browse/categories", ¶ms).await?; self.convert_result::(&result) .map(|x| x.categories) @@ -1582,11 +1546,11 @@ impl Spotify { pub fn category_playlists<'a>( &'a self, category_id: &'a str, - country: Option<&'a Market>, + market: Option<&'a Market>, ) -> impl Paginator> + 'a { paginate( move |limit, offset| { - self.category_playlists_manual(category_id, country, limit, offset) + self.category_playlists_manual(category_id, market, Some(limit), Some(offset)) }, self.pagination_chunks, ) @@ -1594,13 +1558,15 @@ impl Spotify { /// The manually paginated version of [`Spotify::category_playlists`]. #[maybe_async] - pub async fn category_playlists_manual>, O: Into>>( + pub async fn category_playlists_manual( &self, category_id: &str, - country: Option<&Market>, - limit: L, - offset: O, + market: Option<&Market>, + limit: Option, + offset: Option, ) -> ClientResult> { + let limit = limit.map(|x| x.to_string()); + let offset = offset.map(|x| x.to_string()); let params = map_params! { opt limit => &limit, opt offset => &offset, @@ -1629,18 +1595,27 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-recommendations) #[maybe_async] - pub async fn recommendations>>( + pub async fn recommendations( &self, seed_artists: Option>, seed_genres: Option>, seed_tracks: Option>, - limit: L, + limit: Option, market: Option, payload: &Map, ) -> ClientResult { - let mut params = Query::with_capacity(payload.len() + 1); - let limit = limit.into().unwrap_or(20).to_string(); - params.insert("limit", &limit); + let seed_artists = seed_artists.map(join_ids); + let seed_genres = seed_genres.map(|x| x.join(",")); + let seed_tracks = seed_tracks.map(join_ids); + let limit = limit.map(|x| x.to_string()); + let mut params = map_params! { + opt seed_artists => seed_artists, + opt seed_genres => seed_genres, + opt seed_tracks => seed_tracks, + opt market => market.as_ref(), + opt limit => &limit, + }; + // TODO: this probably can be improved. let attributes = [ "acousticness", @@ -1675,24 +1650,6 @@ impl Spotify { params.insert(key, value); } - let seed_artists = seed_artists.map(join_ids); - if let Some(ref seed_artists) = seed_artists { - params.insert("seed_artists", seed_artists); - } - - let seed_genres = seed_genres.map(|x| x.join(",")); - if let Some(ref seed_genres) = seed_genres { - params.insert("seed_genres", seed_genres); - } - - let seed_tracks = seed_tracks.map(join_ids); - if let Some(ref seed_tracks) = seed_tracks { - params.insert("seed_tracks", seed_tracks); - } - - if let Some(ref market) = market { - params.insert("market", market.as_ref()); - } let result = self.endpoint_get("recommendations", ¶ms).await?; self.convert_result(&result) } @@ -1772,16 +1729,12 @@ impl Spotify { market: Option, additional_types: Option>, ) -> ClientResult> { - let mut params = Query::new(); - if let Some(ref market) = market { - params.insert("country", market.as_ref()); - } let additional_types = additional_types.map(|x| x.iter().map(|x| x.as_ref()).collect::>().join(",")); - - if let Some(ref additional_types) = additional_types { - params.insert("additional_types", additional_types); - } + let params = map_params! { + opt market => market.as_ref(), + opt additional_types => additional_types, + }; let result = self.endpoint_get("me/player", ¶ms).await?; if result.is_empty() { @@ -1806,15 +1759,12 @@ impl Spotify { market: Option, additional_types: Option>, ) -> ClientResult> { - let mut params = Query::new(); - if let Some(ref market) = market { - params.insert("market", market.as_ref()); - } let additional_types = additional_types.map(|x| x.iter().map(|x| x.as_ref()).collect::>().join(",")); - if let Some(ref additional_types) = additional_types { - params.insert("additional_types", additional_types); - } + let params = map_params! { + opt market => market.as_ref(), + opt additional_types => additional_types, + }; let result = self .get("me/player/currently-playing", None, ¶ms) @@ -2108,23 +2058,25 @@ impl Spotify { /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-users-saved-shows) pub fn get_saved_show(&self) -> impl Paginator> + '_ { paginate( - move |limit, offset| self.get_saved_show_manual(limit, offset), + move |limit, offset| self.get_saved_show_manual(Some(limit), Some(offset)), self.pagination_chunks, ) } /// The manually paginated version of [`Spotify::get_saved_show`]. #[maybe_async] - pub async fn get_saved_show_manual>, O: Into>>( + pub async fn get_saved_show_manual( &self, - limit: L, - offset: O, + limit: Option, + offset: Option, ) -> ClientResult> { - let mut params = Query::with_capacity(2); - let limit = limit.into().unwrap_or(20).to_string(); - let offset = offset.into().unwrap_or(0).to_string(); - params.insert("limit", &limit); - params.insert("offset", &offset); + let limit = limit.map(|x| x.to_string()); + let offset = offset.map(|x| x.to_string()); + let params = map_params! { + opt limit => &limit, + opt offset => &offset, + }; + let result = self.endpoint_get("me/shows", ¶ms).await?; self.convert_result(&result) } @@ -2140,10 +2092,10 @@ impl Spotify { /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-a-show) #[maybe_async] pub async fn get_a_show(&self, id: &ShowId, market: Option) -> ClientResult { - let mut params = Query::new(); - if let Some(ref market) = market { - params.insert("market", market.as_ref()); - } + let params = map_params! { + opt market => market.as_ref(), + }; + let url = format!("shows/{}", id.id()); let result = self.endpoint_get(&url, ¶ms).await?; self.convert_result(&result) @@ -2163,12 +2115,12 @@ impl Spotify { ids: impl IntoIterator, market: Option, ) -> ClientResult> { - let mut params = Query::with_capacity(1); let ids = join_ids(ids); - params.insert("ids", ids.as_ref()); - if let Some(ref market) = market { - params.insert("market", market.as_ref()); - } + let params = map_params! { + req ids => ids.as_ref(), + opt market => market.as_ref(), + }; + let result = self.endpoint_get("shows", ¶ms).await?; self.convert_result::(&result) .map(|x| x.shows) @@ -2195,28 +2147,28 @@ impl Spotify { market: Option<&'a Market>, ) -> impl Paginator> + 'a { paginate( - move |limit, offset| self.get_shows_episodes_manual(id, market, limit, offset), + move |limit, offset| self.get_shows_episodes_manual(id, market, Some(limit), Some(offset)), self.pagination_chunks, ) } /// The manually paginated version of [`Spotify::get_shows_episodes`]. #[maybe_async] - pub async fn get_shows_episodes_manual>, O: Into>>( + pub async fn get_shows_episodes_manual( &self, id: &ShowId, market: Option<&Market>, - limit: L, - offset: O, + limit: Option, + offset: Option, ) -> ClientResult> { - let mut params = Query::with_capacity(2); - let limit = limit.into().unwrap_or(20).to_string(); - let offset = offset.into().unwrap_or(0).to_string(); - params.insert("limit", &limit); - params.insert("offset", &offset); - if let Some(ref market) = market { - params.insert("market", market.as_ref()); - } + let limit = limit.map(|x| x.to_string()); + let offset = offset.map(|x| x.to_string()); + let params = map_params! { + opt limit => &limit, + opt offset => &offset, + opt market => market.as_ref(), + }; + let url = format!("shows/{}/episodes", id.id()); let result = self.endpoint_get(&url, ¶ms).await?; self.convert_result(&result) @@ -2238,10 +2190,9 @@ impl Spotify { market: Option, ) -> ClientResult { let url = format!("episodes/{}", id.id()); - let mut params = Query::new(); - if let Some(ref market) = market { - params.insert("market", market.as_ref()); - } + let params = map_params! { + opt market => market.as_ref(), + }; let result = self.endpoint_get(&url, ¶ms).await?; self.convert_result(&result) @@ -2260,12 +2211,11 @@ impl Spotify { ids: impl IntoIterator, market: Option, ) -> ClientResult> { - let mut params = Query::with_capacity(1); let ids = join_ids(ids); - params.insert("ids", ids.as_ref()); - if let Some(ref market) = market { - params.insert("market", market.as_ref()); - } + let params = map_params! { + req ids => ids.as_ref(), + opt market => market.as_ref(), + }; let result = self.endpoint_get("episodes", ¶ms).await?; self.convert_result::(&result) @@ -2283,9 +2233,10 @@ impl Spotify { &self, ids: impl IntoIterator, ) -> ClientResult> { - let mut params = Query::with_capacity(1); let ids = join_ids(ids); - params.insert("ids", ids.as_str()); + let params = map_params! { + req ids => ids.as_str(), + }; let result = self.endpoint_get("me/shows/contains", ¶ms).await?; self.convert_result(&result) } From 8a3fccf0c7371ee280a234fda7bf9a1e8850d385 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 00:35:43 +0200 Subject: [PATCH 04/39] json macro --- src/client.rs | 269 ++++++++++++++++++++++---------------------------- src/macros.rs | 59 ++++++----- 2 files changed, 150 insertions(+), 178 deletions(-) diff --git a/src/client.rs b/src/client.rs index 9d73ce1b..4dc3033a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -12,10 +12,10 @@ use thiserror::Error; use std::path::PathBuf; use super::http::{HTTPClient, Query}; -use super::{map_params, json_insert}; use super::model::*; use super::oauth2::{Credentials, OAuth, Token}; use super::pagination::{paginate, Paginator}; +use super::{map_json, map_query}; use crate::model::idtypes::{IdType, PlayContextIdType}; use std::collections::HashMap; @@ -193,7 +193,7 @@ impl Spotify { market: Option, ) -> ClientResult> { let ids = join_ids(track_ids); - let params = map_params! { + let params = map_query! { opt market => market.as_ref(), }; @@ -273,11 +273,11 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); - let params = map_params! { + let params = map_query! { opt album_type => album_type.as_ref(), - opt market => market.as_ref(), - opt limit => limit.as_ref(), - opt offset => offset.as_ref(), + opt market => &market.as_ref(), + opt limit => &limit, + opt offset => &offset, }; let url = format!("artists/{}/albums", artist_id.id()); @@ -299,7 +299,7 @@ impl Spotify { artist_id: &ArtistId, market: Market, ) -> ClientResult> { - let params = map_params! { + let params = map_query! { req market => market.as_ref() }; @@ -385,7 +385,7 @@ impl Spotify { ) -> ClientResult { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); - let params = map_params! { + let params = map_query! { req q => q, req r#type => r#type.as_ref(), opt market => market.as_ref(), @@ -429,7 +429,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); - let params = map_params! { + let params = map_query! { opt limit => &limit, opt offset => &offset, }; @@ -466,7 +466,7 @@ impl Spotify { fields: Option<&str>, market: Option, ) -> ClientResult { - let params = map_params! { + let params = map_query! { opt fields => fields, opt market => market.as_ref(), }; @@ -502,7 +502,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); - let params = map_params! { + let params = map_query! { opt limit => &limit, opt offset => &offset, }; @@ -542,7 +542,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); - let params = map_params! { + let params = map_query! { opt limit => &limit, opt offset => &offset, }; @@ -567,7 +567,7 @@ impl Spotify { playlist_id: Option<&PlaylistId>, fields: Option<&str>, ) -> ClientResult { - let params = map_params! { + let params = map_query! { opt fields => fields, }; match playlist_id { @@ -623,7 +623,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); - let params = map_params! { + let params = map_query! { opt limit => &limit, opt offset => &offset, opt market => market.as_ref(), @@ -646,27 +646,20 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-create-playlist) #[maybe_async] - pub async fn user_playlist_create>, D: Into>>( + pub async fn user_playlist_creat( &self, user_id: &UserId, name: &str, - public: P, + public: Option, collaborative: Option, - description: D, + description: Option<&str>, ) -> ClientResult { - let mut params = json!({ - "name": name, - }); - - if let Some(public) = public.into() { - json_insert!(params, "public", public); - } - if let Some(collaborative) = collaborative { - json_insert!(params, "collaborative", collaborative); - } - if let Some(description) = description.into() { - json_insert!(params, "description", description); - } + let params = map_json! { + req name => name, + opt public => public, + opt collaborative => collaborative, + opt description => description, + }; let url = format!("users/{}/playlists", user_id.id()); let result = self.endpoint_post(&url, ¶ms).await?; @@ -692,19 +685,13 @@ impl Spotify { description: Option, collaborative: Option, ) -> ClientResult { - let mut params = json!({}); - if let Some(name) = name { - json_insert!(params, "name", name); - } - if let Some(public) = public { - json_insert!(params, "public", public); - } - if let Some(collaborative) = collaborative { - json_insert!(params, "collaborative", collaborative); - } - if let Some(description) = description { - json_insert!(params, "description", description); - } + let params = map_json! { + opt name => name, + opt public => public, + opt collaborative => collaborative, + opt description => description, + }; + let url = format!("playlists/{}", playlist_id); self.endpoint_put(&url, ¶ms).await } @@ -737,11 +724,11 @@ impl Spotify { position: Option, ) -> ClientResult { let uris = track_ids.into_iter().map(|id| id.uri()).collect::>(); + let params = map_json! { + req uris => uris, + req position => position, + }; - let mut params = json!({ "uris": uris }); - if let Some(position) = position { - json_insert!(params, "position", position); - } let url = format!("playlists/{}/tracks", playlist_id.id()); let result = self.endpoint_post(&url, ¶ms).await?; self.convert_result(&result) @@ -763,7 +750,9 @@ impl Spotify { ) -> ClientResult<()> { let uris = track_ids.into_iter().map(|id| id.uri()).collect::>(); - let params = json!({ "uris": uris }); + let params = map_json! { + req uris => uris + }; let url = format!("playlists/{}/tracks", playlist_id.id()); self.endpoint_put(&url, ¶ms).await?; @@ -792,26 +781,15 @@ impl Spotify { range_length: Option, snapshot_id: Option, ) -> ClientResult { - let mut params = json! ({ - "playlist_id": playlist_id - }); - let uris = uris.map(|u| u.iter().map(|id| id.uri()).collect::>()); - if let Some(uris) = uris { - json_insert!(params, "uris", uris); - } - if let Some(range_start) = range_start { - json_insert!(params, "range_start", range_start); - } - if let Some(insert_before) = insert_before { - json_insert!(params, "insert_before", insert_before); - } - if let Some(range_length) = range_length { - json_insert!(params, "range_length", range_length); - } - if let Some(snapshot_id) = snapshot_id { - json_insert!(params, "snapshot_id", snapshot_id); - } + let params = map_json! { + req playlist_id => playlist_id, + opt uris => uris, + opt range_start => range_start, + opt insert_before => insert_before, + opt range_length => range_length, + opt snapshot_id => snapshot_id, + }; let url = format!("playlists/{}/tracks", playlist_id.id()); let result = self.endpoint_put(&url, ¶ms).await?; @@ -842,11 +820,10 @@ impl Spotify { }) .collect::>(); - let mut params = json!({ "tracks": tracks }); - - if let Some(snapshot_id) = snapshot_id { - json_insert!(params, "snapshot_id", snapshot_id); - } + let params = map_json! { + req tracks => tracks, + opt snapshot_id => snapshot_id, + }; let url = format!("playlists/{}/tracks", playlist_id.id()); let result = self.endpoint_delete(&url, ¶ms).await?; @@ -889,7 +866,7 @@ impl Spotify { tracks: Vec>, snapshot_id: Option, ) -> ClientResult { - let ftracks = tracks + let tracks = tracks .into_iter() .map(|track| { let mut map = Map::new(); @@ -899,10 +876,11 @@ impl Spotify { }) .collect::>(); - let mut params = json!({ "tracks": ftracks }); - if let Some(snapshot_id) = snapshot_id { - json_insert!(params, "snapshot_id", snapshot_id); - } + let params = map_json! { + req tracks => tracks, + opt snapshot_id => snapshot_id, + }; + let url = format!("playlists/{}/tracks", playlist_id.id()); let result = self.endpoint_delete(&url, ¶ms).await?; self.convert_result(&result) @@ -922,13 +900,11 @@ impl Spotify { ) -> ClientResult<()> { let url = format!("playlists/{}/followers", playlist_id.id()); - let mut params = json!({}); - if let Some(public) = public { - json_insert!(params, "public", public); - } + let params = map_json! { + opt public => public, + }; - self.endpoint_put(&url, ¶ms) - .await?; + self.endpoint_put(&url, ¶ms).await?; Ok(()) } @@ -1028,7 +1004,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); - let params = map_params! { + let params = map_query! { opt limit => &limit, opt offset => &offset, }; @@ -1066,7 +1042,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); - let params = map_params! { + let params = map_query! { opt limit => &limit, opt offset => &offset, }; @@ -1089,7 +1065,7 @@ impl Spotify { limit: Option, ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); - let params = map_params! { + let params = map_query! { req r#type => Type::Artist.as_ref(), opt after => &after, opt limit => &limit, @@ -1167,7 +1143,9 @@ impl Spotify { time_range: Option<&'a TimeRange>, ) -> impl Paginator> + 'a { paginate( - move |limit, offset| self.current_user_top_artists_manual(time_range, Some(limit), Some(offset)), + move |limit, offset| { + self.current_user_top_artists_manual(time_range, Some(limit), Some(offset)) + }, self.pagination_chunks, ) } @@ -1182,7 +1160,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); - let params = map_params! { + let params = map_query! { opt time_range => time_range.as_ref(), opt limit => &limit, opt offset => &offset, @@ -1208,7 +1186,9 @@ impl Spotify { time_range: Option<&'a TimeRange>, ) -> impl Paginator> + 'a { paginate( - move |limit, offset| self.current_user_top_tracks_manual(time_range, Some(limit), Some(offset)), + move |limit, offset| { + self.current_user_top_tracks_manual(time_range, Some(limit), Some(offset)) + }, self.pagination_chunks, ) } @@ -1223,7 +1203,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); - let params = map_params! { + let params = map_query! { opt time_range => time_range.as_ref(), opt limit => &limit, opt offset => &offset, @@ -1245,7 +1225,7 @@ impl Spotify { limit: Option, ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); - let params = map_params! { + let params = map_query! { opt limit => &limit, }; @@ -1423,7 +1403,7 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let timestamp = timestamp.map(|x| x.to_rfc3339()); - let params = map_params! { + let params = map_query! { opt limit => &limit, opt offset => &offset, opt locale => locale, @@ -1470,7 +1450,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); - let params = map_params! { + let params = map_query! { opt limit => &limit, opt offset => &offset, opt market => market.as_ref(), @@ -1518,7 +1498,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); - let params = map_params! { + let params = map_query! { opt limit => &limit, opt offset => &offset, opt locale => locale, @@ -1567,7 +1547,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); - let params = map_params! { + let params = map_query! { opt limit => &limit, opt offset => &offset, opt market => market.as_ref(), @@ -1608,7 +1588,7 @@ impl Spotify { let seed_genres = seed_genres.map(|x| x.join(",")); let seed_tracks = seed_tracks.map(join_ids); let limit = limit.map(|x| x.to_string()); - let mut params = map_params! { + let mut params = map_query! { opt seed_artists => seed_artists, opt seed_genres => seed_genres, opt seed_tracks => seed_tracks, @@ -1731,7 +1711,7 @@ impl Spotify { ) -> ClientResult> { let additional_types = additional_types.map(|x| x.iter().map(|x| x.as_ref()).collect::>().join(",")); - let params = map_params! { + let params = map_query! { opt market => market.as_ref(), opt additional_types => additional_types, }; @@ -1761,7 +1741,7 @@ impl Spotify { ) -> ClientResult> { let additional_types = additional_types.map(|x| x.iter().map(|x| x.as_ref()).collect::>().join(",")); - let params = map_params! { + let params = map_query! { opt market => market.as_ref(), opt additional_types => additional_types, }; @@ -1788,20 +1768,17 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-transfer-a-users-playback) #[maybe_async] - pub async fn transfer_playback>>( + pub async fn transfer_playback( &self, device_id: &str, - force_play: T, + force_play: Option, ) -> ClientResult<()> { - self.endpoint_put( - "me/player", - &json! ({ - "device_ids": vec![device_id.to_owned()], - "play": force_play.into().unwrap_or(true) - }), - ) - .await?; + let params = map_json! { + req device_ids => vec![device_id.to_owned()], + opt force_play => force_play, + }; + self.endpoint_put("me/player", ¶ms).await?; Ok(()) } @@ -1830,21 +1807,16 @@ impl Spotify { ) -> ClientResult<()> { use super::model::Offset; - let mut params = json!({}); - json_insert!(params, "context_uri", context_uri.uri()); - if let Some(offset) = offset { - match offset { - Offset::Position(position) => { - json_insert!(params, "offset", json!({ "position": position })); - } - Offset::Uri(uri) => { - json_insert!(params, "offset", json!({ "uri": uri.uri() })); - } - } - } - if let Some(position_ms) = position_ms { - json_insert!(params, "position_ms", position_ms); + let params = map_json! { + req context_uri => context_uri.uri(), + opt offset => match offset { + Offset::Position(position) => json!({ "position": position }), + Offset::Uri(uri) => json!({ "uri": uri.uri() }), + }, + opt position_ms => position_ms + }; + let url = self.append_device_id("me/player/play", device_id); self.put(&url, None, ¶ms).await?; @@ -1861,25 +1833,15 @@ impl Spotify { ) -> ClientResult<()> { use super::model::Offset; - let mut params = json!({}); - json_insert!( - params, - "uris", - uris.iter().map(|id| id.uri()).collect::>() - ); - if let Some(offset) = offset { - match offset { - Offset::Position(position) => { - json_insert!(params, "offset", json!({ "position": position })); - } - Offset::Uri(uri) => { - json_insert!(params, "offset", json!({ "uri": uri.uri() })); - } - } - } - if let Some(position_ms) = position_ms { - json_insert!(params, "position_ms", position_ms); + let params = map_json! { + req uris => uris.iter().map(|id| id.uri()).collect::>(), + opt offset => match offset { + Offset::Position(position) => json!({ "position": position }), + Offset::Uri(uri) => json!({ "uri": uri.uri() }), + }, + opt position_ms => position_ms, }; + let url = self.append_device_id("me/player/play", device_id); self.endpoint_put(&url, ¶ms).await?; @@ -2072,7 +2034,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); - let params = map_params! { + let params = map_query! { opt limit => &limit, opt offset => &offset, }; @@ -2092,7 +2054,7 @@ impl Spotify { /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-a-show) #[maybe_async] pub async fn get_a_show(&self, id: &ShowId, market: Option) -> ClientResult { - let params = map_params! { + let params = map_query! { opt market => market.as_ref(), }; @@ -2116,7 +2078,7 @@ impl Spotify { market: Option, ) -> ClientResult> { let ids = join_ids(ids); - let params = map_params! { + let params = map_query! { req ids => ids.as_ref(), opt market => market.as_ref(), }; @@ -2147,7 +2109,9 @@ impl Spotify { market: Option<&'a Market>, ) -> impl Paginator> + 'a { paginate( - move |limit, offset| self.get_shows_episodes_manual(id, market, Some(limit), Some(offset)), + move |limit, offset| { + self.get_shows_episodes_manual(id, market, Some(limit), Some(offset)) + }, self.pagination_chunks, ) } @@ -2163,7 +2127,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); - let params = map_params! { + let params = map_query! { opt limit => &limit, opt offset => &offset, opt market => market.as_ref(), @@ -2190,7 +2154,7 @@ impl Spotify { market: Option, ) -> ClientResult { let url = format!("episodes/{}", id.id()); - let params = map_params! { + let params = map_query! { opt market => market.as_ref(), }; @@ -2212,7 +2176,7 @@ impl Spotify { market: Option, ) -> ClientResult> { let ids = join_ids(ids); - let params = map_params! { + let params = map_query! { req ids => ids.as_ref(), opt market => market.as_ref(), }; @@ -2234,7 +2198,7 @@ impl Spotify { ids: impl IntoIterator, ) -> ClientResult> { let ids = join_ids(ids); - let params = map_params! { + let params = map_query! { req ids => ids.as_str(), }; let result = self.endpoint_get("me/shows/contains", ¶ms).await?; @@ -2253,13 +2217,12 @@ impl Spotify { pub async fn remove_users_saved_shows<'a>( &self, show_ids: impl IntoIterator, - market: Option, + country: Option, ) -> ClientResult<()> { let url = format!("me/shows?ids={}", join_ids(show_ids)); - let mut params = json!({}); - if let Some(market) = market { - json_insert!(params, "country", market.as_ref()); - } + let params = map_json! { + opt country => country.as_ref() + }; self.endpoint_delete(&url, ¶ms).await?; Ok(()) diff --git a/src/macros.rs b/src/macros.rs index 40ca3b59..5176f5a9 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -33,51 +33,60 @@ macro_rules! scopes { }}; } -/// Reduce boilerplate when inserting new elements in a JSON object. -#[doc(hidden)] -#[macro_export] -macro_rules! json_insert { - ($json:expr, $p1:expr, $p2:expr) => { - $json - .as_object_mut() - .unwrap() - .insert($p1.to_string(), json!($p2)) - }; -} - #[macro_export] macro_rules! params_internal { - ($map:ident, req, $key:ident, $val:expr) => ( - $map.insert(stringify!($key), $val); - ); - ($map:ident, opt, $key:ident, $val:expr) => ( - if let Some(ref $key) = $key { - $map.insert(stringify!($key), $val); + ($map:ident, req, $name:ident, $key:expr, $val:expr) => { + $map.insert($key, $val); + }; + ($map:ident, opt, $name:ident, $key:expr, $val:expr) => { + if let Some(ref $name) = $name { + $map.insert($key, $val); } - ); + }; } /// TODO: use with_capacity? #[macro_export] -macro_rules! map_params { +macro_rules! map_query { ( $( - $kind:ident $key:ident => $val:expr + $kind:ident $name:ident => $val:expr ),* $(,)? ) => ({ let mut params = crate::http::Query::new(); $( - crate::params_internal!(params, $kind, $key, $val); + crate::params_internal!( + params, + $kind, + $name, + stringify!($name), + $val + ); )* params }); } #[macro_export] -macro_rules! json_params { - () => { +macro_rules! map_json { + ( + $( + $kind:ident $name:ident => $val:expr + ),* $(,)? + ) => ({ + let mut params = ::serde_json::map::Map::new(); + $( - } + crate::params_internal!( + params, + $kind, + $name, + stringify!($name).to_string(), + json!($val) + ); + )* + ::serde_json::Value::from(params) + }); } #[cfg(test)] From 6eacf77b371d5364439bb0ceb4acf157b0738b2c Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 00:37:08 +0200 Subject: [PATCH 05/39] small simplification --- src/client.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/client.rs b/src/client.rs index 4dc3033a..22eb244a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -570,18 +570,13 @@ impl Spotify { let params = map_query! { opt fields => fields, }; - match playlist_id { - Some(playlist_id) => { - let url = format!("users/{}/playlists/{}", user_id.id(), playlist_id.id()); - let result = self.endpoint_get(&url, ¶ms).await?; - self.convert_result(&result) - } - None => { - let url = format!("users/{}/starred", user_id.id()); - let result = self.endpoint_get(&url, ¶ms).await?; - self.convert_result(&result) - } - } + + let url = match playlist_id { + 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?; + self.convert_result(&result) } /// Get full details of the tracks of a playlist owned by a user. From 251e80bf0471a8b33b061e9375792bcb04c86a77 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 01:05:27 +0200 Subject: [PATCH 06/39] add support for simple parameters --- src/client.rs | 72 +++++++++++++++++++++++++-------------------------- src/macros.rs | 27 +++++++++++++------ 2 files changed, 55 insertions(+), 44 deletions(-) diff --git a/src/client.rs b/src/client.rs index 22eb244a..2c503b1e 100644 --- a/src/client.rs +++ b/src/client.rs @@ -386,7 +386,7 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = map_query! { - req q => q, + req q, req r#type => r#type.as_ref(), opt market => market.as_ref(), opt include_external => include_external.as_ref(), @@ -467,7 +467,7 @@ impl Spotify { market: Option, ) -> ClientResult { let params = map_query! { - opt fields => fields, + opt fields, opt market => market.as_ref(), }; @@ -568,7 +568,7 @@ impl Spotify { fields: Option<&str>, ) -> ClientResult { let params = map_query! { - opt fields => fields, + opt fields, }; let url = match playlist_id { @@ -619,10 +619,10 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = map_query! { + opt fields, opt limit => &limit, opt offset => &offset, opt market => market.as_ref(), - opt fields => fields, }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -641,7 +641,7 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-create-playlist) #[maybe_async] - pub async fn user_playlist_creat( + pub async fn user_playlist_create( &self, user_id: &UserId, name: &str, @@ -650,10 +650,10 @@ impl Spotify { description: Option<&str>, ) -> ClientResult { let params = map_json! { - req name => name, - opt public => public, - opt collaborative => collaborative, - opt description => description, + req name, + opt public, + opt collaborative, + opt description, }; let url = format!("users/{}/playlists", user_id.id()); @@ -681,10 +681,10 @@ impl Spotify { collaborative: Option, ) -> ClientResult { let params = map_json! { - opt name => name, - opt public => public, - opt collaborative => collaborative, - opt description => description, + opt name, + opt public, + opt collaborative, + opt description, }; let url = format!("playlists/{}", playlist_id); @@ -720,8 +720,8 @@ impl Spotify { ) -> ClientResult { let uris = track_ids.into_iter().map(|id| id.uri()).collect::>(); let params = map_json! { - req uris => uris, - req position => position, + req uris, + req position, }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -778,12 +778,12 @@ impl Spotify { ) -> ClientResult { let uris = uris.map(|u| u.iter().map(|id| id.uri()).collect::>()); let params = map_json! { - req playlist_id => playlist_id, - opt uris => uris, - opt range_start => range_start, - opt insert_before => insert_before, - opt range_length => range_length, - opt snapshot_id => snapshot_id, + req playlist_id, + opt uris, + opt range_start, + opt insert_before, + opt range_length, + opt snapshot_id, }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -816,8 +816,8 @@ impl Spotify { .collect::>(); let params = map_json! { - req tracks => tracks, - opt snapshot_id => snapshot_id, + req tracks, + opt snapshot_id, }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -872,8 +872,8 @@ impl Spotify { .collect::>(); let params = map_json! { - req tracks => tracks, - opt snapshot_id => snapshot_id, + req tracks, + opt snapshot_id, }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -896,7 +896,7 @@ impl Spotify { let url = format!("playlists/{}/followers", playlist_id.id()); let params = map_json! { - opt public => public, + opt public, }; self.endpoint_put(&url, ¶ms).await?; @@ -1401,9 +1401,9 @@ impl Spotify { let params = map_query! { opt limit => &limit, opt offset => &offset, - opt locale => locale, + opt locale, opt market => market.as_ref(), - opt timestamp => timestamp, + opt timestamp, }; let result = self @@ -1496,7 +1496,7 @@ impl Spotify { let params = map_query! { opt limit => &limit, opt offset => &offset, - opt locale => locale, + opt locale, opt market => market.as_ref(), }; let result = self.endpoint_get("browse/categories", ¶ms).await?; @@ -1584,9 +1584,9 @@ impl Spotify { let seed_tracks = seed_tracks.map(join_ids); let limit = limit.map(|x| x.to_string()); let mut params = map_query! { - opt seed_artists => seed_artists, - opt seed_genres => seed_genres, - opt seed_tracks => seed_tracks, + opt seed_artists, + opt seed_genres, + opt seed_tracks, opt market => market.as_ref(), opt limit => &limit, }; @@ -1708,7 +1708,7 @@ impl Spotify { additional_types.map(|x| x.iter().map(|x| x.as_ref()).collect::>().join(",")); let params = map_query! { opt market => market.as_ref(), - opt additional_types => additional_types, + opt additional_types, }; let result = self.endpoint_get("me/player", ¶ms).await?; @@ -1738,7 +1738,7 @@ impl Spotify { additional_types.map(|x| x.iter().map(|x| x.as_ref()).collect::>().join(",")); let params = map_query! { opt market => market.as_ref(), - opt additional_types => additional_types, + opt additional_types, }; let result = self @@ -1770,7 +1770,7 @@ impl Spotify { ) -> ClientResult<()> { let params = map_json! { req device_ids => vec![device_id.to_owned()], - opt force_play => force_play, + opt force_play, }; self.endpoint_put("me/player", ¶ms).await?; @@ -1830,11 +1830,11 @@ impl Spotify { let params = map_json! { req uris => uris.iter().map(|id| id.uri()).collect::>(), + opt position_ms, opt offset => match offset { Offset::Position(position) => json!({ "position": position }), Offset::Uri(uri) => json!({ "uri": uri.uri() }), }, - opt position_ms => position_ms, }; let url = self.append_device_id("me/player/play", device_id); diff --git a/src/macros.rs b/src/macros.rs index 5176f5a9..08c4dfa3 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -45,22 +45,34 @@ macro_rules! params_internal { }; } +#[macro_export] +macro_rules! opt { + (, $def:expr) => { + $def + }; + ($opt:expr, $def:expr) => { + $opt + }; +} + /// TODO: use with_capacity? +/// The usage of this macro is similar to a struct initialization, in the sense +/// that you may provide a single identifier if #[macro_export] macro_rules! map_query { ( $( - $kind:ident $name:ident => $val:expr + $kind:ident $name:ident $( => $val:expr )? ),* $(,)? ) => ({ - let mut params = crate::http::Query::new(); + let mut params = $crate::http::Query::new(); $( - crate::params_internal!( + $crate::params_internal!( params, $kind, $name, stringify!($name), - $val + $crate::opt!($( $val )?, $name) ); )* params @@ -71,18 +83,17 @@ macro_rules! map_query { macro_rules! map_json { ( $( - $kind:ident $name:ident => $val:expr + $kind:ident $name:ident $( => $val:expr )? ),* $(,)? ) => ({ let mut params = ::serde_json::map::Map::new(); $( - - crate::params_internal!( + $crate::params_internal!( params, $kind, $name, stringify!($name).to_string(), - json!($val) + json!($crate::opt!($( $val )?, $name)) ); )* ::serde_json::Value::from(params) From 60e8628d2d1c1efefefd08c5a42536aa333f707b Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 01:50:02 +0200 Subject: [PATCH 07/39] added docs and tests to macros --- src/client.rs | 10 +++--- src/macros.rs | 92 ++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 77 insertions(+), 25 deletions(-) diff --git a/src/client.rs b/src/client.rs index 2c503b1e..ce926ce8 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1399,11 +1399,11 @@ impl Spotify { let offset = offset.map(|x| x.to_string()); let timestamp = timestamp.map(|x| x.to_rfc3339()); let params = map_query! { - opt limit => &limit, - opt offset => &offset, opt locale, opt market => market.as_ref(), opt timestamp, + opt limit => &limit, + opt offset => &offset, }; let result = self @@ -1494,10 +1494,10 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = map_query! { - opt limit => &limit, - opt offset => &offset, opt locale, opt market => market.as_ref(), + opt limit => &limit, + opt offset => &offset, }; let result = self.endpoint_get("browse/categories", ¶ms).await?; self.convert_result::(&result) @@ -1543,9 +1543,9 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = map_query! { + opt market => market.as_ref(), opt limit => &limit, opt offset => &offset, - opt market => market.as_ref(), }; let url = format!("browse/categories/{}/playlists", category_id); diff --git a/src/macros.rs b/src/macros.rs index 08c4dfa3..ecedb8ff 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -33,31 +33,57 @@ macro_rules! scopes { }}; } +/// If there's an optional value, the macro will return its value. Otherwise, a +/// default value will be returned. This is helpful to handle `$( expr )?` +/// cases in macros. +#[doc(hidden)] +#[macro_export] +macro_rules! opt { + (, $default:expr) => { + $default + }; + ($optional:expr, $default:expr) => { + $optional + }; +} + +/// Private macro to insert either required or optional fields. Pattern matching +/// will accordingly pick the branch, and then insert ($key, $val) into $map. +#[doc(hidden)] #[macro_export] macro_rules! params_internal { ($map:ident, req, $name:ident, $key:expr, $val:expr) => { $map.insert($key, $val); }; ($map:ident, opt, $name:ident, $key:expr, $val:expr) => { + // Will only insert when `$name` is not None. if let Some(ref $name) = $name { $map.insert($key, $val); } }; } -#[macro_export] -macro_rules! opt { - (, $def:expr) => { - $def - }; - ($opt:expr, $def:expr) => { - $opt - }; -} - /// TODO: use with_capacity? -/// The usage of this macro is similar to a struct initialization, in the sense -/// that you may provide a single identifier if +/// This macro and [`map_json`] help make the endpoints as concise as possible +/// and boilerplate-free, which is specially important when initializing the +/// parameters of the query with a HashMap/similar. Their items follow the +/// syntax: +/// +/// [req|opt] key [=> value] +/// +/// The first keyword is just to distinguish between a direct insert into the +/// hashmap (required parameter), and an insert only if the value is +/// `Some(...)`, respectively (optional parameter). This is followed by the +/// variable to be inserted, which shall have the same name as the key in the +/// map (meaning that `r#type` may have to be used). +/// +/// It also works similarly to how struct initialization works. You may provide +/// a key and a value with `MyStruct { key: value }`, or if both have the same +/// name as the key, `MyStruct { key }` is enough. +/// +/// For more information, please refer to the `test::test_map_query` function in +/// this module to see an example, or the real usages in Rspotify's client. +#[doc(hidden)] #[macro_export] macro_rules! map_query { ( @@ -79,6 +105,9 @@ macro_rules! map_query { }); } +/// Refer to the [`map_query`] documentation; this is the same but for JSON +/// maps. +#[doc(hidden)] #[macro_export] macro_rules! map_json { ( @@ -102,8 +131,9 @@ macro_rules! map_json { #[cfg(test)] mod test { - use crate::{json_insert, scopes}; - use serde_json::json; + use crate::{map_query, scopes}; + use crate::model::{AlbumId, Market}; + use crate::http::Query; #[test] fn test_hashset() { @@ -115,11 +145,33 @@ mod test { assert!(scope.contains(&"bar".to_owned())); } - #[test] - fn test_json_insert() { - let mut params = json!({}); - let name = "ramsay"; - json_insert!(params, "name", name); - assert_eq!(params["name"], name); + fn test_map_query() { + // Passed as parameters, for example. + let id = "Pink Lemonade"; + let artist: Option<&str> = None; + let album = Some(AlbumId::from_uri("spotify:album:0XOseclZGO4NnaBz5Shjxp").unwrap()); + let market = Some(Market::FromToken); + + let with_macro = map_query! { + // Mandatory (not an `Option`) + req id, + // Is `None`, so it won't be inserted + opt artist, + // Can be used directly + opt album, + // `Market` needs to be converted to &str + opt market => market.as_ref(), + }; + + let mut manually = Query::new(); + manually.insert("id", id); + if let Some(ref artist) = artist { + manually.insert("artist", artist); + } + if let Some(ref market) = market { + manually.insert("market", market.as_ref()); + } + + assert_eq!(with_macro, manually); } } From 93115553313b0046c6a34cd3d4049feaa287e20c Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 02:05:12 +0200 Subject: [PATCH 08/39] fix tests --- src/client.rs | 44 +++++++++++------------ src/macros.rs | 24 ++++++------- tests/test_with_oauth.rs | 76 +++++++++++++++++++++++++--------------- 3 files changed, 78 insertions(+), 66 deletions(-) diff --git a/src/client.rs b/src/client.rs index ce926ce8..08f635a7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -154,7 +154,7 @@ impl Spotify { } /// Append device ID to an API path. - fn append_device_id(&self, path: &str, device_id: Option) -> String { + fn append_device_id(&self, path: &str, device_id: Option<&str>) -> String { let mut new_path = path.to_string(); if let Some(_device_id) = device_id { if path.contains('?') { @@ -677,7 +677,7 @@ impl Spotify { playlist_id: &str, name: Option<&str>, public: Option, - description: Option, + description: Option<&str>, collaborative: Option, ) -> ClientResult { let params = map_json! { @@ -774,7 +774,7 @@ impl Spotify { range_start: Option, insert_before: Option, range_length: Option, - snapshot_id: Option, + snapshot_id: Option<&str>, ) -> ClientResult { let uris = uris.map(|u| u.iter().map(|id| id.uri()).collect::>()); let params = map_json! { @@ -804,7 +804,7 @@ impl Spotify { &self, playlist_id: &PlaylistId, track_ids: impl IntoIterator, - snapshot_id: Option, + snapshot_id: Option<&str>, ) -> ClientResult { let tracks = track_ids .into_iter() @@ -859,7 +859,7 @@ impl Spotify { &self, playlist_id: &PlaylistId, tracks: Vec>, - snapshot_id: Option, + snapshot_id: Option<&str>, ) -> ClientResult { let tracks = tracks .into_iter() @@ -1056,7 +1056,7 @@ impl Spotify { #[maybe_async] pub async fn current_user_followed_artists( &self, - after: Option, + after: Option<&str>, limit: Option, ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); @@ -1389,7 +1389,7 @@ impl Spotify { #[maybe_async] pub async fn featured_playlists( &self, - locale: Option, + locale: Option<&str>, market: Option, timestamp: Option>, limit: Option, @@ -1572,12 +1572,12 @@ impl Spotify { #[maybe_async] pub async fn recommendations( &self, + payload: &Map, seed_artists: Option>, seed_genres: Option>, seed_tracks: Option>, limit: Option, market: Option, - payload: &Map, ) -> ClientResult { let seed_artists = seed_artists.map(join_ids); let seed_genres = seed_genres.map(|x| x.join(",")); @@ -1796,7 +1796,7 @@ impl Spotify { pub async fn start_context_playback( &self, context_uri: &Id, - device_id: Option, + device_id: Option<&str>, offset: Option>, position_ms: Option, ) -> ClientResult<()> { @@ -1822,7 +1822,7 @@ impl Spotify { pub async fn start_uris_playback( &self, uris: &[&Id], - device_id: Option, + device_id: Option<&str>, offset: Option>, position_ms: Option, ) -> ClientResult<()> { @@ -1850,7 +1850,7 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-pause-a-users-playback) #[maybe_async] - pub async fn pause_playback(&self, device_id: Option) -> ClientResult<()> { + pub async fn pause_playback(&self, device_id: Option<&str>) -> ClientResult<()> { let url = self.append_device_id("me/player/pause", device_id); self.endpoint_put(&url, &json!({})).await?; @@ -1864,7 +1864,7 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-skip-users-playback-to-next-track) #[maybe_async] - pub async fn next_track(&self, device_id: Option) -> ClientResult<()> { + pub async fn next_track(&self, device_id: Option<&str>) -> ClientResult<()> { let url = self.append_device_id("me/player/next", device_id); self.endpoint_post(&url, &json!({})).await?; @@ -1878,7 +1878,7 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-skip-users-playback-to-previous-track) #[maybe_async] - pub async fn previous_track(&self, device_id: Option) -> ClientResult<()> { + pub async fn previous_track(&self, device_id: Option<&str>) -> ClientResult<()> { let url = self.append_device_id("me/player/previous", device_id); self.endpoint_post(&url, &json!({})).await?; @@ -1893,11 +1893,7 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-seek-to-position-in-currently-playing-track) #[maybe_async] - pub async fn seek_track( - &self, - position_ms: u32, - device_id: Option, - ) -> ClientResult<()> { + pub async fn seek_track(&self, position_ms: u32, device_id: Option<&str>) -> ClientResult<()> { let url = self.append_device_id( &format!("me/player/seek?position_ms={}", position_ms), device_id, @@ -1915,7 +1911,7 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-set-repeat-mode-on-users-playback) #[maybe_async] - pub async fn repeat(&self, state: RepeatState, device_id: Option) -> ClientResult<()> { + pub async fn repeat(&self, state: RepeatState, device_id: Option<&str>) -> ClientResult<()> { let url = self.append_device_id( &format!("me/player/repeat?state={}", state.as_ref()), device_id, @@ -1933,7 +1929,7 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-set-volume-for-users-playback) #[maybe_async] - pub async fn volume(&self, volume_percent: u8, device_id: Option) -> ClientResult<()> { + pub async fn volume(&self, volume_percent: u8, device_id: Option<&str>) -> ClientResult<()> { if volume_percent > 100u8 { error!("volume must be between 0 and 100, inclusive"); } @@ -1954,7 +1950,7 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-toggle-shuffle-for-users-playback) #[maybe_async] - pub async fn shuffle(&self, state: bool, device_id: Option) -> ClientResult<()> { + pub async fn shuffle(&self, state: bool, device_id: Option<&str>) -> ClientResult<()> { let url = self.append_device_id(&format!("me/player/shuffle?state={}", state), device_id); self.endpoint_put(&url, &json!({})).await?; @@ -1974,7 +1970,7 @@ impl Spotify { pub async fn add_item_to_queue( &self, item: &Id, - device_id: Option, + device_id: Option<&str>, ) -> ClientResult<()> { let url = self.append_device_id(&format!("me/player/queue?uri={}", item), device_id); self.endpoint_post(&url, &json!({})).await?; @@ -2244,7 +2240,7 @@ mod test { #[test] fn test_append_device_id_without_question_mark() { let path = "me/player/play"; - let device_id = Some("fdafdsadfa".to_owned()); + let device_id = Some("fdafdsadfa"); let spotify = SpotifyBuilder::default().build().unwrap(); let new_path = spotify.append_device_id(path, device_id); assert_eq!(new_path, "me/player/play?device_id=fdafdsadfa"); @@ -2253,7 +2249,7 @@ mod test { #[test] fn test_append_device_id_with_question_mark() { let path = "me/player/shuffle?state=true"; - let device_id = Some("fdafdsadfa".to_owned()); + let device_id = Some("fdafdsadfa"); let spotify = SpotifyBuilder::default().build().unwrap(); let new_path = spotify.append_device_id(path, device_id); assert_eq!( diff --git a/src/macros.rs b/src/macros.rs index ecedb8ff..a8dd5719 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -70,7 +70,7 @@ macro_rules! params_internal { /// syntax: /// /// [req|opt] key [=> value] -/// +/// /// The first keyword is just to distinguish between a direct insert into the /// hashmap (required parameter), and an insert only if the value is /// `Some(...)`, respectively (optional parameter). This is followed by the @@ -131,9 +131,9 @@ macro_rules! map_json { #[cfg(test)] mod test { - use crate::{map_query, scopes}; - use crate::model::{AlbumId, Market}; use crate::http::Query; + use crate::model::Modality; + use crate::{map_query, scopes}; #[test] fn test_hashset() { @@ -145,22 +145,20 @@ mod test { assert!(scope.contains(&"bar".to_owned())); } + #[test] fn test_map_query() { // Passed as parameters, for example. let id = "Pink Lemonade"; - let artist: Option<&str> = None; - let album = Some(AlbumId::from_uri("spotify:album:0XOseclZGO4NnaBz5Shjxp").unwrap()); - let market = Some(Market::FromToken); + let artist = Some("The Wombats"); + let modality: Option = None; let with_macro = map_query! { // Mandatory (not an `Option`) req id, - // Is `None`, so it won't be inserted - opt artist, // Can be used directly - opt album, - // `Market` needs to be converted to &str - opt market => market.as_ref(), + opt artist, + // `Modality` needs to be converted to &str + opt modality => modality.as_ref(), }; let mut manually = Query::new(); @@ -168,8 +166,8 @@ mod test { if let Some(ref artist) = artist { manually.insert("artist", artist); } - if let Some(ref market) = market { - manually.insert("market", market.as_ref()); + if let Some(ref modality) = modality { + manually.insert("modality", modality.as_ref()); } assert_eq!(with_macro, manually); diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index 0eba0c1c..67e3cff0 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -101,7 +101,12 @@ pub async fn oauth_client() -> Spotify { async fn test_categories() { oauth_client() .await - .categories_manual(None, Some(&Market::Country(Country::UnitedStates)), 10, 0) + .categories_manual( + None, + Some(&Market::Country(Country::UnitedStates)), + Some(10), + Some(0), + ) .await .unwrap(); } @@ -112,7 +117,12 @@ async fn test_categories() { async fn test_category_playlists() { oauth_client() .await - .category_playlists_manual("pop", Some(&Market::Country(Country::UnitedStates)), 10, 0) + .category_playlists_manual( + "pop", + Some(&Market::Country(Country::UnitedStates)), + Some(10), + Some(0), + ) .await .unwrap(); } @@ -145,7 +155,7 @@ async fn test_current_playing() { async fn test_current_user_followed_artists() { oauth_client() .await - .current_user_followed_artists(10, None) + .current_user_followed_artists(None, Some(10)) .await .unwrap(); } @@ -167,7 +177,7 @@ async fn test_current_user_playing_track() { async fn test_current_user_playlists() { oauth_client() .await - .current_user_playlists_manual(10, None) + .current_user_playlists_manual(Some(10), None) .await .unwrap(); } @@ -178,7 +188,7 @@ async fn test_current_user_playlists() { async fn test_current_user_recently_played() { oauth_client() .await - .current_user_recently_played(10) + .current_user_recently_played(Some(10)) .await .unwrap(); } @@ -221,7 +231,7 @@ async fn test_current_user_saved_albums_delete() { async fn test_current_user_saved_albums() { oauth_client() .await - .current_user_saved_albums_manual(10, 0) + .current_user_saved_albums_manual(Some(10), Some(0)) .await .unwrap(); } @@ -280,7 +290,7 @@ async fn test_current_user_saved_tracks_delete() { async fn test_current_user_saved_tracks() { oauth_client() .await - .current_user_saved_tracks_manual(10, 0) + .current_user_saved_tracks_manual(Some(10), Some(0)) .await .unwrap(); } @@ -291,7 +301,7 @@ async fn test_current_user_saved_tracks() { async fn test_current_user_top_artists() { oauth_client() .await - .current_user_top_artists_manual(Some(&TimeRange::ShortTerm), 10, 0) + .current_user_top_artists_manual(Some(&TimeRange::ShortTerm), Some(10), Some(0)) .await .unwrap(); } @@ -302,7 +312,7 @@ async fn test_current_user_top_artists() { async fn test_current_user_top_tracks() { oauth_client() .await - .current_user_top_tracks_manual(Some(&TimeRange::ShortTerm), 10, 0) + .current_user_top_tracks_manual(Some(&TimeRange::ShortTerm), Some(10), Some(0)) .await .unwrap(); } @@ -321,7 +331,7 @@ async fn test_featured_playlists() { let now: DateTime = Utc::now(); oauth_client() .await - .featured_playlists(None, None, Some(now), 10, 0) + .featured_playlists(None, None, Some(now), Some(10), Some(0)) .await .unwrap(); } @@ -339,7 +349,7 @@ async fn test_me() { async fn test_new_releases() { oauth_client() .await - .new_releases_manual(Some(&Market::Country(Country::Sweden)), 10, 0) + .new_releases_manual(Some(&Market::Country(Country::Sweden)), Some(10), Some(0)) .await .unwrap(); } @@ -350,7 +360,7 @@ async fn test_new_releases() { async fn test_new_releases_with_from_token() { oauth_client() .await - .new_releases_manual(Some(&Market::FromToken), 10, 0) + .new_releases_manual(Some(&Market::FromToken), Some(10), Some(0)) .await .unwrap(); } @@ -359,7 +369,7 @@ async fn test_new_releases_with_from_token() { #[maybe_async_test] #[ignore] async fn test_next_playback() { - let device_id = String::from("74ASZWbe4lXaubB36ztrGX"); + let device_id = "74ASZWbe4lXaubB36ztrGX"; oauth_client() .await .next_track(Some(device_id)) @@ -371,7 +381,7 @@ async fn test_next_playback() { #[maybe_async_test] #[ignore] async fn test_pause_playback() { - let device_id = String::from("74ASZWbe4lXaubB36ztrGX"); + let device_id = "74ASZWbe4lXaubB36ztrGX"; oauth_client() .await .pause_playback(Some(device_id)) @@ -383,7 +393,7 @@ async fn test_pause_playback() { #[maybe_async_test] #[ignore] async fn test_previous_playback() { - let device_id = String::from("74ASZWbe4lXaubB36ztrGX"); + let device_id = "74ASZWbe4lXaubB36ztrGX"; oauth_client() .await .previous_track(Some(device_id)) @@ -403,12 +413,12 @@ async fn test_recommendations() { oauth_client() .await .recommendations( + &payload, Some(seed_artists), None, Some(seed_tracks), - 10, + Some(10), Some(Market::Country(Country::UnitedStates)), - &payload, ) .await .unwrap(); @@ -432,7 +442,7 @@ async fn test_search_album() { let query = "album:arrival artist:abba"; oauth_client() .await - .search(query, SearchType::Album, 10, 0, None, None) + .search(query, SearchType::Album, None, None, Some(10), Some(0)) .await .unwrap(); } @@ -447,10 +457,10 @@ async fn test_search_artist() { .search( query, SearchType::Artist, - 10, - 0, Some(Market::Country(Country::UnitedStates)), None, + Some(10), + Some(0), ) .await .unwrap(); @@ -466,10 +476,10 @@ async fn test_search_playlist() { .search( query, SearchType::Playlist, - 10, - 0, Some(Market::Country(Country::UnitedStates)), None, + Some(10), + Some(0), ) .await .unwrap(); @@ -485,10 +495,10 @@ async fn test_search_track() { .search( query, SearchType::Track, - 10, - 0, Some(Market::Country(Country::UnitedStates)), None, + Some(10), + Some(0), ) .await .unwrap(); @@ -512,7 +522,7 @@ async fn test_shuffle() { #[maybe_async_test] #[ignore] async fn test_start_playback() { - let device_id = String::from("74ASZWbe4lXaubB36ztrGX"); + let device_id = "74ASZWbe4lXaubB36ztrGX"; let uris = vec![TrackId::from_uri("spotify:track:4iV5W9uYEdYUVa79Axb7Rh").unwrap()]; oauth_client() .await @@ -528,7 +538,7 @@ async fn test_transfer_playback() { let device_id = "74ASZWbe4lXaubB36ztrGX"; oauth_client() .await - .transfer_playback(device_id, true) + .transfer_playback(device_id, Some(true)) .await .unwrap(); } @@ -644,7 +654,7 @@ async fn test_user_playlist_create() { let playlist_name = "A New Playlist"; oauth_client() .await - .user_playlist_create(user_id, playlist_name, false, None) + .user_playlist_create(user_id, playlist_name, Some(false), None, None) .await .unwrap(); } @@ -656,7 +666,7 @@ async fn test_playlist_follow_playlist() { let playlist_id = Id::from_id("2v3iNvBX8Ay1Gt2uXtUKUT").unwrap(); oauth_client() .await - .playlist_follow(playlist_id, true) + .playlist_follow(playlist_id, Some(true)) .await .unwrap(); } @@ -665,13 +675,21 @@ async fn test_playlist_follow_playlist() { #[maybe_async_test] #[ignore] async fn test_playlist_recorder_tracks() { + let uris: Option<&[&EpisodeId]> = None; let playlist_id = Id::from_id("5jAOgWXCBKuinsGiZxjDQ5").unwrap(); let range_start = 0; let insert_before = 1; let range_length = 1; oauth_client() .await - .playlist_reorder_tracks(playlist_id, range_start, range_length, insert_before, None) + .playlist_reorder_tracks( + playlist_id, + uris, + Some(range_start), + Some(insert_before), + Some(range_length), + None, + ) .await .unwrap(); } From fd4866aa92204ebc34868eaf8a27778232096f83 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 02:35:17 +0200 Subject: [PATCH 09/39] fixed some examples --- examples/current_user_recently_played.rs | 2 +- examples/pagination_manual.rs | 2 +- examples/ureq/device.rs | 4 +--- examples/ureq/me.rs | 4 +--- examples/ureq/search.rs | 22 +++++++++----------- src/client.rs | 1 + src/macros.rs | 26 ++++++++++++++++++++++-- 7 files changed, 39 insertions(+), 22 deletions(-) diff --git a/examples/current_user_recently_played.rs b/examples/current_user_recently_played.rs index 3001e137..d68b0eb1 100644 --- a/examples/current_user_recently_played.rs +++ b/examples/current_user_recently_played.rs @@ -45,7 +45,7 @@ async fn main() { spotify.prompt_for_user_token().await.unwrap(); // Running the requests - let history = spotify.current_user_recently_played(10).await; + let history = spotify.current_user_recently_played(Some(10)).await; println!("Response: {:?}", history); } diff --git a/examples/pagination_manual.rs b/examples/pagination_manual.rs index a5804456..80b338fd 100644 --- a/examples/pagination_manual.rs +++ b/examples/pagination_manual.rs @@ -54,7 +54,7 @@ async fn main() { println!("Items:"); loop { let page = spotify - .current_user_saved_tracks_manual(limit, offset) + .current_user_saved_tracks_manual(Some(limit), Some(offset)) .await .unwrap(); for item in page.items { diff --git a/examples/ureq/device.rs b/examples/ureq/device.rs index fb322050..4154394f 100644 --- a/examples/ureq/device.rs +++ b/examples/ureq/device.rs @@ -1,8 +1,6 @@ use rspotify::client::SpotifyBuilder; use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder}; -use rspotify::scope; - -use std::collections::HashSet; +use rspotify::scopes; fn main() { // You can use any logger for debugging. diff --git a/examples/ureq/me.rs b/examples/ureq/me.rs index 4667ac1d..e089c2fd 100644 --- a/examples/ureq/me.rs +++ b/examples/ureq/me.rs @@ -1,8 +1,6 @@ use rspotify::client::SpotifyBuilder; use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder}; -use rspotify::scope; - -use std::collections::HashSet; +use rspotify::scopes; fn main() { // You can use any logger for debugging. diff --git a/examples/ureq/search.rs b/examples/ureq/search.rs index 1f939f23..cfc30832 100644 --- a/examples/ureq/search.rs +++ b/examples/ureq/search.rs @@ -1,9 +1,7 @@ use rspotify::client::SpotifyBuilder; use rspotify::model::{Country, Market, SearchType}; use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder}; -use rspotify::scope; - -use std::collections::HashSet; +use rspotify::scopes; fn main() { // You can use any logger for debugging. @@ -47,7 +45,7 @@ fn main() { spotify.request_client_token().unwrap(); let album_query = "album:arrival artist:abba"; - let result = spotify.search(album_query, SearchType::Album, 10, 0, None, 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), @@ -57,10 +55,10 @@ fn main() { let result = spotify.search( artist_query, SearchType::Artist, - 10, - 0, Some(Market::Country(Country::UnitedStates)), None, + Some(10), + None, ); match result { Ok(album) => println!("searched artist:{:?}", album), @@ -71,10 +69,10 @@ fn main() { let result = spotify.search( playlist_query, SearchType::Playlist, - 10, - 0, Some(Market::Country(Country::UnitedStates)), None, + Some(10), + None, ); match result { Ok(album) => println!("searched playlist:{:?}", album), @@ -85,10 +83,10 @@ fn main() { let result = spotify.search( track_query, SearchType::Track, - 10, - 0, Some(Market::Country(Country::UnitedStates)), None, + Some(10), + None, ); match result { Ok(album) => println!("searched track:{:?}", album), @@ -96,14 +94,14 @@ fn main() { } let show_query = "love"; - let result = spotify.search(show_query, SearchType::Show, 10, 0, None, 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), } let episode_query = "love"; - let result = spotify.search(episode_query, SearchType::Episode, 10, 0, None, None); + let result = spotify.search(episode_query, SearchType::Episode, None, None, Some(10), None); match result { Ok(episode) => println!("searched episode:{:?}", episode), Err(err) => println!("search error!{:?}", err), diff --git a/src/client.rs b/src/client.rs index 08f635a7..fd0b0f81 100644 --- a/src/client.rs +++ b/src/client.rs @@ -393,6 +393,7 @@ impl Spotify { opt limit => &limit, opt offset => &offset, }; + println!("params: {:#?}", params); let result = self.endpoint_get("search", ¶ms).await?; self.convert_result(&result) diff --git a/src/macros.rs b/src/macros.rs index a8dd5719..0f5d24f3 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -47,6 +47,19 @@ macro_rules! opt { }; } +/// Hack to convert an identifier to a string, including raw identifiers. +#[doc(hidden)] +#[macro_export] +macro_rules! ident_str { + ($name:ident) => {{ + const NAME: &str = stringify!($name); + match &NAME.get(..2) { + Some("r#") => &NAME[2..], + _ => &NAME[..] + } + }}; +} + /// Private macro to insert either required or optional fields. Pattern matching /// will accordingly pick the branch, and then insert ($key, $val) into $map. #[doc(hidden)] @@ -97,7 +110,7 @@ macro_rules! map_query { params, $kind, $name, - stringify!($name), + $crate::ident_str!($name), $crate::opt!($( $val )?, $name) ); )* @@ -121,7 +134,7 @@ macro_rules! map_json { params, $kind, $name, - stringify!($name).to_string(), + $crate::ident_str!($name).to_string(), json!($crate::opt!($( $val )?, $name)) ); )* @@ -172,4 +185,13 @@ mod test { assert_eq!(with_macro, manually); } + + #[test] + fn test_ident_str() { + assert_eq!(ident_str!(i), "i"); + assert_eq!(ident_str!(r#i), "i"); + assert_eq!(ident_str!(test), "test"); + assert_eq!(ident_str!(a_b_c_d), "a_b_c_d"); + assert_eq!(ident_str!(r#type), "type"); + } } From b603d5437c91c9eaf79c1bb0ad7f629ce4906379 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 02:37:35 +0200 Subject: [PATCH 10/39] cleanup test --- src/macros.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 0f5d24f3..64984175 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -152,10 +152,10 @@ mod test { fn test_hashset() { let scope = scopes!("hello", "world", "foo", "bar"); assert_eq!(scope.len(), 4); - assert!(scope.contains(&"hello".to_owned())); - assert!(scope.contains(&"world".to_owned())); - assert!(scope.contains(&"foo".to_owned())); - assert!(scope.contains(&"bar".to_owned())); + assert!(scope.contains("hello")); + assert!(scope.contains("world")); + assert!(scope.contains("foo")); + assert!(scope.contains("bar")); } #[test] From 1f1b6096f9a1c499ffbd8d2a8e49bba87bf3c954 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 02:45:22 +0200 Subject: [PATCH 11/39] update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 524d4bdd..36b65102 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -211,8 +211,8 @@ If we missed any change or there's something you'd like to discuss about this ve The pagination chunk size can be configured with the `Spotify::pagination_chunks` field, which is set to 50 items by default. - No default values are set from Rspotify now, they will be left to the Spotify API. -- []() Add a `collaborative` parameter to `user_playlist_create`. -- []() Add a `uris` parameter to `playlist_reorder_tracks`. +- [#202](https://github.com/ramsayleung/rspotify/pull/202) Add a `collaborative` parameter to `user_playlist_create`. +- [#202](https://github.com/ramsayleung/rspotify/pull/202) Add a `uris` parameter to `playlist_reorder_tracks`. ## 0.10 (2020/07/01) From fdc6e11a36cb7ca56a9ff4fd71f99656f8226402 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 02:50:43 +0200 Subject: [PATCH 12/39] update changelog, test for json --- CHANGELOG.md | 1 + src/macros.rs | 28 +++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36b65102..a0283215 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ If we missed any change or there's something you'd like to discuss about this ve - 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. - Add `add_item_to_queue` endpoint. - Add `category_playlists` endpoint ([#153](https://github.com/ramsayleung/rspotify/pull/153)). - Fix race condition when using a single client from multiple threads ([#114](https://github.com/ramsayleung/rspotify/pull/114)). diff --git a/src/macros.rs b/src/macros.rs index 64984175..ebe23168 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -144,9 +144,10 @@ macro_rules! map_json { #[cfg(test)] mod test { + use crate::{map_query, map_json, scopes}; use crate::http::Query; use crate::model::Modality; - use crate::{map_query, scopes}; + use serde_json::{Map, Value, json}; #[test] fn test_hashset() { @@ -186,6 +187,31 @@ mod test { assert_eq!(with_macro, manually); } + #[test] + fn test_json_query() { + // Passed as parameters, for example. + let id = "Pink Lemonade"; + let artist = Some("The Wombats"); + let modality: Option = None; + + let with_macro = map_json! { + req id, + opt artist, + opt modality => modality.as_ref(), + }; + + let mut manually = Map::new(); + manually.insert("id".to_string(), json!(id)); + if let Some(ref artist) = artist { + manually.insert("artist".to_string(), json!(artist)); + } + if let Some(ref modality) = modality { + manually.insert("modality".to_string(), json!(modality.as_ref())); + } + + assert_eq!(with_macro, Value::from(manually)); + } + #[test] fn test_ident_str() { assert_eq!(ident_str!(i), "i"); From 1f955b4aef54e2e58a4b598b1edd73e9f71d25d0 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 02:54:13 +0200 Subject: [PATCH 13/39] update changelog --- CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0283215..9fdce9b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,7 @@ If we missed any change or there's something you'd like to discuss about this ve - ([#189](https://github.com/ramsayleung/rspotify/pull/189)) Add `scopes!` macro to generate scope for `Token` from string literal **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. - `SpotifyClientCredentials` has been renamed to `Credentials` ([#129](https://github.com/ramsayleung/rspotify/pull/129)), and its members `client_id` and `client_secret` to `id` and `secret`, respectively. - `TokenInfo` has been renamed to `Token`. It no longer has the `token_type` member, as it's always `Bearer` for now ([#129](https://github.com/ramsayleung/rspotify/pull/129)). - `SpotifyOAuth` has been renamed to `OAuth`. It only contains the necessary parameters for OAuth authorization instead of repeating the items from `Credentials` and `Spotify`, so `client_id`, `client_secret` and `cache_path` are no longer in `OAuth` ([#129](https://github.com/ramsayleung/rspotify/pull/129)). @@ -206,14 +207,14 @@ If we missed any change or there's something you'd like to discuss about this ve + Rename `ClientError::IO` to `ClientError::Io` + Rename `ClientError::CLI` to `ClientError::Cli` + Rename `BaseHTTPClient` to `BaseHttpClient` -- [#166](https://github.com/ramsayleung/rspotify/pull/166) [#201](https://github.com/ramsayleung/rspotify/pull/201) Add automatic pagination, which is now enabled by default. You can still use the methods with the `_manual` suffix to have access to manual pagination. There are three new examples for this, check out `examples/pagination*` to learn more! +- ([#166](https://github.com/ramsayleung/rspotify/pull/166) [#201](https://github.com/ramsayleung/rspotify/pull/201)) Add automatic pagination, which is now enabled by default. You can still use the methods with the `_manual` suffix to have access to manual pagination. There are three new examples for this, check out `examples/pagination*` to learn more! As a side effect, some methods now take references instead of values (so that they can be used multiple times when querying), and the parameters have been reordered so that the `limit` and `offset` are consistently the last two. The pagination chunk size can be configured with the `Spotify::pagination_chunks` field, which is set to 50 items by default. - No default values are set from Rspotify now, they will be left to the Spotify API. -- [#202](https://github.com/ramsayleung/rspotify/pull/202) Add a `collaborative` parameter to `user_playlist_create`. -- [#202](https://github.com/ramsayleung/rspotify/pull/202) Add a `uris` parameter to `playlist_reorder_tracks`. +- ([#202](https://github.com/ramsayleung/rspotify/pull/202)) Add a `collaborative` parameter to `user_playlist_create`. +- ([#202](https://github.com/ramsayleung/rspotify/pull/202)) Add a `uris` parameter to `playlist_reorder_tracks`. ## 0.10 (2020/07/01) From 28bc52dc586fcfe9bc5ebc129bb94f08bc29e2e4 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 02:56:40 +0200 Subject: [PATCH 14/39] rename to build_* --- src/client.rs | 86 +++++++++++++++++++++++++-------------------------- src/macros.rs | 18 +++++------ 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/src/client.rs b/src/client.rs index fd0b0f81..d881d9e2 100644 --- a/src/client.rs +++ b/src/client.rs @@ -15,7 +15,7 @@ use super::http::{HTTPClient, Query}; use super::model::*; use super::oauth2::{Credentials, OAuth, Token}; use super::pagination::{paginate, Paginator}; -use super::{map_json, map_query}; +use super::{build_json, build_map}; use crate::model::idtypes::{IdType, PlayContextIdType}; use std::collections::HashMap; @@ -193,7 +193,7 @@ impl Spotify { market: Option, ) -> ClientResult> { let ids = join_ids(track_ids); - let params = map_query! { + let params = build_map! { opt market => market.as_ref(), }; @@ -273,7 +273,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); - let params = map_query! { + let params = build_map! { opt album_type => album_type.as_ref(), opt market => &market.as_ref(), opt limit => &limit, @@ -299,7 +299,7 @@ impl Spotify { artist_id: &ArtistId, market: Market, ) -> ClientResult> { - let params = map_query! { + let params = build_map! { req market => market.as_ref() }; @@ -385,7 +385,7 @@ impl Spotify { ) -> ClientResult { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); - let params = map_query! { + let params = build_map! { req q, req r#type => r#type.as_ref(), opt market => market.as_ref(), @@ -430,7 +430,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); - let params = map_query! { + let params = build_map! { opt limit => &limit, opt offset => &offset, }; @@ -467,7 +467,7 @@ impl Spotify { fields: Option<&str>, market: Option, ) -> ClientResult { - let params = map_query! { + let params = build_map! { opt fields, opt market => market.as_ref(), }; @@ -503,7 +503,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); - let params = map_query! { + let params = build_map! { opt limit => &limit, opt offset => &offset, }; @@ -543,7 +543,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); - let params = map_query! { + let params = build_map! { opt limit => &limit, opt offset => &offset, }; @@ -568,7 +568,7 @@ impl Spotify { playlist_id: Option<&PlaylistId>, fields: Option<&str>, ) -> ClientResult { - let params = map_query! { + let params = build_map! { opt fields, }; @@ -619,7 +619,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); - let params = map_query! { + let params = build_map! { opt fields, opt limit => &limit, opt offset => &offset, @@ -650,7 +650,7 @@ impl Spotify { collaborative: Option, description: Option<&str>, ) -> ClientResult { - let params = map_json! { + let params = build_json! { req name, opt public, opt collaborative, @@ -681,7 +681,7 @@ impl Spotify { description: Option<&str>, collaborative: Option, ) -> ClientResult { - let params = map_json! { + let params = build_json! { opt name, opt public, opt collaborative, @@ -720,7 +720,7 @@ impl Spotify { position: Option, ) -> ClientResult { let uris = track_ids.into_iter().map(|id| id.uri()).collect::>(); - let params = map_json! { + let params = build_json! { req uris, req position, }; @@ -746,7 +746,7 @@ impl Spotify { ) -> ClientResult<()> { let uris = track_ids.into_iter().map(|id| id.uri()).collect::>(); - let params = map_json! { + let params = build_json! { req uris => uris }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -778,7 +778,7 @@ impl Spotify { snapshot_id: Option<&str>, ) -> ClientResult { let uris = uris.map(|u| u.iter().map(|id| id.uri()).collect::>()); - let params = map_json! { + let params = build_json! { req playlist_id, opt uris, opt range_start, @@ -816,7 +816,7 @@ impl Spotify { }) .collect::>(); - let params = map_json! { + let params = build_json! { req tracks, opt snapshot_id, }; @@ -872,7 +872,7 @@ impl Spotify { }) .collect::>(); - let params = map_json! { + let params = build_json! { req tracks, opt snapshot_id, }; @@ -896,7 +896,7 @@ impl Spotify { ) -> ClientResult<()> { let url = format!("playlists/{}/followers", playlist_id.id()); - let params = map_json! { + let params = build_json! { opt public, }; @@ -1000,7 +1000,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); - let params = map_query! { + let params = build_map! { opt limit => &limit, opt offset => &offset, }; @@ -1038,7 +1038,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); - let params = map_query! { + let params = build_map! { opt limit => &limit, opt offset => &offset, }; @@ -1061,7 +1061,7 @@ impl Spotify { limit: Option, ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); - let params = map_query! { + let params = build_map! { req r#type => Type::Artist.as_ref(), opt after => &after, opt limit => &limit, @@ -1156,7 +1156,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); - let params = map_query! { + let params = build_map! { opt time_range => time_range.as_ref(), opt limit => &limit, opt offset => &offset, @@ -1199,7 +1199,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); - let params = map_query! { + let params = build_map! { opt time_range => time_range.as_ref(), opt limit => &limit, opt offset => &offset, @@ -1221,7 +1221,7 @@ impl Spotify { limit: Option, ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); - let params = map_query! { + let params = build_map! { opt limit => &limit, }; @@ -1399,7 +1399,7 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let timestamp = timestamp.map(|x| x.to_rfc3339()); - let params = map_query! { + let params = build_map! { opt locale, opt market => market.as_ref(), opt timestamp, @@ -1446,7 +1446,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); - let params = map_query! { + let params = build_map! { opt limit => &limit, opt offset => &offset, opt market => market.as_ref(), @@ -1494,7 +1494,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); - let params = map_query! { + let params = build_map! { opt locale, opt market => market.as_ref(), opt limit => &limit, @@ -1543,7 +1543,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); - let params = map_query! { + let params = build_map! { opt market => market.as_ref(), opt limit => &limit, opt offset => &offset, @@ -1584,7 +1584,7 @@ impl Spotify { let seed_genres = seed_genres.map(|x| x.join(",")); let seed_tracks = seed_tracks.map(join_ids); let limit = limit.map(|x| x.to_string()); - let mut params = map_query! { + let mut params = build_map! { opt seed_artists, opt seed_genres, opt seed_tracks, @@ -1707,7 +1707,7 @@ impl Spotify { ) -> ClientResult> { let additional_types = additional_types.map(|x| x.iter().map(|x| x.as_ref()).collect::>().join(",")); - let params = map_query! { + let params = build_map! { opt market => market.as_ref(), opt additional_types, }; @@ -1737,7 +1737,7 @@ impl Spotify { ) -> ClientResult> { let additional_types = additional_types.map(|x| x.iter().map(|x| x.as_ref()).collect::>().join(",")); - let params = map_query! { + let params = build_map! { opt market => market.as_ref(), opt additional_types, }; @@ -1769,7 +1769,7 @@ impl Spotify { device_id: &str, force_play: Option, ) -> ClientResult<()> { - let params = map_json! { + let params = build_json! { req device_ids => vec![device_id.to_owned()], opt force_play, }; @@ -1803,7 +1803,7 @@ impl Spotify { ) -> ClientResult<()> { use super::model::Offset; - let params = map_json! { + let params = build_json! { req context_uri => context_uri.uri(), opt offset => match offset { Offset::Position(position) => json!({ "position": position }), @@ -1829,7 +1829,7 @@ impl Spotify { ) -> ClientResult<()> { use super::model::Offset; - let params = map_json! { + let params = build_json! { req uris => uris.iter().map(|id| id.uri()).collect::>(), opt position_ms, opt offset => match offset { @@ -2026,7 +2026,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); - let params = map_query! { + let params = build_map! { opt limit => &limit, opt offset => &offset, }; @@ -2046,7 +2046,7 @@ impl Spotify { /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-a-show) #[maybe_async] pub async fn get_a_show(&self, id: &ShowId, market: Option) -> ClientResult { - let params = map_query! { + let params = build_map! { opt market => market.as_ref(), }; @@ -2070,7 +2070,7 @@ impl Spotify { market: Option, ) -> ClientResult> { let ids = join_ids(ids); - let params = map_query! { + let params = build_map! { req ids => ids.as_ref(), opt market => market.as_ref(), }; @@ -2119,7 +2119,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); - let params = map_query! { + let params = build_map! { opt limit => &limit, opt offset => &offset, opt market => market.as_ref(), @@ -2146,7 +2146,7 @@ impl Spotify { market: Option, ) -> ClientResult { let url = format!("episodes/{}", id.id()); - let params = map_query! { + let params = build_map! { opt market => market.as_ref(), }; @@ -2168,7 +2168,7 @@ impl Spotify { market: Option, ) -> ClientResult> { let ids = join_ids(ids); - let params = map_query! { + let params = build_map! { req ids => ids.as_ref(), opt market => market.as_ref(), }; @@ -2190,7 +2190,7 @@ impl Spotify { ids: impl IntoIterator, ) -> ClientResult> { let ids = join_ids(ids); - let params = map_query! { + let params = build_map! { req ids => ids.as_str(), }; let result = self.endpoint_get("me/shows/contains", ¶ms).await?; @@ -2212,7 +2212,7 @@ impl Spotify { country: Option, ) -> ClientResult<()> { let url = format!("me/shows?ids={}", join_ids(show_ids)); - let params = map_json! { + let params = build_json! { opt country => country.as_ref() }; self.endpoint_delete(&url, ¶ms).await?; diff --git a/src/macros.rs b/src/macros.rs index ebe23168..f44984ab 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -77,7 +77,7 @@ macro_rules! params_internal { } /// TODO: use with_capacity? -/// This macro and [`map_json`] help make the endpoints as concise as possible +/// This macro and [`build_json`] help make the endpoints as concise as possible /// and boilerplate-free, which is specially important when initializing the /// parameters of the query with a HashMap/similar. Their items follow the /// syntax: @@ -94,11 +94,11 @@ macro_rules! params_internal { /// a key and a value with `MyStruct { key: value }`, or if both have the same /// name as the key, `MyStruct { key }` is enough. /// -/// For more information, please refer to the `test::test_map_query` function in +/// For more information, please refer to the `test::test_build_map` function in /// this module to see an example, or the real usages in Rspotify's client. #[doc(hidden)] #[macro_export] -macro_rules! map_query { +macro_rules! build_map { ( $( $kind:ident $name:ident $( => $val:expr )? @@ -118,11 +118,11 @@ macro_rules! map_query { }); } -/// Refer to the [`map_query`] documentation; this is the same but for JSON +/// Refer to the [`build_map`] documentation; this is the same but for JSON /// maps. #[doc(hidden)] #[macro_export] -macro_rules! map_json { +macro_rules! build_json { ( $( $kind:ident $name:ident $( => $val:expr )? @@ -144,7 +144,7 @@ macro_rules! map_json { #[cfg(test)] mod test { - use crate::{map_query, map_json, scopes}; + use crate::{build_map, build_json, scopes}; use crate::http::Query; use crate::model::Modality; use serde_json::{Map, Value, json}; @@ -160,13 +160,13 @@ mod test { } #[test] - fn test_map_query() { + fn test_build_map() { // Passed as parameters, for example. let id = "Pink Lemonade"; let artist = Some("The Wombats"); let modality: Option = None; - let with_macro = map_query! { + let with_macro = build_map! { // Mandatory (not an `Option`) req id, // Can be used directly @@ -194,7 +194,7 @@ mod test { let artist = Some("The Wombats"); let modality: Option = None; - let with_macro = map_json! { + let with_macro = build_json! { req id, opt artist, opt modality => modality.as_ref(), From 5c8c05f2f41fa9ac4886733a838f6486e3ae3b7c Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 03:14:13 +0200 Subject: [PATCH 15/39] format --- examples/ureq/search.rs | 9 ++++++++- src/macros.rs | 6 +++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/examples/ureq/search.rs b/examples/ureq/search.rs index cfc30832..2a601877 100644 --- a/examples/ureq/search.rs +++ b/examples/ureq/search.rs @@ -101,7 +101,14 @@ fn main() { } let episode_query = "love"; - let result = spotify.search(episode_query, SearchType::Episode, None, None, Some(10), None); + let result = spotify.search( + episode_query, + SearchType::Episode, + None, + None, + Some(10), + None, + ); match result { Ok(episode) => println!("searched episode:{:?}", episode), Err(err) => println!("search error!{:?}", err), diff --git a/src/macros.rs b/src/macros.rs index f44984ab..b1374db8 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -55,7 +55,7 @@ macro_rules! ident_str { const NAME: &str = stringify!($name); match &NAME.get(..2) { Some("r#") => &NAME[2..], - _ => &NAME[..] + _ => &NAME[..], } }}; } @@ -144,10 +144,10 @@ macro_rules! build_json { #[cfg(test)] mod test { - use crate::{build_map, build_json, scopes}; use crate::http::Query; use crate::model::Modality; - use serde_json::{Map, Value, json}; + use crate::{build_json, build_map, scopes}; + use serde_json::{json, Map, Value}; #[test] fn test_hashset() { From f94fbf44517625cb6c685cc45411f2df4e51186e Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 03:26:32 +0200 Subject: [PATCH 16/39] fix clippy --- src/macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/macros.rs b/src/macros.rs index b1374db8..4e91e55c 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -55,7 +55,7 @@ macro_rules! ident_str { const NAME: &str = stringify!($name); match &NAME.get(..2) { Some("r#") => &NAME[2..], - _ => &NAME[..], + _ => NAME, } }}; } From b64e085a8b9ba9744e7b13bb157c6746a14cb47e Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 13:14:56 +0200 Subject: [PATCH 17/39] some fixes and small improvements --- src/client.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/client.rs b/src/client.rs index d881d9e2..a1c7ab2c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -621,9 +621,9 @@ impl Spotify { let offset = offset.map(|s| s.to_string()); let params = build_map! { opt fields, + opt market => market.as_ref(), opt limit => &limit, opt offset => &offset, - opt market => market.as_ref(), }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -745,10 +745,10 @@ impl Spotify { track_ids: impl IntoIterator, ) -> ClientResult<()> { let uris = track_ids.into_iter().map(|id| id.uri()).collect::>(); - let params = build_json! { - req uris => uris + req uris }; + let url = format!("playlists/{}/tracks", playlist_id.id()); self.endpoint_put(&url, ¶ms).await?; @@ -1063,8 +1063,8 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let params = build_map! { req r#type => Type::Artist.as_ref(), - opt after => &after, - opt limit => &limit, + opt after, + opt limit, }; let result = self.endpoint_get("me/following", ¶ms).await?; @@ -1447,9 +1447,9 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { + opt market => market.as_ref(), opt limit => &limit, opt offset => &offset, - opt market => market.as_ref(), }; let result = self.endpoint_get("browse/new-releases", ¶ms).await?; @@ -1809,7 +1809,7 @@ impl Spotify { Offset::Position(position) => json!({ "position": position }), Offset::Uri(uri) => json!({ "uri": uri.uri() }), }, - opt position_ms => position_ms + opt position_ms, }; @@ -2120,9 +2120,9 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { + opt market => market.as_ref(), opt limit => &limit, opt offset => &offset, - opt market => market.as_ref(), }; let url = format!("shows/{}/episodes", id.id()); @@ -2191,7 +2191,7 @@ impl Spotify { ) -> ClientResult> { let ids = join_ids(ids); let params = build_map! { - req ids => ids.as_str(), + req ids => &ids, }; let result = self.endpoint_get("me/shows/contains", ¶ms).await?; self.convert_result(&result) From da41642793b18d02cf29cac190a945a089fcebba Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 13:39:10 +0200 Subject: [PATCH 18/39] some more simplifications --- src/client.rs | 74 +++++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/client.rs b/src/client.rs index a1c7ab2c..21263286 100644 --- a/src/client.rs +++ b/src/client.rs @@ -275,9 +275,9 @@ impl Spotify { let offset = offset.map(|x| x.to_string()); let params = build_map! { opt album_type => album_type.as_ref(), - opt market => &market.as_ref(), - opt limit => &limit, - opt offset => &offset, + opt market => market.as_ref(), + opt limit, + opt offset, }; let url = format!("artists/{}/albums", artist_id.id()); @@ -390,8 +390,8 @@ impl Spotify { req r#type => r#type.as_ref(), opt market => market.as_ref(), opt include_external => include_external.as_ref(), - opt limit => &limit, - opt offset => &offset, + opt limit, + opt offset, }; println!("params: {:#?}", params); @@ -431,8 +431,8 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - opt limit => &limit, - opt offset => &offset, + opt limit, + opt offset, }; let url = format!("albums/{}/tracks", album_id.id()); @@ -504,8 +504,8 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - opt limit => &limit, - opt offset => &offset, + opt limit, + opt offset, }; let result = self.endpoint_get("me/playlists", ¶ms).await?; @@ -544,8 +544,8 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - opt limit => &limit, - opt offset => &offset, + opt limit, + opt offset, }; let url = format!("users/{}/playlists", user_id.id()); @@ -622,8 +622,8 @@ impl Spotify { let params = build_map! { opt fields, opt market => market.as_ref(), - opt limit => &limit, - opt offset => &offset, + opt limit, + opt offset, }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -1001,8 +1001,8 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - opt limit => &limit, - opt offset => &offset, + opt limit, + opt offset, }; let result = self.endpoint_get("me/albums", ¶ms).await?; @@ -1039,8 +1039,8 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - opt limit => &limit, - opt offset => &offset, + opt limit, + opt offset, }; let result = self.endpoint_get("me/tracks", ¶ms).await?; @@ -1158,8 +1158,8 @@ impl Spotify { let offset = offset.map(|s| s.to_string()); let params = build_map! { opt time_range => time_range.as_ref(), - opt limit => &limit, - opt offset => &offset, + opt limit, + opt offset, }; let result = self.endpoint_get(&"me/top/artists", ¶ms).await?; @@ -1201,8 +1201,8 @@ impl Spotify { let offset = offset.map(|x| x.to_string()); let params = build_map! { opt time_range => time_range.as_ref(), - opt limit => &limit, - opt offset => &offset, + opt limit, + opt offset, }; let result = self.endpoint_get("me/top/tracks", ¶ms).await?; @@ -1222,7 +1222,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); let params = build_map! { - opt limit => &limit, + opt limit, }; let result = self @@ -1403,8 +1403,8 @@ impl Spotify { opt locale, opt market => market.as_ref(), opt timestamp, - opt limit => &limit, - opt offset => &offset, + opt limit, + opt offset, }; let result = self @@ -1448,8 +1448,8 @@ impl Spotify { let offset = offset.map(|x| x.to_string()); let params = build_map! { opt market => market.as_ref(), - opt limit => &limit, - opt offset => &offset, + opt limit, + opt offset, }; let result = self.endpoint_get("browse/new-releases", ¶ms).await?; @@ -1497,8 +1497,8 @@ impl Spotify { let params = build_map! { opt locale, opt market => market.as_ref(), - opt limit => &limit, - opt offset => &offset, + opt limit, + opt offset, }; let result = self.endpoint_get("browse/categories", ¶ms).await?; self.convert_result::(&result) @@ -1545,8 +1545,8 @@ impl Spotify { let offset = offset.map(|x| x.to_string()); let params = build_map! { opt market => market.as_ref(), - opt limit => &limit, - opt offset => &offset, + opt limit, + opt offset, }; let url = format!("browse/categories/{}/playlists", category_id); @@ -1589,7 +1589,7 @@ impl Spotify { opt seed_genres, opt seed_tracks, opt market => market.as_ref(), - opt limit => &limit, + opt limit, }; // TODO: this probably can be improved. @@ -2027,8 +2027,8 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { - opt limit => &limit, - opt offset => &offset, + opt limit, + opt offset, }; let result = self.endpoint_get("me/shows", ¶ms).await?; @@ -2071,7 +2071,7 @@ impl Spotify { ) -> ClientResult> { let ids = join_ids(ids); let params = build_map! { - req ids => ids.as_ref(), + req ids => &ids, opt market => market.as_ref(), }; @@ -2121,8 +2121,8 @@ impl Spotify { let offset = offset.map(|x| x.to_string()); let params = build_map! { opt market => market.as_ref(), - opt limit => &limit, - opt offset => &offset, + opt limit, + opt offset, }; let url = format!("shows/{}/episodes", id.id()); @@ -2169,7 +2169,7 @@ impl Spotify { ) -> ClientResult> { let ids = join_ids(ids); let params = build_map! { - req ids => ids.as_ref(), + req ids => &ids, opt market => market.as_ref(), }; From 3e35987a8b5fc6e0dec962086280cc22b65e1dff Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 13:44:50 +0200 Subject: [PATCH 19/39] vec! can be [] --- src/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.rs b/src/client.rs index 21263286..7c772927 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1770,7 +1770,7 @@ impl Spotify { force_play: Option, ) -> ClientResult<()> { let params = build_json! { - req device_ids => vec![device_id.to_owned()], + req device_ids => [device_id], opt force_play, }; From a0b2d8bf1f17df581e246ca5279dab7c0ca05067 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 13:48:46 +0200 Subject: [PATCH 20/39] fix incorrect market -> country changes --- src/client.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/client.rs b/src/client.rs index 7c772927..74dbe161 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1391,7 +1391,7 @@ impl Spotify { pub async fn featured_playlists( &self, locale: Option<&str>, - market: Option, + country: Option, timestamp: Option>, limit: Option, offset: Option, @@ -1401,7 +1401,7 @@ impl Spotify { let timestamp = timestamp.map(|x| x.to_rfc3339()); let params = build_map! { opt locale, - opt market => market.as_ref(), + opt country => country.as_ref(), opt timestamp, opt limit, opt offset, @@ -1428,10 +1428,10 @@ impl Spotify { /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-new-releases) pub fn new_releases<'a>( &'a self, - market: Option<&'a Market>, + country: Option<&'a Market>, ) -> impl Paginator> + 'a { paginate( - move |limit, offset| self.new_releases_manual(market, Some(limit), Some(offset)), + move |limit, offset| self.new_releases_manual(country, Some(limit), Some(offset)), self.pagination_chunks, ) } @@ -1440,14 +1440,14 @@ impl Spotify { #[maybe_async] pub async fn new_releases_manual( &self, - market: Option<&Market>, + country: Option<&Market>, limit: Option, offset: Option, ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { - opt market => market.as_ref(), + opt country => country.as_ref(), opt limit, opt offset, }; @@ -1475,10 +1475,10 @@ impl Spotify { pub fn categories<'a>( &'a self, locale: Option<&'a str>, - market: Option<&'a Market>, + country: Option<&'a Market>, ) -> impl Paginator> + 'a { paginate( - move |limit, offset| self.categories_manual(locale, market, Some(limit), Some(offset)), + move |limit, offset| self.categories_manual(locale, country, Some(limit), Some(offset)), self.pagination_chunks, ) } @@ -1488,7 +1488,7 @@ impl Spotify { pub async fn categories_manual( &self, locale: Option<&str>, - market: Option<&Market>, + country: Option<&Market>, limit: Option, offset: Option, ) -> ClientResult> { @@ -1496,7 +1496,7 @@ impl Spotify { let offset = offset.map(|x| x.to_string()); let params = build_map! { opt locale, - opt market => market.as_ref(), + opt country => country.as_ref(), opt limit, opt offset, }; @@ -1522,11 +1522,11 @@ impl Spotify { pub fn category_playlists<'a>( &'a self, category_id: &'a str, - market: Option<&'a Market>, + country: Option<&'a Market>, ) -> impl Paginator> + 'a { paginate( move |limit, offset| { - self.category_playlists_manual(category_id, market, Some(limit), Some(offset)) + self.category_playlists_manual(category_id, country, Some(limit), Some(offset)) }, self.pagination_chunks, ) @@ -1537,14 +1537,14 @@ impl Spotify { pub async fn category_playlists_manual( &self, category_id: &str, - market: Option<&Market>, + country: Option<&Market>, limit: Option, offset: Option, ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { - opt market => market.as_ref(), + opt country => country.as_ref(), opt limit, opt offset, }; @@ -1702,13 +1702,13 @@ impl Spotify { #[maybe_async] pub async fn current_playback( &self, - market: Option, + country: Option, additional_types: Option>, ) -> ClientResult> { let additional_types = additional_types.map(|x| x.iter().map(|x| x.as_ref()).collect::>().join(",")); let params = build_map! { - opt market => market.as_ref(), + opt country => country.as_ref(), opt additional_types, }; From d79ea89afd4db969b69ebd250de54f6f75514e81 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 14:06:33 +0200 Subject: [PATCH 21/39] consistent brackets in macros --- src/macros.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 4e91e55c..fb959d93 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -103,7 +103,7 @@ macro_rules! build_map { $( $kind:ident $name:ident $( => $val:expr )? ),* $(,)? - ) => ({ + ) => {{ let mut params = $crate::http::Query::new(); $( $crate::params_internal!( @@ -115,7 +115,7 @@ macro_rules! build_map { ); )* params - }); + }}; } /// Refer to the [`build_map`] documentation; this is the same but for JSON @@ -127,7 +127,7 @@ macro_rules! build_json { $( $kind:ident $name:ident $( => $val:expr )? ),* $(,)? - ) => ({ + ) => {{ let mut params = ::serde_json::map::Map::new(); $( $crate::params_internal!( @@ -139,7 +139,7 @@ macro_rules! build_json { ); )* ::serde_json::Value::from(params) - }); + }}; } #[cfg(test)] From d73239e1f200eaa0c849294f8c55bcf7712ced0f Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 14:35:08 +0200 Subject: [PATCH 22/39] now using with_capacity! done :) --- src/macros.rs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index fb959d93..fe34e257 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -47,6 +47,21 @@ macro_rules! opt { }; } +/// Count items in a list of items within a macro, taken from here: +/// https://danielkeep.github.io/tlborm/book/blk-counting.html +#[doc(hidden)] +#[macro_export] +macro_rules! replace_expr { + ($_t:tt $sub:expr) => { + $sub + }; +} +#[doc(hidden)] +#[macro_export] +macro_rules! count_items { + ($($item:expr),*) => {<[()]>::len(&[$($crate::replace_expr!($item ())),*])}; +} + /// Hack to convert an identifier to a string, including raw identifiers. #[doc(hidden)] #[macro_export] @@ -76,7 +91,6 @@ macro_rules! params_internal { }; } -/// TODO: use with_capacity? /// This macro and [`build_json`] help make the endpoints as concise as possible /// and boilerplate-free, which is specially important when initializing the /// parameters of the query with a HashMap/similar. Their items follow the @@ -104,7 +118,9 @@ macro_rules! build_map { $kind:ident $name:ident $( => $val:expr )? ),* $(,)? ) => {{ - let mut params = $crate::http::Query::new(); + let mut params = $crate::http::Query::with_capacity( + $crate::count_items!($( $name ),*) + ); $( $crate::params_internal!( params, @@ -128,7 +144,9 @@ macro_rules! build_json { $kind:ident $name:ident $( => $val:expr )? ),* $(,)? ) => {{ - let mut params = ::serde_json::map::Map::new(); + let mut params = ::serde_json::map::Map::with_capacity( + $crate::count_items!($( $name ),*) + ); $( $crate::params_internal!( params, From 6c4a9c58f112ba5f237fa4d84bd8486d4b728d52 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 15:05:17 +0200 Subject: [PATCH 23/39] update tests --- src/macros.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index fe34e257..44de2077 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -193,7 +193,7 @@ mod test { opt modality => modality.as_ref(), }; - let mut manually = Query::new(); + let mut manually = Query::with_capacity(3); manually.insert("id", id); if let Some(ref artist) = artist { manually.insert("artist", artist); @@ -218,7 +218,7 @@ mod test { opt modality => modality.as_ref(), }; - let mut manually = Map::new(); + let mut manually = Map::with_capacity(3); manually.insert("id".to_string(), json!(id)); if let Some(ref artist) = artist { manually.insert("artist".to_string(), json!(artist)); From 0975391b8054b2a6a29f8aa338fad694d588194b Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 15:07:16 +0200 Subject: [PATCH 24/39] fix in docs --- src/macros.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 44de2077..b1f37bf0 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -99,10 +99,10 @@ macro_rules! params_internal { /// [req|opt] key [=> value] /// /// The first keyword is just to distinguish between a direct insert into the -/// hashmap (required parameter), and an insert only if the value is -/// `Some(...)`, respectively (optional parameter). This is followed by the -/// variable to be inserted, which shall have the same name as the key in the -/// map (meaning that `r#type` may have to be used). +/// hashmap (required parameter), and an insert only if the value is `Some(...)` +/// (optional parameter). This is followed by the variable to be inserted, which +/// shall have the same name as the key in the map (meaning that `r#type` may +/// have to be used). /// /// It also works similarly to how struct initialization works. You may provide /// a key and a value with `MyStruct { key: value }`, or if both have the same From 1eef166a53802c4057ecb06c6ac4bfa987a017b7 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 16:26:35 +0200 Subject: [PATCH 25/39] rename opt/req to optional/required --- src/client.rs | 212 +++++++++++++++++++++++++------------------------- src/macros.rs | 18 ++--- 2 files changed, 115 insertions(+), 115 deletions(-) diff --git a/src/client.rs b/src/client.rs index 74dbe161..f3dd1dab 100644 --- a/src/client.rs +++ b/src/client.rs @@ -194,7 +194,7 @@ impl Spotify { ) -> ClientResult> { let ids = join_ids(track_ids); let params = build_map! { - opt market => market.as_ref(), + optional market => market.as_ref(), }; let url = format!("tracks/?ids={}", ids); @@ -274,10 +274,10 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { - opt album_type => album_type.as_ref(), - opt market => market.as_ref(), - opt limit, - opt offset, + optional album_type => album_type.as_ref(), + optional market => market.as_ref(), + optional limit, + optional offset, }; let url = format!("artists/{}/albums", artist_id.id()); @@ -300,7 +300,7 @@ impl Spotify { market: Market, ) -> ClientResult> { let params = build_map! { - req market => market.as_ref() + required market => market.as_ref() }; let url = format!("artists/{}/top-tracks", artist_id.id()); @@ -386,12 +386,12 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - req q, - req r#type => r#type.as_ref(), - opt market => market.as_ref(), - opt include_external => include_external.as_ref(), - opt limit, - opt offset, + required q, + required r#type => r#type.as_ref(), + optional market => market.as_ref(), + optional include_external => include_external.as_ref(), + optional limit, + optional offset, }; println!("params: {:#?}", params); @@ -431,8 +431,8 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - opt limit, - opt offset, + optional limit, + optional offset, }; let url = format!("albums/{}/tracks", album_id.id()); @@ -468,8 +468,8 @@ impl Spotify { market: Option, ) -> ClientResult { let params = build_map! { - opt fields, - opt market => market.as_ref(), + optional fields, + optional market => market.as_ref(), }; let url = format!("playlists/{}", playlist_id.id()); @@ -504,8 +504,8 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - opt limit, - opt offset, + optional limit, + optional offset, }; let result = self.endpoint_get("me/playlists", ¶ms).await?; @@ -544,8 +544,8 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - opt limit, - opt offset, + optional limit, + optional offset, }; let url = format!("users/{}/playlists", user_id.id()); @@ -569,7 +569,7 @@ impl Spotify { fields: Option<&str>, ) -> ClientResult { let params = build_map! { - opt fields, + optional fields, }; let url = match playlist_id { @@ -620,10 +620,10 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - opt fields, - opt market => market.as_ref(), - opt limit, - opt offset, + optional fields, + optional market => market.as_ref(), + optional limit, + optional offset, }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -651,10 +651,10 @@ impl Spotify { description: Option<&str>, ) -> ClientResult { let params = build_json! { - req name, - opt public, - opt collaborative, - opt description, + required name, + optional public, + optional collaborative, + optional description, }; let url = format!("users/{}/playlists", user_id.id()); @@ -682,10 +682,10 @@ impl Spotify { collaborative: Option, ) -> ClientResult { let params = build_json! { - opt name, - opt public, - opt collaborative, - opt description, + optional name, + optional public, + optional collaborative, + optional description, }; let url = format!("playlists/{}", playlist_id); @@ -721,8 +721,8 @@ impl Spotify { ) -> ClientResult { let uris = track_ids.into_iter().map(|id| id.uri()).collect::>(); let params = build_json! { - req uris, - req position, + required uris, + required position, }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -746,7 +746,7 @@ impl Spotify { ) -> ClientResult<()> { let uris = track_ids.into_iter().map(|id| id.uri()).collect::>(); let params = build_json! { - req uris + required uris }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -779,12 +779,12 @@ impl Spotify { ) -> ClientResult { let uris = uris.map(|u| u.iter().map(|id| id.uri()).collect::>()); let params = build_json! { - req playlist_id, - opt uris, - opt range_start, - opt insert_before, - opt range_length, - opt snapshot_id, + required playlist_id, + optional uris, + optional range_start, + optional insert_before, + optional range_length, + optional snapshot_id, }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -817,8 +817,8 @@ impl Spotify { .collect::>(); let params = build_json! { - req tracks, - opt snapshot_id, + required tracks, + optional snapshot_id, }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -873,8 +873,8 @@ impl Spotify { .collect::>(); let params = build_json! { - req tracks, - opt snapshot_id, + required tracks, + optional snapshot_id, }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -897,7 +897,7 @@ impl Spotify { let url = format!("playlists/{}/followers", playlist_id.id()); let params = build_json! { - opt public, + optional public, }; self.endpoint_put(&url, ¶ms).await?; @@ -1001,8 +1001,8 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - opt limit, - opt offset, + optional limit, + optional offset, }; let result = self.endpoint_get("me/albums", ¶ms).await?; @@ -1039,8 +1039,8 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - opt limit, - opt offset, + optional limit, + optional offset, }; let result = self.endpoint_get("me/tracks", ¶ms).await?; @@ -1062,9 +1062,9 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); let params = build_map! { - req r#type => Type::Artist.as_ref(), - opt after, - opt limit, + required r#type => Type::Artist.as_ref(), + optional after, + optional limit, }; let result = self.endpoint_get("me/following", ¶ms).await?; @@ -1157,9 +1157,9 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - opt time_range => time_range.as_ref(), - opt limit, - opt offset, + optional time_range => time_range.as_ref(), + optional limit, + optional offset, }; let result = self.endpoint_get(&"me/top/artists", ¶ms).await?; @@ -1200,9 +1200,9 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { - opt time_range => time_range.as_ref(), - opt limit, - opt offset, + optional time_range => time_range.as_ref(), + optional limit, + optional offset, }; let result = self.endpoint_get("me/top/tracks", ¶ms).await?; @@ -1222,7 +1222,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); let params = build_map! { - opt limit, + optional limit, }; let result = self @@ -1400,11 +1400,11 @@ impl Spotify { let offset = offset.map(|x| x.to_string()); let timestamp = timestamp.map(|x| x.to_rfc3339()); let params = build_map! { - opt locale, - opt country => country.as_ref(), - opt timestamp, - opt limit, - opt offset, + optional locale, + optional country => country.as_ref(), + optional timestamp, + optional limit, + optional offset, }; let result = self @@ -1447,9 +1447,9 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { - opt country => country.as_ref(), - opt limit, - opt offset, + optional country => country.as_ref(), + optional limit, + optional offset, }; let result = self.endpoint_get("browse/new-releases", ¶ms).await?; @@ -1495,10 +1495,10 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { - opt locale, - opt country => country.as_ref(), - opt limit, - opt offset, + optional locale, + optional country => country.as_ref(), + optional limit, + optional offset, }; let result = self.endpoint_get("browse/categories", ¶ms).await?; self.convert_result::(&result) @@ -1544,9 +1544,9 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { - opt country => country.as_ref(), - opt limit, - opt offset, + optional country => country.as_ref(), + optional limit, + optional offset, }; let url = format!("browse/categories/{}/playlists", category_id); @@ -1585,11 +1585,11 @@ impl Spotify { let seed_tracks = seed_tracks.map(join_ids); let limit = limit.map(|x| x.to_string()); let mut params = build_map! { - opt seed_artists, - opt seed_genres, - opt seed_tracks, - opt market => market.as_ref(), - opt limit, + optional seed_artists, + optional seed_genres, + optional seed_tracks, + optional market => market.as_ref(), + optional limit, }; // TODO: this probably can be improved. @@ -1708,8 +1708,8 @@ impl Spotify { let additional_types = additional_types.map(|x| x.iter().map(|x| x.as_ref()).collect::>().join(",")); let params = build_map! { - opt country => country.as_ref(), - opt additional_types, + optional country => country.as_ref(), + optional additional_types, }; let result = self.endpoint_get("me/player", ¶ms).await?; @@ -1738,8 +1738,8 @@ impl Spotify { let additional_types = additional_types.map(|x| x.iter().map(|x| x.as_ref()).collect::>().join(",")); let params = build_map! { - opt market => market.as_ref(), - opt additional_types, + optional market => market.as_ref(), + optional additional_types, }; let result = self @@ -1770,8 +1770,8 @@ impl Spotify { force_play: Option, ) -> ClientResult<()> { let params = build_json! { - req device_ids => [device_id], - opt force_play, + required device_ids => [device_id], + optional force_play, }; self.endpoint_put("me/player", ¶ms).await?; @@ -1804,12 +1804,12 @@ impl Spotify { use super::model::Offset; let params = build_json! { - req context_uri => context_uri.uri(), - opt offset => match offset { + required context_uri => context_uri.uri(), + optional offset => match offset { Offset::Position(position) => json!({ "position": position }), Offset::Uri(uri) => json!({ "uri": uri.uri() }), }, - opt position_ms, + optional position_ms, }; @@ -1830,9 +1830,9 @@ impl Spotify { use super::model::Offset; let params = build_json! { - req uris => uris.iter().map(|id| id.uri()).collect::>(), - opt position_ms, - opt offset => match offset { + required uris => uris.iter().map(|id| id.uri()).collect::>(), + optional position_ms, + optional offset => match offset { Offset::Position(position) => json!({ "position": position }), Offset::Uri(uri) => json!({ "uri": uri.uri() }), }, @@ -2027,8 +2027,8 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { - opt limit, - opt offset, + optional limit, + optional offset, }; let result = self.endpoint_get("me/shows", ¶ms).await?; @@ -2047,7 +2047,7 @@ impl Spotify { #[maybe_async] pub async fn get_a_show(&self, id: &ShowId, market: Option) -> ClientResult { let params = build_map! { - opt market => market.as_ref(), + optional market => market.as_ref(), }; let url = format!("shows/{}", id.id()); @@ -2071,8 +2071,8 @@ impl Spotify { ) -> ClientResult> { let ids = join_ids(ids); let params = build_map! { - req ids => &ids, - opt market => market.as_ref(), + required ids => &ids, + optional market => market.as_ref(), }; let result = self.endpoint_get("shows", ¶ms).await?; @@ -2120,9 +2120,9 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { - opt market => market.as_ref(), - opt limit, - opt offset, + optional market => market.as_ref(), + optional limit, + optional offset, }; let url = format!("shows/{}/episodes", id.id()); @@ -2147,7 +2147,7 @@ impl Spotify { ) -> ClientResult { let url = format!("episodes/{}", id.id()); let params = build_map! { - opt market => market.as_ref(), + optional market => market.as_ref(), }; let result = self.endpoint_get(&url, ¶ms).await?; @@ -2169,8 +2169,8 @@ impl Spotify { ) -> ClientResult> { let ids = join_ids(ids); let params = build_map! { - req ids => &ids, - opt market => market.as_ref(), + required ids => &ids, + optional market => market.as_ref(), }; let result = self.endpoint_get("episodes", ¶ms).await?; @@ -2191,7 +2191,7 @@ impl Spotify { ) -> ClientResult> { let ids = join_ids(ids); let params = build_map! { - req ids => &ids, + required ids => &ids, }; let result = self.endpoint_get("me/shows/contains", ¶ms).await?; self.convert_result(&result) @@ -2213,7 +2213,7 @@ impl Spotify { ) -> ClientResult<()> { let url = format!("me/shows?ids={}", join_ids(show_ids)); let params = build_json! { - opt country => country.as_ref() + optional country => country.as_ref() }; self.endpoint_delete(&url, ¶ms).await?; diff --git a/src/macros.rs b/src/macros.rs index b1f37bf0..4df2b4e8 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -80,10 +80,10 @@ macro_rules! ident_str { #[doc(hidden)] #[macro_export] macro_rules! params_internal { - ($map:ident, req, $name:ident, $key:expr, $val:expr) => { + ($map:ident, required, $name:ident, $key:expr, $val:expr) => { $map.insert($key, $val); }; - ($map:ident, opt, $name:ident, $key:expr, $val:expr) => { + ($map:ident, optional, $name:ident, $key:expr, $val:expr) => { // Will only insert when `$name` is not None. if let Some(ref $name) = $name { $map.insert($key, $val); @@ -96,7 +96,7 @@ macro_rules! params_internal { /// parameters of the query with a HashMap/similar. Their items follow the /// syntax: /// -/// [req|opt] key [=> value] +/// [required|optional] key [=> value] /// /// The first keyword is just to distinguish between a direct insert into the /// hashmap (required parameter), and an insert only if the value is `Some(...)` @@ -186,11 +186,11 @@ mod test { let with_macro = build_map! { // Mandatory (not an `Option`) - req id, + required id, // Can be used directly - opt artist, + optional artist, // `Modality` needs to be converted to &str - opt modality => modality.as_ref(), + optional modality => modality.as_ref(), }; let mut manually = Query::with_capacity(3); @@ -213,9 +213,9 @@ mod test { let modality: Option = None; let with_macro = build_json! { - req id, - opt artist, - opt modality => modality.as_ref(), + required id, + optional artist, + optional modality => modality.as_ref(), }; let mut manually = Map::with_capacity(3); From 61d67f34c9de1e5f4c2432a44b7bc278fde995df Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 16:30:06 +0200 Subject: [PATCH 26/39] clarify ident_str --- src/macros.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/macros.rs b/src/macros.rs index 4df2b4e8..58672941 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -62,7 +62,8 @@ macro_rules! count_items { ($($item:expr),*) => {<[()]>::len(&[$($crate::replace_expr!($item ())),*])}; } -/// Hack to convert an identifier to a string, including raw identifiers. +/// Hack to convert an identifier to a string, including raw identifiers like +/// `r#type`. #[doc(hidden)] #[macro_export] macro_rules! ident_str { From 657495b71619a8c196d61df6ff8c50c32999f629 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 16:31:08 +0200 Subject: [PATCH 27/39] fix syntax notation --- src/macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/macros.rs b/src/macros.rs index 58672941..e64309d1 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -97,7 +97,7 @@ macro_rules! params_internal { /// parameters of the query with a HashMap/similar. Their items follow the /// syntax: /// -/// [required|optional] key [=> value] +/// (required|optional) key [=> value] /// /// The first keyword is just to distinguish between a direct insert into the /// hashmap (required parameter), and an insert only if the value is `Some(...)` From 059343d70220403dd47869d0b7741e342d717c6b Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 20 Apr 2021 16:55:34 +0200 Subject: [PATCH 28/39] at least one parameter is required for the macros --- src/macros.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index e64309d1..18a54919 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -117,7 +117,7 @@ macro_rules! build_map { ( $( $kind:ident $name:ident $( => $val:expr )? - ),* $(,)? + ),+ $(,)? ) => {{ let mut params = $crate::http::Query::with_capacity( $crate::count_items!($( $name ),*) @@ -130,7 +130,7 @@ macro_rules! build_map { $crate::ident_str!($name), $crate::opt!($( $val )?, $name) ); - )* + )+ params }}; } @@ -143,7 +143,7 @@ macro_rules! build_json { ( $( $kind:ident $name:ident $( => $val:expr )? - ),* $(,)? + ),+ $(,)? ) => {{ let mut params = ::serde_json::map::Map::with_capacity( $crate::count_items!($( $name ),*) @@ -156,7 +156,7 @@ macro_rules! build_json { $crate::ident_str!($name).to_string(), json!($crate::opt!($( $val )?, $name)) ); - )* + )+ ::serde_json::Value::from(params) }}; } From 53ff617d1ad707c569c41e49714dc130a4b25cb0 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 24 Apr 2021 14:48:31 +0200 Subject: [PATCH 29/39] new macro syntax --- src/client.rs | 289 ++++++++++++++++++++-------------------- src/macros.rs | 133 +++++++----------- src/model/enums/misc.rs | 5 +- 3 files changed, 193 insertions(+), 234 deletions(-) diff --git a/src/client.rs b/src/client.rs index f3dd1dab..9f8386b3 100644 --- a/src/client.rs +++ b/src/client.rs @@ -187,14 +187,14 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-several-tracks) #[maybe_async] - pub async fn tracks<'a>( + pub async fn tracks( &self, - track_ids: impl IntoIterator, - market: Option, + track_ids: impl IntoIterator, + market: Option<&Market>, ) -> ClientResult> { let ids = join_ids(track_ids); let params = build_map! { - optional market => market.as_ref(), + optional "market": market.map(|x| x.as_ref()), }; let url = format!("tracks/?ids={}", ids); @@ -222,9 +222,9 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-multiple-artists) #[maybe_async] - pub async fn artists<'a>( + pub async fn artists( &self, - artist_ids: impl IntoIterator, + artist_ids: impl IntoIterator, ) -> ClientResult> { let ids = join_ids(artist_ids); let url = format!("artists/?ids={}", ids); @@ -250,7 +250,7 @@ impl Spotify { pub fn artist_albums<'a>( &'a self, artist_id: &'a ArtistId, - album_type: Option, + album_type: Option<&'a AlbumType>, market: Option<&'a Market>, ) -> impl Paginator> + 'a { paginate( @@ -266,18 +266,18 @@ impl Spotify { pub async fn artist_albums_manual( &self, artist_id: &ArtistId, - album_type: Option, + album_type: Option<&AlbumType>, market: Option<&Market>, limit: Option, offset: Option, ) -> ClientResult> { - let limit = limit.map(|x| x.to_string()); + let limit: Option = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { - optional album_type => album_type.as_ref(), - optional market => market.as_ref(), - optional limit, - optional offset, + optional "album_type": album_type.map(|x| x.as_ref()), + optional "market": market.map(|x| x.as_ref()), + optional "limit": limit.as_deref(), + optional "offset": offset.as_deref(), }; let url = format!("artists/{}/albums", artist_id.id()); @@ -300,7 +300,7 @@ impl Spotify { market: Market, ) -> ClientResult> { let params = build_map! { - required market => market.as_ref() + "market": market.as_ref() }; let url = format!("artists/{}/top-tracks", artist_id.id()); @@ -348,9 +348,9 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-multiple-albums) #[maybe_async] - pub async fn albums<'a>( + pub async fn albums( &self, - album_ids: impl IntoIterator, + album_ids: impl IntoIterator, ) -> ClientResult> { let ids = join_ids(album_ids); let url = format!("albums/?ids={}", ids); @@ -377,23 +377,22 @@ impl Spotify { pub async fn search( &self, q: &str, - r#type: SearchType, - market: Option, - include_external: Option, + _type: SearchType, + market: Option<&Market>, + include_external: Option<&IncludeExternal>, limit: Option, offset: Option, ) -> ClientResult { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - required q, - required r#type => r#type.as_ref(), - optional market => market.as_ref(), - optional include_external => include_external.as_ref(), - optional limit, - optional offset, + "q": q, + "type": _type.as_ref(), + optional "market": market.map(|x| x.as_ref()), + optional "include_external": include_external.map(|x| x.as_ref()), + optional "limit": limit.as_deref(), + optional "offset": offset.as_deref(), }; - println!("params: {:#?}", params); let result = self.endpoint_get("search", ¶ms).await?; self.convert_result(&result) @@ -431,8 +430,8 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - optional limit, - optional offset, + optional "limit": limit.as_deref(), + optional "offset": offset.as_deref(), }; let url = format!("albums/{}/tracks", album_id.id()); @@ -465,11 +464,11 @@ impl Spotify { &self, playlist_id: &PlaylistId, fields: Option<&str>, - market: Option, + market: Option<&Market>, ) -> ClientResult { let params = build_map! { - optional fields, - optional market => market.as_ref(), + optional "fields": fields, + optional "market": market.map(|x| x.as_ref()), }; let url = format!("playlists/{}", playlist_id.id()); @@ -504,8 +503,8 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - optional limit, - optional offset, + optional "limit": limit.as_deref(), + optional "offset": offset.as_deref(), }; let result = self.endpoint_get("me/playlists", ¶ms).await?; @@ -544,8 +543,8 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - optional limit, - optional offset, + optional "limit": limit.as_deref(), + optional "offset": offset.as_deref(), }; let url = format!("users/{}/playlists", user_id.id()); @@ -569,7 +568,7 @@ impl Spotify { fields: Option<&str>, ) -> ClientResult { let params = build_map! { - optional fields, + optional "fields": fields, }; let url = match playlist_id { @@ -620,10 +619,10 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - optional fields, - optional market => market.as_ref(), - optional limit, - optional offset, + optional "fields": fields, + optional "market": market.map(|x| x.as_ref()), + optional "limit": limit.as_deref(), + optional "offset": offset.as_deref(), }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -651,10 +650,10 @@ impl Spotify { description: Option<&str>, ) -> ClientResult { let params = build_json! { - required name, - optional public, - optional collaborative, - optional description, + "name": name, + optional "public": public, + optional "collaborative": collaborative, + optional "description": description, }; let url = format!("users/{}/playlists", user_id.id()); @@ -682,10 +681,10 @@ impl Spotify { collaborative: Option, ) -> ClientResult { let params = build_json! { - optional name, - optional public, - optional collaborative, - optional description, + optional "name": name, + optional "public": public, + optional "collaborative": collaborative, + optional "description": description, }; let url = format!("playlists/{}", playlist_id); @@ -713,16 +712,16 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-add-tracks-to-playlist) #[maybe_async] - pub async fn playlist_add_tracks<'a>( + pub async fn playlist_add_tracks( &self, playlist_id: &PlaylistId, - track_ids: impl IntoIterator, + track_ids: impl IntoIterator, position: Option, ) -> ClientResult { let uris = track_ids.into_iter().map(|id| id.uri()).collect::>(); let params = build_json! { - required uris, - required position, + "uris": uris, + "position": position, }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -739,14 +738,14 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-reorder-or-replace-playlists-tracks) #[maybe_async] - pub async fn playlist_replace_tracks<'a>( + pub async fn playlist_replace_tracks( &self, playlist_id: &PlaylistId, - track_ids: impl IntoIterator, + track_ids: impl IntoIterator, ) -> ClientResult<()> { let uris = track_ids.into_iter().map(|id| id.uri()).collect::>(); let params = build_json! { - required uris + "uris": uris }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -779,12 +778,12 @@ impl Spotify { ) -> ClientResult { let uris = uris.map(|u| u.iter().map(|id| id.uri()).collect::>()); let params = build_json! { - required playlist_id, - optional uris, - optional range_start, - optional insert_before, - optional range_length, - optional snapshot_id, + "playlist_id": playlist_id, + optional "uris": uris, + optional "range_start": range_start, + optional "insert_before": insert_before, + optional "range_length": range_length, + optional "snapshot_id": snapshot_id, }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -801,10 +800,10 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-remove-tracks-playlist) #[maybe_async] - pub async fn playlist_remove_all_occurrences_of_tracks<'a>( + pub async fn playlist_remove_all_occurrences_of_tracks( &self, playlist_id: &PlaylistId, - track_ids: impl IntoIterator, + track_ids: impl IntoIterator, snapshot_id: Option<&str>, ) -> ClientResult { let tracks = track_ids @@ -817,8 +816,8 @@ impl Spotify { .collect::>(); let params = build_json! { - required tracks, - optional snapshot_id, + "tracks": tracks, + optional "snapshot_id": snapshot_id, }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -873,8 +872,8 @@ impl Spotify { .collect::>(); let params = build_json! { - required tracks, - optional snapshot_id, + "tracks": tracks, + optional "snapshot_id": snapshot_id, }; let url = format!("playlists/{}/tracks", playlist_id.id()); @@ -897,7 +896,7 @@ impl Spotify { let url = format!("playlists/{}/followers", playlist_id.id()); let params = build_json! { - optional public, + optional "public": public, }; self.endpoint_put(&url, ¶ms).await?; @@ -914,10 +913,10 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-check-if-user-follows-playlist) #[maybe_async] - pub async fn playlist_check_follow<'a>( + pub async fn playlist_check_follow( &self, playlist_id: &PlaylistId, - user_ids: &'a [&'a UserId], + user_ids: &[&UserId], ) -> ClientResult> { if user_ids.len() > 5 { error!("The maximum length of user ids is limited to 5 :-)"); @@ -1001,8 +1000,8 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - optional limit, - optional offset, + optional "limit": limit.as_deref(), + optional "offset": offset.as_deref(), }; let result = self.endpoint_get("me/albums", ¶ms).await?; @@ -1039,8 +1038,8 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - optional limit, - optional offset, + optional "limit": limit.as_deref(), + optional "offset": offset.as_deref(), }; let result = self.endpoint_get("me/tracks", ¶ms).await?; @@ -1062,9 +1061,9 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); let params = build_map! { - required r#type => Type::Artist.as_ref(), - optional after, - optional limit, + "r#type": Type::Artist.as_ref(), + optional "after": after, + optional "limit": limit.as_deref(), }; let result = self.endpoint_get("me/following", ¶ms).await?; @@ -1079,9 +1078,9 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-remove-tracks-user) #[maybe_async] - pub async fn current_user_saved_tracks_delete<'a>( + pub async fn current_user_saved_tracks_delete( &self, - track_ids: impl IntoIterator, + track_ids: impl IntoIterator, ) -> ClientResult<()> { let url = format!("me/tracks/?ids={}", join_ids(track_ids)); self.endpoint_delete(&url, &json!({})).await?; @@ -1157,9 +1156,9 @@ impl Spotify { let limit = limit.map(|s| s.to_string()); let offset = offset.map(|s| s.to_string()); let params = build_map! { - optional time_range => time_range.as_ref(), - optional limit, - optional offset, + optional "time_range": time_range.map(|x| x.as_ref()), + optional "limit": limit.as_deref(), + optional "offset": offset.as_deref(), }; let result = self.endpoint_get(&"me/top/artists", ¶ms).await?; @@ -1200,9 +1199,9 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { - optional time_range => time_range.as_ref(), - optional limit, - optional offset, + optional "time_range": time_range.map(|x| x.as_ref()), + optional "limit": limit.as_deref(), + optional "offset": offset.as_deref(), }; let result = self.endpoint_get("me/top/tracks", ¶ms).await?; @@ -1222,7 +1221,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|x| x.to_string()); let params = build_map! { - optional limit, + optional "limit": limit.as_deref(), }; let result = self @@ -1391,7 +1390,7 @@ impl Spotify { pub async fn featured_playlists( &self, locale: Option<&str>, - country: Option, + country: Option<&Market>, timestamp: Option>, limit: Option, offset: Option, @@ -1400,11 +1399,11 @@ impl Spotify { let offset = offset.map(|x| x.to_string()); let timestamp = timestamp.map(|x| x.to_rfc3339()); let params = build_map! { - optional locale, - optional country => country.as_ref(), - optional timestamp, - optional limit, - optional offset, + optional "locale": locale, + optional "country": country.map(|x| x.as_ref()), + optional "timestamp": timestamp.as_deref(), + optional "limit": limit.as_deref(), + optional "offset": offset.as_deref(), }; let result = self @@ -1447,9 +1446,9 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { - optional country => country.as_ref(), - optional limit, - optional offset, + optional "country": country.map(|x| x.as_ref()), + optional "limit": limit.as_deref(), + optional "offset": offset.as_deref(), }; let result = self.endpoint_get("browse/new-releases", ¶ms).await?; @@ -1495,10 +1494,10 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { - optional locale, - optional country => country.as_ref(), - optional limit, - optional offset, + optional "locale": locale, + optional "country": country.map(|x| x.as_ref()), + optional "limit": limit.as_deref(), + optional "offset": offset.as_deref(), }; let result = self.endpoint_get("browse/categories", ¶ms).await?; self.convert_result::(&result) @@ -1544,9 +1543,9 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { - optional country => country.as_ref(), - optional limit, - optional offset, + optional "country": country.map(|x| x.as_ref()), + optional "limit": limit.as_deref(), + optional "offset": offset.as_deref(), }; let url = format!("browse/categories/{}/playlists", category_id); @@ -1575,21 +1574,21 @@ impl Spotify { &self, payload: &Map, seed_artists: Option>, - seed_genres: Option>, + seed_genres: Option>, seed_tracks: Option>, limit: Option, - market: Option, + market: Option<&Market>, ) -> ClientResult { let seed_artists = seed_artists.map(join_ids); let seed_genres = seed_genres.map(|x| x.join(",")); let seed_tracks = seed_tracks.map(join_ids); let limit = limit.map(|x| x.to_string()); let mut params = build_map! { - optional seed_artists, - optional seed_genres, - optional seed_tracks, - optional market => market.as_ref(), - optional limit, + optional "seed_artists": seed_artists.as_ref(), + optional "seed_genres": seed_genres.as_ref(), + optional "seed_tracks": seed_tracks.as_ref(), + optional "market": market.map(|x| x.as_ref()), + optional "limit": limit.as_ref(), }; // TODO: this probably can be improved. @@ -1702,14 +1701,14 @@ impl Spotify { #[maybe_async] pub async fn current_playback( &self, - country: Option, + country: Option<&Market>, additional_types: Option>, ) -> ClientResult> { let additional_types = additional_types.map(|x| x.iter().map(|x| x.as_ref()).collect::>().join(",")); let params = build_map! { - optional country => country.as_ref(), - optional additional_types, + optional "country": country.map(|x| x.as_ref()), + optional "additional_types": additional_types.as_ref(), }; let result = self.endpoint_get("me/player", ¶ms).await?; @@ -1732,14 +1731,14 @@ impl Spotify { #[maybe_async] pub async fn current_playing( &self, - market: Option, + market: Option<&Market>, additional_types: Option>, ) -> ClientResult> { let additional_types = additional_types.map(|x| x.iter().map(|x| x.as_ref()).collect::>().join(",")); let params = build_map! { - optional market => market.as_ref(), - optional additional_types, + optional "market": market.map(|x| x.as_ref()), + optional "additional_types": additional_types.as_ref(), }; let result = self @@ -1770,8 +1769,8 @@ impl Spotify { force_play: Option, ) -> ClientResult<()> { let params = build_json! { - required device_ids => [device_id], - optional force_play, + "device_ids": [device_id], + optional "force_play": force_play, }; self.endpoint_put("me/player", ¶ms).await?; @@ -1804,12 +1803,12 @@ impl Spotify { use super::model::Offset; let params = build_json! { - required context_uri => context_uri.uri(), - optional offset => match offset { + "context_uri": context_uri.uri(), + optional "offset": offset.map(|x| match x { Offset::Position(position) => json!({ "position": position }), Offset::Uri(uri) => json!({ "uri": uri.uri() }), - }, - optional position_ms, + }), + optional "position_ms": position_ms, }; @@ -1830,12 +1829,12 @@ impl Spotify { use super::model::Offset; let params = build_json! { - required uris => uris.iter().map(|id| id.uri()).collect::>(), - optional position_ms, - optional offset => match offset { + "uris": uris.iter().map(|id| id.uri()).collect::>(), + optional "position_ms": position_ms, + optional "offset": offset.map(|x| match x { Offset::Position(position) => json!({ "position": position }), Offset::Uri(uri) => json!({ "uri": uri.uri() }), - }, + }), }; let url = self.append_device_id("me/player/play", device_id); @@ -2027,8 +2026,8 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { - optional limit, - optional offset, + optional "limit": limit.as_ref(), + optional "offset": offset.as_ref(), }; let result = self.endpoint_get("me/shows", ¶ms).await?; @@ -2045,9 +2044,9 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-a-show) #[maybe_async] - pub async fn get_a_show(&self, id: &ShowId, market: Option) -> ClientResult { + pub async fn get_a_show(&self, id: &ShowId, market: Option<&Market>) -> ClientResult { let params = build_map! { - optional market => market.as_ref(), + optional "market": market.map(|x| x.as_ref()), }; let url = format!("shows/{}", id.id()); @@ -2067,12 +2066,12 @@ impl Spotify { pub async fn get_several_shows<'a>( &self, ids: impl IntoIterator, - market: Option, + market: Option<&Market>, ) -> ClientResult> { let ids = join_ids(ids); let params = build_map! { - required ids => &ids, - optional market => market.as_ref(), + "ids": &ids, + optional "market": market.map(|x| x.as_ref()), }; let result = self.endpoint_get("shows", ¶ms).await?; @@ -2120,9 +2119,9 @@ impl Spotify { let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { - optional market => market.as_ref(), - optional limit, - optional offset, + optional "market": market.map(|x| x.as_ref()), + optional "limit": limit.as_ref(), + optional "offset": offset.as_ref(), }; let url = format!("shows/{}/episodes", id.id()); @@ -2143,11 +2142,11 @@ impl Spotify { pub async fn get_an_episode( &self, id: &EpisodeId, - market: Option, + market: Option<&Market>, ) -> ClientResult { let url = format!("episodes/{}", id.id()); let params = build_map! { - optional market => market.as_ref(), + optional "market": market.map(|x| x.as_ref()), }; let result = self.endpoint_get(&url, ¶ms).await?; @@ -2165,12 +2164,12 @@ impl Spotify { pub async fn get_several_episodes<'a>( &self, ids: impl IntoIterator, - market: Option, + market: Option<&Market>, ) -> ClientResult> { let ids = join_ids(ids); let params = build_map! { - required ids => &ids, - optional market => market.as_ref(), + "ids": &ids, + optional "market": market.map(|x| x.as_ref()), }; let result = self.endpoint_get("episodes", ¶ms).await?; @@ -2191,7 +2190,7 @@ impl Spotify { ) -> ClientResult> { let ids = join_ids(ids); let params = build_map! { - required ids => &ids, + "ids": &ids, }; let result = self.endpoint_get("me/shows/contains", ¶ms).await?; self.convert_result(&result) @@ -2206,14 +2205,14 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-remove-shows-user) #[maybe_async] - pub async fn remove_users_saved_shows<'a>( + pub async fn remove_users_saved_shows( &self, - show_ids: impl IntoIterator, - country: Option, + show_ids: impl IntoIterator, + country: Option<&Market>, ) -> ClientResult<()> { let url = format!("me/shows?ids={}", join_ids(show_ids)); let params = build_json! { - optional country => country.as_ref() + optional "country": country.map(|x| x.as_ref()) }; self.endpoint_delete(&url, ¶ms).await?; diff --git a/src/macros.rs b/src/macros.rs index 18a54919..4c1c0978 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -33,20 +33,6 @@ macro_rules! scopes { }}; } -/// If there's an optional value, the macro will return its value. Otherwise, a -/// default value will be returned. This is helpful to handle `$( expr )?` -/// cases in macros. -#[doc(hidden)] -#[macro_export] -macro_rules! opt { - (, $default:expr) => { - $default - }; - ($optional:expr, $default:expr) => { - $optional - }; -} - /// Count items in a list of items within a macro, taken from here: /// https://danielkeep.github.io/tlborm/book/blk-counting.html #[doc(hidden)] @@ -62,42 +48,12 @@ macro_rules! count_items { ($($item:expr),*) => {<[()]>::len(&[$($crate::replace_expr!($item ())),*])}; } -/// Hack to convert an identifier to a string, including raw identifiers like -/// `r#type`. -#[doc(hidden)] -#[macro_export] -macro_rules! ident_str { - ($name:ident) => {{ - const NAME: &str = stringify!($name); - match &NAME.get(..2) { - Some("r#") => &NAME[2..], - _ => NAME, - } - }}; -} - -/// Private macro to insert either required or optional fields. Pattern matching -/// will accordingly pick the branch, and then insert ($key, $val) into $map. -#[doc(hidden)] -#[macro_export] -macro_rules! params_internal { - ($map:ident, required, $name:ident, $key:expr, $val:expr) => { - $map.insert($key, $val); - }; - ($map:ident, optional, $name:ident, $key:expr, $val:expr) => { - // Will only insert when `$name` is not None. - if let Some(ref $name) = $name { - $map.insert($key, $val); - } - }; -} - /// This macro and [`build_json`] help make the endpoints as concise as possible /// and boilerplate-free, which is specially important when initializing the /// parameters of the query with a HashMap/similar. Their items follow the /// syntax: /// -/// (required|optional) key [=> value] +/// [optional] "key": value /// /// The first keyword is just to distinguish between a direct insert into the /// hashmap (required parameter), and an insert only if the value is `Some(...)` @@ -114,21 +70,29 @@ macro_rules! params_internal { #[doc(hidden)] #[macro_export] macro_rules! build_map { + (@/* required */, $map:ident, $key:expr, $val:expr) => { + $map.insert($key, $val); + }; + (@optional, $map:ident, $key:expr, $val:expr) => { + if let Some(val) = $val { + $map.insert($key, val); + } + }; + ( $( - $kind:ident $name:ident $( => $val:expr )? + $( $kind:ident )? $key:literal : $val:expr ),+ $(,)? ) => {{ let mut params = $crate::http::Query::with_capacity( - $crate::count_items!($( $name ),*) + $crate::count_items!($( $key ),*) ); $( - $crate::params_internal!( + $crate::build_map!( + @$( $kind )?, params, - $kind, - $name, - $crate::ident_str!($name), - $crate::opt!($( $val )?, $name) + $key, + $val ); )+ params @@ -140,21 +104,29 @@ macro_rules! build_map { #[doc(hidden)] #[macro_export] macro_rules! build_json { + (@/* required */, $map:ident, $key:expr, $val:expr) => { + $map.insert($key.to_string(), json!($val)); + }; + (@optional, $map:ident, $key:expr, $val:expr) => { + if let Some(val) = $val { + $map.insert($key.to_string(), json!(val)); + } + }; + ( $( - $kind:ident $name:ident $( => $val:expr )? + $( $kind:ident )? $key:literal : $val:expr ),+ $(,)? ) => {{ let mut params = ::serde_json::map::Map::with_capacity( - $crate::count_items!($( $name ),*) + $crate::count_items!($( $key ),*) ); $( - $crate::params_internal!( + $crate::build_json!( + @$( $kind )?, params, - $kind, - $name, - $crate::ident_str!($name).to_string(), - json!($crate::opt!($( $val )?, $name)) + $key, + $val ); )+ ::serde_json::Value::from(params) @@ -164,7 +136,7 @@ macro_rules! build_json { #[cfg(test)] mod test { use crate::http::Query; - use crate::model::Modality; + use crate::model::Market; use crate::{build_json, build_map, scopes}; use serde_json::{json, Map, Value}; @@ -183,24 +155,24 @@ mod test { // Passed as parameters, for example. let id = "Pink Lemonade"; let artist = Some("The Wombats"); - let modality: Option = None; + let market: Option = None; let with_macro = build_map! { // Mandatory (not an `Option`) - required id, + "id": id, // Can be used directly - optional artist, + optional "artist": artist, // `Modality` needs to be converted to &str - optional modality => modality.as_ref(), + optional "market": market.map(|x| x.as_ref()), }; let mut manually = Query::with_capacity(3); manually.insert("id", id); - if let Some(ref artist) = artist { - manually.insert("artist", artist); + if let Some(val) = artist { + manually.insert("artist", val); } - if let Some(ref modality) = modality { - manually.insert("modality", modality.as_ref()); + if let Some(val) = market.map(|x| x.as_ref()) { + manually.insert("market", val); } assert_eq!(with_macro, manually); @@ -211,32 +183,23 @@ mod test { // Passed as parameters, for example. let id = "Pink Lemonade"; let artist = Some("The Wombats"); - let modality: Option = None; + let market: Option = None; let with_macro = build_json! { - required id, - optional artist, - optional modality => modality.as_ref(), + "id": id, + optional "artist": artist, + optional "market": market.map(|x| x.as_ref()), }; let mut manually = Map::with_capacity(3); manually.insert("id".to_string(), json!(id)); - if let Some(ref artist) = artist { - manually.insert("artist".to_string(), json!(artist)); + if let Some(val) = artist.map(|x| json!(x)) { + manually.insert("artist".to_string(), val); } - if let Some(ref modality) = modality { - manually.insert("modality".to_string(), json!(modality.as_ref())); + if let Some(val) = market.map(|x| x.as_ref()).map(|x| json!(x)) { + manually.insert("market".to_string(), val); } assert_eq!(with_macro, Value::from(manually)); } - - #[test] - fn test_ident_str() { - assert_eq!(ident_str!(i), "i"); - assert_eq!(ident_str!(r#i), "i"); - assert_eq!(ident_str!(test), "test"); - assert_eq!(ident_str!(a_b_c_d), "a_b_c_d"); - assert_eq!(ident_str!(r#type), "type"); - } } diff --git a/src/model/enums/misc.rs b/src/model/enums/misc.rs index e7ee0dce..cb7e1211 100644 --- a/src/model/enums/misc.rs +++ b/src/model/enums/misc.rs @@ -102,11 +102,8 @@ pub enum Market { Country(Country), FromToken, } -pub trait AsRefStr { - fn as_ref(&self) -> &str; -} -impl AsRefStr for Market { +impl AsRef for Market { fn as_ref(&self) -> &str { match self { Market::Country(country) => country.as_ref(), From fd03c3fae2ed981f42a3d6a9d3387e2e68197d35 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 24 Apr 2021 14:58:16 +0200 Subject: [PATCH 30/39] update docs --- src/macros.rs | 57 +++++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 4c1c0978..2e855f0d 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -50,35 +50,35 @@ macro_rules! count_items { /// This macro and [`build_json`] help make the endpoints as concise as possible /// and boilerplate-free, which is specially important when initializing the -/// parameters of the query with a HashMap/similar. Their items follow the -/// syntax: +/// parameters of the query. In the case of `build_map` this will construct a +/// `HashMap<&str, &str>`, and `build_json` will initialize a +/// `HashMap`. /// -/// [optional] "key": value +/// The syntax is the following: /// -/// The first keyword is just to distinguish between a direct insert into the -/// hashmap (required parameter), and an insert only if the value is `Some(...)` -/// (optional parameter). This is followed by the variable to be inserted, which -/// shall have the same name as the key in the map (meaning that `r#type` may -/// have to be used). +/// [optional] "key": value /// -/// It also works similarly to how struct initialization works. You may provide -/// a key and a value with `MyStruct { key: value }`, or if both have the same -/// name as the key, `MyStruct { key }` is enough. +/// For an example, refer to the `test::test_build_map` function in this module, +/// or the real usages in Rspotify's client. /// -/// For more information, please refer to the `test::test_build_map` function in -/// this module to see an example, or the real usages in Rspotify's client. +/// The `key` and `value` parameters are what's to be inserted in the HashMap. +/// If `optional` is used, the value will only be inserted if it's a +/// `Some(...)`. #[doc(hidden)] #[macro_export] -macro_rules! build_map { - (@/* required */, $map:ident, $key:expr, $val:expr) => { +macro_rules! internal_build_map { + (/* required */, $map:ident, $key:expr, $val:expr) => { $map.insert($key, $val); }; - (@optional, $map:ident, $key:expr, $val:expr) => { + (optional, $map:ident, $key:expr, $val:expr) => { if let Some(val) = $val { $map.insert($key, val); } }; - +} +#[doc(hidden)] +#[macro_export] +macro_rules! build_map { ( $( $( $kind:ident )? $key:literal : $val:expr @@ -88,8 +88,8 @@ macro_rules! build_map { $crate::count_items!($( $key ),*) ); $( - $crate::build_map!( - @$( $kind )?, + $crate::internal_build_map!( + $( $kind )?, params, $key, $val @@ -103,16 +103,19 @@ macro_rules! build_map { /// maps. #[doc(hidden)] #[macro_export] -macro_rules! build_json { - (@/* required */, $map:ident, $key:expr, $val:expr) => { +macro_rules! internal_build_json { + (/* required */, $map:ident, $key:expr, $val:expr) => { $map.insert($key.to_string(), json!($val)); }; - (@optional, $map:ident, $key:expr, $val:expr) => { + (optional, $map:ident, $key:expr, $val:expr) => { if let Some(val) = $val { $map.insert($key.to_string(), json!(val)); } }; - +} +#[doc(hidden)] +#[macro_export] +macro_rules! build_json { ( $( $( $kind:ident )? $key:literal : $val:expr @@ -122,8 +125,8 @@ macro_rules! build_json { $crate::count_items!($( $key ),*) ); $( - $crate::build_json!( - @$( $kind )?, + $crate::internal_build_json!( + $( $kind )?, params, $key, $val @@ -155,7 +158,7 @@ mod test { // Passed as parameters, for example. let id = "Pink Lemonade"; let artist = Some("The Wombats"); - let market: Option = None; + let market: Option<&Market> = None; let with_macro = build_map! { // Mandatory (not an `Option`) @@ -183,7 +186,7 @@ mod test { // Passed as parameters, for example. let id = "Pink Lemonade"; let artist = Some("The Wombats"); - let market: Option = None; + let market: Option<&Market> = None; let with_macro = build_json! { "id": id, From 439abde609abc24d6c3f0ac61c0ce02e77844b07 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 24 Apr 2021 15:05:21 +0200 Subject: [PATCH 31/39] fix lifetimes --- src/client.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/client.rs b/src/client.rs index 9f8386b3..c74ffe54 100644 --- a/src/client.rs +++ b/src/client.rs @@ -187,9 +187,9 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-several-tracks) #[maybe_async] - pub async fn tracks( + pub async fn tracks<'a>( &self, - track_ids: impl IntoIterator, + track_ids: impl IntoIterator, market: Option<&Market>, ) -> ClientResult> { let ids = join_ids(track_ids); @@ -222,9 +222,9 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-multiple-artists) #[maybe_async] - pub async fn artists( + pub async fn artists<'a>( &self, - artist_ids: impl IntoIterator, + artist_ids: impl IntoIterator, ) -> ClientResult> { let ids = join_ids(artist_ids); let url = format!("artists/?ids={}", ids); @@ -348,9 +348,9 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-multiple-albums) #[maybe_async] - pub async fn albums( + pub async fn albums<'a>( &self, - album_ids: impl IntoIterator, + album_ids: impl IntoIterator, ) -> ClientResult> { let ids = join_ids(album_ids); let url = format!("albums/?ids={}", ids); @@ -712,10 +712,10 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-add-tracks-to-playlist) #[maybe_async] - pub async fn playlist_add_tracks( + pub async fn playlist_add_tracks<'a>( &self, playlist_id: &PlaylistId, - track_ids: impl IntoIterator, + track_ids: impl IntoIterator, position: Option, ) -> ClientResult { let uris = track_ids.into_iter().map(|id| id.uri()).collect::>(); @@ -738,10 +738,10 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-reorder-or-replace-playlists-tracks) #[maybe_async] - pub async fn playlist_replace_tracks( + pub async fn playlist_replace_tracks<'a>( &self, playlist_id: &PlaylistId, - track_ids: impl IntoIterator, + track_ids: impl IntoIterator, ) -> ClientResult<()> { let uris = track_ids.into_iter().map(|id| id.uri()).collect::>(); let params = build_json! { @@ -800,10 +800,10 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-remove-tracks-playlist) #[maybe_async] - pub async fn playlist_remove_all_occurrences_of_tracks( + pub async fn playlist_remove_all_occurrences_of_tracks<'a>( &self, playlist_id: &PlaylistId, - track_ids: impl IntoIterator, + track_ids: impl IntoIterator, snapshot_id: Option<&str>, ) -> ClientResult { let tracks = track_ids @@ -1078,9 +1078,9 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-remove-tracks-user) #[maybe_async] - pub async fn current_user_saved_tracks_delete( + pub async fn current_user_saved_tracks_delete<'a>( &self, - track_ids: impl IntoIterator, + track_ids: impl IntoIterator, ) -> ClientResult<()> { let url = format!("me/tracks/?ids={}", join_ids(track_ids)); self.endpoint_delete(&url, &json!({})).await?; @@ -2205,9 +2205,9 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-remove-shows-user) #[maybe_async] - pub async fn remove_users_saved_shows( + pub async fn remove_users_saved_shows<'a>( &self, - show_ids: impl IntoIterator, + show_ids: impl IntoIterator, country: Option<&Market>, ) -> ClientResult<()> { let url = format!("me/shows?ids={}", join_ids(show_ids)); From 50bec4cad6c899799bbbeffa1c72bb3270202801 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 24 Apr 2021 15:20:33 +0200 Subject: [PATCH 32/39] fix tests --- tests/test_with_credential.rs | 2 +- tests/test_with_oauth.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_with_credential.rs b/tests/test_with_credential.rs index 4df85a6c..e35e24d7 100644 --- a/tests/test_with_credential.rs +++ b/tests/test_with_credential.rs @@ -85,7 +85,7 @@ async fn test_artists_albums() { .await .artist_albums_manual( birdy_uri, - Some(AlbumType::Album), + Some(&AlbumType::Album), Some(&Market::Country(Country::UnitedStates)), Some(10), None, diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index 67e3cff0..f06422f0 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -418,7 +418,7 @@ async fn test_recommendations() { None, Some(seed_tracks), Some(10), - Some(Market::Country(Country::UnitedStates)), + Some(&Market::Country(Country::UnitedStates)), ) .await .unwrap(); @@ -457,7 +457,7 @@ async fn test_search_artist() { .search( query, SearchType::Artist, - Some(Market::Country(Country::UnitedStates)), + Some(&Market::Country(Country::UnitedStates)), None, Some(10), Some(0), @@ -476,7 +476,7 @@ async fn test_search_playlist() { .search( query, SearchType::Playlist, - Some(Market::Country(Country::UnitedStates)), + Some(&Market::Country(Country::UnitedStates)), None, Some(10), Some(0), @@ -495,7 +495,7 @@ async fn test_search_track() { .search( query, SearchType::Track, - Some(Market::Country(Country::UnitedStates)), + Some(&Market::Country(Country::UnitedStates)), None, Some(10), Some(0), From 65ddd086337d78c03de33663d2a1973e2cd508fc Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 24 Apr 2021 18:09:03 +0200 Subject: [PATCH 33/39] apply review suggestions --- src/client.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/client.rs b/src/client.rs index c74ffe54..ea9e82b5 100644 --- a/src/client.rs +++ b/src/client.rs @@ -778,7 +778,6 @@ impl Spotify { ) -> ClientResult { let uris = uris.map(|u| u.iter().map(|id| id.uri()).collect::>()); let params = build_json! { - "playlist_id": playlist_id, optional "uris": uris, optional "range_start": range_start, optional "insert_before": insert_before, @@ -1061,7 +1060,7 @@ impl Spotify { ) -> ClientResult> { let limit = limit.map(|s| s.to_string()); let params = build_map! { - "r#type": Type::Artist.as_ref(), + "type": Type::Artist.as_ref(), optional "after": after, optional "limit": limit.as_deref(), }; @@ -1576,8 +1575,8 @@ impl Spotify { seed_artists: Option>, seed_genres: Option>, seed_tracks: Option>, - limit: Option, market: Option<&Market>, + limit: Option, ) -> ClientResult { let seed_artists = seed_artists.map(join_ids); let seed_genres = seed_genres.map(|x| x.join(",")); @@ -1758,19 +1757,18 @@ impl Spotify { /// /// Parameters: /// - device_id - transfer playback to this device - /// - force_play - true: after transfer, play. false: - /// keep current state. + /// - force_play - true: after transfer, play. false: keep current state. /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-transfer-a-users-playback) #[maybe_async] pub async fn transfer_playback( &self, device_id: &str, - force_play: Option, + play: Option, ) -> ClientResult<()> { let params = build_json! { "device_ids": [device_id], - optional "force_play": force_play, + optional "play": play, }; self.endpoint_put("me/player", ¶ms).await?; From 307c946b784b7efd6b093f1a75271dbc29c22c47 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 24 Apr 2021 18:10:25 +0200 Subject: [PATCH 34/39] remove unnecessary type --- src/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.rs b/src/client.rs index ea9e82b5..802c3a2d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -271,7 +271,7 @@ impl Spotify { limit: Option, offset: Option, ) -> ClientResult> { - let limit: Option = limit.map(|x| x.to_string()); + let limit = limit.map(|x| x.to_string()); let offset = offset.map(|x| x.to_string()); let params = build_map! { optional "album_type": album_type.map(|x| x.as_ref()), From d48b41e9673696d0a3ce77c030921a17f88b0df8 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 24 Apr 2021 18:21:22 +0200 Subject: [PATCH 35/39] use IntoIterator --- src/client.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client.rs b/src/client.rs index 802c3a2d..036cfcb6 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1698,13 +1698,13 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-information-about-the-users-current-playback) #[maybe_async] - pub async fn current_playback( + pub async fn current_playback<'a>( &self, country: Option<&Market>, - additional_types: Option>, + additional_types: Option>, ) -> ClientResult> { let additional_types = - additional_types.map(|x| x.iter().map(|x| x.as_ref()).collect::>().join(",")); + additional_types.map(|x| x.into_iter().map(|x| x.as_ref()).collect::>().join(",")); let params = build_map! { optional "country": country.map(|x| x.as_ref()), optional "additional_types": additional_types.as_ref(), From 87eb49cabf8ee32be1beb39f6c77834c12140142 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 24 Apr 2021 18:39:48 +0200 Subject: [PATCH 36/39] fix some tests --- tests/test_with_oauth.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index f06422f0..bce2753c 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -133,7 +133,7 @@ async fn test_category_playlists() { async fn test_current_playback() { oauth_client() .await - .current_playback(None, None) + .current_playback(None, Option::<&[_]>::None) .await .unwrap(); } @@ -417,8 +417,8 @@ async fn test_recommendations() { Some(seed_artists), None, Some(seed_tracks), - Some(10), Some(&Market::Country(Country::UnitedStates)), + Some(10), ) .await .unwrap(); From 89f325028181d4b46c1699f5a276703852948313 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 24 Apr 2021 18:42:03 +0200 Subject: [PATCH 37/39] somewhat prettier syntax --- src/client.rs | 18 +++++++++--------- tests/test_with_oauth.rs | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/client.rs b/src/client.rs index 036cfcb6..c21d52fb 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1698,13 +1698,17 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-information-about-the-users-current-playback) #[maybe_async] - pub async fn current_playback<'a>( + pub async fn current_playback<'a, A: IntoIterator>( &self, country: Option<&Market>, - additional_types: Option>, + additional_types: Option, ) -> ClientResult> { - let additional_types = - additional_types.map(|x| x.into_iter().map(|x| x.as_ref()).collect::>().join(",")); + let additional_types = additional_types.map(|x| { + x.into_iter() + .map(|x| x.as_ref()) + .collect::>() + .join(",") + }); let params = build_map! { optional "country": country.map(|x| x.as_ref()), optional "additional_types": additional_types.as_ref(), @@ -1761,11 +1765,7 @@ impl Spotify { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-transfer-a-users-playback) #[maybe_async] - pub async fn transfer_playback( - &self, - device_id: &str, - play: Option, - ) -> ClientResult<()> { + pub async fn transfer_playback(&self, device_id: &str, play: Option) -> ClientResult<()> { let params = build_json! { "device_ids": [device_id], optional "play": play, diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index bce2753c..73fedd5b 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -133,7 +133,7 @@ async fn test_category_playlists() { async fn test_current_playback() { oauth_client() .await - .current_playback(None, Option::<&[_]>::None) + .current_playback::<&[_]>(None, None) .await .unwrap(); } From 050215783e3954f837ff1dc9ff2da83a79281801 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 24 Apr 2021 18:45:36 +0200 Subject: [PATCH 38/39] as_deref is clearer than as_ref --- src/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.rs b/src/client.rs index c21d52fb..cb4dbc07 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1711,7 +1711,7 @@ impl Spotify { }); let params = build_map! { optional "country": country.map(|x| x.as_ref()), - optional "additional_types": additional_types.as_ref(), + optional "additional_types": additional_types.as_deref(), }; let result = self.endpoint_get("me/player", ¶ms).await?; From 0e6173b8d2c9f9c0e040bf7408a33621fb1a15e3 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Mon, 26 Apr 2021 08:55:17 +0200 Subject: [PATCH 39/39] fix optional --- src/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.rs b/src/client.rs index cb4dbc07..cf716b30 100644 --- a/src/client.rs +++ b/src/client.rs @@ -721,7 +721,7 @@ impl Spotify { let uris = track_ids.into_iter().map(|id| id.uri()).collect::>(); let params = build_json! { "uris": uris, - "position": position, + optional "position": position, }; let url = format!("playlists/{}/tracks", playlist_id.id());