//! Data model for the Spotify API use std::num::NonZeroU32; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DefaultOnError, DisplayFromStr, VecSkipError}; use time::OffsetDateTime; use spotifyio_model::{ AlbumId, ArtistId, ConcertId, PlaylistId, PrereleaseId, SongwriterId, TrackId, UserId, UserlikeId, }; #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct LyricsWrap { pub lyrics: Lyrics, } #[derive(Debug, Clone, Serialize, Deserialize)] #[non_exhaustive] pub struct GqlWrap { pub data: T, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "__typename")] #[allow(clippy::large_enum_variant)] pub enum ArtistOption { Artist(ArtistItem), #[serde(alias = "GenericError")] NotFound, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "__typename")] #[allow(clippy::large_enum_variant)] pub enum AlbumOption { Album(AlbumItem), #[serde(alias = "GenericError")] NotFound, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "__typename")] #[allow(clippy::large_enum_variant)] pub enum TrackOption { Track(TrackItem), #[serde(alias = "GenericError")] NotFound, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "__typename")] #[allow(clippy::large_enum_variant)] pub enum PlaylistOption { Playlist(GqlPlaylistItem), #[serde(alias = "GenericError")] NotFound, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "__typename")] #[allow(clippy::large_enum_variant)] pub enum ConcertOption { ConcertV2(ConcertGql), #[serde(alias = "GenericError")] NotFound, } #[derive(Debug, Clone, Serialize, Deserialize)] #[non_exhaustive] pub struct GqlPagination { pub items: Vec, pub total_count: Option, } impl Default for GqlPagination { fn default() -> Self { Self { items: Vec::new(), total_count: None, } } } #[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct Lyrics { #[serde_as(as = "DefaultOnError")] pub sync_type: Option, pub lines: Vec, pub provider: Option, pub provider_lyrics_id: Option, pub provider_display_name: Option, pub language: Option, } #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[non_exhaustive] pub enum SyncType { LineSynced, } #[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct LyricsLine { #[serde_as(as = "Option")] pub start_time_ms: Option, pub words: String, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ArtistGqlWrap { pub artist_union: ArtistGql, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ArtistGql { pub uri: ArtistId<'static>, pub profile: ArtistProfile, pub related_content: Option, pub stats: Option, #[serde(default)] pub visuals: Visuals, pub pre_release: Option, #[serde(default)] pub goods: Goods, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ArtistProfile { pub name: String, #[serde(default)] pub verified: bool, #[serde(default)] pub biography: Biography, #[serde(default)] pub external_links: GqlPagination, #[serde(default)] pub playlists_v2: GqlPagination>, } #[derive(Default, Debug, Clone, Serialize, Deserialize)] #[non_exhaustive] pub struct Biography { pub text: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[non_exhaustive] pub struct ExternalLink { pub name: String, pub url: String, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ArtistStats { pub followers: u32, pub monthly_listeners: u32, pub world_rank: u32, pub top_cities: GqlPagination, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct City { pub city: String, pub country: String, pub number_of_listeners: u32, pub region: String, } #[derive(Default, Debug, Clone, Serialize, Deserialize)] #[serde(default, rename_all = "camelCase")] #[non_exhaustive] pub struct Visuals { pub avatar_image: Option, pub gallery: GqlPagination, pub header_image: Option, } #[derive(Default, Debug, Clone, Serialize, Deserialize)] #[serde(default, rename_all = "camelCase")] #[non_exhaustive] pub struct Goods { pub events: Events, pub merch: GqlPagination, } #[derive(Default, Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct Events { pub concerts: GqlPagination, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct Concert { pub uri: ConcertId<'static>, pub title: String, pub date: DateWrap, #[serde(default)] pub festival: bool, pub partner_links: Option>, pub venue: Venue, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct PartnerLink { pub partner_name: String, pub url: String, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct Venue { pub name: String, pub location: Option, pub coordinates: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct Location { pub name: String, pub city: Option, pub coordinates: Option, // ISO-3166 country code pub country: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct Coordinates { pub latitude: f64, pub longitude: f64, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct MerchItem { pub uri: String, pub url: String, pub name: String, pub price: String, pub description: String, pub image: Image, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct Image { pub sources: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] #[non_exhaustive] pub struct ImageSource { pub url: String, pub height: Option, pub width: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[non_exhaustive] pub struct Name { pub name: String, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct GqlPlaylistItem { pub uri: PlaylistId<'static>, pub name: String, pub images: GqlPagination, pub owner_v2: GqlWrap, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct UserItem { pub uri: Option>, #[serde(alias = "displayName")] pub name: Option, pub avatar: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ArtistItem { pub uri: ArtistId<'static>, pub profile: Name, pub visuals: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct RelatedContent { #[serde(default)] pub discovered_on_v2: GqlPagination>, #[serde(default)] pub featuring_v2: GqlPagination>, #[serde(default)] pub related_artists: GqlPagination, } #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct PrereleaseLookup { pub lookup: Vec>, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct PrereleaseItem { /// URI of the prerelease pub uri: PrereleaseId<'static>, pub pre_release_content: PrereleaseContent, pub release_date: DateWrap, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct PrereleaseContent { /// URI of the to-be-released album pub uri: Option>, pub name: String, pub cover_art: Option, pub artists: Option>>, pub tracks: Option>, pub copyright: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct PrereleaseTrackItem { pub uri: TrackId<'static>, pub name: String, pub duration: Option, pub artists: GqlPagination>, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct DateWrap { #[serde(with = "time::serde::iso8601")] pub iso_string: OffsetDateTime, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct DateYear { pub year: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct DurationWrap { pub total_milliseconds: u32, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct SearchResultWrap { pub search_v2: GqlSearchResult, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[non_exhaustive] pub enum MatchedField { Lyrics, } #[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] #[non_exhaustive] pub struct GqlSearchTrackWrap { pub item: GqlWrap, #[serde(default)] #[serde_as(as = "VecSkipError<_>")] pub matched_fields: Vec, } #[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ChipOrder { #[serde_as(as = "VecSkipError<_>")] pub items: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Chip { type_name: SearchItemType, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct GqlSearchResult { #[serde(default)] pub artists: GqlPagination>, #[serde(default)] pub albums_v2: GqlPagination, #[serde(default)] pub tracks_v2: GqlPagination, #[serde(default)] pub playlists: GqlPagination>, #[serde(default)] pub users: GqlPagination>, pub chip_order: ChipOrder, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "__typename")] pub enum AlbumItemWrap { AlbumResponseWrapper { data: AlbumItem, }, PreReleaseResponseWrapper { data: PrereleaseItem, }, #[serde(alias = "GenericError")] NotFound, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct AlbumItem { pub uri: AlbumId<'static>, pub name: String, pub date: Option, pub cover_art: Option, pub artists: Option>, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct TrackItem { pub uri: TrackId<'static>, pub name: String, pub duration: DurationWrap, pub artists: GqlPagination, pub album_of_track: AlbumItem, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ConcertGqlWrap { pub concert: ConcertOption, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ConcertGql { pub uri: ConcertId<'static>, pub title: String, #[serde(default)] pub artists: GqlPagination>, #[serde(default, with = "time::serde::iso8601::option")] pub start_date_iso_string: Option, #[serde(default, with = "time::serde::iso8601::option")] pub doors_open_time_iso_string: Option, #[serde(default)] pub festival: bool, pub html_description: Option, pub location: Option, #[serde(default)] pub offers: GqlPagination, #[serde(default)] pub related_concerts: GqlPagination>, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ConcertOffer { pub access_code: String, pub currency: Option, pub dates: Option, #[serde(default)] pub first_party: bool, pub max_price: Option, pub min_price: Option, pub provider_image_url: Option, pub provider_name: Option, pub sale_type: Option, pub sold_out: Option, pub url: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ConcertOfferDates { #[serde(with = "time::serde::iso8601")] pub start_date_iso_string: OffsetDateTime, #[serde(with = "time::serde::iso8601")] pub end_date_iso_string: OffsetDateTime, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum SearchItemType { Artists, Albums, Tracks, Playlists, Users, } #[derive(Debug, Clone, Serialize, Deserialize)] #[non_exhaustive] pub struct UserProfile { pub uri: UserId<'static>, pub name: Option, pub image_url: Option, pub followers_count: Option, pub following_count: Option, #[serde(default)] pub public_playlists: Vec, pub total_public_playlists_count: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[non_exhaustive] pub struct UserPlaylists { #[serde(default)] pub public_playlists: Vec, pub total_public_playlists_count: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[non_exhaustive] pub struct PublicPlaylistItem { pub uri: PlaylistId<'static>, pub name: String, pub owner_name: String, pub owner_uri: UserId<'static>, /// UID-based image id /// /// - `spotify:image:ab67706c0000da8474fffd106bb7f5be3ba4b758` /// - `spotify:mosaic:ab67616d00001e021c04efd2804b16cf689de7f0:ab67616d00001e0269f63a842ea91ca7c522593a:ab67616d00001e0270dbc9f47669d120ad874ec1:ab67616d00001e027d384516b23347e92a587ed1` pub image_url: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct UserProfilesWrap { pub profiles: Vec, } /// May be an artist or an user #[derive(Debug, Clone, Serialize, Deserialize)] #[non_exhaustive] pub struct FollowerItem { pub uri: UserId<'static>, pub name: Option, pub followers_count: Option, pub image_url: Option, } /// May be an artist or an user #[derive(Debug, Clone, Serialize, Deserialize)] #[non_exhaustive] pub(crate) struct FollowerItemUserlike { pub uri: UserlikeId<'static>, pub name: Option, pub followers_count: Option, pub image_url: Option, } /// Seektable for AAC tracks #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Seektable { pub padding_samples: u32, pub encoder_delay_samples: u32, pub pssh: String, pub timescale: u32, pub init_range: Option<(u32, u32)>, pub index_range: Option<(u32, u32)>, pub segments: Vec<(u32, u32)>, pub offset: usize, } /// Information about a track's artists, writers and producers #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TrackCredits { pub track_uri: TrackId<'static>, pub track_title: String, #[serde(default)] pub role_credits: Vec, // pub extended_credits: (), #[serde(default)] pub source_names: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RoleCredits { pub role_title: String, pub artists: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CreditedArtist { pub name: String, pub uri: Option>, pub creator_uri: Option>, pub external_url: Option, /// Image URL pub image_uri: Option, pub subroles: Vec, pub weight: f32, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PlaylistWrap { pub playlist_v2: PlaylistOption, } impl ArtistOption { pub fn into_option(self) -> Option { match self { ArtistOption::Artist(artist_item) => Some(artist_item), ArtistOption::NotFound => None, } } } impl TrackOption { pub fn into_option(self) -> Option { match self { TrackOption::Track(track_item) => Some(track_item), TrackOption::NotFound => None, } } } impl PlaylistOption { pub fn into_option(self) -> Option { match self { PlaylistOption::Playlist(playlist_item) => Some(playlist_item), PlaylistOption::NotFound => None, } } } impl ConcertOption { pub fn into_option(self) -> Option { match self { ConcertOption::ConcertV2(concert) => Some(concert), ConcertOption::NotFound => None, } } } impl From> for Option { fn from(value: GqlWrap) -> Self { value.data.into_option() } } impl From> for Option { fn from(value: GqlWrap) -> Self { value.data.into_option() } } impl From> for Option { fn from(value: GqlWrap) -> Self { value.data.into_option() } } impl From> for Option { fn from(value: GqlWrap) -> Self { value.data.into_option() } } impl TryFrom for FollowerItem { type Error = (); fn try_from(value: FollowerItemUserlike) -> Result { if let UserlikeId::User(uri) = value.uri { Ok(Self { uri, name: value.name, followers_count: value.followers_count, image_url: value.image_url, }) } else { Err(()) } } }