Add search_artist binding to TUI

This commit is contained in:
Wojciech Kozlowski 2024-08-30 13:38:20 +02:00
parent ab8bdf9f3e
commit f24737d5fa
8 changed files with 78 additions and 34 deletions

View File

@ -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}");

View File

@ -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<SearchArtist<'a>>;
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);

View File

@ -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<TestEntity<'a>>;
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\""

View File

@ -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<SearchReleaseGroup<'a>>;
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);

View File

@ -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 {

View File

@ -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<Http> MusicBrainz<Http> {
}
impl<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> {
fn search_artist(
&mut self,
name: &str,
) -> Result<Vec<Match<musichoard::collection::artist::Artist>>, 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<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> {
}
}
fn from_search_artist_response_artist(entity: SearchArtistResponseArtist) -> Match<Artist> {
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<Album> {

View File

@ -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<Vec<Match<Artist>>, Error>;
fn search_release_group(
&mut self,
arid: &Mbid,
@ -19,11 +20,16 @@ pub trait IMusicBrainz {
pub struct Match<T> {
pub score: u8,
pub item: T,
pub disambiguation: Option<String>,
}
impl<T> Match<T> {
pub fn new(score: u8, item: T) -> Self {
Match { score, item }
Match { score, item, disambiguation: None }
}
pub fn set_disambiguation<S: Into<String>>(&mut self, disambiguation: S) {
self.disambiguation = Some(disambiguation.into())
}
}

View File

@ -230,6 +230,7 @@ mod tests {
let album_match = Match {
score: 80,
item: album.clone(),
disambiguation: None,
};
let mut app = AppPublic {