qobuz_api/api/
http_client.rs1use std::pin::Pin;
4
5use {
6 reqwest::{
7 Client, Response,
8 header::{HeaderMap, HeaderName, HeaderValue},
9 },
10 tracing::warn,
11};
12
13use crate::errors::QobuzApiError;
14
15type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
17
18pub trait HttpClient: Send + Sync {
22 fn get(
33 &self,
34 url: &str,
35 params: &[(&str, &str)],
36 ) -> BoxFuture<'_, Result<Response, QobuzApiError>>;
37
38 fn post_form(
49 &self,
50 url: &str,
51 params: &[(&str, &str)],
52 ) -> BoxFuture<'_, Result<Response, QobuzApiError>>;
53
54 fn get_with_auth(
66 &self,
67 url: &str,
68 token: &str,
69 range: Option<&str>,
70 ) -> BoxFuture<'_, Result<Response, QobuzApiError>>;
71}
72
73#[derive(Debug)]
75pub struct ReqwestClient {
76 inner: Client,
78}
79
80impl ReqwestClient {
81 pub fn new(app_id: &str) -> Result<Self, QobuzApiError> {
95 let mut default_headers = HeaderMap::new();
96 if let Ok(val) = HeaderValue::from_str(app_id)
97 && default_headers
98 .insert(HeaderName::from_static("x-app-id"), val)
99 .is_some()
100 {
101 warn!("fresh header map already contained x-app-id");
102 }
103
104 let inner = Client::builder()
105 .user_agent(
106 "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0",
107 )
108 .default_headers(default_headers)
109 .build()?;
110
111 Ok(Self { inner })
112 }
113
114 #[must_use]
120 pub fn into_boxed(self) -> Box<dyn HttpClient> {
121 Box::new(self)
122 }
123}
124
125impl HttpClient for ReqwestClient {
126 fn get(
127 &self,
128 url: &str,
129 params: &[(&str, &str)],
130 ) -> BoxFuture<'_, Result<Response, QobuzApiError>> {
131 let fut = self.inner.get(url).query(params).send();
132 Box::pin(async move {
133 let resp = fut.await?;
134 Ok(resp)
135 })
136 }
137
138 fn post_form(
139 &self,
140 url: &str,
141 params: &[(&str, &str)],
142 ) -> BoxFuture<'_, Result<Response, QobuzApiError>> {
143 let params: Vec<(String, String)> = params
144 .iter()
145 .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
146 .collect();
147
148 let fut = self.inner.post(url).form(¶ms);
149 Box::pin(async move {
150 let resp = fut.send().await?;
151 Ok(resp)
152 })
153 }
154
155 fn get_with_auth(
156 &self,
157 url: &str,
158 token: &str,
159 range: Option<&str>,
160 ) -> BoxFuture<'_, Result<Response, QobuzApiError>> {
161 let mut req = self.inner.get(url).header("X-User-Auth-Token", token);
162
163 if let Some(r) = range {
164 req = req.header("Range", r);
165 }
166
167 let fut = req.send();
168 Box::pin(async move {
169 let resp = fut.await?;
170 Ok(resp)
171 })
172 }
173}