use crate::error::Error;
use crate::request::builder::MusicRequestBuilder;
use crate::request::context::ContextContainer;
use crate::request::try_resource_response;
use crate::resource::relationship::Relationship;
use crate::resource::{ErrorResponse, Resource, ResourceHeader, ResourceInfo, ResourceType};
use crate::ApiClient;
use am_api_proc_macro::{Context, ResourceProperty};
use serde::{Deserialize, Serialize};
use std::fmt::Formatter;
use std::sync::Arc;
#[derive(Context, Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
pub struct Rating {
#[context(skip)]
#[serde(flatten)]
pub header: ResourceHeader,
#[context(skip)]
#[serde(default)]
pub attributes: Option<RatingAttributes>,
#[serde(default)]
pub relationships: RatingRelationships,
}
impl Rating {
pub fn get<'a>() -> RatingGetRequestBuilder<'a> {
RatingGetRequestBuilder::default()
}
pub fn add_rating<'a>() -> RatingPostRequestBuilder<'a> {
RatingPostRequestBuilder::default()
}
pub fn remove_rating<'a>() -> RatingPostRequestBuilder<'a> {
RatingPostRequestBuilder::default()
}
}
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase", default)]
pub struct RatingAttributes {
pub rating: Option<i32>,
}
#[derive(
Context, ResourceProperty, Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, Hash,
)]
#[serde(rename_all = "camelCase", default)]
#[resource_property(RatingRelationshipType, object = "ratings", relationship)]
pub struct RatingRelationships {
pub content: Relationship<Resource>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RatingType {
Album,
MusicVideo,
Playlist,
Song,
Station,
LibraryAlbum,
LibraryMusicVideo,
LibraryPlaylist,
LibrarySong,
}
impl std::fmt::Display for RatingType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let endpoint = match self {
RatingType::Album => "albums",
RatingType::MusicVideo => "music-videos",
RatingType::Playlist => "playlists",
RatingType::Song => "songs",
RatingType::Station => "stations",
RatingType::LibraryAlbum => "library-albums",
RatingType::LibraryMusicVideo => "library-music-videos",
RatingType::LibraryPlaylist => "library-playlists",
RatingType::LibrarySong => "library-songs",
};
write!(f, "{}", endpoint)
}
}
pub struct RatingGetRequestBuilderMarker;
pub type RatingGetRequestBuilder<'a> = MusicRequestBuilder<'a, RatingGetRequestBuilderMarker>;
impl<'a> RatingGetRequestBuilder<'a> {
pub async fn one(
mut self,
client: &ApiClient,
rating_type: RatingType,
id: &str,
) -> Result<Option<Rating>, Error> {
let request_context = Arc::new(self.get_request_context_drain(client));
let endpoint = rating_type.to_string();
let response = client
.get(&format!("/v1/me/ratings/{endpoint}/{id}"))
.query(&request_context.query)
.send()
.await?;
let mut response = try_resource_response(response).await?;
response.data.set_context(request_context);
Ok(response.data.into_iter().next())
}
pub async fn many(
mut self,
client: &ApiClient,
rating_type: RatingType,
ids: &[&str],
) -> Result<Vec<Rating>, Error> {
let mut request_context = self.get_request_context_drain(client);
request_context
.query
.push((String::from("ids"), ids.to_vec().join(",")));
let request_context = Arc::new(request_context);
let endpoint = rating_type.to_string();
let response = client
.get(&format!("/v1/me/ratings/{endpoint}"))
.query(&request_context.query)
.send()
.await?;
let mut response = try_resource_response(response).await?;
response.data.set_context(request_context);
Ok(response.data)
}
}
pub struct RatingPostRequestBuilderMarker;
pub type RatingPostRequestBuilder<'a> = MusicRequestBuilder<'a, RatingPostRequestBuilderMarker>;
impl<'a> RatingPostRequestBuilder<'a> {
pub async fn add_rating(
mut self,
client: &ApiClient,
resource: &Resource,
rating: i32,
) -> Result<Option<Rating>, Error> {
Self::check_supported(resource)?;
let request_context = Arc::new(self.get_request_context_drain(client));
let endpoint = resource.get_type();
let request = RatingRequest {
attributes: RatingRequestAttributes { value: rating },
ty: String::from("ratings"),
};
let response = client
.put(&format!(
"/v1/me/ratings/{endpoint}/{id}",
id = resource.get_header().id
))
.query(&request_context.query)
.json(&request)
.send()
.await?;
let mut response = try_resource_response(response).await?;
response.data.set_context(request_context);
Ok(response.data.into_iter().next())
}
pub async fn remove_rating(
mut self,
client: &ApiClient,
resource: &Resource,
) -> Result<(), Error> {
Self::check_supported(resource)?;
let request_context = Arc::new(self.get_request_context_drain(client));
let endpoint = resource.get_type();
let response = client
.delete(&format!(
"/v1/me/ratings/{endpoint}/{id}",
id = resource.get_header().id
))
.query(&request_context.query)
.send()
.await?;
if !response.status().is_success() {
let error_response: ErrorResponse = response.json().await?;
return Err(Error::MusicError(error_response));
}
Ok(())
}
fn check_supported(resource: &Resource) -> Result<(), Error> {
let supported = matches!(
resource,
Resource::Album { .. }
| Resource::MusicVideo { .. }
| Resource::Playlist { .. }
| Resource::Song { .. }
| Resource::Station { .. }
| Resource::LibraryAlbum { .. }
| Resource::LibraryMusicVideo { .. }
| Resource::LibraryPlaylist { .. }
| Resource::LibrarySong { .. }
);
match supported {
true => Ok(()),
false => Err(Error::InvalidResourceType),
}
}
}
#[derive(Serialize, Deserialize)]
struct RatingRequest {
attributes: RatingRequestAttributes,
#[serde(rename = "type")]
ty: String,
}
#[derive(Serialize, Deserialize)]
struct RatingRequestAttributes {
value: i32,
}