diff --git a/rspotify-model/src/album.rs b/rspotify-model/src/album.rs index f91f641d..6fe470e5 100644 --- a/rspotify-model/src/album.rs +++ b/rspotify-model/src/album.rs @@ -33,7 +33,7 @@ pub struct SimplifiedAlbum { } /// Full Album Object -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct FullAlbum { pub artists: Vec, pub album_type: AlbumType, @@ -67,7 +67,7 @@ pub struct PageSimplifiedAlbums { } /// Saved Album object -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct SavedAlbum { pub added_at: DateTime, pub album: FullAlbum, diff --git a/rspotify-model/src/lib.rs b/rspotify-model/src/lib.rs index f4f1ac79..11b6de7b 100644 --- a/rspotify-model/src/lib.rs +++ b/rspotify-model/src/lib.rs @@ -7,7 +7,7 @@ pub mod audio; pub mod auth; pub mod category; pub mod context; -pub(in crate) mod custom_serde; +pub(crate) mod custom_serde; pub mod device; pub mod enums; pub mod error; diff --git a/rspotify-model/src/playlist.rs b/rspotify-model/src/playlist.rs index 1a8fa36a..07bb7b2e 100644 --- a/rspotify-model/src/playlist.rs +++ b/rspotify-model/src/playlist.rs @@ -36,7 +36,7 @@ pub struct SimplifiedPlaylist { } /// Full playlist object -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct FullPlaylist { pub collaborative: bool, pub description: Option, @@ -62,7 +62,7 @@ pub struct PlaylistItem { } /// Featured playlists object -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct FeaturedPlaylists { pub message: String, pub playlists: Page, diff --git a/rspotify-model/src/show.rs b/rspotify-model/src/show.rs index 6375b450..496555b9 100644 --- a/rspotify-model/src/show.rs +++ b/rspotify-model/src/show.rs @@ -47,7 +47,7 @@ pub struct Show { } /// Full show object -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct FullShow { pub available_markets: Vec, pub copyrights: Vec, diff --git a/src/auth_code.rs b/src/auth_code.rs index 2c1e9243..7c7fad2a 100644 --- a/src/auth_code.rs +++ b/src/auth_code.rs @@ -68,7 +68,7 @@ pub struct AuthCodeSpotify { pub oauth: OAuth, pub config: Config, pub token: Arc>>, - pub(in crate) http: HttpClient, + pub(crate) http: HttpClient, } /// This client has access to the base methods. diff --git a/src/auth_code_pkce.rs b/src/auth_code_pkce.rs index a4b9d556..a413a4ec 100644 --- a/src/auth_code_pkce.rs +++ b/src/auth_code_pkce.rs @@ -38,7 +38,7 @@ pub struct AuthCodePkceSpotify { pub token: Arc>>, /// The code verifier for the authentication process pub verifier: Option, - pub(in crate) http: HttpClient, + pub(crate) http: HttpClient, } /// This client has access to the base methods. diff --git a/src/client_creds.rs b/src/client_creds.rs index 171c3b3a..7c4263f6 100644 --- a/src/client_creds.rs +++ b/src/client_creds.rs @@ -26,7 +26,7 @@ pub struct ClientCredsSpotify { pub config: Config, pub creds: Credentials, pub token: Arc>>, - pub(in crate) http: HttpClient, + pub(crate) http: HttpClient, } /// This client has access to the base methods. diff --git a/src/clients/base.rs b/src/clients/base.rs index 4d4f838d..2949e3f1 100644 --- a/src/clients/base.rs +++ b/src/clients/base.rs @@ -2,7 +2,7 @@ use crate::{ auth_urls, clients::{ convert_result, - pagination::{paginate, Paginator}, + pagination::{paginate, paginate_with_ctx, Paginator}, }, http::{BaseHttpClient, Form, Headers, HttpClient, Query}, join_ids, @@ -313,13 +313,20 @@ where /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/get-an-artists-albums) fn artist_albums<'a>( &'a self, - artist_id: &'a ArtistId<'_>, + artist_id: ArtistId<'a>, album_type: Option, market: Option, ) -> Paginator<'_, ClientResult> { - paginate( - move |limit, offset| { - self.artist_albums_manual(artist_id, album_type, market, Some(limit), Some(offset)) + paginate_with_ctx( + (self, artist_id), + move |(slf, artist_id), limit, offset| { + slf.artist_albums_manual( + artist_id.as_ref(), + album_type, + market, + Some(limit), + Some(offset), + ) }, self.get_config().pagination_chunks, ) @@ -328,7 +335,7 @@ where /// The manually paginated version of [`Self::artist_albums`]. async fn artist_albums_manual( &self, - artist_id: &ArtistId<'_>, + artist_id: ArtistId<'_>, album_type: Option, market: Option, limit: Option, @@ -466,10 +473,13 @@ where /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/get-an-albums-tracks) fn album_track<'a>( &'a self, - album_id: &'a AlbumId<'_>, + album_id: AlbumId<'a>, ) -> Paginator<'_, ClientResult> { - paginate( - move |limit, offset| self.album_track_manual(album_id, Some(limit), Some(offset)), + paginate_with_ctx( + (self, album_id), + move |(slf, album_id), limit, offset| { + slf.album_track_manual(album_id.as_ref(), Some(limit), Some(offset)) + }, self.get_config().pagination_chunks, ) } @@ -477,7 +487,7 @@ where /// The manually paginated version of [`Self::album_track`]. async fn album_track_manual( &self, - album_id: &AlbumId<'_>, + album_id: AlbumId<'_>, limit: Option, offset: Option, ) -> ClientResult> { @@ -626,12 +636,13 @@ where /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/get-a-shows-episodes) fn get_shows_episodes<'a>( &'a self, - id: &'a ShowId<'_>, + id: ShowId<'a>, market: Option, ) -> Paginator<'_, ClientResult> { - paginate( - move |limit, offset| { - self.get_shows_episodes_manual(id, market, Some(limit), Some(offset)) + paginate_with_ctx( + (self, id), + move |(slf, id), limit, offset| { + slf.get_shows_episodes_manual(id.as_ref(), market, Some(limit), Some(offset)) }, self.get_config().pagination_chunks, ) @@ -640,7 +651,7 @@ where /// The manually paginated version of [`Self::get_shows_episodes`]. async fn get_shows_episodes_manual( &self, - id: &ShowId<'_>, + id: ShowId<'_>, market: Option, limit: Option, offset: Option, @@ -991,15 +1002,16 @@ where /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/get-playlists-tracks) fn playlist_items<'a>( &'a self, - playlist_id: &'a PlaylistId<'_>, + playlist_id: PlaylistId<'a>, fields: Option<&'a str>, market: Option, ) -> Paginator<'_, ClientResult> { - paginate( - move |limit, offset| { - self.playlist_items_manual( + paginate_with_ctx( + (self, playlist_id, fields), + move |(slf, playlist_id, fields), limit, offset| { + slf.playlist_items_manual( playlist_id.as_ref(), - fields, + *fields, market, Some(limit), Some(offset), @@ -1045,10 +1057,13 @@ where /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/get-list-users-playlists) fn user_playlists<'a>( &'a self, - user_id: &'a UserId<'_>, + user_id: UserId<'a>, ) -> Paginator<'_, ClientResult> { - paginate( - move |limit, offset| self.user_playlists_manual(user_id, Some(limit), Some(offset)), + paginate_with_ctx( + (self, user_id), + move |(slf, user_id), limit, offset| { + slf.user_playlists_manual(user_id.as_ref(), Some(limit), Some(offset)) + }, self.get_config().pagination_chunks, ) } @@ -1056,7 +1071,7 @@ where /// The manually paginated version of [`Self::user_playlists`]. async fn user_playlists_manual( &self, - user_id: &UserId<'_>, + user_id: UserId<'_>, limit: Option, offset: Option, ) -> ClientResult> { diff --git a/src/clients/mod.rs b/src/clients/mod.rs index 84464431..c2f3e3ea 100644 --- a/src/clients/mod.rs +++ b/src/clients/mod.rs @@ -12,12 +12,12 @@ use std::fmt::Write as _; use serde::Deserialize; /// Converts a JSON response from Spotify into its model. -pub(in crate) fn convert_result<'a, T: Deserialize<'a>>(input: &'a str) -> ClientResult { +pub(crate) fn convert_result<'a, T: Deserialize<'a>>(input: &'a str) -> ClientResult { serde_json::from_str::(input).map_err(Into::into) } /// Append device ID to an API path. -pub(in crate) fn append_device_id(path: &str, device_id: Option<&str>) -> String { +pub(crate) fn append_device_id(path: &str, device_id: Option<&str>) -> String { let mut new_path = path.to_string(); if let Some(device_id) = device_id { if path.contains('?') { diff --git a/src/clients/pagination/iter.rs b/src/clients/pagination/iter.rs index 80432f0d..07813b56 100644 --- a/src/clients/pagination/iter.rs +++ b/src/clients/pagination/iter.rs @@ -5,6 +5,17 @@ use crate::{model::Page, ClientError, ClientResult}; /// Alias for `Iterator`, since sync mode is enabled. pub type Paginator<'a, T> = Box + 'a>; +pub fn paginate_with_ctx<'a, Ctx: 'a, T: 'a, Request: 'a>( + ctx: Ctx, + req: Request, + page_size: u32, +) -> Paginator<'a, ClientResult> +where + Request: Fn(&Ctx, u32, u32) -> ClientResult>, +{ + paginate(move |limit, offset| req(&ctx, limit, offset), page_size) +} + /// This is used to handle paginated requests automatically. pub fn paginate<'a, T: 'a, Request: 'a>( req: Request, diff --git a/src/clients/pagination/mod.rs b/src/clients/pagination/mod.rs index 363b5cd4..ac6fcab7 100644 --- a/src/clients/pagination/mod.rs +++ b/src/clients/pagination/mod.rs @@ -7,6 +7,10 @@ //! * A `Paginator` struct which wraps the iterable of items //! * A `paginate` function, which returns a `Paginator` based on a request that //! may be repeated in order to return a continuous sequence of `Page`s +//! * A `paginate_with_ctx` function that does the same as the `paginate` +//! function, but accepts a generic context that works around lifetime issues +//! in the async version due to restrictions in HRTBs +//! () //! //! Note that `Paginator` should actually be a trait so that a dynamic //! allocation can be avoided when returning it with `-> impl Iterator`, as @@ -25,6 +29,6 @@ mod iter; mod stream; #[cfg(feature = "__sync")] -pub use iter::{paginate, Paginator}; +pub use iter::{paginate, paginate_with_ctx, Paginator}; #[cfg(feature = "__async")] -pub use stream::{paginate, Paginator}; +pub use stream::{paginate, paginate_with_ctx, Paginator}; diff --git a/src/clients/pagination/stream.rs b/src/clients/pagination/stream.rs index 5990e834..ee5daecd 100644 --- a/src/clients/pagination/stream.rs +++ b/src/clients/pagination/stream.rs @@ -9,7 +9,34 @@ use futures::{future::Future, stream::Stream}; /// Alias for `futures::stream::Stream`, since async mode is enabled. pub type Paginator<'a, T> = Pin + 'a>>; +pub type RequestFuture<'a, T> = Pin>>>>; + /// This is used to handle paginated requests automatically. +pub fn paginate_with_ctx<'a, Ctx: 'a, T, Request>( + ctx: Ctx, + req: Request, + page_size: u32, +) -> Paginator<'a, ClientResult> +where + T: 'a + Unpin, + Request: 'a + for<'ctx> Fn(&'ctx Ctx, u32, u32) -> RequestFuture<'ctx, T>, +{ + use async_stream::stream; + let mut offset = 0; + Box::pin(stream! { + loop { + let page = req(&ctx, page_size, offset).await?; + offset += page.items.len() as u32; + for item in page.items { + yield Ok(item); + } + if page.next.is_none() { + break; + } + } + }) +} + pub fn paginate<'a, T, Fut, Request>(req: Request, page_size: u32) -> Paginator<'a, ClientResult> where T: 'a + Unpin, diff --git a/src/lib.rs b/src/lib.rs index 06b34111..a1fa4142 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -156,7 +156,7 @@ pub mod prelude { } /// Common headers as constants. -pub(in crate) mod params { +pub(crate) mod params { pub const CLIENT_ID: &str = "client_id"; pub const CODE: &str = "code"; pub const GRANT_TYPE: &str = "grant_type"; @@ -177,14 +177,14 @@ pub(in crate) mod params { } /// Common alphabets for random number generation and similars -pub(in crate) mod alphabets { +pub(crate) mod alphabets { pub const ALPHANUM: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; /// From pub const PKCE_CODE_VERIFIER: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"; } -pub(in crate) mod auth_urls { +pub(crate) mod auth_urls { pub const AUTHORIZE: &str = "https://accounts.spotify.com/authorize"; pub const TOKEN: &str = "https://accounts.spotify.com/api/token"; } @@ -276,7 +276,7 @@ impl Default for Config { /// /// It is assumed that system always provides high-quality cryptographically /// secure random data, ideally backed by hardware entropy sources. -pub(in crate) fn generate_random_string(length: usize, alphabet: &[u8]) -> String { +pub(crate) fn generate_random_string(length: usize, alphabet: &[u8]) -> String { let mut buf = vec![0u8; length]; getrandom(&mut buf).unwrap(); let range = alphabet.len(); @@ -287,13 +287,13 @@ pub(in crate) fn generate_random_string(length: usize, alphabet: &[u8]) -> Strin } #[inline] -pub(in crate) fn join_ids<'a, T: Id + 'a>(ids: impl IntoIterator) -> String { +pub(crate) fn join_ids<'a, T: Id + 'a>(ids: impl IntoIterator) -> String { let ids = ids.into_iter().collect::>(); ids.iter().map(Id::id).collect::>().join(",") } #[inline] -pub(in crate) fn join_scopes(scopes: &HashSet) -> String { +pub(crate) fn join_scopes(scopes: &HashSet) -> String { scopes .iter() .map(String::as_str) diff --git a/tests/test_with_credential.rs b/tests/test_with_credential.rs index 0d844999..0bf33c06 100644 --- a/tests/test_with_credential.rs +++ b/tests/test_with_credential.rs @@ -44,7 +44,7 @@ async fn test_album_tracks() { let birdy_uri = AlbumId::from_uri("spotify:album:6akEvsycLGftJxYudPjmqK").unwrap(); creds_client() .await - .album_track_manual(&birdy_uri, Some(2), None) + .album_track_manual(birdy_uri, Some(2), None) .await .unwrap(); } @@ -71,7 +71,7 @@ async fn test_artists_albums() { creds_client() .await .artist_albums_manual( - &birdy_uri, + birdy_uri, Some(AlbumType::Album), Some(Market::Country(Country::UnitedStates)), Some(10), @@ -190,7 +190,7 @@ mod test_pagination { let album = AlbumId::from_uri(ALBUM).unwrap(); let names = client - .album_track(&album) + .album_track(album) .map(|track| track.unwrap().name) .collect::>(); @@ -208,7 +208,7 @@ mod test_pagination { let album = AlbumId::from_uri(ALBUM).unwrap(); let names = client - .album_track(&album) + .album_track(album) .map(|track| track.unwrap().name) .collect::>() .await; diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index f6712fff..cd3def59 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -619,7 +619,7 @@ async fn check_playlist_create(client: &AuthCodeSpotify) -> FullPlaylist { .await .unwrap(); assert_eq!(playlist.id, fetched_playlist.id); - let user_playlists = fetch_all(client.user_playlists(&user.id)).await; + let user_playlists = fetch_all(client.user_playlists(user.id)).await; let current_user_playlists = fetch_all(client.current_user_playlists()).await; assert_eq!(user_playlists.len(), current_user_playlists.len()); @@ -642,7 +642,7 @@ async fn check_playlist_create(client: &AuthCodeSpotify) -> FullPlaylist { #[maybe_async] async fn check_num_tracks(client: &AuthCodeSpotify, playlist_id: PlaylistId<'_>, num: i32) { - let fetched_tracks = fetch_all(client.playlist_items(&playlist_id.as_ref(), None, None)).await; + let fetched_tracks = fetch_all(client.playlist_items(playlist_id, None, None)).await; assert_eq!(fetched_tracks.len() as i32, num); }