use crate::error::Error;
use crate::request::builder::MusicRequestBuilder;
use crate::request::context::ContextContainer;
use crate::resource::catalog::activity::Activity;
use crate::resource::catalog::album::Album;
use crate::resource::catalog::artist::Artist;
use crate::resource::catalog::curator::{AppleCurator, Curator};
use crate::resource::catalog::music_video::MusicVideo;
use crate::resource::catalog::playlist::Playlist;
use crate::resource::catalog::record_label::RecordLabel;
use crate::resource::catalog::song::Song;
use crate::resource::catalog::station::Station;
use crate::resource::search::SearchRelationship;
use crate::resource::ErrorResponse;
use crate::ApiClient;
use am_api_proc_macro::Context;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use std::sync::Arc;
pub struct CatalogSearch;
impl CatalogSearch {
pub fn search<'a>() -> CatalogSearchRequestBuilder<'a> {
CatalogSearchRequestBuilder::default()
}
}
pub struct CatalogSearchRequestBuilderMarker;
pub type CatalogSearchRequestBuilder<'a> =
MusicRequestBuilder<'a, CatalogSearchRequestBuilderMarker>;
impl<'a> CatalogSearchRequestBuilder<'a> {
pub async fn search(
mut self,
client: &ApiClient,
types: &[CatalogSearchType],
term: &str,
) -> Result<CatalogSearchResults, Error> {
let mut request_context = self.get_request_context_drain(client);
request_context.query.push((
String::from("types"),
types
.iter()
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join(","),
));
request_context
.query
.push((String::from("term"), term.to_string()));
let response = client
.get(&format!(
"/v1/catalog/{storefront}/search",
storefront = request_context.storefront.alpha2
))
.query(&request_context.query)
.send()
.await?;
request_context.query.pop();
request_context.query.pop();
let request_context = Arc::new(request_context);
if !response.status().is_success() {
let error_response: ErrorResponse = response.json().await?;
return Err(Error::MusicError(error_response));
}
let mut response = response
.json::<CatalogSearchResponse<CatalogSearchResults>>()
.await?;
response.results.set_context(request_context);
Ok(response.results)
}
pub async fn search_hints(
mut self,
client: &ApiClient,
term: &str,
limit: usize,
) -> Result<Vec<String>, Error> {
let mut request_context = self.get_request_context_drain(client);
request_context
.query
.push((String::from("limit"), limit.to_string()));
request_context
.query
.push((String::from("term"), term.to_string()));
let response = client
.get(&format!(
"/v1/catalog/{storefront}/search/hints",
storefront = request_context.storefront.alpha2
))
.query(&request_context.query)
.send()
.await?;
request_context.query.pop();
if !response.status().is_success() {
let error_response: ErrorResponse = response.json().await?;
return Err(Error::MusicError(error_response));
}
let response: CatalogSearchResponse<CatalogSearchHints> = response.json().await?;
Ok(response.results.terms)
}
pub async fn suggestions(
mut self,
client: &ApiClient,
kinds: &[SuggestionKind],
types: &[CatalogSearchType],
term: &str,
limit: usize,
) -> Result<Vec<CatalogSearchSuggestion>, Error> {
let mut request_context = self.get_request_context_drain(client);
request_context
.query
.push((String::from("limit"), limit.to_string()));
request_context.query.push((
String::from("types"),
types
.iter()
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join(","),
));
request_context.query.push((
String::from("kinds"),
kinds
.iter()
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join(","),
));
request_context
.query
.push((String::from("term"), term.to_string()));
let response = client
.get(&format!(
"/v1/catalog/{storefront}/search/suggestions",
storefront = request_context.storefront.alpha2
))
.query(&request_context.query)
.send()
.await?;
if !response.status().is_success() {
let error_response: ErrorResponse = response.json().await?;
return Err(Error::MusicError(error_response));
}
let response = response
.json::<CatalogSearchResponse<CatalogSearchSuggestions>>()
.await?;
Ok(response.results.suggestions)
}
}
#[derive(Context, Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(default)]
pub struct CatalogSearchResults {
pub activities: SearchRelationship<Activity>,
pub albums: SearchRelationship<Album>,
#[serde(rename = "apple-curators")]
pub apple_curators: SearchRelationship<AppleCurator>,
pub curators: SearchRelationship<Curator>,
pub artists: SearchRelationship<Artist>,
#[serde(rename = "music-videos")]
pub music_videos: SearchRelationship<MusicVideo>,
pub playlists: SearchRelationship<Playlist>,
#[serde(rename = "record-labels")]
pub record_labels: SearchRelationship<RecordLabel>,
pub songs: SearchRelationship<Song>,
pub stations: SearchRelationship<Station>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum CatalogSearchType {
Activities,
Albums,
AppleCurators,
Curators,
Artists,
MusicVideos,
Playlists,
RecordLabels,
Songs,
Stations,
}
impl Display for CatalogSearchType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let s = match self {
CatalogSearchType::Activities => "activities",
CatalogSearchType::Albums => "albums",
CatalogSearchType::AppleCurators => "apple-curators",
CatalogSearchType::Curators => "curators",
CatalogSearchType::Artists => "artists",
CatalogSearchType::MusicVideos => "music-videos",
CatalogSearchType::Playlists => "playlists",
CatalogSearchType::RecordLabels => "record-labels",
CatalogSearchType::Songs => "songs",
CatalogSearchType::Stations => "stations",
};
write!(f, "{}", s)
}
}
#[derive(Serialize, Deserialize)]
struct CatalogSearchHints {
terms: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
pub enum SuggestionKind {
Terms,
TopResults,
}
impl Display for SuggestionKind {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let s = match self {
SuggestionKind::Terms => "terms",
SuggestionKind::TopResults => "topResults",
};
write!(f, "{}", s)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
pub struct CatalogSearchSuggestion {
pub kind: SuggestionKind,
pub search_term: String,
pub display_term: String,
}
#[derive(Serialize, Deserialize)]
struct CatalogSearchSuggestions {
suggestions: Vec<CatalogSearchSuggestion>,
}
#[derive(Serialize, Deserialize)]
struct CatalogSearchResponse<T> {
pub results: T,
}