Decide carefully where external::musicbrainz belongs #196

Merged
wojtek merged 11 commits from 193---decide-carefully-where-external--musicbrainz-belongs into main 2024-08-28 18:21:13 +02:00
5 changed files with 151 additions and 47 deletions
Showing only changes of commit c558fe8c1d - Show all commits

View File

@ -45,14 +45,14 @@ name = "musichoard-edit"
required-features = ["bin", "database-json"] required-features = ["bin", "database-json"]
[[example]] [[example]]
name = "musicbrainz-api---lookup-artist-release-groups" name = "musicbrainz-api---lookup-artist"
path = "examples/musicbrainz_api/lookup_artist_release_groups.rs" path = "examples/musicbrainz_api/lookup_artist.rs"
required-features = ["bin", "musicbrainz-api"] required-features = ["bin", "musicbrainz"]
[[example]] [[example]]
name = "musicbrainz-api---search-release-group" name = "musicbrainz-api---search-release-group"
path = "examples/musicbrainz_api/search_release_group.rs" path = "examples/musicbrainz_api/search_release_group.rs"
required-features = ["bin", "musicbrainz-api"] required-features = ["bin", "musicbrainz"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@ -1,14 +1,14 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use musichoard::{ use musichoard::{
external::musicbrainz::api::{client::MusicBrainzApiClient, MusicBrainzApi}, external::musicbrainz::{http::MusicBrainzHttp, LookupArtistRequest, MusicBrainzClient},
interface::musicbrainz::{IMusicBrainz, Mbid}, interface::musicbrainz::Mbid,
}; };
use structopt::StructOpt; use structopt::StructOpt;
use uuid::Uuid; use uuid::Uuid;
const USER_AGENT: &str = concat!( const USER_AGENT: &str = concat!(
"MusicHoard---examples---musicbrainz-api---lookup-artist-release-groups/", "MusicHoard---examples---musicbrainz-api---lookup-artist/",
env!("CARGO_PKG_VERSION"), env!("CARGO_PKG_VERSION"),
" ( musichoard@thenineworlds.net )" " ( musichoard@thenineworlds.net )"
); );
@ -24,12 +24,15 @@ fn main() {
println!("USER_AGENT: {USER_AGENT}"); println!("USER_AGENT: {USER_AGENT}");
let client = MusicBrainzApiClient::new(USER_AGENT).expect("failed to create API client"); let http = MusicBrainzHttp::new(USER_AGENT).expect("failed to create API client");
let mut api = MusicBrainzApi::new(client); let mut client = MusicBrainzClient::new(http);
let mbid: Mbid = opt.mbid.into(); let mbid: Mbid = opt.mbid.into();
let albums = api let mut request = LookupArtistRequest::new(&mbid);
.lookup_artist_release_groups(&mbid) request.include_release_groups();
let albums = client
.lookup_artist(request)
.expect("failed to make API call"); .expect("failed to make API call");
println!("{albums:#?}"); println!("{albums:#?}");

View File

@ -3,9 +3,9 @@
use std::{num::ParseIntError, str::FromStr}; use std::{num::ParseIntError, str::FromStr};
use musichoard::{ use musichoard::{
collection::album::{Album, AlbumDate, AlbumId}, collection::album::AlbumDate,
external::musicbrainz::api::{client::MusicBrainzApiClient, MusicBrainzApi}, external::musicbrainz::{http::MusicBrainzHttp, MusicBrainzClient, SearchReleaseGroupRequest},
interface::musicbrainz::{IMusicBrainz, Mbid}, interface::musicbrainz::Mbid,
}; };
use structopt::StructOpt; use structopt::StructOpt;
use uuid::Uuid; use uuid::Uuid;
@ -18,16 +18,13 @@ const USER_AGENT: &str = concat!(
#[derive(StructOpt)] #[derive(StructOpt)]
struct Opt { struct Opt {
#[structopt(help = "Release group's artist MBID")]
arid: Uuid,
#[structopt(subcommand)] #[structopt(subcommand)]
command: OptCommand, command: OptCommand,
} }
#[derive(StructOpt)] #[derive(StructOpt)]
enum OptCommand { enum OptCommand {
#[structopt(about = "Search by title (and date)")] #[structopt(about = "Search by artist MBID, title(, and date)")]
Title(OptTitle), Title(OptTitle),
#[structopt(about = "Search by release group MBID")] #[structopt(about = "Search by release group MBID")]
Rgid(OptRgid), Rgid(OptRgid),
@ -35,6 +32,9 @@ enum OptCommand {
#[derive(StructOpt)] #[derive(StructOpt)]
struct OptTitle { struct OptTitle {
#[structopt(help = "Release group's artist MBID")]
arid: Uuid,
#[structopt(help = "Release group title")] #[structopt(help = "Release group title")]
title: String, title: String,
@ -80,30 +80,32 @@ fn main() {
println!("USER_AGENT: {USER_AGENT}"); println!("USER_AGENT: {USER_AGENT}");
let client = MusicBrainzApiClient::new(USER_AGENT).expect("failed to create API client"); let http = MusicBrainzHttp::new(USER_AGENT).expect("failed to create API client");
let mut api = MusicBrainzApi::new(client); let mut client = MusicBrainzClient::new(http);
let arid: Mbid = opt.arid.into(); let mut request = SearchReleaseGroupRequest::default();
let arid: Mbid;
let album = match opt.command { let date: AlbumDate;
let title: String;
let rgid: Mbid;
match opt.command {
OptCommand::Title(opt_title) => { OptCommand::Title(opt_title) => {
let date: AlbumDate = opt_title.date.map(Into::into).unwrap_or_default(); arid = opt_title.arid.into();
Album::new(AlbumId::new(opt_title.title), date, None, vec![]) date = opt_title.date.map(Into::into).unwrap_or_default();
title = opt_title.title;
request
.arid(&arid)
.first_release_date(&date)
.release_group(&title);
} }
OptCommand::Rgid(opt_rgid) => { OptCommand::Rgid(opt_rgid) => {
let mut album = Album::new( rgid = opt_rgid.rgid.into();
AlbumId::new(String::default()), request.rgid(&rgid);
AlbumDate::default(),
None,
vec![],
);
album.set_musicbrainz_ref(opt_rgid.rgid.into());
album
} }
}; };
let matches = api let matches = client
.search_release_group(&arid, &album) .search_release_group(request)
.expect("failed to make API call"); .expect("failed to make API call");
println!("{matches:#?}"); println!("{matches:#?}");

View File

@ -78,7 +78,7 @@ impl<Http> MusicBrainzClient<Http> {
} }
impl<Http: IMusicBrainzHttp> MusicBrainzClient<Http> { impl<Http: IMusicBrainzHttp> MusicBrainzClient<Http> {
fn display_album_date(date: &AlbumDate) -> Option<String> { fn format_album_date(date: &AlbumDate) -> Option<String> {
match date.year { match date.year {
Some(year) => match date.month { Some(year) => match date.month {
Some(month) => match date.day { Some(month) => match date.day {
@ -91,6 +91,26 @@ impl<Http: IMusicBrainzHttp> MusicBrainzClient<Http> {
} }
} }
pub fn lookup_artist(
&mut self,
request: LookupArtistRequest,
) -> Result<LookupArtistResponse, Error> {
let mut include: Vec<String> = vec![];
let mbid: String = request.mbid.uuid().as_hyphenated().to_string();
if request.release_groups {
include.push(String::from("release-groups"));
}
let include: String =
form_urlencoded::byte_serialize(include.join("+").as_bytes()).collect();
let url = format!("{MB_BASE_URL}/artist/{mbid}?inc={include}");
let response: DeserializeLookupArtistResponse = self.http.get(&url)?;
Ok(response.into())
}
pub fn search_release_group( pub fn search_release_group(
&mut self, &mut self,
request: SearchReleaseGroupRequest, request: SearchReleaseGroupRequest,
@ -102,7 +122,7 @@ impl<Http: IMusicBrainzHttp> MusicBrainzClient<Http> {
} }
if let Some(date) = request.first_release_date { if let Some(date) = request.first_release_date {
if let Some(date_string) = Self::display_album_date(date) { if let Some(date_string) = Self::format_album_date(date) {
query.push(format!("firstreleasedate:{date_string}")) query.push(format!("firstreleasedate:{date_string}"))
} }
} }
@ -124,6 +144,75 @@ impl<Http: IMusicBrainzHttp> MusicBrainzClient<Http> {
} }
} }
pub struct LookupArtistRequest<'a> {
mbid: &'a Mbid,
release_groups: bool,
}
impl<'a> LookupArtistRequest<'a> {
pub fn new(mbid: &'a Mbid) -> Self {
LookupArtistRequest {
mbid,
release_groups: false,
}
}
pub fn include_release_groups(&mut self) -> &mut Self {
self.release_groups = true;
self
}
}
#[derive(Debug)]
pub struct LookupArtistResponse {
pub release_groups: Vec<LookupArtistResponseReleaseGroup>,
}
#[derive(Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))]
struct DeserializeLookupArtistResponse {
release_groups: Vec<DeserializeLookupArtistResponseReleaseGroup>,
}
impl From<DeserializeLookupArtistResponse> for LookupArtistResponse {
fn from(value: DeserializeLookupArtistResponse) -> Self {
LookupArtistResponse {
release_groups: value.release_groups.into_iter().map(Into::into).collect(),
}
}
}
#[derive(Debug)]
pub struct LookupArtistResponseReleaseGroup {
pub id: Mbid,
pub title: String,
pub first_release_date: AlbumDate,
pub primary_type: AlbumPrimaryType,
pub secondary_types: Vec<AlbumSecondaryType>,
}
#[derive(Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))]
struct DeserializeLookupArtistResponseReleaseGroup {
id: SerdeMbid,
title: String,
first_release_date: SerdeAlbumDate,
primary_type: SerdeAlbumPrimaryType,
secondary_types: Vec<SerdeAlbumSecondaryType>,
}
impl From<DeserializeLookupArtistResponseReleaseGroup> for LookupArtistResponseReleaseGroup {
fn from(value: DeserializeLookupArtistResponseReleaseGroup) -> Self {
LookupArtistResponseReleaseGroup {
id: value.id.into(),
title: value.title,
first_release_date: value.first_release_date.into(),
primary_type: value.primary_type.into(),
secondary_types: value.secondary_types.into_iter().map(Into::into).collect(),
}
}
}
#[derive(Default)] #[derive(Default)]
pub struct SearchReleaseGroupRequest<'a> { pub struct SearchReleaseGroupRequest<'a> {
arid: Option<&'a Mbid>, arid: Option<&'a Mbid>,
@ -133,6 +222,10 @@ pub struct SearchReleaseGroupRequest<'a> {
} }
impl<'a> SearchReleaseGroupRequest<'a> { impl<'a> SearchReleaseGroupRequest<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn arid(&mut self, arid: &'a Mbid) -> &mut Self { pub fn arid(&mut self, arid: &'a Mbid) -> &mut Self {
self.arid = Some(arid); self.arid = Some(arid);
self self
@ -154,14 +247,15 @@ impl<'a> SearchReleaseGroupRequest<'a> {
} }
} }
#[derive(Debug)]
pub struct SearchReleaseGroupResponse { pub struct SearchReleaseGroupResponse {
pub release_groups: Vec<SearchReleaseGroupResponseUnit>, pub release_groups: Vec<SearchReleaseGroupResponseReleaseGroup>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))] #[serde(rename_all(deserialize = "kebab-case"))]
struct DeserializeSearchReleaseGroupResponse { struct DeserializeSearchReleaseGroupResponse {
release_groups: Vec<DeserializeSearchReleaseGroupResponseUnit>, release_groups: Vec<DeserializeSearchReleaseGroupResponseReleaseGroup>,
} }
impl From<DeserializeSearchReleaseGroupResponse> for SearchReleaseGroupResponse { impl From<DeserializeSearchReleaseGroupResponse> for SearchReleaseGroupResponse {
@ -172,7 +266,8 @@ impl From<DeserializeSearchReleaseGroupResponse> for SearchReleaseGroupResponse
} }
} }
pub struct SearchReleaseGroupResponseUnit { #[derive(Debug)]
pub struct SearchReleaseGroupResponseReleaseGroup {
pub score: u8, pub score: u8,
pub id: Mbid, pub id: Mbid,
pub title: String, pub title: String,
@ -183,7 +278,7 @@ pub struct SearchReleaseGroupResponseUnit {
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))] #[serde(rename_all(deserialize = "kebab-case"))]
struct DeserializeSearchReleaseGroupResponseUnit { struct DeserializeSearchReleaseGroupResponseReleaseGroup {
score: u8, score: u8,
id: SerdeMbid, id: SerdeMbid,
title: String, title: String,
@ -192,9 +287,11 @@ struct DeserializeSearchReleaseGroupResponseUnit {
secondary_types: Option<Vec<SerdeAlbumSecondaryType>>, secondary_types: Option<Vec<SerdeAlbumSecondaryType>>,
} }
impl From<DeserializeSearchReleaseGroupResponseUnit> for SearchReleaseGroupResponseUnit { impl From<DeserializeSearchReleaseGroupResponseReleaseGroup>
fn from(value: DeserializeSearchReleaseGroupResponseUnit) -> Self { for SearchReleaseGroupResponseReleaseGroup
SearchReleaseGroupResponseUnit { {
fn from(value: DeserializeSearchReleaseGroupResponseReleaseGroup) -> Self {
SearchReleaseGroupResponseReleaseGroup {
score: value.score, score: value.score,
id: value.id.into(), id: value.id.into(),
title: value.title, title: value.title,

View File

@ -4,7 +4,7 @@ use musichoard::{
collection::album::{Album, AlbumDate}, collection::album::{Album, AlbumDate},
external::musicbrainz::{ external::musicbrainz::{
IMusicBrainzHttp, MusicBrainzClient, SearchReleaseGroupRequest, IMusicBrainzHttp, MusicBrainzClient, SearchReleaseGroupRequest,
SearchReleaseGroupResponseUnit, SearchReleaseGroupResponseReleaseGroup,
}, },
interface::musicbrainz::Mbid, interface::musicbrainz::Mbid,
}; };
@ -42,12 +42,14 @@ impl<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> {
Ok(mb_response Ok(mb_response
.release_groups .release_groups
.into_iter() .into_iter()
.map(from_search_release_group_response_unit) .map(from_search_release_group_response_release_group)
.collect()) .collect())
} }
} }
fn from_search_release_group_response_unit(entity: SearchReleaseGroupResponseUnit) -> Match<Album> { fn from_search_release_group_response_release_group(
entity: SearchReleaseGroupResponseReleaseGroup,
) -> Match<Album> {
let mut album = Album::new( let mut album = Album::new(
entity.title, entity.title,
entity.first_release_date, entity.first_release_date,