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(Clone, 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}