diff --git a/examples/musicbrainz_api/browse.rs b/examples/musicbrainz_api/browse.rs index 397dd3a..cff2548 100644 --- a/examples/musicbrainz_api/browse.rs +++ b/examples/musicbrainz_api/browse.rs @@ -3,7 +3,7 @@ use std::{thread, time}; use musichoard::{ collection::musicbrainz::Mbid, external::musicbrainz::{ - api::{browse::BrowseReleaseGroupRequest, MusicBrainzClient}, + api::{browse::BrowseReleaseGroupRequest, MusicBrainzClient, PageSettings}, http::MusicBrainzHttp, }, }; @@ -52,13 +52,14 @@ fn main() { OptEntity::ReleaseGroup(opt_release_group) => match opt_release_group { OptReleaseGroup::Artist(opt_mbid) => { let mbid: Mbid = opt_mbid.mbid.into(); - let mut request = BrowseReleaseGroupRequest::artist(&mbid).with_max_limit(); + let request = BrowseReleaseGroupRequest::artist(&mbid); + let mut paging = PageSettings::with_max_limit(); let mut response_counts: Vec = Vec::new(); loop { let response = client - .browse_release_group(&request) + .browse_release_group(&request, &paging) .expect("failed to make API call"); for rg in response.release_groups.iter() { @@ -78,7 +79,7 @@ fn main() { if next_offset == total { break; } - request.with_offset(next_offset); + paging.with_offset(next_offset); thread::sleep(time::Duration::from_secs(1)); } diff --git a/examples/musicbrainz_api/search.rs b/examples/musicbrainz_api/search.rs index c5cb9b2..5d6f44f 100644 --- a/examples/musicbrainz_api/search.rs +++ b/examples/musicbrainz_api/search.rs @@ -5,7 +5,7 @@ use musichoard::{ external::musicbrainz::{ api::{ search::{SearchArtistRequest, SearchReleaseGroupRequest}, - MusicBrainzClient, + MusicBrainzClient, PageSettings, }, http::MusicBrainzHttp, }, @@ -106,8 +106,9 @@ fn main() { println!("Query: {query}"); + let paging = PageSettings::default(); let matches = client - .search_artist(&query) + .search_artist(&query, &paging) .expect("failed to make API call"); println!("{matches:#?}"); @@ -138,8 +139,9 @@ fn main() { println!("Query: {query}"); + let paging = PageSettings::default(); let matches = client - .search_release_group(&query) + .search_release_group(&query, &paging) .expect("failed to make API call"); println!("{matches:#?}"); diff --git a/src/external/musicbrainz/api/browse.rs b/src/external/musicbrainz/api/browse.rs index ce9ba36..0156629 100644 --- a/src/external/musicbrainz/api/browse.rs +++ b/src/external/musicbrainz/api/browse.rs @@ -5,30 +5,27 @@ use serde::Deserialize; use crate::{ collection::musicbrainz::Mbid, external::musicbrainz::{ - api::{Error, MbReleaseGroupMeta, MusicBrainzClient, SerdeMbReleaseGroupMeta, MB_BASE_URL}, + api::{ + Error, MbReleaseGroupMeta, MusicBrainzClient, PageSettings, SerdeMbReleaseGroupMeta, + MB_BASE_URL, + }, IMusicBrainzHttp, }, }; -const MB_MAX_BROWSE_LIMIT: usize = 100; +use super::ApiDisplay; impl MusicBrainzClient { pub fn browse_release_group( &mut self, request: &BrowseReleaseGroupRequest, + paging: &PageSettings, ) -> Result { let entity = &request.entity; let mbid = request.mbid.uuid().as_hyphenated(); - let limit = request - .limit - .map(|l| format!("&limit={l}")) - .unwrap_or_default(); - let offset = request - .offset - .map(|o| format!("&offset={o}")) - .unwrap_or_default(); + let page = ApiDisplay::format_page_settings(paging); - let url = format!("{MB_BASE_URL}/release-group?{entity}={mbid}{limit}{offset}"); + let url = format!("{MB_BASE_URL}/release-group?{entity}={mbid}{page}"); let response: DeserializeBrowseReleaseGroupResponse = self.http.get(&url)?; Ok(response.into()) @@ -38,8 +35,6 @@ impl MusicBrainzClient { pub struct BrowseReleaseGroupRequest<'a> { entity: BrowseReleaseGroupRequestEntity, mbid: &'a Mbid, - limit: Option, - offset: Option, } enum BrowseReleaseGroupRequestEntity { @@ -59,25 +54,8 @@ impl<'a> BrowseReleaseGroupRequest<'a> { BrowseReleaseGroupRequest { entity: BrowseReleaseGroupRequestEntity::Artist, mbid, - limit: None, - offset: None, } } - - #[must_use] - pub const fn with_limit(mut self, limit: usize) -> Self { - self.limit = Some(limit); - self - } - - #[must_use] - pub const fn with_max_limit(self) -> Self { - self.with_limit(MB_MAX_BROWSE_LIMIT) - } - - pub fn with_offset(&mut self, offset: usize) { - self.offset = Some(offset); - } } #[derive(Debug, PartialEq, Eq)] @@ -110,12 +88,15 @@ impl From for BrowseReleaseGroupResponse #[cfg(test)] mod tests { - use mockall::{predicate, Sequence}; + use mockall::predicate; use crate::{ collection::album::{AlbumPrimaryType, AlbumSecondaryType}, external::musicbrainz::{ - api::{SerdeAlbumDate, SerdeAlbumPrimaryType, SerdeAlbumSecondaryType, SerdeMbid}, + api::{ + SerdeAlbumDate, SerdeAlbumPrimaryType, SerdeAlbumSecondaryType, SerdeMbid, + MB_MAX_PAGE_LIMIT, + }, MockIMusicBrainzHttp, }, }; @@ -150,34 +131,12 @@ mod tests { release_groups: vec![de_meta.clone().into()], }; - let mut seq = Sequence::new(); - - let url = format!("https://musicbrainz.org/ws/2/release-group?artist={mbid}",); - let expect_response = de_response.clone(); - http.expect_get() - .times(1) - .in_sequence(&mut seq) - .with(predicate::eq(url)) - .return_once(|_| Ok(expect_response)); - let url = format!( - "https://musicbrainz.org/ws/2/release-group?artist={mbid}&limit={MB_MAX_BROWSE_LIMIT}", + "https://musicbrainz.org/ws/2/release-group?artist={mbid}&limit={MB_MAX_PAGE_LIMIT}", ); let expect_response = de_response.clone(); http.expect_get() .times(1) - .in_sequence(&mut seq) - .with(predicate::eq(url)) - .return_once(|_| Ok(expect_response)); - - let url = format!( - "https://musicbrainz.org/ws/2/release-group?artist={mbid}&limit={MB_MAX_BROWSE_LIMIT}&offset={offset}", - offset = de_release_group_offset, - ); - let expect_response = de_response.clone(); - http.expect_get() - .times(1) - .in_sequence(&mut seq) .with(predicate::eq(url)) .return_once(|_| Ok(expect_response)); @@ -186,16 +145,8 @@ mod tests { let mbid: Mbid = mbid.try_into().unwrap(); let request = BrowseReleaseGroupRequest::artist(&mbid); - let result = client.browse_release_group(&request).unwrap(); - assert_eq!(result, response); - - let request = request.with_max_limit(); - let result = client.browse_release_group(&request).unwrap(); - assert_eq!(result, response); - - let mut request = request; - request.with_offset(de_release_group_offset); - let result = client.browse_release_group(&request).unwrap(); + let paging = PageSettings::with_max_limit(); + let result = client.browse_release_group(&request, &paging).unwrap(); assert_eq!(result, response); } } diff --git a/src/external/musicbrainz/api/mod.rs b/src/external/musicbrainz/api/mod.rs index b269ee1..34ab3cd 100644 --- a/src/external/musicbrainz/api/mod.rs +++ b/src/external/musicbrainz/api/mod.rs @@ -17,6 +17,8 @@ pub mod search; const MB_BASE_URL: &str = "https://musicbrainz.org/ws/2"; const MB_RATE_LIMIT_CODE: u16 = 503; +const MB_MAX_PAGE_LIMIT: usize = 100; + #[derive(Debug, PartialEq, Eq)] pub enum Error { /// The HTTP client failed. @@ -59,6 +61,29 @@ impl MusicBrainzClient { } } +#[derive(Default)] +pub struct PageSettings { + pub limit: Option, + pub offset: Option, +} + +impl PageSettings { + pub fn with_limit(limit: usize) -> Self { + PageSettings { + limit: Some(limit), + ..Default::default() + } + } + + pub fn with_max_limit() -> Self { + Self::with_limit(MB_MAX_PAGE_LIMIT) + } + + pub fn with_offset(&mut self, offset: usize) { + self.offset = Some(offset); + } +} + #[derive(Clone, Debug, PartialEq, Eq)] pub struct MbArtistMeta { pub id: Mbid, @@ -123,6 +148,18 @@ impl From for MbReleaseGroupMeta { pub struct ApiDisplay; impl ApiDisplay { + fn format_page_settings(paging: &PageSettings) -> String { + let limit = paging + .limit + .map(|l| format!("&limit={l}")) + .unwrap_or_default(); + let offset = paging + .offset + .map(|o| format!("&offset={o}")) + .unwrap_or_default(); + format!("{limit}{offset}") + } + fn format_album_date(date: &AlbumDate) -> String { match date.year { Some(year) => match date.month { @@ -305,6 +342,26 @@ mod tests { assert!(!format!("{unk_err:?}").is_empty()); } + #[test] + fn format_page_settings() { + let paging = PageSettings::default(); + assert_eq!(ApiDisplay::format_page_settings(&paging), ""); + + let paging = PageSettings::with_max_limit(); + assert_eq!(ApiDisplay::format_page_settings(&paging), "&limit=100"); + + let mut paging = PageSettings::with_limit(45); + paging.with_offset(145); + assert_eq!( + ApiDisplay::format_page_settings(&paging), + "&limit=45&offset=145" + ); + + let mut paging = PageSettings::default(); + paging.with_offset(26); + assert_eq!(ApiDisplay::format_page_settings(&paging), "&offset=26"); + } + #[test] fn format_album_date() { assert_eq!( diff --git a/src/external/musicbrainz/api/search/artist.rs b/src/external/musicbrainz/api/search/artist.rs index 37780a7..8eaa4ea 100644 --- a/src/external/musicbrainz/api/search/artist.rs +++ b/src/external/musicbrainz/api/search/artist.rs @@ -70,7 +70,7 @@ mod tests { use mockall::predicate; use crate::external::musicbrainz::{ - api::{MusicBrainzClient, SerdeMbid}, + api::{MusicBrainzClient, PageSettings, SerdeMbid}, MockIMusicBrainzHttp, }; @@ -128,7 +128,8 @@ mod tests { let query = SearchArtistRequest::new().string(name); - let matches = client.search_artist(&query).unwrap(); + let paging = PageSettings::default(); + let matches = client.search_artist(&query, &paging).unwrap(); assert_eq!(matches, response); } } diff --git a/src/external/musicbrainz/api/search/mod.rs b/src/external/musicbrainz/api/search/mod.rs index 985528a..c4c1521 100644 --- a/src/external/musicbrainz/api/search/mod.rs +++ b/src/external/musicbrainz/api/search/mod.rs @@ -17,7 +17,7 @@ use crate::external::musicbrainz::{ artist::DeserializeSearchArtistResponse, release_group::DeserializeSearchReleaseGroupResponse, }, - Error, MusicBrainzClient, MB_BASE_URL, + ApiDisplay, Error, MusicBrainzClient, PageSettings, MB_BASE_URL, }, IMusicBrainzHttp, }; @@ -27,11 +27,13 @@ macro_rules! impl_search_entity { paste! { pub fn []( &mut self, - query: &[] + query: &[], + paging: &PageSettings, ) -> Result<[], Error> { let query: String = form_urlencoded::byte_serialize(format!("{query}").as_bytes()).collect(); - let url = format!("{MB_BASE_URL}/{entity}?query={query}", entity = $entity); + let page = ApiDisplay::format_page_settings(paging); + let url = format!("{MB_BASE_URL}/{entity}?query={query}{page}", entity = $entity); let response: [] = self.http.get(&url)?; Ok(response.into()) diff --git a/src/external/musicbrainz/api/search/release_group.rs b/src/external/musicbrainz/api/search/release_group.rs index 7dddfa8..be48a7f 100644 --- a/src/external/musicbrainz/api/search/release_group.rs +++ b/src/external/musicbrainz/api/search/release_group.rs @@ -99,8 +99,8 @@ mod tests { collection::album::{AlbumPrimaryType, AlbumSecondaryType}, external::musicbrainz::{ api::{ - MusicBrainzClient, SerdeAlbumDate, SerdeAlbumPrimaryType, SerdeAlbumSecondaryType, - SerdeMbid, + MusicBrainzClient, PageSettings, SerdeAlbumDate, SerdeAlbumPrimaryType, + SerdeAlbumSecondaryType, SerdeMbid, }, MockIMusicBrainzHttp, }, @@ -161,7 +161,8 @@ mod tests { let query = SearchReleaseGroupRequest::new().string(title); - let matches = client.search_release_group(&query).unwrap(); + let paging = PageSettings::default(); + let matches = client.search_release_group(&query, &paging).unwrap(); assert_eq!(matches, response); } @@ -198,7 +199,8 @@ mod tests { .and() .first_release_date(&date); - let matches = client.search_release_group(&query).unwrap(); + let paging = PageSettings::default(); + let matches = client.search_release_group(&query, &paging).unwrap(); assert_eq!(matches, response); } @@ -226,7 +228,8 @@ mod tests { let query = SearchReleaseGroupRequest::new().rgid(&rgid); - let matches = client.search_release_group(&query).unwrap(); + let paging = PageSettings::default(); + let matches = client.search_release_group(&query, &paging).unwrap(); assert_eq!(matches, response); } } diff --git a/src/tui/lib/external/musicbrainz/api/mod.rs b/src/tui/lib/external/musicbrainz/api/mod.rs index c7b7ac9..3e4c0cb 100644 --- a/src/tui/lib/external/musicbrainz/api/mod.rs +++ b/src/tui/lib/external/musicbrainz/api/mod.rs @@ -18,7 +18,7 @@ use musichoard::{ SearchArtistRequest, SearchArtistResponseArtist, SearchReleaseGroupRequest, SearchReleaseGroupResponseReleaseGroup, }, - MusicBrainzClient, + MusicBrainzClient, PageSettings, }, IMusicBrainzHttp, }, @@ -57,7 +57,8 @@ impl IMusicBrainz for MusicBrainz { fn search_artist(&mut self, artist: &ArtistMeta) -> Result>, Error> { let query = SearchArtistRequest::new().string(&artist.id.name); - let mb_response = self.client.search_artist(&query)?; + let paging = PageSettings::with_max_limit(); + let mb_response = self.client.search_artist(&query, &paging)?; Ok(mb_response .artists @@ -82,7 +83,8 @@ impl IMusicBrainz for MusicBrainz { .and() .release_group(&album.id.title); - let mb_response = self.client.search_release_group(&query)?; + let paging = PageSettings::with_max_limit(); + let mb_response = self.client.search_release_group(&query, &paging)?; Ok(mb_response .release_groups