cloud-storage-lite 0.1.9

A simple, flexible Google Cloud Storage client.
Documentation
//! Contains [`Client`], and the [`BucketClient`] trait + impls.

pub(crate) mod bucket;

use std::sync::Arc;

use futures::Future;
use reqwest::{Method, RequestBuilder, Response, StatusCode, Url};

use crate::{errors::Error, token_provider::TokenProvider};

pub use bucket::{BucketClient, GcsBucketClient};

const GCS_API_URL: &str = "https://www.googleapis.com/storage/v1/";

/// A Google Cloud Storage client.
///
/// Note: This type does not need to be wrapped in an `Arc` as it uses it internally.
#[derive(Clone)]
pub struct Client {
    token_provider: Arc<dyn TokenProvider>,
    client: reqwest::Client,
    api_url: Arc<Url>,
}

impl Client {
    /// Creates a new GCS client.
    pub fn new(token_provider: impl TokenProvider + 'static) -> Self {
        Self::new_with_client(token_provider, Default::default())
    }

    /// Creates a new GCS client using a provided reqwest::Client.
    pub fn new_with_client(
        token_provider: impl TokenProvider + 'static,
        client: reqwest::Client,
    ) -> Self {
        Self {
            token_provider: Arc::new(token_provider),
            client,
            api_url: Arc::new(Url::parse(GCS_API_URL).unwrap()),
        }
    }

    /// Returns a `BucketClient` scoped to the provided bucket.
    pub fn into_bucket_client(self, bucket_name: String) -> bucket::GcsBucketClient {
        bucket::GcsBucketClient::new(self, bucket_name)
    }

    async fn make_request<B, F>(
        &self,
        method: &Method,
        path: &str,
        make_request: B,
    ) -> Result<Response, Error>
    where
        B: FnOnce(RequestBuilder) -> F,
        F: Future<Output = Result<Response, reqwest::Error>>,
    {
        self.make_request_to_url(
            method,
            &self.api_url.join(path).expect("malformed url"),
            make_request,
        )
        .await
    }

    async fn make_request_to_url<B, F>(
        &self,
        method: &Method,
        url: &Url,
        make_request: B,
    ) -> Result<Response, Error>
    where
        B: FnOnce(RequestBuilder) -> F,
        F: Future<Output = Result<Response, reqwest::Error>>,
    {
        let auth_token = self
            .token_provider
            .get_token()
            .await
            .map_err(Error::TokenFetch)?;
        let builder = self
            .client
            .request(method.clone(), url.clone())
            .bearer_auth(&auth_token.token);
        let res = make_request(builder).await?;
        if res.status() == StatusCode::UNAUTHORIZED {
            self.token_provider.invalidate_token().await;
        }
        Ok(res)
    }
}