zoom_api/
lib.rs

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