1#![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 groups;
114pub mod types;
115#[doc(hidden)]
116pub mod utils;
117
118pub use reqwest::{header::HeaderMap, StatusCode};
119
120#[derive(Debug)]
121pub struct Response<T> {
122 pub status: reqwest::StatusCode,
123 pub headers: reqwest::header::HeaderMap,
124 pub body: T,
125}
126
127impl<T> Response<T> {
128 pub fn new(status: reqwest::StatusCode, headers: reqwest::header::HeaderMap, body: T) -> Self {
129 Self {
130 status,
131 headers,
132 body,
133 }
134 }
135}
136
137type ClientResult<T> = Result<T, ClientError>;
138
139use thiserror::Error;
140
141#[derive(Debug, Error)]
143pub enum ClientError {
144 #[error("Refresh AuthToken is empty")]
147 EmptyRefreshToken,
148 #[error(transparent)]
150 FromUtf8Error(#[from] std::string::FromUtf8Error),
151 #[error(transparent)]
153 UrlParserError(#[from] url::ParseError),
154 #[error(transparent)]
156 SerdeJsonError(#[from] serde_json::Error),
157 #[error(transparent)]
159 ReqwestError(#[from] reqwest::Error),
160 #[error(transparent)]
162 InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue),
163 #[cfg(feature = "middleware")]
164 #[error(transparent)]
166 ReqwestMiddleWareError(#[from] reqwest_middleware::Error),
167 #[error("HTTP Error. Code: {status}, message: {error}")]
169 HttpError {
170 status: http::StatusCode,
171 headers: reqwest::header::HeaderMap,
172 error: String,
173 },
174}
175
176pub const FALLBACK_HOST: &str = "https://www.googleapis.com/groups/v1/groups";
177
178mod progenitor_support {
179 use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
180
181 const PATH_SET: &AsciiSet = &CONTROLS
182 .add(b' ')
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
192 #[allow(dead_code)]
193 pub(crate) fn encode_path(pc: &str) -> String {
194 utf8_percent_encode(pc, PATH_SET).to_string()
195 }
196}
197
198#[derive(Debug, Default)]
199pub(crate) struct Message {
200 pub body: Option<reqwest::Body>,
201 pub content_type: Option<String>,
202}
203
204use std::convert::TryInto;
205use std::env;
206use std::ops::Add;
207use std::sync::Arc;
208use std::time::{Duration, Instant};
209use tokio::sync::RwLock;
210
211const TOKEN_ENDPOINT: &str = "https://oauth2.googleapis.com/token";
212const USER_CONSENT_ENDPOINT: &str = "https://accounts.google.com/o/oauth2/v2/auth";
213
214#[derive(Debug, Default, Clone)]
215pub struct RootDefaultServer {}
216
217impl RootDefaultServer {
218 pub fn default_url(&self) -> &str {
219 "https://www.googleapis.com/groups/v1/groups"
220 }
221}
222
223#[derive(Clone)]
225pub struct Client {
226 host: String,
227 host_override: Option<String>,
228 token: Arc<RwLock<InnerToken>>,
229 client_id: String,
230 client_secret: String,
231 redirect_uri: String,
232
233 auto_refresh: bool,
234 #[cfg(feature = "middleware")]
235 client: reqwest_middleware::ClientWithMiddleware,
236 #[cfg(not(feature = "middleware"))]
237 client: reqwest::Client,
238}
239
240use schemars::JsonSchema;
241use serde::{Deserialize, Serialize};
242
243#[derive(Debug, JsonSchema, Clone, Default, Serialize, Deserialize)]
244pub struct AccessToken {
245 #[serde(
246 default,
247 skip_serializing_if = "String::is_empty",
248 deserialize_with = "crate::utils::deserialize_null_string::deserialize"
249 )]
250 pub token_type: String,
251
252 #[serde(
253 default,
254 skip_serializing_if = "String::is_empty",
255 deserialize_with = "crate::utils::deserialize_null_string::deserialize"
256 )]
257 pub access_token: String,
258 #[serde(default)]
259 pub expires_in: i64,
260
261 #[serde(
262 default,
263 skip_serializing_if = "String::is_empty",
264 deserialize_with = "crate::utils::deserialize_null_string::deserialize"
265 )]
266 pub refresh_token: String,
267 #[serde(default, alias = "x_refresh_token_expires_in")]
268 pub refresh_token_expires_in: i64,
269
270 #[serde(
271 default,
272 skip_serializing_if = "String::is_empty",
273 deserialize_with = "crate::utils::deserialize_null_string::deserialize"
274 )]
275 pub scope: String,
276}
277
278const REFRESH_THRESHOLD: Duration = Duration::from_secs(60);
282
283#[derive(Debug, Clone)]
284struct InnerToken {
285 access_token: String,
286 refresh_token: String,
287 expires_at: Option<Instant>,
288}
289
290impl Client {
291 pub fn new<I, K, R, T, Q>(
297 client_id: I,
298 client_secret: K,
299 redirect_uri: R,
300 token: T,
301 refresh_token: Q,
302 ) -> Self
303 where
304 I: ToString,
305 K: ToString,
306 R: ToString,
307 T: ToString,
308 Q: ToString,
309 {
310 let retry_policy =
312 reqwest_retry::policies::ExponentialBackoff::builder().build_with_max_retries(3);
313 let client = reqwest::Client::builder()
314 .redirect(reqwest::redirect::Policy::none())
315 .build();
316 match client {
317 Ok(c) => {
318 #[cfg(feature = "middleware")]
319 let client = {
320 reqwest_middleware::ClientBuilder::new(c)
321 .with(reqwest_tracing::TracingMiddleware::default())
323 .with(reqwest_conditional_middleware::ConditionalMiddleware::new(
325 reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
326 |req: &reqwest::Request| req.try_clone().is_some(),
327 ))
328 .build()
329 };
330 #[cfg(not(feature = "middleware"))]
331 let client = c;
332
333 let host = RootDefaultServer::default().default_url().to_string();
334
335 Client {
336 host,
337 host_override: None,
338 client_id: client_id.to_string(),
339 client_secret: client_secret.to_string(),
340 redirect_uri: redirect_uri.to_string(),
341 token: Arc::new(RwLock::new(InnerToken {
342 access_token: token.to_string(),
343 refresh_token: refresh_token.to_string(),
344 expires_at: None,
345 })),
346
347 auto_refresh: false,
348 client,
349 }
350 }
351 Err(e) => panic!("creating reqwest client failed: {:?}", e),
352 }
353 }
354
355 pub fn set_auto_access_token_refresh(&mut self, enabled: bool) -> &mut Self {
357 self.auto_refresh = enabled;
358 self
359 }
360
361 pub async fn set_expires_at(&self, expires_at: Option<Instant>) -> &Self {
367 self.token.write().await.expires_at = expires_at;
368 self
369 }
370
371 pub async fn expires_at(&self) -> Option<Instant> {
374 self.token.read().await.expires_at
375 }
376
377 pub async fn set_expires_in(&self, expires_in: i64) -> &Self {
380 self.token.write().await.expires_at = Self::compute_expires_at(expires_in);
381 self
382 }
383
384 pub async fn expires_in(&self) -> Option<Duration> {
387 self.token
388 .read()
389 .await
390 .expires_at
391 .map(|i| i.duration_since(Instant::now()))
392 }
393
394 pub async fn is_expired(&self) -> Option<bool> {
397 self.token
398 .read()
399 .await
400 .expires_at
401 .map(|expiration| expiration <= Instant::now())
402 }
403
404 fn compute_expires_at(expires_in: i64) -> Option<Instant> {
405 let seconds_valid = expires_in
406 .try_into()
407 .ok()
408 .map(Duration::from_secs)
409 .and_then(|dur| dur.checked_sub(REFRESH_THRESHOLD))
410 .or_else(|| Some(Duration::from_secs(0)));
411
412 seconds_valid.map(|seconds_valid| Instant::now().add(seconds_valid))
413 }
414
415 pub fn with_host_override<H>(&mut self, host: H) -> &mut Self
417 where
418 H: ToString,
419 {
420 self.host_override = Some(host.to_string());
421 self
422 }
423
424 pub fn remove_host_override(&mut self) -> &mut Self {
426 self.host_override = None;
427 self
428 }
429
430 pub fn get_host_override(&self) -> Option<&str> {
431 self.host_override.as_deref()
432 }
433
434 pub(crate) fn url(&self, path: &str, host: Option<&str>) -> String {
435 format!(
436 "{}{}",
437 self.get_host_override()
438 .or(host)
439 .unwrap_or(self.host.as_str()),
440 path
441 )
442 }
443
444 pub async fn new_from_env<T, R>(token: T, refresh_token: R) -> Self
455 where
456 T: ToString,
457 R: ToString,
458 {
459 use base64::{engine::general_purpose::STANDARD, Engine};
460
461 let google_key = env::var("GOOGLE_KEY_ENCODED").unwrap_or_default();
462 let decoded_google_key = STANDARD.decode(google_key).unwrap();
463 let secret = yup_oauth2::parse_application_secret(decoded_google_key)
464 .expect("failed to read from google credential env var");
465
466 let client = reqwest::Client::builder()
467 .redirect(reqwest::redirect::Policy::none())
468 .build();
469 let retry_policy =
470 reqwest_retry::policies::ExponentialBackoff::builder().build_with_max_retries(3);
471
472 match client {
473 Ok(c) => {
474 #[cfg(feature = "middleware")]
475 let client = {
476 reqwest_middleware::ClientBuilder::new(c)
477 .with(reqwest_tracing::TracingMiddleware::default())
479 .with(reqwest_conditional_middleware::ConditionalMiddleware::new(
481 reqwest_retry::RetryTransientMiddleware::new_with_policy(retry_policy),
482 |req: &reqwest::Request| req.try_clone().is_some(),
483 ))
484 .build()
485 };
486 #[cfg(not(feature = "middleware"))]
487 let client = c;
488 let host = RootDefaultServer::default().default_url().to_string();
489
490 Client {
491 host,
492 host_override: None,
493 client_id: secret.client_id.to_string(),
494 client_secret: secret.client_secret.to_string(),
495 redirect_uri: secret.redirect_uris[0].to_string(),
496 token: Arc::new(RwLock::new(InnerToken {
497 access_token: token.to_string(),
498 refresh_token: refresh_token.to_string(),
499 expires_at: None,
500 })),
501 auto_refresh: false,
502 client,
503 }
504 }
505 Err(e) => panic!("creating reqwest client failed: {:?}", e),
506 }
507 }
508
509 pub fn user_consent_url(&self, scopes: &[String]) -> String {
512 let state = uuid::Uuid::new_v4();
513
514 let url = format!(
515 "{}?client_id={}&access_type=offline&response_type=code&redirect_uri={}&state={}",
516 USER_CONSENT_ENDPOINT, self.client_id, self.redirect_uri, state
517 );
518
519 if scopes.is_empty() {
520 return url;
521 }
522
523 format!("{}&scope={}", url, scopes.join(" "))
525 }
526
527 pub async fn refresh_access_token(&self) -> ClientResult<AccessToken> {
530 let response = {
531 let refresh_token = &self.token.read().await.refresh_token;
532
533 if refresh_token.is_empty() {
534 return Err(ClientError::EmptyRefreshToken);
535 }
536
537 let mut headers = reqwest::header::HeaderMap::new();
538 headers.append(
539 reqwest::header::ACCEPT,
540 reqwest::header::HeaderValue::from_static("application/json"),
541 );
542
543 let params = [
544 ("grant_type", "refresh_token"),
545 ("refresh_token", refresh_token),
546 ("client_id", &self.client_id),
547 ("client_secret", &self.client_secret),
548 ("redirect_uri", &self.redirect_uri),
549 ];
550 let client = reqwest::Client::new();
551 client
552 .post(TOKEN_ENDPOINT)
553 .headers(headers)
554 .form(¶ms)
555 .basic_auth(&self.client_id, Some(&self.client_secret))
556 .send()
557 .await?
558 };
559
560 let t: AccessToken = response.json().await?;
562
563 let refresh_token = self.token.read().await.refresh_token.clone();
564
565 *self.token.write().await = InnerToken {
566 access_token: t.access_token.clone(),
567 refresh_token,
568 expires_at: Self::compute_expires_at(t.expires_in),
569 };
570
571 Ok(t)
572 }
573
574 pub async fn get_access_token(&mut self, code: &str, state: &str) -> ClientResult<AccessToken> {
577 let mut headers = reqwest::header::HeaderMap::new();
578 headers.append(
579 reqwest::header::ACCEPT,
580 reqwest::header::HeaderValue::from_static("application/json"),
581 );
582
583 let params = [
584 ("grant_type", "authorization_code"),
585 ("code", code),
586 ("client_id", &self.client_id),
587 ("client_secret", &self.client_secret),
588 ("redirect_uri", &self.redirect_uri),
589 ("state", state),
590 ];
591 let client = reqwest::Client::new();
592 let resp = client
593 .post(TOKEN_ENDPOINT)
594 .headers(headers)
595 .form(¶ms)
596 .basic_auth(&self.client_id, Some(&self.client_secret))
597 .send()
598 .await?;
599
600 let t: AccessToken = resp.json().await?;
602
603 *self.token.write().await = InnerToken {
604 access_token: t.access_token.clone(),
605 refresh_token: t.refresh_token.clone(),
606 expires_at: Self::compute_expires_at(t.expires_in),
607 };
608
609 Ok(t)
610 }
611
612 async fn url_and_auth(&self, uri: &str) -> ClientResult<(reqwest::Url, Option<String>)> {
613 let parsed_url = uri.parse::<reqwest::Url>()?;
614
615 let auth = format!("Bearer {}", self.token.read().await.access_token);
616 Ok((parsed_url, Some(auth)))
617 }
618
619 async fn make_request(
620 &self,
621 method: &reqwest::Method,
622 uri: &str,
623 message: Message,
624 ) -> ClientResult<reqwest::Request> {
625 let (url, auth) = self.url_and_auth(uri).await?;
626
627 let instance = <&Client>::clone(&self);
628
629 let mut req = instance.client.request(method.clone(), url);
630
631 req = req.header(
633 reqwest::header::ACCEPT,
634 reqwest::header::HeaderValue::from_static("application/json"),
635 );
636
637 if let Some(content_type) = &message.content_type {
638 req = req.header(
639 reqwest::header::CONTENT_TYPE,
640 reqwest::header::HeaderValue::from_str(content_type).unwrap(),
641 );
642 } else {
643 req = req.header(
644 reqwest::header::CONTENT_TYPE,
645 reqwest::header::HeaderValue::from_static("application/json"),
646 );
647 }
648
649 if let Some(auth_str) = auth {
650 req = req.header(http::header::AUTHORIZATION, &*auth_str);
651 }
652
653 if let Some(body) = message.body {
654 req = req.body(body);
655 }
656
657 Ok(req.build()?)
658 }
659
660 async fn request_raw(
661 &self,
662 method: reqwest::Method,
663 uri: &str,
664 message: Message,
665 ) -> ClientResult<reqwest::Response> {
666 if self.auto_refresh {
667 let expired = self.is_expired().await;
668
669 match expired {
670 Some(true) => {
673 self.refresh_access_token().await?;
674 }
675
676 Some(false) => (),
681
682 None => (),
690 }
691 }
692
693 let req = self.make_request(&method, uri, message).await?;
694 let resp = self.client.execute(req).await?;
695
696 Ok(resp)
697 }
698
699 async fn request<Out>(
700 &self,
701 method: reqwest::Method,
702 uri: &str,
703 message: Message,
704 ) -> ClientResult<crate::Response<Out>>
705 where
706 Out: serde::de::DeserializeOwned + 'static + Send,
707 {
708 let response = self.request_raw(method, uri, message).await?;
709
710 let status = response.status();
711 let headers = response.headers().clone();
712
713 let response_body = response.bytes().await?;
714
715 if status.is_success() {
716 log::debug!("Received successful response. Read payload.");
717 let parsed_response = if status == http::StatusCode::NO_CONTENT
718 || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
719 {
720 serde_json::from_str("null")?
721 } else {
722 serde_json::from_slice::<Out>(&response_body)?
723 };
724 Ok(crate::Response::new(status, headers, parsed_response))
725 } else {
726 let error = if response_body.is_empty() {
727 ClientError::HttpError {
728 status,
729 headers,
730 error: "empty response".into(),
731 }
732 } else {
733 ClientError::HttpError {
734 status,
735 headers,
736 error: String::from_utf8_lossy(&response_body).into(),
737 }
738 };
739
740 Err(error)
741 }
742 }
743
744 async fn request_with_links<Out>(
745 &self,
746 method: http::Method,
747 uri: &str,
748 message: Message,
749 ) -> ClientResult<(Option<crate::utils::NextLink>, crate::Response<Out>)>
750 where
751 Out: serde::de::DeserializeOwned + 'static + Send,
752 {
753 let response = self.request_raw(method, uri, message).await?;
754
755 let status = response.status();
756 let headers = response.headers().clone();
757 let link = response
758 .headers()
759 .get(http::header::LINK)
760 .and_then(|l| l.to_str().ok())
761 .and_then(|l| parse_link_header::parse(l).ok())
762 .as_ref()
763 .and_then(crate::utils::next_link);
764
765 let response_body = response.bytes().await?;
766
767 if status.is_success() {
768 log::debug!("Received successful response. Read payload.");
769
770 let parsed_response = if status == http::StatusCode::NO_CONTENT
771 || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
772 {
773 serde_json::from_str("null")?
774 } else {
775 serde_json::from_slice::<Out>(&response_body)?
776 };
777 Ok((link, crate::Response::new(status, headers, parsed_response)))
778 } else {
779 let error = if response_body.is_empty() {
780 ClientError::HttpError {
781 status,
782 headers,
783 error: "empty response".into(),
784 }
785 } else {
786 ClientError::HttpError {
787 status,
788 headers,
789 error: String::from_utf8_lossy(&response_body).into(),
790 }
791 };
792 Err(error)
793 }
794 }
795
796 #[allow(dead_code)]
798 async fn post_form<Out>(
799 &self,
800 uri: &str,
801 form: reqwest::multipart::Form,
802 ) -> ClientResult<crate::Response<Out>>
803 where
804 Out: serde::de::DeserializeOwned + 'static + Send,
805 {
806 let (url, auth) = self.url_and_auth(uri).await?;
807
808 let instance = <&Client>::clone(&self);
809
810 let mut req = instance.client.request(http::Method::POST, url);
811
812 req = req.header(
814 reqwest::header::ACCEPT,
815 reqwest::header::HeaderValue::from_static("application/json"),
816 );
817
818 if let Some(auth_str) = auth {
819 req = req.header(http::header::AUTHORIZATION, &*auth_str);
820 }
821
822 req = req.multipart(form);
823
824 let response = req.send().await?;
825
826 let status = response.status();
827 let headers = response.headers().clone();
828
829 let response_body = response.bytes().await?;
830
831 if status.is_success() {
832 log::debug!("Received successful response. Read payload.");
833 let parsed_response = if status == http::StatusCode::NO_CONTENT
834 || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
835 {
836 serde_json::from_str("null")?
837 } else if std::any::TypeId::of::<Out>() == std::any::TypeId::of::<String>() {
838 let s = String::from_utf8(response_body.to_vec())?;
840 serde_json::from_value(serde_json::json!(&s))?
841 } else {
842 serde_json::from_slice::<Out>(&response_body)?
843 };
844 Ok(crate::Response::new(status, headers, parsed_response))
845 } else {
846 let error = if response_body.is_empty() {
847 ClientError::HttpError {
848 status,
849 headers,
850 error: "empty response".into(),
851 }
852 } else {
853 ClientError::HttpError {
854 status,
855 headers,
856 error: String::from_utf8_lossy(&response_body).into(),
857 }
858 };
859
860 Err(error)
861 }
862 }
863
864 #[allow(dead_code)]
866 async fn request_with_accept_mime<Out>(
867 &self,
868 method: reqwest::Method,
869 uri: &str,
870 accept_mime_type: &str,
871 ) -> ClientResult<crate::Response<Out>>
872 where
873 Out: serde::de::DeserializeOwned + 'static + Send,
874 {
875 let (url, auth) = self.url_and_auth(uri).await?;
876
877 let instance = <&Client>::clone(&self);
878
879 let mut req = instance.client.request(method, url);
880
881 req = req.header(
883 reqwest::header::ACCEPT,
884 reqwest::header::HeaderValue::from_str(accept_mime_type)?,
885 );
886
887 if let Some(auth_str) = auth {
888 req = req.header(http::header::AUTHORIZATION, &*auth_str);
889 }
890
891 let response = req.send().await?;
892
893 let status = response.status();
894 let headers = response.headers().clone();
895
896 let response_body = response.bytes().await?;
897
898 if status.is_success() {
899 log::debug!("Received successful response. Read payload.");
900 let parsed_response = if status == http::StatusCode::NO_CONTENT
901 || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
902 {
903 serde_json::from_str("null")?
904 } else if std::any::TypeId::of::<Out>() == std::any::TypeId::of::<String>() {
905 let s = String::from_utf8(response_body.to_vec())?;
907 serde_json::from_value(serde_json::json!(&s))?
908 } else {
909 serde_json::from_slice::<Out>(&response_body)?
910 };
911 Ok(crate::Response::new(status, headers, parsed_response))
912 } else {
913 let error = if response_body.is_empty() {
914 ClientError::HttpError {
915 status,
916 headers,
917 error: "empty response".into(),
918 }
919 } else {
920 ClientError::HttpError {
921 status,
922 headers,
923 error: String::from_utf8_lossy(&response_body).into(),
924 }
925 };
926
927 Err(error)
928 }
929 }
930
931 #[allow(dead_code)]
933 async fn request_with_mime<Out>(
934 &self,
935 method: reqwest::Method,
936 uri: &str,
937 content: &[u8],
938 mime_type: &str,
939 ) -> ClientResult<crate::Response<Out>>
940 where
941 Out: serde::de::DeserializeOwned + 'static + Send,
942 {
943 let (url, auth) = self.url_and_auth(uri).await?;
944
945 let instance = <&Client>::clone(&self);
946
947 let mut req = instance.client.request(method, url);
948
949 req = req.header(
951 reqwest::header::ACCEPT,
952 reqwest::header::HeaderValue::from_static("application/json"),
953 );
954 req = req.header(
955 reqwest::header::CONTENT_TYPE,
956 reqwest::header::HeaderValue::from_bytes(mime_type.as_bytes()).unwrap(),
957 );
958 req = req.header(
960 reqwest::header::HeaderName::from_static("x-upload-content-type"),
961 reqwest::header::HeaderValue::from_static("application/octet-stream"),
962 );
963 req = req.header(
964 reqwest::header::HeaderName::from_static("x-upload-content-length"),
965 reqwest::header::HeaderValue::from_bytes(format!("{}", content.len()).as_bytes())
966 .unwrap(),
967 );
968
969 if let Some(auth_str) = auth {
970 req = req.header(http::header::AUTHORIZATION, &*auth_str);
971 }
972
973 if content.len() > 1 {
974 let b = bytes::Bytes::copy_from_slice(content);
975 req = req.body(b);
977 }
978
979 let response = req.send().await?;
980
981 let status = response.status();
982 let headers = response.headers().clone();
983
984 let response_body = response.bytes().await?;
985
986 if status.is_success() {
987 log::debug!("Received successful response. Read payload.");
988 let parsed_response = if status == http::StatusCode::NO_CONTENT
989 || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
990 {
991 serde_json::from_str("null")?
992 } else {
993 serde_json::from_slice::<Out>(&response_body)?
994 };
995 Ok(crate::Response::new(status, headers, parsed_response))
996 } else {
997 let error = if response_body.is_empty() {
998 ClientError::HttpError {
999 status,
1000 headers,
1001 error: "empty response".into(),
1002 }
1003 } else {
1004 ClientError::HttpError {
1005 status,
1006 headers,
1007 error: String::from_utf8_lossy(&response_body).into(),
1008 }
1009 };
1010
1011 Err(error)
1012 }
1013 }
1014
1015 async fn request_entity<D>(
1016 &self,
1017 method: http::Method,
1018 uri: &str,
1019 message: Message,
1020 ) -> ClientResult<crate::Response<D>>
1021 where
1022 D: serde::de::DeserializeOwned + 'static + Send,
1023 {
1024 let r = self.request(method, uri, message).await?;
1025 Ok(r)
1026 }
1027
1028 #[allow(dead_code)]
1029 async fn get<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
1030 where
1031 D: serde::de::DeserializeOwned + 'static + Send,
1032 {
1033 self.request_entity(http::Method::GET, uri, message).await
1034 }
1035
1036 #[allow(dead_code)]
1037 async fn get_all_pages<D>(&self, uri: &str, _message: Message) -> ClientResult<Response<Vec<D>>>
1038 where
1039 D: serde::de::DeserializeOwned + 'static + Send,
1040 {
1041 self.unfold(uri).await
1043 }
1044
1045 #[allow(dead_code)]
1047 async fn unfold<D>(&self, uri: &str) -> ClientResult<crate::Response<Vec<D>>>
1048 where
1049 D: serde::de::DeserializeOwned + 'static + Send,
1050 {
1051 let mut global_items = Vec::new();
1052 let (new_link, mut response) = self.get_pages(uri).await?;
1053 let mut link = new_link;
1054 while !response.body.is_empty() {
1055 global_items.append(&mut response.body);
1056 if let Some(url) = &link {
1058 let url = reqwest::Url::parse(&url.0)?;
1059 let (new_link, new_response) = self.get_pages_url(&url).await?;
1060 link = new_link;
1061 response = new_response;
1062 }
1063 }
1064
1065 Ok(Response::new(
1066 response.status,
1067 response.headers,
1068 global_items,
1069 ))
1070 }
1071
1072 #[allow(dead_code)]
1073 async fn get_pages<D>(
1074 &self,
1075 uri: &str,
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, uri, Message::default())
1081 .await
1082 }
1083
1084 #[allow(dead_code)]
1085 async fn get_pages_url<D>(
1086 &self,
1087 url: &reqwest::Url,
1088 ) -> ClientResult<(Option<crate::utils::NextLink>, crate::Response<Vec<D>>)>
1089 where
1090 D: serde::de::DeserializeOwned + 'static + Send,
1091 {
1092 self.request_with_links(http::Method::GET, url.as_str(), Message::default())
1093 .await
1094 }
1095
1096 #[allow(dead_code)]
1097 async fn post<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
1098 where
1099 D: serde::de::DeserializeOwned + 'static + Send,
1100 {
1101 self.request_entity(http::Method::POST, uri, message).await
1102 }
1103
1104 #[allow(dead_code)]
1105 async fn patch<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
1106 where
1107 D: serde::de::DeserializeOwned + 'static + Send,
1108 {
1109 self.request_entity(http::Method::PATCH, uri, message).await
1110 }
1111
1112 #[allow(dead_code)]
1113 async fn put<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
1114 where
1115 D: serde::de::DeserializeOwned + 'static + Send,
1116 {
1117 self.request_entity(http::Method::PUT, uri, message).await
1118 }
1119
1120 #[allow(dead_code)]
1121 async fn delete<D>(&self, uri: &str, message: Message) -> ClientResult<crate::Response<D>>
1122 where
1123 D: serde::de::DeserializeOwned + 'static + Send,
1124 {
1125 self.request_entity(http::Method::DELETE, uri, message)
1126 .await
1127 }
1128
1129 pub fn groups(&self) -> groups::Groups {
1131 groups::Groups::new(self.clone())
1132 }
1133}