From 0cc477fb0549f70bb6c1e90f593431400cfedc98 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sun, 22 Sep 2024 17:00:20 +0200 Subject: [PATCH] Commonise MB structs --- src/external/musicbrainz/api/lookup.rs | 126 +++++------------- src/external/musicbrainz/api/mod.rs | 64 ++++++++- src/external/musicbrainz/api/search/artist.rs | 45 +++---- .../musicbrainz/api/search/release_group.rs | 59 ++++---- src/tui/lib/external/musicbrainz/api/mod.rs | 40 +++--- 5 files changed, 161 insertions(+), 173 deletions(-) diff --git a/src/external/musicbrainz/api/lookup.rs b/src/external/musicbrainz/api/lookup.rs index 1bd3c7f..f3599af 100644 --- a/src/external/musicbrainz/api/lookup.rs +++ b/src/external/musicbrainz/api/lookup.rs @@ -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 MusicBrainzClient { 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, - pub release_groups: Vec, + pub meta: MbArtistMeta, + pub release_groups: Vec, } #[derive(Deserialize)] #[serde(rename_all(deserialize = "kebab-case"))] struct DeserializeLookupArtistResponse { - id: SerdeMbid, - name: String, - sort_name: String, - disambiguation: Option, - release_groups: Option>, + #[serde(flatten)] + meta: SerdeMbArtistMeta, + release_groups: Option>, } impl From 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 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, -} - -#[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, -} - -impl From 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>, + 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>, + #[serde(flatten)] + meta: SerdeMbReleaseGroupMeta, } impl From 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 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], }; diff --git a/src/external/musicbrainz/api/mod.rs b/src/external/musicbrainz/api/mod.rs index 16173cd..2bb9f43 100644 --- a/src/external/musicbrainz/api/mod.rs +++ b/src/external/musicbrainz/api/mod.rs @@ -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 MusicBrainzClient { } } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MbArtistMeta { + pub id: Mbid, + pub name: ArtistId, + pub sort_name: ArtistId, + pub disambiguation: Option, +} + +#[derive(Clone, Deserialize)] +#[serde(rename_all(deserialize = "kebab-case"))] +struct SerdeMbArtistMeta { + id: SerdeMbid, + name: String, + sort_name: String, + disambiguation: Option, +} + +impl From 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>, +} + +#[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>, +} + +impl From 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 { diff --git a/src/external/musicbrainz/api/search/artist.rs b/src/external/musicbrainz/api/search/artist.rs index e671fe5..12d1bf1 100644 --- a/src/external/musicbrainz/api/search/artist.rs +++ b/src/external/musicbrainz/api/search/artist.rs @@ -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 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, + 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, + #[serde(flatten)] + meta: SerdeMbArtistMeta, } impl From 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 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(), } diff --git a/src/external/musicbrainz/api/search/release_group.rs b/src/external/musicbrainz/api/search/release_group.rs index ae9394e..d33d558 100644 --- a/src/external/musicbrainz/api/search/release_group.rs +++ b/src/external/musicbrainz/api/search/release_group.rs @@ -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 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>, + 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>, + #[serde(flatten)] + meta: SerdeMbReleaseGroupMeta, } impl From @@ -96,13 +86,7 @@ impl From 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 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(), } diff --git a/src/tui/lib/external/musicbrainz/api/mod.rs b/src/tui/lib/external/musicbrainz/api/mod.rs index a74a78e..4b54c22 100644 --- a/src/tui/lib/external/musicbrainz/api/mod.rs +++ b/src/tui/lib/external/musicbrainz/api/mod.rs @@ -93,47 +93,47 @@ impl IMusicBrainz for MusicBrainz { } fn from_lookup_artist_response(entity: LookupArtistResponse) -> Lookup { - let sort: Option = Some(entity.sort_name) - .filter(|s| s != &entity.name) + let sort: Option = 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 { 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 { - let sort: Option = Some(entity.sort_name) - .filter(|s| s != &entity.name) + let sort: Option = 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, }