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 Ok(self
140 .client
141 .get(self.url(path))
142 .headers(self.headers())
143 .build()?)
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 Ok(self
166 .client
167 .get(self.url(path))
168 .query(query)
169 .headers(self.headers())
170 .build()?)
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 Ok(self
191 .client
192 .delete(self.url(path))
193 .headers(self.headers())
194 .build()?)
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 Ok(self
217 .client
218 .delete(self.url(path))
219 .query(query)
220 .headers(self.headers())
221 .build()?)
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 Ok(self
244 .client
245 .post(self.url(path))
246 .headers(self.headers())
247 .json(&request)
248 .build()?)
249 };
250
251 self.execute(request_maker).await
252 }
253
254 /// Makes a POST request to the specified path with the given request body and query parameters, and deserializes the response body.
255 ///
256 /// # Arguments
257 ///
258 /// * `path` - The path for the POST request.
259 /// * `request` - The request body for the POST request.
260 /// * `query` - The query parameters for the POST request.
261 ///
262 /// # Returns
263 ///
264 /// A `Result` containing the deserialized response body or an `AriError`.
265 pub(crate) async fn post_with_query<I, Q, O>(
266 &self,
267 path: &str,
268 request: I,
269 query: &Q,
270 ) -> Result<O, AriError>
271 where
272 I: Serialize,
273 O: DeserializeOwned,
274 Q: Serialize + ?Sized,
275 {
276 let request_maker = || async {
277 Ok(self
278 .client
279 .post(self.url(path))
280 .query(query)
281 .json(&request)
282 .headers(self.headers())
283 .build()?)
284 };
285
286 self.execute(request_maker).await
287 }
288
289 /// Makes a PUT request to the specified path with the given request body and query parameters, and deserializes the response body.
290 ///
291 /// # Arguments
292 ///
293 /// * `path` - The path for the PUT request.
294 /// * `request` - The request body for the PUT request.
295 /// * `query` - The query parameters for the PUT request.
296 ///
297 /// # Returns
298 ///
299 /// A `Result` containing the deserialized response body or an `AriError`.
300 pub(crate) async fn put_with_query<I, O, Q>(
301 &self,
302 path: &str,
303 request: I,
304 query: &Q,
305 ) -> Result<O, AriError>
306 where
307 I: Serialize,
308 O: DeserializeOwned,
309 Q: Serialize + ?Sized,
310 {
311 let request_maker = || async {
312 Ok(self
313 .client
314 .put(self.url(path))
315 .headers(self.headers())
316 .query(query)
317 .json(&request)
318 .build()?)
319 };
320
321 self.execute(request_maker).await
322 }
323
324 /// Makes a PUT request to the specified path with the given request body and deserializes the response body.
325 ///
326 /// # Arguments
327 ///
328 /// * `path` - The path for the PUT request.
329 /// * `request` - The request body for the PUT request.
330 ///
331 /// # Returns
332 ///
333 /// A `Result` containing the deserialized response body or an `AriError`.
334 pub(crate) async fn put<I, O>(&self, path: &str, request: I) -> Result<O, AriError>
335 where
336 I: Serialize,
337 O: DeserializeOwned,
338 {
339 let request_maker = || async {
340 Ok(self
341 .client
342 .put(self.url(path))
343 .headers(self.headers())
344 .json(&request)
345 .build()?)
346 };
347
348 self.execute(request_maker).await
349 }
350
351 /// Constructs the full URL for the given path.
352 ///
353 /// # Arguments
354 ///
355 /// * `path` - The path to append to the base URL.
356 ///
357 /// # Returns
358 ///
359 /// The full URL as a string.
360 pub(crate) fn url(&self, path: impl Into<String> + Display) -> String {
361 format!("{}/ari{}", self.config.api_base, path)
362 }
363
364 /// Constructs the headers for the HTTP requests.
365 ///
366 /// # Returns
367 ///
368 /// A `HeaderMap` containing the necessary headers.
369 pub(crate) fn headers(&self) -> reqwest::header::HeaderMap {
370 let mut headers = reqwest::header::HeaderMap::new();
371 headers.insert(
372 reqwest::header::CONTENT_TYPE,
373 "application/json".parse().unwrap(),
374 );
375 headers.insert(
376 reqwest::header::AUTHORIZATION,
377 format!(
378 "Basic {}",
379 BASE64_STANDARD
380 .encode(format!("{}:{}", self.config.username, self.config.password))
381 )
382 .parse()
383 .unwrap(),
384 );
385 headers
386 }
387
388 /// Executes an HTTP request and retries on rate limit.
389 ///
390 /// # Arguments
391 ///
392 /// * `request_maker` - A closure that creates the request.
393 ///
394 /// # Returns
395 ///
396 /// A `Result` containing the deserialized response body or an `AriError`.
397 ///
398 /// The `request_maker` serves one purpose: to be able to create the request again
399 /// to retry the API call after getting rate limited. `request_maker` is async because
400 /// `reqwest::multipart::Form` is created by async calls to read files for uploads.
401 async fn execute<O, M, Fut>(&self, request_maker: M) -> Result<O, AriError>
402 where
403 O: DeserializeOwned,
404 M: Fn() -> Fut,
405 Fut: core::future::Future<Output = Result<reqwest::Request, AriError>>,
406 {
407 match self
408 .client
409 .execute(request_maker().await?)
410 .await?
411 .error_for_status()
412 {
413 Ok(resp) => {
414 let body = resp.text().await?;
415 if body.is_empty() {
416 return Ok(serde_json::from_str("null")?);
417 }
418
419 Ok(serde_json::from_str(&body)?)
420 }
421 Err(e) => Err(e.into()),
422 }
423 }
424}