The MusicBrainz API search call should use the MBID if available #171

Merged
wojtek merged 2 commits from 169---the-musicbrainz-api-search-call-should-use-the-mbid-if-available into main 2024-03-17 17:18:06 +01:00
3 changed files with 93 additions and 25 deletions

View File

@ -21,6 +21,20 @@ struct Opt {
#[structopt(help = "Release group's artist MBID")] #[structopt(help = "Release group's artist MBID")]
arid: Uuid, arid: Uuid,
#[structopt(subcommand)]
command: OptCommand,
}
#[derive(StructOpt)]
enum OptCommand {
#[structopt(about = "Search by title (and date)")]
Title(OptTitle),
#[structopt(about = "Search by release group MBID")]
Rgid(OptRgid),
}
#[derive(StructOpt)]
struct OptTitle {
#[structopt(help = "Release group title")] #[structopt(help = "Release group title")]
title: String, title: String,
@ -28,6 +42,12 @@ struct Opt {
date: Option<Date>, date: Option<Date>,
} }
#[derive(StructOpt)]
struct OptRgid {
#[structopt(help = "Release group MBID")]
rgid: Uuid,
}
struct Date(AlbumDate); struct Date(AlbumDate);
impl FromStr for Date { impl FromStr for Date {
@ -64,10 +84,26 @@ fn main() {
let mut api = MusicBrainzApi::new(client); let mut api = MusicBrainzApi::new(client);
let arid: Mbid = opt.arid.into(); let arid: Mbid = opt.arid.into();
let date: AlbumDate = opt.date.map(Into::into).unwrap_or_default();
let album = Album::new(AlbumId::new(opt.title), date, None, vec![]); let album = match opt.command {
OptCommand::Title(opt_title) => {
let date: AlbumDate = opt_title.date.map(Into::into).unwrap_or_default();
Album::new(AlbumId::new(opt_title.title), date, None, vec![])
}
OptCommand::Rgid(opt_rgid) => {
let mut album = Album::new(
AlbumId::new(String::default()),
AlbumDate::default(),
None,
vec![],
);
album.set_musicbrainz_ref(opt_rgid.rgid.into());
album
}
};
let matches = api let matches = api
.search_release_group(&arid, album) .search_release_group(&arid, &album)
.expect("failed to make API call"); .expect("failed to make API call");
println!("{matches:#?}"); println!("{matches:#?}");

View File

@ -16,7 +16,7 @@ pub trait IMusicBrainz {
fn search_release_group( fn search_release_group(
&mut self, &mut self,
arid: &Mbid, arid: &Mbid,
album: Album, album: &Album,
) -> Result<Vec<Match<Album>>, Error>; ) -> Result<Vec<Match<Album>>, Error>;
} }
@ -44,7 +44,7 @@ impl IMusicBrainz for NullMusicBrainz {
fn search_release_group( fn search_release_group(
&mut self, &mut self,
_arid: &Mbid, _arid: &Mbid,
_album: Album, _album: &Album,
) -> Result<Vec<Match<Album>>, Error> { ) -> Result<Vec<Match<Album>>, Error> {
Ok(vec![]) Ok(vec![])
} }
@ -143,7 +143,7 @@ mod tests {
let mbid: Mbid = "d368baa8-21ca-4759-9731-0b2753071ad8".try_into().unwrap(); let mbid: Mbid = "d368baa8-21ca-4759-9731-0b2753071ad8".try_into().unwrap();
let album = Album::new(AlbumId::new("an album"), AlbumDate::default(), None, vec![]); let album = Album::new(AlbumId::new("an album"), AlbumDate::default(), None, vec![]);
assert!(musicbrainz assert!(musicbrainz
.search_release_group(&mbid, album) .search_release_group(&mbid, &album)
.unwrap() .unwrap()
.is_empty()); .is_empty());
} }

View File

@ -11,7 +11,7 @@ use mockall::automock;
use crate::core::{ use crate::core::{
collection::{ collection::{
album::{Album, AlbumDate, AlbumPrimaryType, AlbumSecondaryType}, album::{Album, AlbumDate, AlbumPrimaryType, AlbumSecondaryType},
musicbrainz::MbAlbumRef, musicbrainz::{IMusicBrainzRef, MbAlbumRef},
}, },
interface::musicbrainz::{Error, IMusicBrainz, Match, Mbid}, interface::musicbrainz::{Error, IMusicBrainz, Match, Mbid},
}; };
@ -70,14 +70,23 @@ impl<Mbc: IMusicBrainzApiClient> IMusicBrainz for MusicBrainzApi<Mbc> {
fn search_release_group( fn search_release_group(
&mut self, &mut self,
arid: &Mbid, arid: &Mbid,
album: Album, album: &Album,
) -> Result<Vec<Match<Album>>, Error> { ) -> Result<Vec<Match<Album>>, Error> {
let title = &album.id.title; let title = &album.id.title;
let arid = arid.uuid().as_hyphenated().to_string(); let arid = arid.uuid().as_hyphenated().to_string();
let mut query = format!("releasegroup:\"{title}\" AND arid:{arid}"); let mut query = format!("arid:{arid}");
if let Some(year) = album.date.year { match album.musicbrainz {
query.push_str(&format!(" AND firstreleasedate:{year}")); Some(ref mbref) => {
let rgid = mbref.mbid().uuid().as_hyphenated().to_string();
query.push_str(&format!(" AND rgid:{rgid}"));
}
None => {
query.push_str(&format!(" AND releasegroup:\"{title}\""));
if let Some(year) = album.date.year {
query.push_str(&format!(" AND firstreleasedate:{year}"));
}
}
} }
let query: String = form_urlencoded::byte_serialize(query.as_bytes()).collect(); let query: String = form_urlencoded::byte_serialize(query.as_bytes()).collect();
@ -127,13 +136,13 @@ impl TryFrom<LookupReleaseGroup> for Album {
} }
} }
#[derive(Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))] #[serde(rename_all(deserialize = "kebab-case"))]
struct ResponseSearchReleaseGroup { struct ResponseSearchReleaseGroup {
release_groups: Vec<SearchReleaseGroup>, release_groups: Vec<SearchReleaseGroup>,
} }
#[derive(Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))] #[serde(rename_all(deserialize = "kebab-case"))]
struct SearchReleaseGroup { struct SearchReleaseGroup {
score: u8, score: u8,
@ -190,7 +199,7 @@ pub enum SerdeAlbumPrimaryTypeDef {
Other, Other,
} }
#[derive(Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct SerdeAlbumPrimaryType(#[serde(with = "SerdeAlbumPrimaryTypeDef")] AlbumPrimaryType); pub struct SerdeAlbumPrimaryType(#[serde(with = "SerdeAlbumPrimaryTypeDef")] AlbumPrimaryType);
impl From<SerdeAlbumPrimaryType> for AlbumPrimaryType { impl From<SerdeAlbumPrimaryType> for AlbumPrimaryType {
@ -233,7 +242,7 @@ impl From<SerdeAlbumSecondaryType> for AlbumSecondaryType {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use mockall::predicate; use mockall::{predicate, Sequence};
use crate::collection::album::AlbumId; use crate::collection::album::AlbumId;
@ -289,14 +298,21 @@ mod tests {
#[test] #[test]
fn search_release_group() { fn search_release_group() {
let mut client = MockIMusicBrainzApiClient::new(); let mut client = MockIMusicBrainzApiClient::new();
let url = format!( let url_title = format!(
"https://musicbrainz.org/ws/2\ "https://musicbrainz.org/ws/2\
/release-group\ /release-group\
?query=releasegroup%3A%22{title}%22+AND+arid%3A{arid}+AND+firstreleasedate%3A{year}", ?query=arid%3A{arid}+AND+releasegroup%3A%22{title}%22+AND+firstreleasedate%3A{year}",
title = "an+album",
arid = "00000000-0000-0000-0000-000000000000", arid = "00000000-0000-0000-0000-000000000000",
title = "an+album",
year = "1986" year = "1986"
); );
let url_rgid = format!(
"https://musicbrainz.org/ws/2\
/release-group\
?query=arid%3A{arid}+AND+rgid%3A{rgid}",
arid = "00000000-0000-0000-0000-000000000000",
rgid = "11111111-1111-1111-1111-111111111111",
);
let release_group = SearchReleaseGroup { let release_group = SearchReleaseGroup {
score: 67, score: 67,
@ -312,17 +328,23 @@ mod tests {
// For code coverage of derive(Debug). // For code coverage of derive(Debug).
assert!(!format!("{response:?}").is_empty()); assert!(!format!("{response:?}").is_empty());
let mut seq = Sequence::new();
let title_response = response.clone();
client client
.expect_get() .expect_get()
.times(1) .times(1)
.with(predicate::eq(url)) .with(predicate::eq(url_title))
.return_once(|_| Ok(response)); .return_once(|_| Ok(title_response))
.in_sequence(&mut seq);
let mut api = MusicBrainzApi::new(client); let rgid_response = response;
client
let arid: Mbid = "00000000-0000-0000-0000-000000000000".try_into().unwrap(); .expect_get()
let album = Album::new(AlbumId::new("an album"), (1986, 4), None, vec![]); .times(1)
let matches = api.search_release_group(&arid, album).unwrap(); .with(predicate::eq(url_rgid))
.return_once(|_| Ok(rgid_response))
.in_sequence(&mut seq);
let mut album = Album::new( let mut album = Album::new(
AlbumId::new("an album"), AlbumId::new("an album"),
@ -335,6 +357,16 @@ mod tests {
); );
let expected = vec![Match::new(67, album)]; let expected = vec![Match::new(67, album)];
let mut api = MusicBrainzApi::new(client);
let arid: Mbid = "00000000-0000-0000-0000-000000000000".try_into().unwrap();
let mut album = Album::new(AlbumId::new("an album"), (1986, 4), None, vec![]);
let matches = api.search_release_group(&arid, &album).unwrap();
assert_eq!(matches, expected);
let rgid = MbAlbumRef::from_uuid_str("11111111-1111-1111-1111-111111111111").unwrap();
album.set_musicbrainz_ref(rgid);
let matches = api.search_release_group(&arid, &album).unwrap();
assert_eq!(matches, expected); assert_eq!(matches, expected);
} }