1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//! Rust types corresponding to those returned by the GCS API.

use reqwest::StatusCode;
use serde::{de, Deserialize, Serialize};

#[derive(Debug, thiserror::Error)]
pub(crate) enum Error {
    #[error(transparent)]
    Http(#[from] reqwest::Error),

    #[error(transparent)]
    Google(#[from] GoogleError),
}

#[derive(Deserialize)]
#[serde(untagged)]
pub(crate) enum GoogleErrorReponse<T> {
    Error { error: GoogleError },
    Ok(T),
}

/// A raw error returned by the GCS API. This is only returned when there's no more
/// specific error known to this library (e.g., server error, but not `NotFound`).
#[derive(Debug, Deserialize, thiserror::Error)]
#[error("request failed with status {status} and message: {message}")]
pub struct GoogleError {
    #[serde(rename = "code", deserialize_with = "deserialize_status_code_from_u16")]
    pub(crate) status: StatusCode,
    pub(crate) message: String,
}

fn deserialize_status_code_from_u16<'de, D: de::Deserializer<'de>>(
    d: D,
) -> Result<StatusCode, D::Error> {
    StatusCode::from_u16(u16::deserialize(d)?).map_err(de::Error::custom)
}

#[async_trait::async_trait]
pub(crate) trait DecodeResponse {
    async fn decode_response<T: de::DeserializeOwned + Default>(self) -> Result<T, Error>;
}

#[async_trait::async_trait]
impl DecodeResponse for reqwest::Response {
    async fn decode_response<T: de::DeserializeOwned + Default>(self) -> Result<T, Error> {
        let res_status = self.status();
        if res_status == StatusCode::NO_CONTENT {
            return Ok(Default::default());
        }
        let res: GoogleErrorReponse<T> =
            self.json()
                .await
                .unwrap_or_else(|_| GoogleErrorReponse::Error {
                    error: GoogleError {
                        status: res_status,
                        message: "".into(),
                    },
                });
        match res {
            GoogleErrorReponse::Ok(t) => Ok(t),
            GoogleErrorReponse::Error { error } => Err(error.into()),
        }
    }
}

/// A page of results.
#[derive(Debug, Default, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct Page<T> {
    /// If present, the value to set as the `page_token` when making a subsequent `list` call
    /// that causes the next page of results to be fetched.
    #[serde(default)]
    pub next_page_token: Option<String>,

    /// The returned results.
    #[serde(default = "Vec::new")]
    pub items: Vec<T>,
}

/// A parsimonious representation of an object's metadata.
#[derive(Debug, Clone, Default, Deserialize, Hash, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct Object {
    /// The object's fully qualified ID.
    pub id: String,

    /// The object's name.
    pub name: String,

    /// The object's size in bytes.
    #[serde(deserialize_with = "serde_with::rust::display_fromstr::deserialize")]
    pub size: u64,
}

/// Options for filtering which objects are returned from a `list` call.
#[serde_with::skip_serializing_none]
#[derive(Debug, Default, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ListObjectOptions<'a> {
    /// Return only objects having keys starting with this prefix.
    pub prefix: Option<&'a str>,

    /// The start offset of results.
    pub start_offset: Option<&'a str>,

    /// The end offset of results.
    pub end_offset: Option<&'a str>,

    /// The maximum number of results to return.
    pub max_results: Option<u32>,

    /// The page token.
    pub page_token: Option<&'a str>,
}

impl<'a> From<&'a str> for ListObjectOptions<'a> {
    fn from(prefix: &'a str) -> Self {
        Self {
            prefix: Some(prefix),
            ..Default::default()
        }
    }
}

const PERCENT_ENCODE_SET: &percent_encoding::AsciiSet = &percent_encoding::NON_ALPHANUMERIC
    .remove(b'*')
    .remove(b'-')
    .remove(b'.')
    .remove(b'_');

pub(crate) fn percent_encode(input: &str) -> std::borrow::Cow<'_, str> {
    percent_encoding::utf8_percent_encode(input, PERCENT_ENCODE_SET).into()
}