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}