use {
serde::{Deserialize, Serialize},
tokio::try_join,
};
use crate::{
api::{
content::{
albums::{Album, search_albums},
artists::{Artist, search_artists},
playlists::{Playlist, search_playlists},
tracks::{Track, search_tracks},
},
service::QobuzApiService,
},
errors::QobuzApiError,
};
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct AlbumSearchResponse {
pub albums: ItemSearchResult<Box<Album>>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct ArtistSearchResponse {
pub artists: ItemSearchResult<Box<Artist>>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct ItemSearchResult<T> {
pub items: Option<Vec<T>>,
pub total: Option<i32>,
pub limit: Option<i32>,
pub offset: Option<i32>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct PlaylistSearchResponse {
pub playlists: ItemSearchResult<Box<Playlist>>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct SearchResult {
pub albums: Option<ItemSearchResult<Box<Album>>>,
pub artists: Option<ItemSearchResult<Box<Artist>>>,
pub tracks: Option<ItemSearchResult<Box<Track>>>,
pub playlists: Option<ItemSearchResult<Box<Playlist>>>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct TrackSearchResponse {
pub tracks: ItemSearchResult<Box<Track>>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct UserFavorites {
pub albums: Option<ItemSearchResult<Box<Album>>>,
pub artists: Option<ItemSearchResult<Box<Artist>>>,
pub tracks: Option<ItemSearchResult<Box<Track>>>,
pub article_ids: Option<Vec<i32>>,
pub artist_ids: Option<Vec<i32>>,
pub album_ids: Option<Vec<i32>>,
pub track_ids: Option<Vec<i32>>,
}
pub async fn search_catalog(
service: &QobuzApiService,
query: &str,
limit: Option<i32>,
offset: Option<i32>,
) -> Result<SearchResult, QobuzApiError> {
let (albums, artists, tracks, playlists) = try_join!(
search_albums(service, query, limit, offset),
search_artists(service, query, limit, offset),
search_tracks(service, query, limit, offset),
search_playlists(service, query, limit, offset),
)?;
Ok(SearchResult {
albums: Some(albums),
artists: Some(artists),
tracks: Some(tracks),
playlists: Some(playlists),
})
}
#[cfg(test)]
mod tests {
use {
anyhow::{Result, ensure},
tokio::runtime::Runtime,
};
use crate::api::{
content::catalog::search_catalog,
fixture::{MockServer, make_service},
};
#[test]
fn search_catalog_groups_all_types() -> Result<()> {
let body = r#"{"albums":{"items":[],"total":0},"artists":{"items":[],"total":0},"tracks":{"items":[],"total":0},"playlists":{"items":[],"total":0}}"#;
let server = MockServer::start_with_max_requests(200, body, 16)?;
let service = make_service(&server.base_url())?;
let rt = Runtime::new()?;
let result = rt.block_on(search_catalog(&service, "Test", Some(5), None))?;
ensure!(result.albums.is_some());
ensure!(result.artists.is_some());
ensure!(result.tracks.is_some());
ensure!(result.playlists.is_some());
Ok(())
}
#[test]
fn search_catalog_error_stops_all() -> Result<()> {
let body = r#"{"status":"error","code":500,"message":"Fail"}"#;
let server = MockServer::start_with_max_requests(500, body, 16)?;
let service = make_service(&server.base_url())?;
let rt = Runtime::new()?;
let result = rt.block_on(search_catalog(&service, "fail", None, None));
ensure!(result.is_err());
Ok(())
}
}