cloud_storage_lite/client/
mod.rs

1//! Contains [`Client`], and the [`BucketClient`] trait + impls.
2
3pub(crate) mod bucket;
4
5use std::sync::Arc;
6
7use futures::Future;
8use reqwest::{Method, RequestBuilder, Response, StatusCode, Url};
9
10use crate::{errors::Error, token_provider::TokenProvider};
11
12pub use bucket::{BucketClient, GcsBucketClient};
13
14const GCS_API_URL: &str = "https://www.googleapis.com/storage/v1/";
15
16/// A Google Cloud Storage client.
17///
18/// Note: This type does not need to be wrapped in an `Arc` as it uses it internally.
19#[derive(Clone)]
20pub struct Client {
21    token_provider: Arc<dyn TokenProvider>,
22    client: reqwest::Client,
23    api_url: Arc<Url>,
24}
25
26impl Client {
27    /// Creates a new GCS client.
28    pub fn new(token_provider: impl TokenProvider + 'static) -> Self {
29        Self::new_with_client(token_provider, Default::default())
30    }
31
32    /// Creates a new GCS client using a provided reqwest::Client.
33    pub fn new_with_client(
34        token_provider: impl TokenProvider + 'static,
35        client: reqwest::Client,
36    ) -> Self {
37        Self {
38            token_provider: Arc::new(token_provider),
39            client,
40            api_url: Arc::new(Url::parse(GCS_API_URL).unwrap()),
41        }
42    }
43
44    /// Returns a `BucketClient` scoped to the provided bucket.
45    pub fn into_bucket_client(self, bucket_name: String) -> bucket::GcsBucketClient {
46        bucket::GcsBucketClient::new(self, bucket_name)
47    }
48
49    async fn make_request<B, F>(
50        &self,
51        method: &Method,
52        path: &str,
53        make_request: B,
54    ) -> Result<Response, Error>
55    where
56        B: FnOnce(RequestBuilder) -> F,
57        F: Future<Output = Result<Response, reqwest::Error>>,
58    {
59        self.make_request_to_url(
60            method,
61            &self.api_url.join(path).expect("malformed url"),
62            make_request,
63        )
64        .await
65    }
66
67    async fn make_request_to_url<B, F>(
68        &self,
69        method: &Method,
70        url: &Url,
71        make_request: B,
72    ) -> Result<Response, Error>
73    where
74        B: FnOnce(RequestBuilder) -> F,
75        F: Future<Output = Result<Response, reqwest::Error>>,
76    {
77        let auth_token = self
78            .token_provider
79            .get_token()
80            .await
81            .map_err(Error::TokenFetch)?;
82        let builder = self
83            .client
84            .request(method.clone(), url.clone())
85            .bearer_auth(&auth_token.token);
86        let res = make_request(builder).await?;
87        if res.status() == StatusCode::UNAUTHORIZED {
88            self.token_provider.invalidate_token().await;
89        }
90        Ok(res)
91    }
92}