zoom_api/
lib.rs

1//! A fully generated, opinionated API client library for Zoom.
2//!
3//! [![docs.rs](https://docs.rs/zoom-api/badge.svg)](https://docs.rs/zoom-api)
4//!
5//! ## API Details
6//!
7//! The Zoom API allows developers to access information from Zoom. You can use this API to build private services or public applications on the [Zoom App Marketplace](http://marketplace.zoom.us). To learn how to get your credentials and create private/public applications, read our [Authorization Guide](https://marketplace.zoom.us/docs/guides/authorization/credentials).
8//! All endpoints are available via `https` and are located at `api.zoom.us/v2/`.
9//!
10//! For instance you can list all users on an account via `https://api.zoom.us/v2/users/`.
11//!
12//! [API Terms of Service](https://zoom.us/docs/en-us/zoom_api_license_and_tou.html)
13//!
14//! ### Contact
15//!
16//!
17//! | name | url | email |
18//! |----|----|----|
19//! | Zoom Developers | <https://developer.zoom.us/> | developersupport@zoom.us |
20//!
21//! ### License
22//!
23//!
24//! | name | url |
25//! |----|----|
26//! | MIT for OAS 2.0 | <https://opensource.org/licenses/MIT> |
27//!
28//!
29//! ## Client Details
30//!
31//! This client is generated from the [Zoom OpenAPI
32//! specs](https://marketplace.zoom.us/docs/api-reference/zoom-api/Zoom%20API.oas2.json) based on API spec version `2.0.0`. This way it will remain
33//! up to date as features are added. The documentation for the crate is generated
34//! along with the code to make this library easy to use.
35//!
36//!
37//! To install the library, add the following to your `Cargo.toml` file.
38//!
39//! ```toml
40//! [dependencies]
41//! zoom-api = "0.7.0"
42//! ```
43//!
44//! ## Basic example
45//!
46//! Typical use will require intializing a `Client`. This requires
47//! a user agent string and set of credentials.
48//!
49//! ```rust
50//! use zoom_api::Client;
51//!
52//! let zoom = Client::new(
53//!     String::from("client-id"),
54//!     String::from("client-secret"),
55//!     String::from("redirect-uri"),
56//!     String::from("token"),
57//!     String::from("refresh-token")
58//! );
59//! ```
60//!
61//! Alternatively, the library can search for most of the variables required for
62//! the client in the environment:
63//!
64//! - `ZOOM_CLIENT_ID`
65//! - `ZOOM_CLIENT_SECRET`
66//! - `ZOOM_REDIRECT_URI`
67//!
68//! And then you can create a client from the environment.
69//!
70//! ```rust
71//! use zoom_api::Client;
72//!
73//! let zoom = Client::new_from_env(
74//!     String::from("token"),
75//!     String::from("refresh-token")
76//! );
77//! ```
78//!
79//! It is okay to pass empty values for `token` and `refresh_token`. In
80//! the initial state of the client, you will not know these values.
81//!
82//! To start off a fresh client and get a `token` and `refresh_token`, use the following.
83//!
84//! ```rust
85//! use zoom_api::Client;
86//!
87//! async fn do_call() {
88//!     let mut zoom = Client::new_from_env("", "");
89//!
90//!     // Get the URL to request consent from the user.
91//!     // You can optionally pass in scopes. If none are provided, then the
92//!     // resulting URL will not have any scopes.
93//!     let user_consent_url = zoom.user_consent_url(&["some-scope".to_string()]);
94//!
95//!     // In your redirect URL capture the code sent and our state.
96//!     // Send it along to the request for the token.
97//!     let code = "thing-from-redirect-url";
98//!     let state = "state-from-redirect-url";
99//!     let mut access_token = zoom.get_access_token(code, state).await.unwrap();
100//!
101//!     // You can additionally refresh the access token with the following.
102//!     // You must have a refresh token to be able to call this function.
103//!     access_token = zoom.refresh_access_token().await.unwrap();
104//! }
105//! ```
106//!
107#![allow(clippy::derive_partial_eq_without_eq)]
108#![allow(clippy::too_many_arguments)]
109#![allow(clippy::nonstandard_macro_braces)]
110#![allow(clippy::large_enum_variant)]
111#![allow(clippy::tabs_in_doc_comments)]
112#![allow(missing_docs)]
113#![cfg_attr(docsrs, feature(doc_cfg))]
114
115pub mod accounts;
116pub mod archiving;
117pub mod billing;
118pub mod chat_channels;
119pub mod chat_channels_account_level;
120pub mod chat_messages;
121pub mod chatbot_messages;
122pub mod cloud_recording;
123pub mod common_area_phones;
124pub mod contacts;
125pub mod dashboards;
126pub mod deprecated_api_endpoints;
127pub mod devices;
128pub mod groups;
129pub mod im_chat;
130pub mod im_groups;
131pub mod meetings;
132pub mod pac;
133pub mod phone;
134pub mod phone_auto_receptionists;
135pub mod phone_blocked_list;
136pub mod phone_call_queues;
137pub mod phone_devices;
138pub mod phone_reports;
139pub mod phone_shared_line_groups;
140pub mod phone_site;
141pub mod reports;
142pub mod roles;
143pub mod rooms;
144pub mod rooms_account;
145pub mod rooms_devices;
146pub mod rooms_location;
147pub mod sip_connected_audio;
148pub mod sip_phone;
149pub mod tracking_field;
150pub mod tsp;
151pub mod types;
152pub mod users;
153#[doc(hidden)]
154pub mod utils;
155pub mod webinars;
156
157pub use reqwest::{header::HeaderMap, StatusCode};
158
159#[derive(Debug)]
160pub struct Response<T> {
161    pub status: reqwest::StatusCode,
162    pub headers: reqwest::header::HeaderMap,
163    pub body: T,
164}
165
166impl<T> Response<T> {
167    pub fn new(status: reqwest::StatusCode, headers: reqwest::header::HeaderMap, body: T) -> Self {
168        Self {
169            status,
170            headers,
171            body,
172        }
173    }
174}
175
176type ClientResult<T> = Result<T, ClientError>;
177
178use thiserror::Error;
179
180/// Errors returned by the client
181#[derive(Debug, Error)]
182pub enum ClientError {
183    // Generic Token Client
184    /// Empty refresh auth token
185    #[error("Refresh AuthToken is empty")]
186    EmptyRefreshToken,
187    /// utf8 convertion error
188    #[error(transparent)]
189    FromUtf8Error(#[from] std::string::FromUtf8Error),
190    /// URL Parsing Error
191    #[error(transparent)]
192    UrlParserError(#[from] url::ParseError),
193    /// Serde JSON parsing error
194    #[error(transparent)]
195    SerdeJsonError(#[from] serde_json::Error),
196    /// Errors returned by reqwest
197    #[error(transparent)]
198    ReqwestError(#[from] reqwest::Error),
199    /// Errors returned by reqwest::header
200    #[error(transparent)]
201    InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue),
202    /// Errors returned by reqwest middleware
203    #[error(transparent)]
204    ReqwestMiddleWareError(#[from] reqwest_middleware::Error),
205    /// Generic HTTP Error
206    #[error("HTTP Error. Code: {status}, message: {error}")]
207    HttpError {
208        status: http::StatusCode,
209        headers: reqwest::header::HeaderMap,
210        error: String,
211    },
212}
213
214pub const FALLBACK_HOST: &str = "https://api.zoom.us/v2";
215
216mod progenitor_support {
217    use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
218
219    const PATH_SET: &AsciiSet = &CONTROLS
220        .add(b' ')
221        .add(b'"')
222        .add(b'#')
223        .add(b'<')
224        .add(b'>')
225        .add(b'?')
226        .add(b'`')
227        .add(b'{')
228        .add(b'}');
229
230    #[allow(dead_code)]
231    pub(crate) fn encode_path(pc: &str) -> String {
232        utf8_percent_encode(pc, PATH_SET).to_string()
233    }
234}
235
236#[derive(Debug, Default)]
237pub(crate) struct Message {
238    pub body: Option<reqwest::Body>,
239    pub content_type: Option<String>,
240}
241
242use std::convert::TryInto;
243use std::env;
244use std::ops::Add;
245use std::sync::Arc;
246use std::time::{Duration, Instant};
247use tokio::sync::RwLock;
248
249const TOKEN_ENDPOINT: &str = "https://zoom.us/oauth/token";
250const USER_CONSENT_ENDPOINT: &str = "https://zoom.us/oauth/authorize";
251
252/// Entrypoint for interacting with the API client.
253#[derive(Clone)]
254pub struct Client {
255    host: String,
256    host_override: Option<String>,
257    token: Arc<RwLock<InnerToken>>,
258    client_id: String,
259    client_secret: String,
260    redirect_uri: String,
261
262    auto_refresh: bool,
263    client: reqwest_middleware::ClientWithMiddleware,
264}
265
266use schemars::JsonSchema;
267use serde::{Deserialize, Serialize};
268
269#[derive(Debug, JsonSchema, Clone, Default, Serialize, Deserialize)]
270pub struct AccessToken {
271    #[serde(
272        default,
273        skip_serializing_if = "String::is_empty",
274        deserialize_with = "crate::utils::deserialize_null_string::deserialize"
275    )]
276    pub token_type: String,
277
278    #[serde(
279        default,
280        skip_serializing_if = "String::is_empty",
281        deserialize_with = "crate::utils::deserialize_null_string::deserialize"
282    )]
283    pub access_token: String,
284    #[serde(default)]
285    pub expires_in: i64,
286
287    #[serde(
288        default,
289        skip_serializing_if = "String::is_empty",
290        deserialize_with = "crate::utils::deserialize_null_string::deserialize"
291    )]
292    pub refresh_token: String,
293    #[serde(default, alias = "x_refresh_token_expires_in")]
294    pub refresh_token_expires_in: i64,
295
296    #[serde(
297        default,
298        skip_serializing_if = "String::is_empty",
299        deserialize_with = "crate::utils::deserialize_null_string::deserialize"
300    )]
301    pub scope: String,
302}
303
304/// Time in seconds before the access token expiration point that a refresh should
305/// be performed. This value is subtracted from the `expires_in` value returned by
306/// the provider prior to storing
307const REFRESH_THRESHOLD: Duration = Duration::from_secs(60);
308
309#[derive(Debug, Clone)]
310struct InnerToken {
311    access_token: String,
312    refresh_token: String,
313    expires_at: Option<Instant>,
314}
315
316impl Client {
317    /// Create a new Client struct. Requires OAuth2 configuration values as well as an access and refresh token.
318    ///
319    /// # Panics
320    ///
321    /// This function will panic if the internal http client fails to create
322    pub fn new<I, K, R, T, Q>(
323        client_id: I,
324        client_secret: K,
325        redirect_uri: R,
326        token: T,
327        refresh_token: Q,
328    ) -> Self
329    where
330        I: ToString,
331        K: ToString,
332        R: ToString,
333        T: ToString,
334        Q: ToString,
335    {
336        // Retry up to 3 times with increasing intervals between attempts.
337        let retry_policy =
338            reqwest_retry::policies::ExponentialBackoff::builder().build_with_max_retries(3);
339        let client = reqwest::Client::builder()
340            .redirect(reqwest::redirect::Policy::none())
341            .build();
342        match client {
343            Ok(c) => {
344                let client = reqwest_middleware::ClientBuilder::new(c)
345                    // Trace HTTP requests. See the tracing crate to make use of these traces.
346                    .with(reqwest_tracing::TracingMiddleware::default())
347                    // Retry failed requests.
348                    .with(reqwest_conditional_middleware::ConditionalMiddleware::new(
349                        reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
350                        |req: &reqwest::Request| req.try_clone().is_some(),
351                    ))
352                    .build();
353
354                let host = FALLBACK_HOST.to_string();
355
356                Client {
357                    host,
358                    host_override: None,
359                    client_id: client_id.to_string(),
360                    client_secret: client_secret.to_string(),
361                    redirect_uri: redirect_uri.to_string(),
362                    token: Arc::new(RwLock::new(InnerToken {
363                        access_token: token.to_string(),
364                        refresh_token: refresh_token.to_string(),
365                        expires_at: None,
366                    })),
367
368                    auto_refresh: false,
369                    client,
370                }
371            }
372            Err(e) => panic!("creating reqwest client failed: {:?}", e),
373        }
374    }
375
376    /// Enables or disables the automatic refreshing of access tokens upon expiration
377    pub fn set_auto_access_token_refresh(&mut self, enabled: bool) -> &mut Self {
378        self.auto_refresh = enabled;
379        self
380    }
381
382    /// Sets a specific `Instant` at which the access token should be considered expired.
383    /// The expiration value will only be used when automatic access token refreshing is
384    /// also enabled. `None` may be passed in if the expiration is unknown. In this case
385    /// automatic refreshes will be attempted when encountering an UNAUTHENTICATED status
386    /// code on a response.
387    pub async fn set_expires_at(&self, expires_at: Option<Instant>) -> &Self {
388        self.token.write().await.expires_at = expires_at;
389        self
390    }
391
392    /// Gets the `Instant` at which the access token used by this client is set to expire
393    /// if one is known
394    pub async fn expires_at(&self) -> Option<Instant> {
395        self.token.read().await.expires_at
396    }
397
398    /// Sets the number of seconds in which the current access token should be considered
399    /// expired
400    pub async fn set_expires_in(&self, expires_in: i64) -> &Self {
401        self.token.write().await.expires_at = Self::compute_expires_at(expires_in);
402        self
403    }
404
405    /// Gets the number of seconds from now in which the current access token will be
406    /// considered expired if one is known
407    pub async fn expires_in(&self) -> Option<Duration> {
408        self.token
409            .read()
410            .await
411            .expires_at
412            .map(|i| i.duration_since(Instant::now()))
413    }
414
415    /// Determines if the access token currently stored in the client is expired. If the
416    /// expiration can not be determined, None is returned
417    pub async fn is_expired(&self) -> Option<bool> {
418        self.token
419            .read()
420            .await
421            .expires_at
422            .map(|expiration| expiration <= Instant::now())
423    }
424
425    fn compute_expires_at(expires_in: i64) -> Option<Instant> {
426        let seconds_valid = expires_in
427            .try_into()
428            .ok()
429            .map(Duration::from_secs)
430            .and_then(|dur| dur.checked_sub(REFRESH_THRESHOLD))
431            .or_else(|| Some(Duration::from_secs(0)));
432
433        seconds_valid.map(|seconds_valid| Instant::now().add(seconds_valid))
434    }
435
436    /// Override the host for all endpoins in the client.
437    pub fn with_host_override<H>(&mut self, host: H) -> &mut Self
438    where
439        H: ToString,
440    {
441        self.host_override = Some(host.to_string());
442        self
443    }
444
445    /// Disables the global host override for the client.
446    pub fn remove_host_override(&mut self) -> &mut Self {
447        self.host_override = None;
448        self
449    }
450
451    pub fn get_host_override(&self) -> Option<&str> {
452        self.host_override.as_deref()
453    }
454
455    pub(crate) fn url(&self, path: &str, host: Option<&str>) -> String {
456        format!(
457            "{}{}",
458            self.get_host_override()
459                .or(host)
460                .unwrap_or(self.host.as_str()),
461            path
462        )
463    }
464
465    /// Create a new Client struct from environment variables. Requires an existing access and refresh token.
466    ///
467    /// The following environment variables are expected to be set:
468    ///   * `ZOOM_CLIENT_ID`
469    ///   * `ZOOM_CLIENT_SECRET`
470    ///   * `ZOOM_REDIRECT_URI`
471    ///
472    /// # Panics
473    ///
474    /// This function will panic if the expected environment variables can not be found
475    pub fn new_from_env<T, R>(token: T, refresh_token: R) -> Self
476    where
477        T: ToString,
478        R: ToString,
479    {
480        let client_id = env::var("ZOOM_CLIENT_ID").expect("must set ZOOM_CLIENT_ID");
481        let client_secret = env::var("ZOOM_CLIENT_SECRET").expect("must set ZOOM_CLIENT_SECRET");
482        let redirect_uri = env::var("ZOOM_REDIRECT_URI").expect("must set ZOOM_REDIRECT_URI");
483
484        Client::new(client_id, client_secret, redirect_uri, token, refresh_token)
485    }
486
487    /// Return a user consent url with an optional set of scopes.
488    /// If no scopes are provided, they will not be passed in the url.
489    pub fn user_consent_url(&self, scopes: &[String]) -> String {
490        let state = uuid::Uuid::new_v4();
491
492        let url = format!(
493            "{}?client_id={}&response_type=code&redirect_uri={}&state={}",
494            USER_CONSENT_ENDPOINT, self.client_id, self.redirect_uri, state
495        );
496
497        if scopes.is_empty() {
498            return url;
499        }
500
501        // Add the scopes.
502        format!("{}&scope={}", url, scopes.join(" "))
503    }
504
505    /// Refresh an access token from a refresh token. Client must have a refresh token
506    /// for this to work.
507    pub async fn refresh_access_token(&self) -> ClientResult<AccessToken> {
508        let response = {
509            let refresh_token = &self.token.read().await.refresh_token;
510
511            if refresh_token.is_empty() {
512                return Err(ClientError::EmptyRefreshToken);
513            }
514
515            let mut headers = reqwest::header::HeaderMap::new();
516            headers.append(
517                reqwest::header::ACCEPT,
518                reqwest::header::HeaderValue::from_static("application/json"),
519            );
520
521            let params = [
522                ("grant_type", "refresh_token"),
523                ("refresh_token", refresh_token),
524                ("client_id", &self.client_id),
525                ("client_secret", &self.client_secret),
526                ("redirect_uri", &self.redirect_uri),
527            ];
528            let client = reqwest::Client::new();
529            client
530                .post(TOKEN_ENDPOINT)
531                .headers(headers)
532                .form(&params)
533                .basic_auth(&self.client_id, Some(&self.client_secret))
534                .send()
535                .await?
536        };
537
538        // Unwrap the response.
539        let t: AccessToken = response.json().await?;
540
541        let refresh_token = self.token.read().await.refresh_token.clone();
542
543        *self.token.write().await = InnerToken {
544            access_token: t.access_token.clone(),
545            refresh_token,
546            expires_at: Self::compute_expires_at(t.expires_in),
547        };
548
549        Ok(t)
550    }
551
552    /// Get an access token from the code returned by the URL paramter sent to the
553    /// redirect URL.
554    pub async fn get_access_token(&mut self, code: &str, state: &str) -> ClientResult<AccessToken> {
555        let mut headers = reqwest::header::HeaderMap::new();
556        headers.append(
557            reqwest::header::ACCEPT,
558            reqwest::header::HeaderValue::from_static("application/json"),
559        );
560
561        let params = [
562            ("grant_type", "authorization_code"),
563            ("code", code),
564            ("client_id", &self.client_id),
565            ("client_secret", &self.client_secret),
566            ("redirect_uri", &self.redirect_uri),
567            ("state", state),
568        ];
569        let client = reqwest::Client::new();
570        let resp = client
571            .post(TOKEN_ENDPOINT)
572            .headers(headers)
573            .form(&params)
574            .basic_auth(&self.client_id, Some(&self.client_secret))
575            .send()
576            .await?;
577
578        // Unwrap the response.
579        let t: AccessToken = resp.json().await?;
580
581        *self.token.write().await = InnerToken {
582            access_token: t.access_token.clone(),
583            refresh_token: t.refresh_token.clone(),
584            expires_at: Self::compute_expires_at(t.expires_in),
585        };
586
587        Ok(t)
588    }
589
590    async fn url_and_auth(&self, uri: &str) -> ClientResult<(reqwest::Url, Option<String>)> {
591        let parsed_url = uri.parse::<reqwest::Url>()?;
592
593        let auth = format!("Bearer {}", self.token.read().await.access_token);
594        Ok((parsed_url, Some(auth)))
595    }
596
597    async fn make_request(
598        &self,
599        method: &reqwest::Method,
600        uri: &str,
601        message: Message,
602    ) -> ClientResult<reqwest::Request> {
603        let (url, auth) = self.url_and_auth(uri).await?;
604
605        let instance = <&Client>::clone(&self);
606
607        let mut req = instance.client.request(method.clone(), url);
608
609        // Set the default headers.
610        req = req.header(
611            reqwest::header::ACCEPT,
612            reqwest::header::HeaderValue::from_static("application/json"),
613        );
614
615        if let Some(content_type) = &message.content_type {
616            req = req.header(
617                reqwest::header::CONTENT_TYPE,
618                reqwest::header::HeaderValue::from_str(content_type).unwrap(),
619            );
620        } else {
621            req = req.header(
622                reqwest::header::CONTENT_TYPE,
623                reqwest::header::HeaderValue::from_static("application/json"),
624            );
625        }
626
627        if let Some(auth_str) = auth {
628            req = req.header(http::header::AUTHORIZATION, &*auth_str);
629        }
630
631        if let Some(body) = message.body {
632            req = req.body(body);
633        }
634
635        Ok(req.build()?)
636    }
637
638    async fn request_raw(
639        &self,
640        method: reqwest::Method,
641        uri: &str,
642        message: Message,
643    ) -> ClientResult<reqwest::Response> {
644        if self.auto_refresh {
645            let expired = self.is_expired().await;
646
647            match expired {
648                // We have a known expired token, we know we need to perform a refresh prior to
649                // attempting to make a request
650                Some(true) => {
651                    self.refresh_access_token().await?;
652                }
653
654                // We have a (theoretically) known good token available. We make an optimistic
655                // attempting at the request. If the token is no longer good, then something other
656                // than the expiration is triggering the failure. We defer handling of these errors
657                // to the caller
658                Some(false) => (),
659
660                // We do not know what state we are in. We could have a valid or expired token.
661                // Generally this means we are in one of two cases:
662                //   1. We have not yet performed a token refresh, nor has the user provided
663                //      expiration data, and therefore do not know the expiration of the user
664                //      provided token
665                //   2. The provider is returning unusable expiration times, at which point we
666                //      choose to ignore them
667                None => (),
668            }
669        }
670
671        let req = self.make_request(&method, uri, message).await?;
672        let resp = self.client.execute(req).await?;
673
674        Ok(resp)
675    }
676
677    async fn request<Out>(
678        &self,
679        method: reqwest::Method,
680        uri: &str,
681        message: Message,
682    ) -> ClientResult<crate::Response<Out>>
683    where
684        Out: serde::de::DeserializeOwned + 'static + Send,
685    {
686        let response = self.request_raw(method, uri, message).await?;
687
688        let status = response.status();
689        let headers = response.headers().clone();
690
691        let response_body = response.bytes().await?;
692
693        if status.is_success() {
694            log::debug!("Received successful response. Read payload.");
695            let parsed_response = if status == http::StatusCode::NO_CONTENT
696                || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
697            {
698                serde_json::from_str("null")?
699            } else {
700                serde_json::from_slice::<Out>(&response_body)?
701            };
702            Ok(crate::Response::new(status, headers, parsed_response))
703        } else {
704            let error = if response_body.is_empty() {
705                ClientError::HttpError {
706                    status,
707                    headers,
708                    error: "empty response".into(),
709                }
710            } else {
711                ClientError::HttpError {
712                    status,
713                    headers,
714                    error: String::from_utf8_lossy(&response_body).into(),
715                }
716            };
717
718            Err(error)
719        }
720    }
721
722    async fn request_with_links<Out>(
723        &self,
724        method: http::Method,
725        uri: &str,
726        message: Message,
727    ) -> ClientResult<(Option<crate::utils::NextLink>, crate::Response<Out>)>
728    where
729        Out: serde::de::DeserializeOwned + 'static + Send,
730    {
731        let response = self.request_raw(method, uri, message).await?;
732
733        let status = response.status();
734        let headers = response.headers().clone();
735        let link = response
736            .headers()
737            .get(http::header::LINK)
738            .and_then(|l| l.to_str().ok())
739            .and_then(|l| parse_link_header::parse(l).ok())
740            .as_ref()
741            .and_then(crate::utils::next_link);
742
743        let response_body = response.bytes().await?;
744
745        if status.is_success() {
746            log::debug!("Received successful response. Read payload.");
747
748            let parsed_response = if status == http::StatusCode::NO_CONTENT
749                || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
750            {
751                serde_json::from_str("null")?
752            } else {
753                serde_json::from_slice::<Out>(&response_body)?
754            };
755            Ok((link, crate::Response::new(status, headers, parsed_response)))
756        } else {
757            let error = if response_body.is_empty() {
758                ClientError::HttpError {
759                    status,
760                    headers,
761                    error: "empty response".into(),
762                }
763            } else {
764                ClientError::HttpError {
765                    status,
766                    headers,
767                    error: String::from_utf8_lossy(&response_body).into(),
768                }
769            };
770            Err(error)
771        }
772    }
773
774    /* TODO: make this more DRY */
775    #[allow(dead_code)]
776    async fn post_form<Out>(
777        &self,
778        uri: &str,
779        form: reqwest::multipart::Form,
780    ) -> ClientResult<crate::Response<Out>>
781    where
782        Out: serde::de::DeserializeOwned + 'static + Send,
783    {
784        let (url, auth) = self.url_and_auth(uri).await?;
785
786        let instance = <&Client>::clone(&self);
787
788        let mut req = instance.client.request(http::Method::POST, url);
789
790        // Set the default headers.
791        req = req.header(
792            reqwest::header::ACCEPT,
793            reqwest::header::HeaderValue::from_static("application/json"),
794        );
795
796        if let Some(auth_str) = auth {
797            req = req.header(http::header::AUTHORIZATION, &*auth_str);
798        }
799
800        req = req.multipart(form);
801
802        let response = req.send().await?;
803
804        let status = response.status();
805        let headers = response.headers().clone();
806
807        let response_body = response.bytes().await?;
808
809        if status.is_success() {
810            log::debug!("Received successful response. Read payload.");
811            let parsed_response = if status == http::StatusCode::NO_CONTENT
812                || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
813            {
814                serde_json::from_str("null")?
815            } else if std::any::TypeId::of::<Out>() == std::any::TypeId::of::<String>() {
816                // Parse the output as a string.
817                let s = String::from_utf8(response_body.to_vec())?;
818                serde_json::from_value(serde_json::json!(&s))?
819            } else {
820                serde_json::from_slice::<Out>(&response_body)?
821            };
822            Ok(crate::Response::new(status, headers, parsed_response))
823        } else {
824            let error = if response_body.is_empty() {
825                ClientError::HttpError {
826                    status,
827                    headers,
828                    error: "empty response".into(),
829                }
830            } else {
831                ClientError::HttpError {
832                    status,
833                    headers,
834                    error: String::from_utf8_lossy(&response_body).into(),
835                }
836            };
837
838            Err(error)
839        }
840    }
841
842    /* TODO: make this more DRY */
843    #[allow(dead_code)]
844    async fn request_with_accept_mime<Out>(
845        &self,
846        method: reqwest::Method,
847        uri: &str,
848        accept_mime_type: &str,
849    ) -> ClientResult<crate::Response<Out>>
850    where
851        Out: serde::de::DeserializeOwned + 'static + Send,
852    {
853        let (url, auth) = self.url_and_auth(uri).await?;
854
855        let instance = <&Client>::clone(&self);
856
857        let mut req = instance.client.request(method, url);
858
859        // Set the default headers.
860        req = req.header(
861            reqwest::header::ACCEPT,
862            reqwest::header::HeaderValue::from_str(accept_mime_type)?,
863        );
864
865        if let Some(auth_str) = auth {
866            req = req.header(http::header::AUTHORIZATION, &*auth_str);
867        }
868
869        let response = req.send().await?;
870
871        let status = response.status();
872        let headers = response.headers().clone();
873
874        let response_body = response.bytes().await?;
875
876        if status.is_success() {
877            log::debug!("Received successful response. Read payload.");
878            let parsed_response = if status == http::StatusCode::NO_CONTENT
879                || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
880            {
881                serde_json::from_str("null")?
882            } else if std::any::TypeId::of::<Out>() == std::any::TypeId::of::<String>() {
883                // Parse the output as a string.
884                let s = String::from_utf8(response_body.to_vec())?;
885                serde_json::from_value(serde_json::json!(&s))?
886            } else {
887                serde_json::from_slice::<Out>(&response_body)?
888            };
889            Ok(crate::Response::new(status, headers, parsed_response))
890        } else {
891            let error = if response_body.is_empty() {
892                ClientError::HttpError {
893                    status,
894                    headers,
895                    error: "empty response".into(),
896                }
897            } else {
898                ClientError::HttpError {
899                    status,
900                    headers,
901                    error: String::from_utf8_lossy(&response_body).into(),
902                }
903            };
904
905            Err(error)
906        }
907    }
908
909    /* TODO: make this more DRY */
910    #[allow(dead_code)]
911    async fn request_with_mime<Out>(
912        &self,
913        method: reqwest::Method,
914        uri: &str,
915        content: &[u8],
916        mime_type: &str,
917    ) -> ClientResult<crate::Response<Out>>
918    where
919        Out: serde::de::DeserializeOwned + 'static + Send,
920    {
921        let (url, auth) = self.url_and_auth(uri).await?;
922
923        let instance = <&Client>::clone(&self);
924
925        let mut req = instance.client.request(method, url);
926
927        // Set the default headers.
928        req = req.header(
929            reqwest::header::ACCEPT,
930            reqwest::header::HeaderValue::from_static("application/json"),
931        );
932        req = req.header(
933            reqwest::header::CONTENT_TYPE,
934            reqwest::header::HeaderValue::from_bytes(mime_type.as_bytes()).unwrap(),
935        );
936        // We are likely uploading a file so add the right headers.
937        req = req.header(
938            reqwest::header::HeaderName::from_static("x-upload-content-type"),
939            reqwest::header::HeaderValue::from_static("application/octet-stream"),
940        );
941        req = req.header(
942            reqwest::header::HeaderName::from_static("x-upload-content-length"),
943            reqwest::header::HeaderValue::from_bytes(format!("{}", content.len()).as_bytes())
944                .unwrap(),
945        );
946
947        if let Some(auth_str) = auth {
948            req = req.header(http::header::AUTHORIZATION, &*auth_str);
949        }
950
951        if content.len() > 1 {
952            let b = bytes::Bytes::copy_from_slice(content);
953            // We are uploading a file so add that as the body.
954            req = req.body(b);
955        }
956
957        let response = req.send().await?;
958
959        let status = response.status();
960        let headers = response.headers().clone();
961
962        let response_body = response.bytes().await?;
963
964        if status.is_success() {
965            log::debug!("Received successful response. Read payload.");
966            let parsed_response = if status == http::StatusCode::NO_CONTENT
967                || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
968            {
969                serde_json::from_str("null")?
970            } else {
971                serde_json::from_slice::<Out>(&response_body)?
972            };
973            Ok(crate::Response::new(status, headers, parsed_response))
974        } else {
975            let error = if response_body.is_empty() {
976                ClientError::HttpError {
977                    status,
978                    headers,
979                    error: "empty response".into(),
980                }
981            } else {
982                ClientError::HttpError {
983                    status,
984                    headers,
985                    error: String::from_utf8_lossy(&response_body).into(),
986                }
987            };
988
989            Err(error)
990        }
991    }
992
993    async fn request_entity<D>(
994        &self,
995        method: http::Method,
996        uri: &str,
997        message: Message,
998    ) -> ClientResult<crate::Response<D>>
999    where
1000        D: serde::de::DeserializeOwned + 'static + Send,
1001    {
1002        let r = self.request(method, uri, message).await?;
1003        Ok(r)
1004    }
1005
1006    #[allow(dead_code)]
1007    async fn get<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
1008    where
1009        D: serde::de::DeserializeOwned + 'static + Send,
1010    {
1011        self.request_entity(http::Method::GET, uri, message).await
1012    }
1013
1014    #[allow(dead_code)]
1015    async fn get_all_pages<D>(&self, uri: &str, _message: Message) -> ClientResult<Response<Vec<D>>>
1016    where
1017        D: serde::de::DeserializeOwned + 'static + Send,
1018    {
1019        // TODO: implement this.
1020        self.unfold(uri).await
1021    }
1022
1023    /// "unfold" paginated results of a vector of items
1024    #[allow(dead_code)]
1025    async fn unfold<D>(&self, uri: &str) -> ClientResult<crate::Response<Vec<D>>>
1026    where
1027        D: serde::de::DeserializeOwned + 'static + Send,
1028    {
1029        let mut global_items = Vec::new();
1030        let (new_link, mut response) = self.get_pages(uri).await?;
1031        let mut link = new_link;
1032        while !response.body.is_empty() {
1033            global_items.append(&mut response.body);
1034            // We need to get the next link.
1035            if let Some(url) = &link {
1036                let url = reqwest::Url::parse(&url.0)?;
1037                let (new_link, new_response) = self.get_pages_url(&url).await?;
1038                link = new_link;
1039                response = new_response;
1040            }
1041        }
1042
1043        Ok(Response::new(
1044            response.status,
1045            response.headers,
1046            global_items,
1047        ))
1048    }
1049
1050    #[allow(dead_code)]
1051    async fn get_pages<D>(
1052        &self,
1053        uri: &str,
1054    ) -> ClientResult<(Option<crate::utils::NextLink>, crate::Response<Vec<D>>)>
1055    where
1056        D: serde::de::DeserializeOwned + 'static + Send,
1057    {
1058        self.request_with_links(http::Method::GET, uri, Message::default())
1059            .await
1060    }
1061
1062    #[allow(dead_code)]
1063    async fn get_pages_url<D>(
1064        &self,
1065        url: &reqwest::Url,
1066    ) -> ClientResult<(Option<crate::utils::NextLink>, crate::Response<Vec<D>>)>
1067    where
1068        D: serde::de::DeserializeOwned + 'static + Send,
1069    {
1070        self.request_with_links(http::Method::GET, url.as_str(), Message::default())
1071            .await
1072    }
1073
1074    #[allow(dead_code)]
1075    async fn post<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
1076    where
1077        D: serde::de::DeserializeOwned + 'static + Send,
1078    {
1079        self.request_entity(http::Method::POST, uri, message).await
1080    }
1081
1082    #[allow(dead_code)]
1083    async fn patch<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
1084    where
1085        D: serde::de::DeserializeOwned + 'static + Send,
1086    {
1087        self.request_entity(http::Method::PATCH, uri, message).await
1088    }
1089
1090    #[allow(dead_code)]
1091    async fn put<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
1092    where
1093        D: serde::de::DeserializeOwned + 'static + Send,
1094    {
1095        self.request_entity(http::Method::PUT, uri, message).await
1096    }
1097
1098    #[allow(dead_code)]
1099    async fn delete<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
1100    where
1101        D: serde::de::DeserializeOwned + 'static + Send,
1102    {
1103        self.request_entity(http::Method::DELETE, uri, message)
1104            .await
1105    }
1106
1107    pub fn accounts(&self) -> accounts::Accounts {
1108        accounts::Accounts::new(self.clone())
1109    }
1110
1111    pub fn archiving(&self) -> archiving::Archiving {
1112        archiving::Archiving::new(self.clone())
1113    }
1114
1115    pub fn billing(&self) -> billing::Billing {
1116        billing::Billing::new(self.clone())
1117    }
1118
1119    pub fn chat_channels(&self) -> chat_channels::ChatChannels {
1120        chat_channels::ChatChannels::new(self.clone())
1121    }
1122
1123    pub fn chat_channels_account_level(
1124        &self,
1125    ) -> chat_channels_account_level::ChatChannelsAccountLevel {
1126        chat_channels_account_level::ChatChannelsAccountLevel::new(self.clone())
1127    }
1128
1129    pub fn chat_messages(&self) -> chat_messages::ChatMessages {
1130        chat_messages::ChatMessages::new(self.clone())
1131    }
1132
1133    pub fn chatbot_messages(&self) -> chatbot_messages::ChatbotMessages {
1134        chatbot_messages::ChatbotMessages::new(self.clone())
1135    }
1136
1137    pub fn cloud_recording(&self) -> cloud_recording::CloudRecording {
1138        cloud_recording::CloudRecording::new(self.clone())
1139    }
1140
1141    pub fn common_area_phones(&self) -> common_area_phones::CommonAreaPhones {
1142        common_area_phones::CommonAreaPhones::new(self.clone())
1143    }
1144
1145    pub fn contacts(&self) -> contacts::Contacts {
1146        contacts::Contacts::new(self.clone())
1147    }
1148
1149    pub fn dashboards(&self) -> dashboards::Dashboards {
1150        dashboards::Dashboards::new(self.clone())
1151    }
1152
1153    pub fn deprecated_api_endpoints(&self) -> deprecated_api_endpoints::DeprecatedApiEndpoints {
1154        deprecated_api_endpoints::DeprecatedApiEndpoints::new(self.clone())
1155    }
1156
1157    pub fn devices(&self) -> devices::Devices {
1158        devices::Devices::new(self.clone())
1159    }
1160
1161    pub fn groups(&self) -> groups::Groups {
1162        groups::Groups::new(self.clone())
1163    }
1164
1165    pub fn im_chat(&self) -> im_chat::ImChat {
1166        im_chat::ImChat::new(self.clone())
1167    }
1168
1169    pub fn im_groups(&self) -> im_groups::ImGroups {
1170        im_groups::ImGroups::new(self.clone())
1171    }
1172
1173    pub fn meetings(&self) -> meetings::Meetings {
1174        meetings::Meetings::new(self.clone())
1175    }
1176
1177    pub fn pac(&self) -> pac::Pac {
1178        pac::Pac::new(self.clone())
1179    }
1180
1181    pub fn phone(&self) -> phone::Phone {
1182        phone::Phone::new(self.clone())
1183    }
1184
1185    pub fn phone_auto_receptionists(&self) -> phone_auto_receptionists::PhoneAutoReceptionists {
1186        phone_auto_receptionists::PhoneAutoReceptionists::new(self.clone())
1187    }
1188
1189    pub fn phone_blocked_list(&self) -> phone_blocked_list::PhoneBlockedList {
1190        phone_blocked_list::PhoneBlockedList::new(self.clone())
1191    }
1192
1193    pub fn phone_call_queues(&self) -> phone_call_queues::PhoneCallQueues {
1194        phone_call_queues::PhoneCallQueues::new(self.clone())
1195    }
1196
1197    pub fn phone_devices(&self) -> phone_devices::PhoneDevices {
1198        phone_devices::PhoneDevices::new(self.clone())
1199    }
1200
1201    pub fn phone_reports(&self) -> phone_reports::PhoneReports {
1202        phone_reports::PhoneReports::new(self.clone())
1203    }
1204
1205    pub fn phone_shared_line_groups(&self) -> phone_shared_line_groups::PhoneSharedLineGroups {
1206        phone_shared_line_groups::PhoneSharedLineGroups::new(self.clone())
1207    }
1208
1209    pub fn phone_site(&self) -> phone_site::PhoneSite {
1210        phone_site::PhoneSite::new(self.clone())
1211    }
1212
1213    pub fn reports(&self) -> reports::Reports {
1214        reports::Reports::new(self.clone())
1215    }
1216
1217    pub fn roles(&self) -> roles::Roles {
1218        roles::Roles::new(self.clone())
1219    }
1220
1221    pub fn rooms(&self) -> rooms::Rooms {
1222        rooms::Rooms::new(self.clone())
1223    }
1224
1225    pub fn rooms_account(&self) -> rooms_account::RoomsAccount {
1226        rooms_account::RoomsAccount::new(self.clone())
1227    }
1228
1229    pub fn rooms_devices(&self) -> rooms_devices::RoomsDevices {
1230        rooms_devices::RoomsDevices::new(self.clone())
1231    }
1232
1233    pub fn rooms_location(&self) -> rooms_location::RoomsLocation {
1234        rooms_location::RoomsLocation::new(self.clone())
1235    }
1236
1237    pub fn sip_connected_audio(&self) -> sip_connected_audio::SipConnectedAudio {
1238        sip_connected_audio::SipConnectedAudio::new(self.clone())
1239    }
1240
1241    pub fn sip_phone(&self) -> sip_phone::SipPhone {
1242        sip_phone::SipPhone::new(self.clone())
1243    }
1244
1245    pub fn tracking_field(&self) -> tracking_field::TrackingField {
1246        tracking_field::TrackingField::new(self.clone())
1247    }
1248
1249    pub fn tsp(&self) -> tsp::Tsp {
1250        tsp::Tsp::new(self.clone())
1251    }
1252
1253    pub fn users(&self) -> users::Users {
1254        users::Users::new(self.clone())
1255    }
1256
1257    pub fn webinars(&self) -> webinars::Webinars {
1258        webinars::Webinars::new(self.clone())
1259    }
1260}