Add option for manual input during fetch #219

Merged
wojtek merged 9 commits from 188---add-option-for-manual-input-during-fetch into main 2024-09-23 22:40:25 +02:00
5 changed files with 161 additions and 173 deletions
Showing only changes of commit 0cc477fb05 - Show all commits

View File

@ -2,20 +2,15 @@ use serde::Deserialize;
use url::form_urlencoded;
use crate::{
collection::{
album::{AlbumDate, AlbumId, AlbumPrimaryType, AlbumSecondaryType},
artist::ArtistId,
musicbrainz::Mbid,
},
collection::musicbrainz::Mbid,
external::musicbrainz::{
api::{
Error, MusicBrainzClient, SerdeAlbumDate, SerdeAlbumPrimaryType,
SerdeAlbumSecondaryType, SerdeMbid, MB_BASE_URL,
},
api::{Error, MusicBrainzClient, MB_BASE_URL},
IMusicBrainzHttp,
},
};
use super::{MbArtistMeta, MbReleaseGroupMeta, SerdeMbArtistMeta, SerdeMbReleaseGroupMeta};
impl<Http: IMusicBrainzHttp> MusicBrainzClient<Http> {
pub fn lookup_artist(
&mut self,
@ -73,30 +68,22 @@ impl<'a> LookupArtistRequest<'a> {
#[derive(Debug, PartialEq, Eq)]
pub struct LookupArtistResponse {
pub id: Mbid,
pub name: ArtistId,
pub sort_name: ArtistId,
pub disambiguation: Option<String>,
pub release_groups: Vec<LookupArtistResponseReleaseGroup>,
pub meta: MbArtistMeta,
pub release_groups: Vec<MbReleaseGroupMeta>,
}
#[derive(Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))]
struct DeserializeLookupArtistResponse {
id: SerdeMbid,
name: String,
sort_name: String,
disambiguation: Option<String>,
release_groups: Option<Vec<DeserializeLookupArtistResponseReleaseGroup>>,
#[serde(flatten)]
meta: SerdeMbArtistMeta,
release_groups: Option<Vec<SerdeMbReleaseGroupMeta>>,
}
impl From<DeserializeLookupArtistResponse> for LookupArtistResponse {
fn from(value: DeserializeLookupArtistResponse) -> Self {
LookupArtistResponse {
id: value.id.into(),
name: value.name.into(),
sort_name: value.sort_name.into(),
disambiguation: value.disambiguation,
meta: value.meta.into(),
release_groups: value
.release_groups
.map(|rgs| rgs.into_iter().map(Into::into).collect())
@ -105,37 +92,6 @@ impl From<DeserializeLookupArtistResponse> for LookupArtistResponse {
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct LookupArtistResponseReleaseGroup {
pub id: Mbid,
pub title: AlbumId,
pub first_release_date: AlbumDate,
pub primary_type: AlbumPrimaryType,
pub secondary_types: Vec<AlbumSecondaryType>,
}
#[derive(Clone, 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.into(),
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(),
}
}
}
pub struct LookupReleaseGroupRequest<'a> {
mbid: &'a Mbid,
}
@ -148,33 +104,20 @@ impl<'a> LookupReleaseGroupRequest<'a> {
#[derive(Debug, PartialEq, Eq)]
pub struct LookupReleaseGroupResponse {
pub id: Mbid,
pub title: AlbumId,
pub first_release_date: AlbumDate,
pub primary_type: AlbumPrimaryType,
pub secondary_types: Option<Vec<AlbumSecondaryType>>,
pub meta: MbReleaseGroupMeta,
}
#[derive(Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))]
struct DeserializeLookupReleaseGroupResponse {
id: SerdeMbid,
title: String,
first_release_date: SerdeAlbumDate,
primary_type: SerdeAlbumPrimaryType,
secondary_types: Option<Vec<SerdeAlbumSecondaryType>>,
#[serde(flatten)]
meta: SerdeMbReleaseGroupMeta,
}
impl From<DeserializeLookupReleaseGroupResponse> for LookupReleaseGroupResponse {
fn from(value: DeserializeLookupReleaseGroupResponse) -> Self {
LookupReleaseGroupResponse {
id: value.id.into(),
title: value.title.into(),
first_release_date: value.first_release_date.into(),
primary_type: value.primary_type.into(),
secondary_types: value
.secondary_types
.map(|v| v.into_iter().map(Into::into).collect()),
meta: value.meta.into(),
}
}
}
@ -183,7 +126,13 @@ impl From<DeserializeLookupReleaseGroupResponse> for LookupReleaseGroupResponse
mod tests {
use mockall::predicate;
use crate::external::musicbrainz::MockIMusicBrainzHttp;
use crate::{
collection::album::{AlbumPrimaryType, AlbumSecondaryType},
external::musicbrainz::{
api::{SerdeAlbumDate, SerdeAlbumPrimaryType, SerdeAlbumSecondaryType, SerdeMbid},
MockIMusicBrainzHttp,
},
};
use super::*;
@ -193,41 +142,38 @@ mod tests {
let mut http = MockIMusicBrainzHttp::new();
let url = format!("https://musicbrainz.org/ws/2/artist/{mbid}?inc=release-groups",);
let de_id = SerdeMbid(mbid.try_into().unwrap());
let de_name = String::from("the artist");
let de_sort_name = String::from("artist, the");
let de_disambiguation = Some(String::from("disambig"));
let de_release_group = DeserializeLookupArtistResponseReleaseGroup {
let de_meta = SerdeMbArtistMeta {
id: SerdeMbid(mbid.try_into().unwrap()),
name: String::from("the artist"),
sort_name: String::from("artist, the"),
disambiguation: Some(String::from("disambig")),
};
let de_release_group = SerdeMbReleaseGroupMeta {
id: SerdeMbid("11111111-1111-1111-1111-111111111111".try_into().unwrap()),
title: String::from("an album"),
first_release_date: SerdeAlbumDate((1986, 4).into()),
primary_type: SerdeAlbumPrimaryType(AlbumPrimaryType::Album),
secondary_types: vec![SerdeAlbumSecondaryType(AlbumSecondaryType::Compilation)],
secondary_types: Some(vec![SerdeAlbumSecondaryType(
AlbumSecondaryType::Compilation,
)]),
};
let de_response = DeserializeLookupArtistResponse {
id: de_id.clone(),
name: de_name.clone(),
sort_name: de_sort_name.clone(),
disambiguation: de_disambiguation.clone(),
meta: de_meta.clone(),
release_groups: Some(vec![de_release_group.clone()]),
};
let release_group = LookupArtistResponseReleaseGroup {
let release_group = MbReleaseGroupMeta {
id: de_release_group.id.0,
title: de_release_group.title.into(),
first_release_date: de_release_group.first_release_date.0,
primary_type: de_release_group.primary_type.0,
secondary_types: de_release_group
.secondary_types
.into_iter()
.map(|st| st.0)
.collect(),
.as_ref()
.map(|v| v.into_iter().map(|st| st.0).collect()),
};
let response = LookupArtistResponse {
id: de_id.0,
name: de_name.into(),
sort_name: de_sort_name.into(),
disambiguation: de_disambiguation,
meta: de_meta.into(),
release_groups: vec![release_group],
};

View File

@ -4,7 +4,8 @@ use serde::{de::Visitor, Deserialize, Deserializer};
use crate::{
collection::{
album::{AlbumDate, AlbumPrimaryType, AlbumSecondaryType},
album::{AlbumDate, AlbumId, AlbumPrimaryType, AlbumSecondaryType},
artist::ArtistId,
musicbrainz::Mbid,
Error as CollectionError,
},
@ -58,6 +59,67 @@ impl<Http> MusicBrainzClient<Http> {
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MbArtistMeta {
pub id: Mbid,
pub name: ArtistId,
pub sort_name: ArtistId,
pub disambiguation: Option<String>,
}
#[derive(Clone, Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))]
struct SerdeMbArtistMeta {
id: SerdeMbid,
name: String,
sort_name: String,
disambiguation: Option<String>,
}
impl From<SerdeMbArtistMeta> for MbArtistMeta {
fn from(value: SerdeMbArtistMeta) -> Self {
MbArtistMeta {
id: value.id.into(),
name: value.name.into(),
sort_name: value.sort_name.into(),
disambiguation: value.disambiguation,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MbReleaseGroupMeta {
pub id: Mbid,
pub title: AlbumId,
pub first_release_date: AlbumDate,
pub primary_type: AlbumPrimaryType,
pub secondary_types: Option<Vec<AlbumSecondaryType>>,
}
#[derive(Clone, Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))]
pub struct SerdeMbReleaseGroupMeta {
id: SerdeMbid,
title: String,
first_release_date: SerdeAlbumDate,
primary_type: SerdeAlbumPrimaryType,
secondary_types: Option<Vec<SerdeAlbumSecondaryType>>,
}
impl From<SerdeMbReleaseGroupMeta> for MbReleaseGroupMeta {
fn from(value: SerdeMbReleaseGroupMeta) -> Self {
MbReleaseGroupMeta {
id: value.id.into(),
title: value.title.into(),
first_release_date: value.first_release_date.into(),
primary_type: value.primary_type.into(),
secondary_types: value
.secondary_types
.map(|v| v.into_iter().map(Into::into).collect()),
}
}
}
pub struct ApiDisplay;
impl ApiDisplay {

View File

@ -2,12 +2,9 @@ use std::fmt;
use serde::Deserialize;
use crate::{
collection::{artist::ArtistId, musicbrainz::Mbid},
external::musicbrainz::api::{
search::query::{impl_term, EmptyQuery, EmptyQueryJoin, Query, QueryJoin},
SerdeMbid,
},
use crate::external::musicbrainz::api::{
search::query::{impl_term, EmptyQuery, EmptyQueryJoin, Query, QueryJoin},
MbArtistMeta, SerdeMbArtistMeta,
};
pub enum SearchArtist<'a> {
@ -48,30 +45,22 @@ impl From<DeserializeSearchArtistResponse> for SearchArtistResponse {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SearchArtistResponseArtist {
pub score: u8,
pub id: Mbid,
pub name: ArtistId,
pub sort_name: ArtistId,
pub disambiguation: Option<String>,
pub meta: MbArtistMeta,
}
#[derive(Clone, Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))]
struct DeserializeSearchArtistResponseArtist {
score: u8,
id: SerdeMbid,
name: String,
sort_name: String,
disambiguation: Option<String>,
#[serde(flatten)]
meta: SerdeMbArtistMeta,
}
impl From<DeserializeSearchArtistResponseArtist> for SearchArtistResponseArtist {
fn from(value: DeserializeSearchArtistResponseArtist) -> Self {
SearchArtistResponseArtist {
score: value.score,
id: value.id.into(),
name: value.name.into(),
sort_name: value.sort_name.into(),
disambiguation: value.disambiguation,
meta: value.meta.into(),
}
}
}
@ -80,17 +69,22 @@ impl From<DeserializeSearchArtistResponseArtist> for SearchArtistResponseArtist
mod tests {
use mockall::predicate;
use crate::external::musicbrainz::{api::MusicBrainzClient, MockIMusicBrainzHttp};
use crate::external::musicbrainz::{
api::{MusicBrainzClient, SerdeMbid},
MockIMusicBrainzHttp,
};
use super::*;
fn de_response() -> DeserializeSearchArtistResponse {
let de_artist = DeserializeSearchArtistResponseArtist {
score: 67,
id: SerdeMbid("11111111-1111-1111-1111-111111111111".try_into().unwrap()),
name: String::from("an artist"),
sort_name: String::from("artist, an"),
disambiguation: None,
meta: SerdeMbArtistMeta {
id: SerdeMbid("11111111-1111-1111-1111-111111111111".try_into().unwrap()),
name: String::from("an artist"),
sort_name: String::from("artist, an"),
disambiguation: None,
},
};
DeserializeSearchArtistResponse {
artists: vec![de_artist.clone()],
@ -104,10 +98,7 @@ mod tests {
.into_iter()
.map(|a| SearchArtistResponseArtist {
score: 67,
id: a.id.0,
name: a.name.clone().into(),
sort_name: a.sort_name.clone().into(),
disambiguation: a.disambiguation,
meta: a.meta.into(),
})
.collect(),
}

View File

@ -3,13 +3,10 @@ use std::fmt;
use serde::Deserialize;
use crate::{
collection::{
album::{AlbumDate, AlbumId, AlbumPrimaryType, AlbumSecondaryType},
musicbrainz::Mbid,
},
collection::{album::AlbumDate, musicbrainz::Mbid},
external::musicbrainz::api::{
search::query::{impl_term, EmptyQuery, EmptyQueryJoin, Query, QueryJoin},
ApiDisplay, SerdeAlbumDate, SerdeAlbumPrimaryType, SerdeAlbumSecondaryType, SerdeMbid,
ApiDisplay, MbReleaseGroupMeta, SerdeMbReleaseGroupMeta,
},
};
@ -72,22 +69,15 @@ impl From<DeserializeSearchReleaseGroupResponse> for SearchReleaseGroupResponse
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SearchReleaseGroupResponseReleaseGroup {
pub score: u8,
pub id: Mbid,
pub title: AlbumId,
pub first_release_date: AlbumDate,
pub primary_type: AlbumPrimaryType,
pub secondary_types: Option<Vec<AlbumSecondaryType>>,
pub meta: MbReleaseGroupMeta,
}
#[derive(Clone, Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))]
pub struct DeserializeSearchReleaseGroupResponseReleaseGroup {
score: u8,
id: SerdeMbid,
title: String,
first_release_date: SerdeAlbumDate,
primary_type: SerdeAlbumPrimaryType,
secondary_types: Option<Vec<SerdeAlbumSecondaryType>>,
#[serde(flatten)]
meta: SerdeMbReleaseGroupMeta,
}
impl From<DeserializeSearchReleaseGroupResponseReleaseGroup>
@ -96,13 +86,7 @@ impl From<DeserializeSearchReleaseGroupResponseReleaseGroup>
fn from(value: DeserializeSearchReleaseGroupResponseReleaseGroup) -> Self {
SearchReleaseGroupResponseReleaseGroup {
score: value.score,
id: value.id.into(),
title: value.title.into(),
first_release_date: value.first_release_date.into(),
primary_type: value.primary_type.into(),
secondary_types: value
.secondary_types
.map(|v| v.into_iter().map(Into::into).collect()),
meta: value.meta.into(),
}
}
}
@ -111,18 +95,29 @@ impl From<DeserializeSearchReleaseGroupResponseReleaseGroup>
mod tests {
use mockall::predicate;
use crate::external::musicbrainz::{api::MusicBrainzClient, MockIMusicBrainzHttp};
use crate::{
collection::album::{AlbumPrimaryType, AlbumSecondaryType},
external::musicbrainz::{
api::{
MusicBrainzClient, SerdeAlbumDate, SerdeAlbumPrimaryType, SerdeAlbumSecondaryType,
SerdeMbid,
},
MockIMusicBrainzHttp,
},
};
use super::*;
fn de_response() -> DeserializeSearchReleaseGroupResponse {
let de_release_group = DeserializeSearchReleaseGroupResponseReleaseGroup {
score: 67,
id: SerdeMbid("11111111-1111-1111-1111-111111111111".try_into().unwrap()),
title: String::from("an album"),
first_release_date: SerdeAlbumDate((1986, 4).into()),
primary_type: SerdeAlbumPrimaryType(AlbumPrimaryType::Album),
secondary_types: Some(vec![SerdeAlbumSecondaryType(AlbumSecondaryType::Live)]),
meta: SerdeMbReleaseGroupMeta {
id: SerdeMbid("11111111-1111-1111-1111-111111111111".try_into().unwrap()),
title: String::from("an album"),
first_release_date: SerdeAlbumDate((1986, 4).into()),
primary_type: SerdeAlbumPrimaryType(AlbumPrimaryType::Album),
secondary_types: Some(vec![SerdeAlbumSecondaryType(AlbumSecondaryType::Live)]),
},
};
DeserializeSearchReleaseGroupResponse {
release_groups: vec![de_release_group.clone()],
@ -136,13 +131,7 @@ mod tests {
.into_iter()
.map(|rg| SearchReleaseGroupResponseReleaseGroup {
score: 67,
id: rg.id.0,
title: rg.title.into(),
first_release_date: rg.first_release_date.0,
primary_type: rg.primary_type.0,
secondary_types: rg
.secondary_types
.map(|v| v.into_iter().map(|st| st.0).collect()),
meta: rg.meta.into(),
})
.collect(),
}

View File

@ -93,47 +93,47 @@ impl<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> {
}
fn from_lookup_artist_response(entity: LookupArtistResponse) -> Lookup<ArtistMeta> {
let sort: Option<ArtistId> = Some(entity.sort_name)
.filter(|s| s != &entity.name)
let sort: Option<ArtistId> = Some(entity.meta.sort_name)
.filter(|s| s != &entity.meta.name)
.map(Into::into);
Lookup {
item: ArtistMeta {
id: entity.name,
id: entity.meta.name,
sort,
musicbrainz: Some(entity.id.into()),
musicbrainz: Some(entity.meta.id.into()),
properties: HashMap::new(),
},
disambiguation: entity.disambiguation,
disambiguation: entity.meta.disambiguation,
}
}
fn from_lookup_release_group_response(entity: LookupReleaseGroupResponse) -> Lookup<AlbumMeta> {
Lookup {
item: AlbumMeta {
id: entity.title,
date: entity.first_release_date,
id: entity.meta.title,
date: entity.meta.first_release_date,
seq: AlbumSeq::default(),
musicbrainz: Some(entity.id.into()),
primary_type: Some(entity.primary_type),
secondary_types: entity.secondary_types.unwrap_or_default(),
musicbrainz: Some(entity.meta.id.into()),
primary_type: Some(entity.meta.primary_type),
secondary_types: entity.meta.secondary_types.unwrap_or_default(),
},
disambiguation: None,
}
}
fn from_search_artist_response_artist(entity: SearchArtistResponseArtist) -> Match<ArtistMeta> {
let sort: Option<ArtistId> = Some(entity.sort_name)
.filter(|s| s != &entity.name)
let sort: Option<ArtistId> = Some(entity.meta.sort_name)
.filter(|s| s != &entity.meta.name)
.map(Into::into);
Match {
score: entity.score,
item: ArtistMeta {
id: entity.name,
id: entity.meta.name,
sort,
musicbrainz: Some(entity.id.into()),
musicbrainz: Some(entity.meta.id.into()),
properties: HashMap::new(),
},
disambiguation: entity.disambiguation,
disambiguation: entity.meta.disambiguation,
}
}
@ -143,12 +143,12 @@ fn from_search_release_group_response_release_group(
Match {
score: entity.score,
item: AlbumMeta {
id: entity.title,
date: entity.first_release_date,
id: entity.meta.title,
date: entity.meta.first_release_date,
seq: AlbumSeq::default(),
musicbrainz: Some(entity.id.into()),
primary_type: Some(entity.primary_type),
secondary_types: entity.secondary_types.unwrap_or_default(),
musicbrainz: Some(entity.meta.id.into()),
primary_type: Some(entity.meta.primary_type),
secondary_types: entity.meta.secondary_types.unwrap_or_default(),
},
disambiguation: None,
}