Skip to main content

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}