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"]
[[example]]
name = "musicbrainz-api---lookup-artist-release-groups"
path = "examples/musicbrainz_api/lookup_artist_release_groups.rs"
required-features = ["bin", "musicbrainz-api"]
name = "musicbrainz-api---lookup-artist"
path = "examples/musicbrainz_api/lookup_artist.rs"
required-features = ["bin", "musicbrainz"]
[[example]]
name = "musicbrainz-api---search-release-group"
path = "examples/musicbrainz_api/search_release_group.rs"
required-features = ["bin", "musicbrainz-api"]
required-features = ["bin", "musicbrainz"]
[package.metadata.docs.rs]
all-features = true

View File

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

View File

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

View File

@ -78,7 +78,7 @@ impl<Http> 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 {
Some(year) => match date.month {
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(
&mut self,
request: SearchReleaseGroupRequest,
@ -102,7 +122,7 @@ impl<Http: IMusicBrainzHttp> MusicBrainzClient<Http> {
}
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}"))
}
}
@ -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)]
pub struct SearchReleaseGroupRequest<'a> {
arid: Option<&'a Mbid>,
@ -133,6 +222,10 @@ pub struct SearchReleaseGroupRequest<'a> {
}
impl<'a> SearchReleaseGroupRequest<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn arid(&mut self, arid: &'a Mbid) -> &mut Self {
self.arid = Some(arid);
self
@ -154,14 +247,15 @@ impl<'a> SearchReleaseGroupRequest<'a> {
}
}
#[derive(Debug)]
pub struct SearchReleaseGroupResponse {
pub release_groups: Vec<SearchReleaseGroupResponseUnit>,
pub release_groups: Vec<SearchReleaseGroupResponseReleaseGroup>,
}
#[derive(Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))]
struct DeserializeSearchReleaseGroupResponse {
release_groups: Vec<DeserializeSearchReleaseGroupResponseUnit>,
release_groups: Vec<DeserializeSearchReleaseGroupResponseReleaseGroup>,
}
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 id: Mbid,
pub title: String,
@ -183,7 +278,7 @@ pub struct SearchReleaseGroupResponseUnit {
#[derive(Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))]
struct DeserializeSearchReleaseGroupResponseUnit {
struct DeserializeSearchReleaseGroupResponseReleaseGroup {
score: u8,
id: SerdeMbid,
title: String,
@ -192,9 +287,11 @@ struct DeserializeSearchReleaseGroupResponseUnit {
secondary_types: Option<Vec<SerdeAlbumSecondaryType>>,
}
impl From<DeserializeSearchReleaseGroupResponseUnit> for SearchReleaseGroupResponseUnit {
fn from(value: DeserializeSearchReleaseGroupResponseUnit) -> Self {
SearchReleaseGroupResponseUnit {
impl From<DeserializeSearchReleaseGroupResponseReleaseGroup>
for SearchReleaseGroupResponseReleaseGroup
{
fn from(value: DeserializeSearchReleaseGroupResponseReleaseGroup) -> Self {
SearchReleaseGroupResponseReleaseGroup {
score: value.score,
id: value.id.into(),
title: value.title,

View File

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