asterisk_ari/apis/client.rs
1use crate::config::Config;
2use crate::errors::AriError;
3use base64::prelude::BASE64_STANDARD;
4use base64::Engine;
5use serde::de::DeserializeOwned;
6use serde::Serialize;
7use std::fmt::Display;
8
9/// Represents the ARI client.
10///
11/// This struct holds the configuration and HTTP client for making requests to the ARI API.
12#[derive(Debug)]
13pub struct Client {
14 /// Configuration for the ARI client.
15 pub(crate) config: Config,
16 /// HTTP client for making requests.
17 pub(crate) client: reqwest::Client,
18}
19
20impl Client {
21 /// Creates a new client with the given configuration and HTTP client.
22 ///
23 /// # Arguments
24 ///
25 /// * `config` - The configuration for the ARI client.
26 /// * `client` - The HTTP client for making requests.
27 ///
28 /// # Returns
29 ///
30 /// A new instance of `Client`.
31 pub fn build(config: Config, client: reqwest::Client) -> Self {
32 Client { config, client }
33 }
34
35 /// Creates a new client with the given configuration.
36 ///
37 /// # Arguments
38 ///
39 /// * `config` - The configuration for the ARI client.
40 ///
41 /// # Returns
42 ///
43 /// A new instance of `Client`.
44 pub fn with_config(config: Config) -> Self {
45 Client {
46 config,
47 client: reqwest::Client::builder()
48 .connect_timeout(std::time::Duration::from_secs(5))
49 .http2_keep_alive_interval(Some(std::time::Duration::from_secs(5)))
50 .http2_keep_alive_while_idle(true)
51 .build()
52 .unwrap(),
53 }
54 }
55
56 /// Sets the HTTP client for making requests.
57 ///
58 /// # Arguments
59 ///
60 /// * `client` - The HTTP client to use.
61 ///
62 /// # Returns
63 ///
64 /// The updated `Client` instance.
65 pub fn with_client(mut self, client: reqwest::Client) -> Self {
66 self.client = client;
67 self
68 }
69
70 /// Returns an instance of the `Applications` API.
71 pub fn applications(&self) -> crate::apis::applications::Applications {
72 crate::apis::applications::Applications::new(self)
73 }
74
75 /// Returns an instance of the `Asterisk` API.
76 pub fn asterisk(&self) -> crate::apis::asterisk::Asterisk {
77 crate::apis::asterisk::Asterisk::new(self)
78 }
79
80 /// Returns an instance of the `Endpoints` API.
81 pub fn endpoints(&self) -> crate::apis::endpoints::Endpoints {
82 crate::apis::endpoints::Endpoints::new(self)
83 }
84
85 /// Returns an instance of the `Channels` API.
86 pub fn channels(&self) -> crate::apis::channels::Channels {
87 crate::apis::channels::Channels::new(self)
88 }
89
90 /// Returns an instance of the `Bridges` API.
91 pub fn bridges(&self) -> crate::apis::bridges::Bridges {
92 crate::apis::bridges::Bridges::new(self)
93 }
94
95 /// Returns an instance of the `Recordings` API.
96 pub fn recordings(&self) -> crate::apis::recordings::Recordings {
97 crate::apis::recordings::Recordings::new(self)
98 }
99
100 /// Returns an instance of the `Sounds` API.
101 pub fn sounds(&self) -> crate::apis::sounds::Sounds {
102 crate::apis::sounds::Sounds::new(self)
103 }
104
105 /// Returns an instance of the `Playbacks` API.
106 pub fn playbacks(&self) -> crate::apis::playbacks::Playbacks {
107 crate::apis::playbacks::Playbacks::new(self)
108 }
109
110 /// Returns an instance of the `DeviceStats` API.
111 pub fn device_stats(&self) -> crate::apis::device_stats::DeviceStats {
112 crate::apis::device_stats::DeviceStats::new(self)
113 }
114
115 /// Returns an instance of the `Mailboxes` API.
116 pub fn mailboxes(&self) -> crate::apis::mailboxes::Mailboxes {
117 crate::apis::mailboxes::Mailboxes::new(self)
118 }
119
120 /// Returns an instance of the `Events` API.
121 pub fn events(&self) -> crate::apis::events::Events {
122 crate::apis::events::Events::new(self)
123 }
124
125 /// Makes a GET request to the specified path and deserializes the response body.
126 ///
127 /// # Arguments
128 ///
129 /// * `path` - The path for the GET request.
130 ///
131 /// # Returns
132 ///
133 /// A `Result` containing the deserialized response body or an `AriError`.
134 pub(crate) async fn get<O>(&self, path: &str) -> Result<O, AriError>
135 where
136 O: DeserializeOwned,
137 {
138 let request_maker = || async {
139 self.client
140 .get(self.url(path))
141 .headers(self.headers())
142 .build()
143 .map_err(|e| AriError::Internal(e.to_string()))
144 };
145
146 self.execute(request_maker).await
147 }
148
149 /// Makes a GET request to the specified path with the given query and deserializes the response body.
150 ///
151 /// # Arguments
152 ///
153 /// * `path` - The path for the GET request.
154 /// * `query` - The query parameters for the GET request.
155 ///
156 /// # Returns
157 ///
158 /// A `Result` containing the deserialized response body or an `AriError`.
159 pub(crate) async fn get_with_query<Q, O>(&self, path: &str, query: &Q) -> Result<O, AriError>
160 where
161 O: DeserializeOwned,
162 Q: Serialize + ?Sized,
163 {
164 let request_maker = || async {
165 self.client
166 .get(self.url(path))
167 .query(query)
168 .headers(self.headers())
169 .build()
170 .map_err(|e| AriError::Internal(e.to_string()))
171 };
172
173 self.execute(request_maker).await
174 }
175
176 /// Makes a DELETE request to the specified path and deserializes the response body.
177 ///
178 /// # Arguments
179 ///
180 /// * `path` - The path for the DELETE request.
181 ///
182 /// # Returns
183 ///
184 /// A `Result` containing the deserialized response body or an `AriError`.
185 pub(crate) async fn delete<O>(&self, path: &str) -> Result<O, AriError>
186 where
187 O: DeserializeOwned,
188 {
189 let request_maker = || async {
190 self.client
191 .delete(self.url(path))
192 .headers(self.headers())
193 .build()
194 .map_err(|e| AriError::Internal(e.to_string()))
195 };
196
197 self.execute(request_maker).await
198 }
199
200 /// Makes a DELETE request to the specified path with the given query and deserializes the response body.
201 ///
202 /// # Arguments
203 ///
204 /// * `path` - The path for the DELETE request.
205 /// * `query` - The query parameters for the DELETE request.
206 ///
207 /// # Returns
208 ///
209 /// A `Result` containing the deserialized response body or an `AriError`.
210 pub(crate) async fn delete_with_query<O, Q>(&self, path: &str, query: &Q) -> Result<O, AriError>
211 where
212 O: DeserializeOwned,
213 Q: Serialize + ?Sized,
214 {
215 let request_maker = || async {
216 self.client
217 .delete(self.url(path))
218 .query(query)
219 .headers(self.headers())
220 .build()
221 .map_err(|e| AriError::Internal(e.to_string()))
222 };
223
224 self.execute(request_maker).await
225 }
226
227 /// Makes a POST request to the specified path with the given request body and deserializes the response body.
228 ///
229 /// # Arguments
230 ///
231 /// * `path` - The path for the POST request.
232 /// * `request` - The request body for the POST request.
233 ///
234 /// # Returns
235 ///
236 /// A `Result` containing the deserialized response body or an `AriError`.
237 pub(crate) async fn post<I, O>(&self, path: &str, request: I) -> Result<O, AriError>
238 where
239 I: Serialize,
240 O: DeserializeOwned,
241 {
242 let request_maker = || async {
243 let mut req = self.client.post(self.url(path)).headers(self.headers());
244 if !serde_json::to_value(&request)?.is_null() {
245 req = req.json(&request);
246 }
247 req.build().map_err(|e| AriError::Internal(e.to_string()))
248 };
249
250 self.execute(request_maker).await
251 }
252
253 /// Makes a POST request to the specified path with the given request body and query parameters, and deserializes the response body.
254 ///
255 /// # Arguments
256 ///
257 /// * `path` - The path for the POST request.
258 /// * `request` - The request body for the POST request.
259 /// * `query` - The query parameters for the POST request.
260 ///
261 /// # Returns
262 ///
263 /// A `Result` containing the deserialized response body or an `AriError`.
264 pub(crate) async fn post_with_query<I, Q, O>(
265 &self,
266 path: &str,
267 request: I,
268 query: &Q,
269 ) -> Result<O, AriError>
270 where
271 I: Serialize,
272 O: DeserializeOwned,
273 Q: Serialize + ?Sized,
274 {
275 let request_maker = || async {
276 let mut req = self
277 .client
278 .post(self.url(path))
279 .query(query)
280 .headers(self.headers());
281 if !serde_json::to_value(&request)?.is_null() {
282 req = req.json(&request);
283 }
284 req.build().map_err(|e| AriError::Internal(e.to_string()))
285 };
286
287 self.execute(request_maker).await
288 }
289
290 /// Makes a PUT request to the specified path with the given request body and query parameters, and deserializes the response body.
291 ///
292 /// # Arguments
293 ///
294 /// * `path` - The path for the PUT request.
295 /// * `request` - The request body for the PUT request.
296 /// * `query` - The query parameters for the PUT request.
297 ///
298 /// # Returns
299 ///
300 /// A `Result` containing the deserialized response body or an `AriError`.
301 pub(crate) async fn put_with_query<I, O, Q>(
302 &self,
303 path: &str,
304 request: I,
305 query: &Q,
306 ) -> Result<O, AriError>
307 where
308 I: Serialize,
309 O: DeserializeOwned,
310 Q: Serialize + ?Sized,
311 {
312 let request_maker = || async {
313 let mut req = self
314 .client
315 .put(self.url(path))
316 .query(query)
317 .headers(self.headers());
318 if !serde_json::to_value(&request)?.is_null() {
319 req = req.json(&request);
320 }
321 req.build().map_err(|e| AriError::Internal(e.to_string()))
322 };
323
324 self.execute(request_maker).await
325 }
326
327 /// Makes a PUT request to the specified path with the given request body and deserializes the response body.
328 ///
329 /// # Arguments
330 ///
331 /// * `path` - The path for the PUT request.
332 /// * `request` - The request body for the PUT request.
333 ///
334 /// # Returns
335 ///
336 /// A `Result` containing the deserialized response body or an `AriError`.
337 pub(crate) async fn put<I, O>(&self, path: &str, request: I) -> Result<O, AriError>
338 where
339 I: Serialize,
340 O: DeserializeOwned,
341 {
342 let request_maker = || async {
343 let mut req = self.client.put(self.url(path)).headers(self.headers());
344 if !serde_json::to_value(&request)?.is_null() {
345 req = req.json(&request);
346 }
347 req.build().map_err(|e| AriError::Internal(e.to_string()))
348 };
349
350 self.execute(request_maker).await
351 }
352
353 /// Constructs the full URL for the given path.
354 ///
355 /// # Arguments
356 ///
357 /// * `path` - The path to append to the base URL.
358 ///
359 /// # Returns
360 ///
361 /// The full URL as a string.
362 pub(crate) fn url(&self, path: impl Into<String> + Display) -> String {
363 format!("{}/ari{}", self.config.api_base, path)
364 }
365
366 /// Constructs the headers for the HTTP requests.
367 ///
368 /// # Returns
369 ///
370 /// A `HeaderMap` containing the necessary headers.
371 pub(crate) fn headers(&self) -> reqwest::header::HeaderMap {
372 let mut headers = reqwest::header::HeaderMap::new();
373 headers.insert(
374 reqwest::header::CONTENT_TYPE,
375 "application/json".parse().unwrap(),
376 );
377 headers.insert(
378 reqwest::header::AUTHORIZATION,
379 format!(
380 "Basic {}",
381 BASE64_STANDARD
382 .encode(format!("{}:{}", self.config.username, self.config.password))
383 )
384 .parse()
385 .unwrap(),
386 );
387 headers
388 }
389
390 /// Executes an HTTP request and retries on rate limit.
391 ///
392 /// # Arguments
393 ///
394 /// * `request_maker` - A closure that creates the request.
395 ///
396 /// # Returns
397 ///
398 /// A `Result` containing the deserialized response body or an `AriError`.
399 ///
400 /// The `request_maker` serves one purpose: to be able to create the request again
401 /// to retry the API call after getting rate limited. `request_maker` is async because
402 /// `reqwest::multipart::Form` is created by async calls to read files for uploads.
403 async fn execute<O, M, Fut>(&self, request_maker: M) -> Result<O, AriError>
404 where
405 O: DeserializeOwned,
406 M: Fn() -> Fut,
407 Fut: core::future::Future<Output = Result<reqwest::Request, AriError>>,
408 {
409 let response = self
410 .client
411 .execute(request_maker().await?)
412 .await
413 .map_err(|e| AriError::Internal(e.to_string()))?;
414
415 match response.error_for_status_ref() {
416 Ok(_) => {
417 let body = response
418 .text()
419 .await
420 .map_err(|e| AriError::Internal(e.to_string()))?;
421 if body.is_empty() {
422 return Ok(serde_json::from_str("null")?);
423 }
424
425 Ok(serde_json::from_str(&body)?)
426 }
427 Err(e) => Err(AriError::Http {
428 raw: e,
429 body: response.text().await.unwrap_or_default(),
430 }),
431 }
432 }
433}