diff --git a/examples/musicbrainz_api/search.rs b/examples/musicbrainz_api/search.rs index c88da97..96fd893 100644 --- a/examples/musicbrainz_api/search.rs +++ b/examples/musicbrainz_api/search.rs @@ -104,7 +104,7 @@ fn main() { match opt.entity { OptEntity::Artist(opt_artist) => { - let query = SearchArtistRequest::new().no_field(&opt_artist.string); + let query = SearchArtistRequest::new().string(&opt_artist.string); println!("Query: {query}"); diff --git a/src/external/musicbrainz/api/search/artist.rs b/src/external/musicbrainz/api/search/artist.rs index e7a1594..6ad7a41 100644 --- a/src/external/musicbrainz/api/search/artist.rs +++ b/src/external/musicbrainz/api/search/artist.rs @@ -10,20 +10,20 @@ use crate::{ use super::query::{impl_term, EmptyQuery, EmptyQueryJoin, Query, QueryJoin}; pub enum SearchArtist<'a> { - NoField(&'a str), + String(&'a str), } impl<'a> fmt::Display for SearchArtist<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::NoField(s) => write!(f, "\"{s}\""), + Self::String(s) => write!(f, "\"{s}\""), } } } pub type SearchArtistRequest<'a> = Query>; -impl_term!(no_field, SearchArtist<'a>, NoField, &'a str); +impl_term!(string, SearchArtist<'a>, String, &'a str); #[derive(Debug, PartialEq, Eq)] pub struct SearchArtistResponse { @@ -116,7 +116,7 @@ mod tests { } #[test] - fn search_no_field() { + fn search_string() { let mut http = MockIMusicBrainzHttp::new(); let url = format!( "https://musicbrainz.org/ws/2\ @@ -137,7 +137,7 @@ mod tests { let name = "an artist"; - let query = SearchArtistRequest::new().no_field(name); + let query = SearchArtistRequest::new().string(name); let matches = client.search_artist(query).unwrap(); assert_eq!(matches, response); diff --git a/src/external/musicbrainz/api/search/query.rs b/src/external/musicbrainz/api/search/query.rs index 1a1e243..6147d3c 100644 --- a/src/external/musicbrainz/api/search/query.rs +++ b/src/external/musicbrainz/api/search/query.rs @@ -225,52 +225,52 @@ mod tests { use super::*; pub enum TestEntity<'a> { - NoField(&'a str), + String(&'a str), } impl<'a> fmt::Display for TestEntity<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::NoField(s) => write!(f, "\"{s}\""), + Self::String(s) => write!(f, "\"{s}\""), } } } type TestEntityRequest<'a> = Query>; - impl_term!(no_field, TestEntity<'a>, NoField, &'a str); + impl_term!(string, TestEntity<'a>, String, &'a str); #[test] fn lucene_logical() { let query = TestEntityRequest::new() - .no_field("jakarta apache") + .string("jakarta apache") .or() - .no_field("jakarta"); + .string("jakarta"); assert_eq!(format!("{query}"), "\"jakarta apache\" OR \"jakarta\""); let query = TestEntityRequest::new() - .no_field("jakarta apache") + .string("jakarta apache") .and() - .no_field("jakarta"); + .string("jakarta"); assert_eq!(format!("{query}"), "\"jakarta apache\" AND \"jakarta\""); let query = TestEntityRequest::new() .require() - .no_field("jakarta") + .string("jakarta") .or() - .no_field("lucene"); + .string("lucene"); assert_eq!(format!("{query}"), "+\"jakarta\" OR \"lucene\""); let query = TestEntityRequest::new() - .no_field("lucene") + .string("lucene") .require() - .no_field("jakarta"); + .string("jakarta"); assert_eq!(format!("{query}"), "\"lucene\" +\"jakarta\""); let query = TestEntityRequest::new() - .no_field("jakarta apache") + .string("jakarta apache") .not() - .no_field("Apache Lucene"); + .string("Apache Lucene"); assert_eq!( format!("{query}"), "\"jakarta apache\" NOT \"Apache Lucene\"" @@ -278,18 +278,18 @@ mod tests { let query = TestEntityRequest::new() .prohibit() - .no_field("Apache Lucene") + .string("Apache Lucene") .or() - .no_field("jakarta apache"); + .string("jakarta apache"); assert_eq!( format!("{query}"), "-\"Apache Lucene\" OR \"jakarta apache\"" ); let query = TestEntityRequest::new() - .no_field("jakarta apache") + .string("jakarta apache") .prohibit() - .no_field("Apache Lucene"); + .string("Apache Lucene"); assert_eq!(format!("{query}"), "\"jakarta apache\" -\"Apache Lucene\""); } @@ -298,12 +298,12 @@ mod tests { let query = TestEntityRequest::new() .expression( TestEntityRequest::new() - .no_field("jakarta") + .string("jakarta") .or() - .no_field("apache"), + .string("apache"), ) .and() - .no_field("website"); + .string("website"); assert_eq!( format!("{query}"), "(\"jakarta\" OR \"apache\") AND \"website\"" diff --git a/src/external/musicbrainz/api/search/release_group.rs b/src/external/musicbrainz/api/search/release_group.rs index b340e4d..4192630 100644 --- a/src/external/musicbrainz/api/search/release_group.rs +++ b/src/external/musicbrainz/api/search/release_group.rs @@ -15,7 +15,7 @@ use crate::{ use super::query::{impl_term, EmptyQuery, EmptyQueryJoin, Query, QueryJoin}; pub enum SearchReleaseGroup<'a> { - NoField(&'a str), + String(&'a str), Arid(&'a Mbid), FirstReleaseDate(&'a AlbumDate), ReleaseGroup(&'a str), @@ -25,7 +25,7 @@ pub enum SearchReleaseGroup<'a> { impl<'a> fmt::Display for SearchReleaseGroup<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::NoField(s) => write!(f, "\"{s}\""), + Self::String(s) => write!(f, "\"{s}\""), Self::Arid(arid) => write!(f, "arid:{}", arid.uuid().as_hyphenated()), Self::FirstReleaseDate(date) => write!( f, @@ -40,7 +40,7 @@ impl<'a> fmt::Display for SearchReleaseGroup<'a> { pub type SearchReleaseGroupRequest<'a> = Query>; -impl_term!(no_field, SearchReleaseGroup<'a>, NoField, &'a str); +impl_term!(string, SearchReleaseGroup<'a>, String, &'a str); impl_term!(arid, SearchReleaseGroup<'a>, Arid, &'a Mbid); impl_term!( first_release_date, @@ -150,7 +150,7 @@ mod tests { } #[test] - fn search_no_field() { + fn search_string() { let mut http = MockIMusicBrainzHttp::new(); let url = format!( "https://musicbrainz.org/ws/2\ @@ -171,7 +171,7 @@ mod tests { let title = "an album"; - let query = SearchReleaseGroupRequest::new().no_field(title); + let query = SearchReleaseGroupRequest::new().string(title); let matches = client.search_release_group(query).unwrap(); assert_eq!(matches, response); diff --git a/src/tui/app/machine/matches.rs b/src/tui/app/machine/matches.rs index ee1e5df..3423e61 100644 --- a/src/tui/app/machine/matches.rs +++ b/src/tui/app/machine/matches.rs @@ -146,6 +146,7 @@ mod tests { let album_match_1_1 = Match { score: 100, item: album_1_1, + disambiguation: None, }; let mut album_1_2 = album_1.clone(); @@ -154,6 +155,7 @@ mod tests { let album_match_1_2 = Match { score: 100, item: album_1_2, + disambiguation: None, }; let matches_info_1 = AppMatchesInfo { @@ -172,6 +174,7 @@ mod tests { let album_match_2_1 = Match { score: 100, item: album_2_1, + disambiguation: None, }; let matches_info_2 = AppMatchesInfo { diff --git a/src/tui/lib/external/musicbrainz/mod.rs b/src/tui/lib/external/musicbrainz/mod.rs index c5fd275..bde0d23 100644 --- a/src/tui/lib/external/musicbrainz/mod.rs +++ b/src/tui/lib/external/musicbrainz/mod.rs @@ -3,11 +3,15 @@ use musichoard::{ collection::{ album::{Album, AlbumDate}, + artist::Artist, musicbrainz::Mbid, }, external::musicbrainz::{ api::{ - search::{SearchReleaseGroupRequest, SearchReleaseGroupResponseReleaseGroup}, + search::{ + SearchArtistRequest, SearchArtistResponseArtist, SearchReleaseGroupRequest, + SearchReleaseGroupResponseReleaseGroup, + }, MusicBrainzClient, }, IMusicBrainzHttp, @@ -28,6 +32,21 @@ impl MusicBrainz { } impl IMusicBrainz for MusicBrainz { + fn search_artist( + &mut self, + name: &str, + ) -> Result>, Error> { + let query = SearchArtistRequest::new().string(name); + + let mb_response = self.client.search_artist(query)?; + + Ok(mb_response + .artists + .into_iter() + .map(from_search_artist_response_artist) + .collect()) + } + fn search_release_group( &mut self, arid: &Mbid, @@ -54,6 +73,21 @@ impl IMusicBrainz for MusicBrainz { } } +fn from_search_artist_response_artist(entity: SearchArtistResponseArtist) -> Match { + let mut artist = Artist::new(entity.name); + if let Some(sort) = entity.sort { + artist.set_sort_key(sort); + } + artist.set_musicbrainz_ref(entity.id.into()); + + let mut artist_match = Match::new(entity.score, artist); + if let Some(disambiguation) = entity.disambiguation { + artist_match.set_disambiguation(disambiguation); + } + + artist_match +} + fn from_search_release_group_response_release_group( entity: SearchReleaseGroupResponseReleaseGroup, ) -> Match { diff --git a/src/tui/lib/interface/musicbrainz/mod.rs b/src/tui/lib/interface/musicbrainz/mod.rs index 1df8176..51ac8df 100644 --- a/src/tui/lib/interface/musicbrainz/mod.rs +++ b/src/tui/lib/interface/musicbrainz/mod.rs @@ -3,11 +3,12 @@ #[cfg(test)] use mockall::automock; -use musichoard::collection::{album::Album, musicbrainz::Mbid}; +use musichoard::collection::{album::Album, artist::Artist, musicbrainz::Mbid}; /// Trait for interacting with the MusicBrainz API. #[cfg_attr(test, automock)] pub trait IMusicBrainz { + fn search_artist(&mut self, name: &str) -> Result>, Error>; fn search_release_group( &mut self, arid: &Mbid, @@ -19,11 +20,16 @@ pub trait IMusicBrainz { pub struct Match { pub score: u8, pub item: T, + pub disambiguation: Option, } impl Match { pub fn new(score: u8, item: T) -> Self { - Match { score, item } + Match { score, item, disambiguation: None } + } + + pub fn set_disambiguation>(&mut self, disambiguation: S) { + self.disambiguation = Some(disambiguation.into()) } } diff --git a/src/tui/ui/mod.rs b/src/tui/ui/mod.rs index cf8ab4d..1835339 100644 --- a/src/tui/ui/mod.rs +++ b/src/tui/ui/mod.rs @@ -230,6 +230,7 @@ mod tests { let album_match = Match { score: 80, item: album.clone(), + disambiguation: None, }; let mut app = AppPublic {