qobuz_api/api/
http_client.rs1use std::{future::Future, pin::Pin};
4
5use reqwest::{
6 Client, Response,
7 header::{HeaderMap, HeaderName, HeaderValue},
8};
9
10use crate::errors::QobuzApiError;
11
12type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
14
15pub trait HttpClient: Send + Sync {
19 fn get(
30 &self,
31 url: &str,
32 params: &[(&str, &str)],
33 ) -> BoxFuture<'_, Result<Response, QobuzApiError>>;
34
35 fn post_form(
46 &self,
47 url: &str,
48 params: &[(&str, &str)],
49 ) -> BoxFuture<'_, Result<Response, QobuzApiError>>;
50
51 fn get_with_auth(
63 &self,
64 url: &str,
65 token: &str,
66 range: Option<&str>,
67 ) -> BoxFuture<'_, Result<Response, QobuzApiError>>;
68}
69
70pub struct ReqwestClient {
72 inner: Client,
74}
75
76impl ReqwestClient {
77 pub fn new(app_id: &str) -> Result<Self, QobuzApiError> {
91 let mut default_headers = HeaderMap::new();
92 if let Ok(val) = HeaderValue::from_str(app_id) {
93 default_headers.insert(HeaderName::from_static("x-app-id"), val);
94 }
95
96 let inner = Client::builder()
97 .user_agent(
98 "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0",
99 )
100 .default_headers(default_headers)
101 .build()?;
102
103 Ok(Self { inner })
104 }
105
106 #[must_use]
112 pub fn into_boxed(self) -> Box<dyn HttpClient> {
113 Box::new(self)
114 }
115}
116
117impl HttpClient for ReqwestClient {
118 fn get(
119 &self,
120 url: &str,
121 params: &[(&str, &str)],
122 ) -> BoxFuture<'_, Result<Response, QobuzApiError>> {
123 let fut = self.inner.get(url).query(params).send();
124 Box::pin(async move {
125 let resp = fut.await?;
126 Ok(resp)
127 })
128 }
129
130 fn post_form(
131 &self,
132 url: &str,
133 params: &[(&str, &str)],
134 ) -> BoxFuture<'_, Result<Response, QobuzApiError>> {
135 let params: Vec<(String, String)> = params
136 .iter()
137 .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
138 .collect();
139
140 let fut = self.inner.post(url).form(¶ms);
141 Box::pin(async move {
142 let resp = fut.send().await?;
143 Ok(resp)
144 })
145 }
146
147 fn get_with_auth(
148 &self,
149 url: &str,
150 token: &str,
151 range: Option<&str>,
152 ) -> BoxFuture<'_, Result<Response, QobuzApiError>> {
153 let mut req = self.inner.get(url).header("X-User-Auth-Token", token);
154
155 if let Some(r) = range {
156 req = req.header("Range", r);
157 }
158
159 let fut = req.send();
160 Box::pin(async move {
161 let resp = fut.await?;
162 Ok(resp)
163 })
164 }
165}