google_calendar/
lib.rs

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