1use {
4 serde::{Deserialize, Serialize},
5 tokio::try_join,
6};
7
8use crate::{
9 api::{
10 content::{
11 albums::{Album, search_albums},
12 artists::{Artist, search_artists},
13 playlists::{Playlist, search_playlists},
14 tracks::{Track, search_tracks},
15 },
16 service::QobuzApiService,
17 },
18 errors::QobuzApiError,
19};
20
21#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
23pub struct AlbumSearchResponse {
24 pub albums: ItemSearchResult<Box<Album>>,
26}
27
28#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
30pub struct ArtistSearchResponse {
31 pub artists: ItemSearchResult<Box<Artist>>,
33}
34
35#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
37pub struct ItemSearchResult<T> {
38 pub items: Option<Vec<T>>,
40 pub total: Option<i32>,
42 pub limit: Option<i32>,
44 pub offset: Option<i32>,
46}
47
48#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
50pub struct PlaylistSearchResponse {
51 pub playlists: ItemSearchResult<Box<Playlist>>,
53}
54
55#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
57pub struct SearchResult {
58 pub albums: Option<ItemSearchResult<Box<Album>>>,
60 pub artists: Option<ItemSearchResult<Box<Artist>>>,
62 pub tracks: Option<ItemSearchResult<Box<Track>>>,
64 pub playlists: Option<ItemSearchResult<Box<Playlist>>>,
66}
67
68#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
70pub struct TrackSearchResponse {
71 pub tracks: ItemSearchResult<Box<Track>>,
73}
74
75#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
77pub struct UserFavorites {
78 pub albums: Option<ItemSearchResult<Box<Album>>>,
80 pub artists: Option<ItemSearchResult<Box<Artist>>>,
82 pub tracks: Option<ItemSearchResult<Box<Track>>>,
84 pub article_ids: Option<Vec<i32>>,
86 pub artist_ids: Option<Vec<i32>>,
88 pub album_ids: Option<Vec<i32>>,
90 pub track_ids: Option<Vec<i32>>,
92}
93
94pub async fn search_catalog(
113 service: &QobuzApiService,
114 query: &str,
115 limit: Option<i32>,
116 offset: Option<i32>,
117) -> Result<SearchResult, QobuzApiError> {
118 let (albums, artists, tracks, playlists) = try_join!(
119 search_albums(service, query, limit, offset),
120 search_artists(service, query, limit, offset),
121 search_tracks(service, query, limit, offset),
122 search_playlists(service, query, limit, offset),
123 )?;
124
125 Ok(SearchResult {
126 albums: Some(albums),
127 artists: Some(artists),
128 tracks: Some(tracks),
129 playlists: Some(playlists),
130 })
131}
132
133#[cfg(test)]
134mod tests {
135 use {
136 anyhow::{Result, ensure},
137 tokio::runtime::Runtime,
138 };
139
140 use crate::api::{
141 content::catalog::search_catalog,
142 fixture::{MockServer, make_service},
143 };
144
145 #[test]
151 fn search_catalog_groups_all_types() -> Result<()> {
152 let body = r#"{"albums":{"items":[],"total":0},"artists":{"items":[],"total":0},"tracks":{"items":[],"total":0},"playlists":{"items":[],"total":0}}"#;
153 let server = MockServer::start_with_max_requests(200, body, 16)?;
154 let service = make_service(&server.base_url())?;
155 let rt = Runtime::new()?;
156 let result = rt.block_on(search_catalog(&service, "Test", Some(5), None))?;
157 ensure!(result.albums.is_some());
158 ensure!(result.artists.is_some());
159 ensure!(result.tracks.is_some());
160 ensure!(result.playlists.is_some());
161 Ok(())
162 }
163
164 #[test]
170 fn search_catalog_error_stops_all() -> Result<()> {
171 let body = r#"{"status":"error","code":500,"message":"Fail"}"#;
172 let server = MockServer::start_with_max_requests(500, body, 16)?;
173 let service = make_service(&server.base_url())?;
174 let rt = Runtime::new()?;
175 let result = rt.block_on(search_catalog(&service, "fail", None, None));
176 ensure!(result.is_err());
177 Ok(())
178 }
179}