google_cloud_resource_manager/
lib.rs

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