use educe::Educe;
use qstring::QString;
use serde::Deserialize;
use std::fmt::Debug;
use strum::{EnumDiscriminants, IntoDiscriminant};
use url::Url;
use crate::command::CommandError;
use crate::data::impl_try_from_response_payload;
use crate::data::option::impl_has_options;
use crate::data::player::PlayerId;
use crate::data::queue::QueuedTrackInfo;
use crate::data::response::RawResponse;
pub trait MediaItemBase: Debug {
fn name(&self) -> &str;
fn album(&self) -> Option<&str> {
None
}
fn artist(&self) -> Option<&str> {
None
}
fn image_url(&self) -> Option<&Url>;
fn playable(&self) -> bool;
fn media_id(&self) -> Option<&str>;
fn into_media_item(self) -> MediaItem where Self: Sized;
fn as_media_item_ref(&self) -> MediaItemRef<'_>;
fn try_into_media_container(self) -> Result<MediaContainer, Self> where Self: Sized {
Err(self)
}
fn try_as_media_container_ref(&self) -> Option<MediaContainerRef<'_>> {
None
}
}
pub trait MediaContainerBase: MediaItemBase {
fn container_id(&self) -> &str;
fn into_media_container(self) -> MediaContainer where Self: Sized;
fn as_media_container_ref(&self) -> MediaContainerRef<'_>;
}
#[derive(thiserror::Error, Debug)]
#[error("invalid MediaItem type; expected: {expected:?}, actual: {actual:?}")]
pub struct TryFromMediaItemError {
pub expected: MediaItemType,
pub actual: MediaItemType,
}
macro_rules! impl_media_item_conversions {
($name:ident) => {
impl From<$name> for MediaItem {
#[inline]
fn from(value: $name) -> Self {
value.into_media_item()
}
}
impl TryFrom<MediaItem> for $name {
type Error = TryFromMediaItemError;
fn try_from(item: MediaItem) -> Result<Self, Self::Error> {
if let MediaItem::$name(inner) = item {
Ok(inner)
} else {
Err(TryFromMediaItemError {
expected: MediaItemType::$name,
actual: item.discriminant(),
})
}
}
}
};
}
macro_rules! impl_media_container_conversions {
($name:ident) => {
impl TryFrom<$name> for MediaContainer {
type Error = $name;
#[inline]
fn try_from(value: $name) -> Result<Self, Self::Error> {
value.try_into_media_container()
}
}
impl TryFrom<MediaContainer> for $name {
type Error = TryFromMediaItemError;
fn try_from(container: MediaContainer) -> Result<Self, Self::Error> {
if let MediaContainer::$name(inner) = container {
Ok(inner)
} else {
Err(TryFromMediaItemError {
expected: MediaItemType::$name,
actual: container.discriminant(),
})
}
}
}
};
}
#[derive(Deserialize, Educe, Clone)]
#[educe(Debug)]
pub struct HeosService {
pub name: String,
#[educe(Debug(method(super::maybe_url::fmt)))]
#[serde(deserialize_with = "super::maybe_url::deserialize")]
pub image_url: Option<Url>,
#[serde(rename = "sid")]
pub src_player_id: PlayerId,
}
impl MediaItemBase for HeosService {
#[inline]
fn name(&self) -> &str {
&self.name
}
#[inline]
fn image_url(&self) -> Option<&Url> {
self.image_url.as_ref()
}
#[inline]
fn playable(&self) -> bool {
true
}
#[inline]
fn media_id(&self) -> Option<&str> {
None
}
#[inline]
fn into_media_item(self) -> MediaItem where Self: Sized {
MediaItem::HeosService(self)
}
#[inline]
fn as_media_item_ref(&self) -> MediaItemRef<'_> {
MediaItemRef::HeosService(self)
}
}
impl_media_item_conversions!(HeosService);
#[derive(Deserialize, Educe, Clone)]
#[educe(Debug)]
pub struct HeosServer {
pub name: String,
#[educe(Debug(method(super::maybe_url::fmt)))]
#[serde(deserialize_with = "super::maybe_url::deserialize")]
pub image_url: Option<Url>,
#[serde(rename = "sid")]
pub src_player_id: PlayerId,
}
impl MediaItemBase for HeosServer {
#[inline]
fn name(&self) -> &str {
&self.name
}
#[inline]
fn image_url(&self) -> Option<&Url> {
self.image_url.as_ref()
}
#[inline]
fn playable(&self) -> bool {
true
}
#[inline]
fn media_id(&self) -> Option<&str> {
None
}
#[inline]
fn into_media_item(self) -> MediaItem where Self: Sized {
MediaItem::HeosServer(self)
}
#[inline]
fn as_media_item_ref(&self) -> MediaItemRef<'_> {
MediaItemRef::HeosServer(self)
}
}
impl_media_item_conversions!(HeosServer);
#[derive(Deserialize, Educe, Clone)]
#[educe(Debug)]
pub struct Artist {
pub name: String,
#[serde(rename = "cid")]
pub container_id: String,
#[serde(deserialize_with = "super::yes_no::deserialize")]
pub playable: bool,
#[educe(Debug(method(super::maybe_url::fmt)))]
#[serde(deserialize_with = "super::maybe_url::deserialize")]
pub image_url: Option<Url>,
#[serde(rename = "mid")]
pub media_id: Option<String>,
}
impl MediaItemBase for Artist {
#[inline]
fn name(&self) -> &str {
&self.name
}
#[inline]
fn image_url(&self) -> Option<&Url> {
self.image_url.as_ref()
}
#[inline]
fn playable(&self) -> bool {
self.playable
}
#[inline]
fn media_id(&self) -> Option<&str> {
self.media_id.as_deref()
}
#[inline]
fn into_media_item(self) -> MediaItem where Self: Sized {
MediaItem::Artist(self)
}
#[inline]
fn as_media_item_ref(&self) -> MediaItemRef<'_> {
MediaItemRef::Artist(self)
}
#[inline]
fn try_into_media_container(self) -> Result<MediaContainer, Self> where Self: Sized {
Ok(MediaContainer::Artist(self))
}
#[inline]
fn try_as_media_container_ref(&self) -> Option<MediaContainerRef<'_>> {
Some(MediaContainerRef::Artist(self))
}
}
impl MediaContainerBase for Artist {
#[inline]
fn container_id(&self) -> &str {
&self.container_id
}
#[inline]
fn into_media_container(self) -> MediaContainer where Self: Sized {
MediaContainer::Artist(self)
}
#[inline]
fn as_media_container_ref(&self) -> MediaContainerRef<'_> {
MediaContainerRef::Artist(self)
}
}
impl_media_item_conversions!(Artist);
impl_media_container_conversions!(Artist);
#[derive(Deserialize, Educe, Clone)]
#[educe(Debug)]
pub struct Album {
pub name: String,
pub artist: String,
#[serde(rename = "cid")]
pub container_id: String,
#[serde(deserialize_with = "super::yes_no::deserialize")]
pub playable: bool,
#[educe(Debug(method(super::maybe_url::fmt)))]
#[serde(deserialize_with = "super::maybe_url::deserialize")]
pub image_url: Option<Url>,
#[serde(rename = "mid")]
pub media_id: Option<String>,
}
impl MediaItemBase for Album {
#[inline]
fn name(&self) -> &str {
&self.name
}
#[inline]
fn artist(&self) -> Option<&str> {
Some(&self.artist)
}
#[inline]
fn image_url(&self) -> Option<&Url> {
self.image_url.as_ref()
}
#[inline]
fn playable(&self) -> bool {
self.playable
}
#[inline]
fn media_id(&self) -> Option<&str> {
self.media_id.as_deref()
}
#[inline]
fn into_media_item(self) -> MediaItem where Self: Sized {
MediaItem::Album(self)
}
#[inline]
fn as_media_item_ref(&self) -> MediaItemRef<'_> {
MediaItemRef::Album(self)
}
#[inline]
fn try_into_media_container(self) -> Result<MediaContainer, Self> where Self: Sized {
Ok(MediaContainer::Album(self))
}
#[inline]
fn try_as_media_container_ref(&self) -> Option<MediaContainerRef<'_>> {
Some(MediaContainerRef::Album(self))
}
}
impl MediaContainerBase for Album {
#[inline]
fn container_id(&self) -> &str {
&self.container_id
}
#[inline]
fn into_media_container(self) -> MediaContainer where Self: Sized {
MediaContainer::Album(self)
}
#[inline]
fn as_media_container_ref(&self) -> MediaContainerRef<'_> {
MediaContainerRef::Album(self)
}
}
impl_media_item_conversions!(Album);
impl_media_container_conversions!(Album);
#[derive(Deserialize, Educe, Clone)]
#[educe(Debug)]
pub struct Song {
pub name: String,
pub artist: String,
pub album: String,
#[serde(rename = "playable", deserialize_with = "super::yes_no::deserialize_assert_yes")]
_playable: bool,
#[educe(Debug(method(super::maybe_url::fmt)))]
#[serde(deserialize_with = "super::maybe_url::deserialize")]
pub image_url: Option<Url>,
#[serde(rename = "mid")]
pub media_id: String,
}
impl MediaItemBase for Song {
#[inline]
fn name(&self) -> &str {
&self.name
}
#[inline]
fn artist(&self) -> Option<&str> {
Some(&self.artist)
}
#[inline]
fn album(&self) -> Option<&str> {
Some(&self.album)
}
#[inline]
fn image_url(&self) -> Option<&Url> {
self.image_url.as_ref()
}
#[inline]
fn playable(&self) -> bool {
true
}
#[inline]
fn media_id(&self) -> Option<&str> {
Some(&self.media_id)
}
#[inline]
fn into_media_item(self) -> MediaItem where Self: Sized {
MediaItem::Song(self)
}
#[inline]
fn as_media_item_ref(&self) -> MediaItemRef<'_> {
MediaItemRef::Song(self)
}
}
impl From<QueuedTrackInfo> for Song {
#[inline]
fn from(value: QueuedTrackInfo) -> Self {
Song {
name: value.song,
artist: value.artist,
album: value.album,
_playable: true,
image_url: value.image_url,
media_id: value.media_id,
}
}
}
impl_media_item_conversions!(Song);
#[derive(Deserialize, Educe, Clone)]
#[educe(Debug)]
pub struct Container {
pub name: String,
#[serde(rename = "cid")]
pub container_id: String,
#[serde(deserialize_with = "super::yes_no::deserialize")]
pub playable: bool,
#[educe(Debug(method(super::maybe_url::fmt)))]
#[serde(deserialize_with = "super::maybe_url::deserialize")]
pub image_url: Option<Url>,
#[serde(rename = "mid")]
pub media_id: Option<String>,
}
impl MediaItemBase for Container {
#[inline]
fn name(&self) -> &str {
&self.name
}
#[inline]
fn image_url(&self) -> Option<&Url> {
self.image_url.as_ref()
}
#[inline]
fn playable(&self) -> bool {
self.playable
}
#[inline]
fn media_id(&self) -> Option<&str> {
self.media_id.as_deref()
}
#[inline]
fn into_media_item(self) -> MediaItem where Self: Sized {
MediaItem::Container(self)
}
#[inline]
fn as_media_item_ref(&self) -> MediaItemRef<'_> {
MediaItemRef::Container(self)
}
#[inline]
fn try_into_media_container(self) -> Result<MediaContainer, Self> where Self: Sized {
Ok(MediaContainer::Container(self))
}
#[inline]
fn try_as_media_container_ref(&self) -> Option<MediaContainerRef<'_>> {
Some(MediaContainerRef::Container(self))
}
}
impl MediaContainerBase for Container {
#[inline]
fn container_id(&self) -> &str {
&self.container_id
}
#[inline]
fn into_media_container(self) -> MediaContainer where Self: Sized {
MediaContainer::Container(self)
}
#[inline]
fn as_media_container_ref(&self) -> MediaContainerRef<'_> {
MediaContainerRef::Container(self)
}
}
impl_media_item_conversions!(Container);
impl_media_container_conversions!(Container);
#[derive(Deserialize, Educe, Clone)]
#[educe(Debug)]
pub struct Station {
pub name: String,
#[serde(rename = "playable", deserialize_with = "super::yes_no::deserialize_assert_yes")]
_playable: bool,
#[educe(Debug(method(super::maybe_url::fmt)))]
#[serde(deserialize_with = "super::maybe_url::deserialize")]
pub image_url: Option<Url>,
#[serde(rename = "mid")]
pub media_id: String,
}
impl MediaItemBase for Station {
#[inline]
fn name(&self) -> &str {
&self.name
}
#[inline]
fn image_url(&self) -> Option<&Url> {
self.image_url.as_ref()
}
#[inline]
fn playable(&self) -> bool {
true
}
#[inline]
fn media_id(&self) -> Option<&str> {
Some(&self.media_id)
}
#[inline]
fn into_media_item(self) -> MediaItem where Self: Sized {
MediaItem::Station(self)
}
#[inline]
fn as_media_item_ref(&self) -> MediaItemRef<'_> {
MediaItemRef::Station(self)
}
}
impl_media_item_conversions!(Station);
#[derive(Deserialize, Educe, Clone)]
#[educe(Debug)]
pub struct Genre {
pub name: String,
#[serde(rename = "playable", deserialize_with = "super::yes_no::deserialize_assert_yes")]
_playable: bool,
#[educe(Debug(method(super::maybe_url::fmt)))]
#[serde(deserialize_with = "super::maybe_url::deserialize")]
pub image_url: Option<Url>,
#[serde(rename = "mid")]
pub media_id: String,
}
impl MediaItemBase for Genre {
#[inline]
fn name(&self) -> &str {
&self.name
}
#[inline]
fn image_url(&self) -> Option<&Url> {
self.image_url.as_ref()
}
#[inline]
fn playable(&self) -> bool {
true
}
#[inline]
fn media_id(&self) -> Option<&str> {
Some(&self.media_id)
}
#[inline]
fn into_media_item(self) -> MediaItem where Self: Sized {
MediaItem::Genre(self)
}
#[inline]
fn as_media_item_ref(&self) -> MediaItemRef<'_> {
MediaItemRef::Genre(self)
}
}
impl_media_item_conversions!(Genre);
macro_rules! delegate_media_item {
(
$(#[$attr:meta])*
$v:vis fn $fn_name:ident(&self) -> $ret_type:ty;
) => {
$(#[$attr])*
$v fn $fn_name(&self) -> $ret_type {
match self {
Self::HeosService(heos_service) => heos_service.$fn_name(),
Self::HeosServer(heos_server) => heos_server.$fn_name(),
Self::Artist(artist) => artist.$fn_name(),
Self::Album(album) => album.$fn_name(),
Self::Song(song) => song.$fn_name(),
Self::Container(container) => container.$fn_name(),
Self::Station(station) => station.$fn_name(),
Self::Genre(genre) => genre.$fn_name(),
}
}
};
(
$(#[$attr:meta])*
$v:vis fn $fn_name:ident(self) -> $ret_type:ty;
) => {
$(#[$attr])*
$v fn $fn_name(self) -> $ret_type where Self: Sized {
match self {
Self::HeosService(heos_service) => heos_service.$fn_name(),
Self::HeosServer(heos_server) => heos_server.$fn_name(),
Self::Artist(artist) => artist.$fn_name(),
Self::Album(album) => album.$fn_name(),
Self::Song(song) => song.$fn_name(),
Self::Container(container) => container.$fn_name(),
Self::Station(station) => station.$fn_name(),
Self::Genre(genre) => genre.$fn_name(),
}
}
};
}
#[derive(Deserialize, EnumDiscriminants, Debug, Clone)]
#[serde(tag = "type", rename_all = "snake_case")]
#[strum_discriminants(name(MediaItemType))]
pub enum MediaItem {
HeosService(HeosService),
HeosServer(HeosServer),
Artist(Artist),
Album(Album),
Song(Song),
Container(Container),
Station(Station),
Genre(Genre),
}
impl_has_options!(Vec<MediaItem>, "browse");
impl_try_from_response_payload!(Vec<MediaItem>);
impl From<QueuedTrackInfo> for MediaItem {
#[inline]
fn from(value: QueuedTrackInfo) -> Self {
Self::Song(Song::from(value))
}
}
impl MediaItemBase for MediaItem {
delegate_media_item! {
#[inline]
fn name(&self) -> &str;
}
delegate_media_item! {
#[inline]
fn image_url(&self) -> Option<&Url>;
}
delegate_media_item! {
#[inline]
fn playable(&self) -> bool;
}
delegate_media_item! {
#[inline]
fn media_id(&self) -> Option<&str>;
}
#[inline]
fn into_media_item(self) -> MediaItem where Self: Sized {
self
}
#[inline]
fn as_media_item_ref(&self) -> MediaItemRef<'_> {
match self {
Self::HeosService(heos_service) => MediaItemRef::HeosService(heos_service),
Self::HeosServer(heos_server) => MediaItemRef::HeosServer(heos_server),
Self::Artist(artist) => MediaItemRef::Artist(artist),
Self::Album(album) => MediaItemRef::Album(album),
Self::Song(song) => MediaItemRef::Song(song),
Self::Container(container) => MediaItemRef::Container(container),
Self::Station(station) => MediaItemRef::Station(station),
Self::Genre(genre) => MediaItemRef::Genre(genre),
}
}
#[inline]
fn try_into_media_container(self) -> Result<MediaContainer, Self> where Self: Sized {
match self {
Self::Artist(artist) => Ok(MediaContainer::Artist(artist)),
Self::Album(album) => Ok(MediaContainer::Album(album)),
Self::Container(container) => Ok(MediaContainer::Container(container)),
other => Err(other),
}
}
#[inline]
fn try_as_media_container_ref(&self) -> Option<MediaContainerRef<'_>> {
match self {
Self::Artist(artist) => Some(MediaContainerRef::Artist(artist)),
Self::Album(album) => Some(MediaContainerRef::Album(album)),
Self::Container(container) => Some(MediaContainerRef::Container(container)),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub enum MediaItemRef<'a> {
HeosService(&'a HeosService),
HeosServer(&'a HeosServer),
Artist(&'a Artist),
Album(&'a Album),
Song(&'a Song),
Container(&'a Container),
Station(&'a Station),
Genre(&'a Genre),
}
impl<'a> IntoDiscriminant for MediaItemRef<'a> {
type Discriminant = MediaItemType;
fn discriminant(&self) -> Self::Discriminant {
match self {
Self::HeosService(_) => MediaItemType::HeosService,
Self::HeosServer(_) => MediaItemType::HeosServer,
Self::Artist(_) => MediaItemType::Artist,
Self::Album(_) => MediaItemType::Album,
Self::Song(_) => MediaItemType::Song,
Self::Container(_) => MediaItemType::Container,
Self::Station(_) => MediaItemType::Station,
Self::Genre(_) => MediaItemType::Genre,
}
}
}
impl<'a> MediaItemBase for MediaItemRef<'a> {
delegate_media_item! {
#[inline]
fn name(&self) -> &str;
}
delegate_media_item! {
#[inline]
fn image_url(&self) -> Option<&Url>;
}
delegate_media_item! {
#[inline]
fn playable(&self) -> bool;
}
delegate_media_item! {
#[inline]
fn media_id(&self) -> Option<&str>;
}
#[inline]
fn into_media_item(self) -> MediaItem where Self: Sized {
match self {
Self::HeosService(heos_service) => MediaItem::HeosService(heos_service.clone()),
Self::HeosServer(heos_server) => MediaItem::HeosServer(heos_server.clone()),
Self::Artist(artist) => MediaItem::Artist(artist.clone()),
Self::Album(album) => MediaItem::Album(album.clone()),
Self::Song(song) => MediaItem::Song(song.clone()),
Self::Container(container) => MediaItem::Container(container.clone()),
Self::Station(station) => MediaItem::Station(station.clone()),
Self::Genre(genre) => MediaItem::Genre(genre.clone()),
}
}
#[inline]
fn as_media_item_ref(&self) -> MediaItemRef<'_> {
self.clone()
}
#[inline]
fn try_into_media_container(self) -> Result<MediaContainer, Self> where Self: Sized {
match self {
Self::Artist(artist) => Ok(MediaContainer::Artist(artist.clone())),
Self::Album(album) => Ok(MediaContainer::Album(album.clone())),
Self::Container(container) => Ok(MediaContainer::Container(container.clone())),
other => Err(other),
}
}
#[inline]
fn try_as_media_container_ref(&self) -> Option<MediaContainerRef<'_>> {
match self {
Self::Artist(artist) => Some(MediaContainerRef::Artist(artist)),
Self::Album(album) => Some(MediaContainerRef::Album(album)),
Self::Container(container) => Some(MediaContainerRef::Container(container)),
_ => None,
}
}
}
impl<'a> From<MediaItemRef<'a>> for MediaItem {
#[inline]
fn from(item_ref: MediaItemRef<'a>) -> Self {
item_ref.into_media_item()
}
}
impl<'a> TryFrom<MediaItemRef<'a>> for MediaContainer {
type Error = MediaItemRef<'a>;
#[inline]
fn try_from(value: MediaItemRef<'a>) -> Result<Self, Self::Error> {
value.try_into_media_container()
}
}
macro_rules! delegate_media_container {
(
$(#[$attr:meta])*
$v:vis fn $fn_name:ident(&self) -> $ret_type:ty;
) => {
$(#[$attr])*
$v fn $fn_name(&self) -> $ret_type {
match self {
Self::Artist(artist) => artist.$fn_name(),
Self::Album(album) => album.$fn_name(),
Self::Container(container) => container.$fn_name(),
}
}
};
(
$(#[$attr:meta])*
$v:vis fn $fn_name:ident(self) -> $ret_type:ty;
) => {
$(#[$attr])*
$v fn $fn_name(self) -> $ret_type where Self: Sized {
match self {
Self::Artist(artist) => artist.$fn_name(),
Self::Album(album) => album.$fn_name(),
Self::Container(container) => container.$fn_name(),
}
}
};
}
#[derive(Debug, Clone)]
pub enum MediaContainer {
Artist(Artist),
Album(Album),
Container(Container),
}
impl IntoDiscriminant for MediaContainer {
type Discriminant = MediaItemType;
fn discriminant(&self) -> Self::Discriminant {
match self {
Self::Artist(_) => Self::Discriminant::Artist,
Self::Album(_) => Self::Discriminant::Album,
Self::Container(_) => Self::Discriminant::Container,
}
}
}
impl MediaItemBase for MediaContainer {
delegate_media_container! {
#[inline]
fn name(&self) -> &str;
}
delegate_media_container! {
#[inline]
fn image_url(&self) -> Option<&Url>;
}
delegate_media_container! {
#[inline]
fn playable(&self) -> bool;
}
delegate_media_container! {
#[inline]
fn media_id(&self) -> Option<&str>;
}
#[inline]
fn into_media_item(self) -> MediaItem where Self: Sized {
match self {
Self::Artist(artist) => MediaItem::Artist(artist),
Self::Album(album) => MediaItem::Album(album),
Self::Container(container) => MediaItem::Container(container),
}
}
#[inline]
fn as_media_item_ref(&self) -> MediaItemRef<'_> {
match self {
Self::Artist(artist) => MediaItemRef::Artist(artist),
Self::Album(album) => MediaItemRef::Album(album),
Self::Container(container) => MediaItemRef::Container(container),
}
}
#[inline]
fn try_into_media_container(self) -> Result<MediaContainer, Self> where Self: Sized {
Ok(self)
}
#[inline]
fn try_as_media_container_ref(&self) -> Option<MediaContainerRef<'_>> {
Some(match self {
Self::Artist(artist) => MediaContainerRef::Artist(artist),
Self::Album(album) => MediaContainerRef::Album(album),
Self::Container(container) => MediaContainerRef::Container(container),
})
}
}
impl MediaContainerBase for MediaContainer {
delegate_media_container! {
#[inline]
fn container_id(&self) -> &str;
}
#[inline]
fn into_media_container(self) -> MediaContainer where Self: Sized {
self
}
#[inline]
fn as_media_container_ref(&self) -> MediaContainerRef<'_> {
match self {
Self::Artist(artist) => MediaContainerRef::Artist(artist),
Self::Album(album) => MediaContainerRef::Album(album),
Self::Container(container) => MediaContainerRef::Container(container),
}
}
}
impl From<MediaContainer> for MediaItem {
fn from(container: MediaContainer) -> Self {
container.into_media_item()
}
}
impl TryFrom<MediaItem> for MediaContainer {
type Error = MediaItem;
fn try_from(item: MediaItem) -> Result<Self, Self::Error> {
item.try_into_media_container()
}
}
#[derive(Debug, Clone, Copy)]
pub enum MediaContainerRef<'a> {
Artist(&'a Artist),
Album(&'a Album),
Container(&'a Container),
}
impl<'a> IntoDiscriminant for MediaContainerRef<'a> {
type Discriminant = MediaItemType;
fn discriminant(&self) -> Self::Discriminant {
match self {
Self::Artist(_) => Self::Discriminant::Artist,
Self::Album(_) => Self::Discriminant::Album,
Self::Container(_) => Self::Discriminant::Container,
}
}
}
impl<'a> MediaItemBase for MediaContainerRef<'a> {
delegate_media_container! {
#[inline]
fn name(&self) -> &str;
}
delegate_media_container! {
#[inline]
fn image_url(&self) -> Option<&Url>;
}
delegate_media_container! {
#[inline]
fn playable(&self) -> bool;
}
delegate_media_container! {
#[inline]
fn media_id(&self) -> Option<&str>;
}
#[inline]
fn into_media_item(self) -> MediaItem where Self: Sized {
match self {
Self::Artist(artist) => MediaItem::Artist(artist.clone()),
Self::Album(album) => MediaItem::Album(album.clone()),
Self::Container(container) => MediaItem::Container(container.clone()),
}
}
#[inline]
fn as_media_item_ref(&self) -> MediaItemRef<'_> {
match self {
Self::Artist(artist) => MediaItemRef::Artist(artist),
Self::Album(album) => MediaItemRef::Album(album),
Self::Container(container) => MediaItemRef::Container(container),
}
}
#[inline]
fn try_into_media_container(self) -> Result<MediaContainer, Self> where Self: Sized {
Ok(match self {
Self::Artist(artist) => MediaContainer::Artist(artist.clone()),
Self::Album(album) => MediaContainer::Album(album.clone()),
Self::Container(container) => MediaContainer::Container(container.clone()),
})
}
#[inline]
fn try_as_media_container_ref(&self) -> Option<MediaContainerRef<'_>> {
Some(self.clone())
}
}
impl<'a> MediaContainerBase for MediaContainerRef<'a> {
delegate_media_container! {
#[inline]
fn container_id(&self) -> &str;
}
#[inline]
fn into_media_container(self) -> MediaContainer where Self: Sized {
match self {
Self::Artist(artist) => MediaContainer::Artist(artist.clone()),
Self::Album(album) => MediaContainer::Album(album.clone()),
Self::Container(container) => MediaContainer::Container(container.clone()),
}
}
#[inline]
fn as_media_container_ref(&self) -> MediaContainerRef<'_> {
self.clone()
}
}
impl<'a> From<MediaContainerRef<'a>> for MediaItem {
#[inline]
fn from(container_ref: MediaContainerRef) -> Self {
container_ref.into_media_item()
}
}
impl<'a> From<MediaContainerRef<'a>> for MediaContainer {
#[inline]
fn from(container_ref: MediaContainerRef<'a>) -> Self {
container_ref.into_media_container()
}
}
#[derive(Debug, Clone)]
pub struct MediaItemsResponse {
pub count: usize,
pub items: Vec<MediaItem>,
}
impl_has_options!(MediaItemsResponse, "browse");
impl TryFrom<RawResponse> for MediaItemsResponse {
type Error = CommandError;
fn try_from(response: RawResponse) -> Result<Self, Self::Error> {
let qs = QString::from(response.heos.message.as_str());
let count: usize = qs.get("count")
.ok_or(CommandError::response_missing_field("message.count"))?
.parse()
.map_err(|err| CommandError::MalformedResponse(format!(
"could not parse 'count': {err:?}"
)))?;
let items = Vec::<MediaItem>::try_from(response)?;
Ok(Self {
count,
items,
})
}
}
#[derive(Deserialize, Educe)]
#[educe(Debug)]
pub struct AlbumImageMetadata {
#[educe(Debug(method(std::fmt::Display::fmt)))]
pub image_url: Url,
pub width: usize,
}
#[derive(Deserialize, Debug)]
pub struct AlbumMetadata {
pub album_id: String,
pub images: Vec<AlbumImageMetadata>,
}
impl_try_from_response_payload!(Vec<AlbumMetadata>);