qobuz_api/api/content/mod.rs
1//! Content API operations: search and browse for albums, artists, tracks, playlists.
2
3pub mod album_download;
4pub mod albums;
5pub mod artist_download;
6pub mod artists;
7pub mod catalog;
8pub mod download_io;
9pub mod playlist_download;
10pub mod playlists;
11pub mod tracks;
12
13use std::{
14 future::Future,
15 sync::atomic::{AtomicBool, Ordering::Relaxed},
16};
17
18use serde::de::DeserializeOwned;
19
20use crate::{
21 api::{
22 requests::{self, RequestAuth},
23 service::QobuzApiService,
24 },
25 errors::QobuzApiError::{self, Canceled},
26};
27
28/// Checks the cancellation flag and returns `Canceled` if the download was cancelled.
29///
30/// # Arguments
31///
32/// * `cancel` - Optional cancellation flag
33///
34/// # Returns
35///
36/// `Ok(())` if not cancelled.
37///
38/// # Errors
39///
40/// Returns `QobuzApiError::Canceled` if the cancellation flag is set.
41pub fn check_cancel(cancel: Option<&AtomicBool>) -> Result<(), QobuzApiError> {
42 if cancel.is_some_and(|c| c.load(Relaxed)) {
43 return Err(Canceled);
44 }
45 Ok(())
46}
47
48/// Checks cancellation before and after an async fetch, discarding the fetch result on cancel.
49///
50/// # Arguments
51///
52/// * `cancel` - Optional cancellation flag
53/// * `f` - Async closure producing the value to fetch
54///
55/// # Returns
56///
57/// The fetch result if not cancelled.
58///
59/// # Errors
60///
61/// Returns `QobuzApiError::Canceled` if cancelled before or after the fetch.
62pub async fn fetch_with_cancel<T, F, Fut>(
63 cancel: Option<&AtomicBool>,
64 f: F,
65) -> Result<T, QobuzApiError>
66where
67 F: FnOnce() -> Fut,
68 Fut: Future<Output = Result<T, QobuzApiError>>,
69{
70 check_cancel(cancel)?;
71 let result = f().await?;
72 check_cancel(cancel)?;
73 Ok(result)
74}
75
76/// Appends optional `limit` and `offset` pagination parameters to a params vector.
77///
78/// # Arguments
79///
80/// * `params` - Parameter vector to modify
81/// * `limit` - Maximum number of results (if provided)
82/// * `offset` - Pagination offset (if provided)
83pub fn push_pagination_params(
84 params: &mut Vec<(String, String)>,
85 limit: Option<i32>,
86 offset: Option<i32>,
87) {
88 if let Some(l) = limit {
89 params.push(("limit".to_string(), l.to_string()));
90 }
91 if let Some(o) = offset {
92 params.push(("offset".to_string(), o.to_string()));
93 }
94}
95
96/// Retrieves the auth token from the service and sends a signed GET request.
97///
98/// # Arguments
99///
100/// * `service` - Authenticated API service
101/// * `endpoint` - API endpoint path
102/// * `params` - Key-value parameter pairs (will be sorted for signing)
103///
104/// # Returns
105///
106/// The deserialized response of type `T`.
107///
108/// # Errors
109///
110/// Returns a `QobuzApiError` if not authenticated or the API request fails.
111async fn do_signed_get<T: DeserializeOwned>(
112 service: &QobuzApiService,
113 endpoint: &str,
114 params: &mut Vec<(String, String)>,
115) -> Result<T, QobuzApiError> {
116 let token = service.require_auth_token()?;
117
118 requests::signed_get(
119 service.http_client(),
120 service.base_url(),
121 endpoint,
122 params,
123 &RequestAuth {
124 app_id: &service.app_id,
125 app_secret: service.app_secret(),
126 user_auth_token: token,
127 },
128 )
129 .await
130}
131
132/// Sends a search request with query, limit, and offset parameters.
133///
134/// # Arguments
135///
136/// * `service` - Authenticated API service
137/// * `endpoint` - API endpoint path (e.g., `"/album/search"`)
138/// * `query` - Search query string
139/// * `limit` - Maximum number of results to return
140/// * `offset` - Pagination offset
141///
142/// # Returns
143///
144/// The deserialized response of type `T`.
145///
146/// # Errors
147///
148/// Returns a `QobuzApiError` if not authenticated or the API request fails.
149pub async fn search<T: DeserializeOwned>(
150 service: &QobuzApiService,
151 endpoint: &str,
152 query: &str,
153 limit: Option<i32>,
154 offset: Option<i32>,
155) -> Result<T, QobuzApiError> {
156 let mut params = vec![("query".to_string(), query.to_string())];
157 push_pagination_params(&mut params, limit, offset);
158
159 do_signed_get(service, endpoint, &mut params).await
160}
161
162/// Sends a get-by-ID request with an optional extra parameter.
163///
164/// # Arguments
165///
166/// * `service` - Authenticated API service
167/// * `endpoint` - API endpoint path (e.g., `"/album/get"`)
168/// * `id_field` - Parameter name for the ID (e.g., `"album_id"`)
169/// * `id` - Resource identifier
170/// * `extra` - Optional extra fields to include
171///
172/// # Returns
173///
174/// The deserialized response of type `T`.
175///
176/// # Errors
177///
178/// Returns a `QobuzApiError` if not authenticated or the API request fails.
179pub async fn get_by_id<T: DeserializeOwned, I: ToString>(
180 service: &QobuzApiService,
181 endpoint: &str,
182 id_field: &str,
183 id: I,
184 extra: Option<&str>,
185) -> Result<T, QobuzApiError> {
186 let mut params = vec![(id_field.to_string(), id.to_string())];
187 if let Some(e) = extra {
188 params.push(("extra".to_string(), e.to_string()));
189 }
190
191 do_signed_get(service, endpoint, &mut params).await
192}
193
194/// Sends a paginated request by ID with limit and offset parameters.
195///
196/// # Arguments
197///
198/// * `service` - Authenticated API service
199/// * `endpoint` - API endpoint path (e.g., `"/artist/getReleasesList"`)
200/// * `id_field` - Parameter name for the ID (e.g., `"artist_id"`)
201/// * `id` - Resource identifier
202/// * `limit` - Maximum number of results to return
203/// * `offset` - Pagination offset
204///
205/// # Returns
206///
207/// The deserialized response of type `T`.
208///
209/// # Errors
210///
211/// Returns a `QobuzApiError` if not authenticated or the API request fails.
212pub async fn paginated<T: DeserializeOwned, I: ToString>(
213 service: &QobuzApiService,
214 endpoint: &str,
215 id_field: &str,
216 id: I,
217 limit: Option<i32>,
218 offset: Option<i32>,
219) -> Result<T, QobuzApiError> {
220 let mut params = vec![(id_field.to_string(), id.to_string())];
221 push_pagination_params(&mut params, limit, offset);
222
223 do_signed_get(service, endpoint, &mut params).await
224}