cloud_storage_lite/
api.rs

1//! Rust types corresponding to those returned by the GCS API.
2
3use reqwest::StatusCode;
4use serde::{de, Deserialize, Serialize};
5
6#[derive(Debug, thiserror::Error)]
7pub(crate) enum Error {
8    #[error("http error: {0}")]
9    Http(#[from] reqwest::Error),
10
11    #[error("Google error response: {0}")]
12    Google(#[from] GoogleError),
13}
14
15#[derive(Deserialize)]
16#[serde(untagged)]
17pub(crate) enum GoogleErrorReponse<T> {
18    Error { error: GoogleError },
19    Ok(T),
20}
21
22/// A raw error returned by the GCS API. This is only returned when there's no more
23/// specific error known to this library (e.g., server error, but not `NotFound`).
24#[derive(Debug, Deserialize, thiserror::Error)]
25#[error("request failed with status {status} and message: {message}")]
26pub struct GoogleError {
27    #[serde(rename = "code", deserialize_with = "deserialize_status_code_from_u16")]
28    pub(crate) status: StatusCode,
29    pub(crate) message: String,
30}
31
32impl GoogleError {
33    /// Returns the HTTP status code associated with the Google error response.
34    pub fn status_code(&self) -> StatusCode {
35        self.status
36    }
37
38    /// Returns the error message, if any.
39    pub fn message(&self) -> &str {
40        &self.message
41    }
42}
43
44fn deserialize_status_code_from_u16<'de, D: de::Deserializer<'de>>(
45    d: D,
46) -> Result<StatusCode, D::Error> {
47    StatusCode::from_u16(u16::deserialize(d)?).map_err(de::Error::custom)
48}
49
50#[async_trait::async_trait]
51pub(crate) trait DecodeResponse {
52    async fn decode_response<T: de::DeserializeOwned + Default>(self) -> Result<T, Error>;
53}
54
55#[async_trait::async_trait]
56impl DecodeResponse for reqwest::Response {
57    async fn decode_response<T: de::DeserializeOwned + Default>(self) -> Result<T, Error> {
58        let res_status = self.status();
59        if res_status == StatusCode::NO_CONTENT {
60            return Ok(Default::default());
61        }
62        let res: GoogleErrorReponse<T> =
63            self.json()
64                .await
65                .unwrap_or_else(|_| GoogleErrorReponse::Error {
66                    error: GoogleError {
67                        status: res_status,
68                        message: "".into(),
69                    },
70                });
71        match res {
72            GoogleErrorReponse::Ok(t) => Ok(t),
73            GoogleErrorReponse::Error { error } => Err(error.into()),
74        }
75    }
76}
77
78/// A page of results.
79#[derive(Debug, Default, Clone, Deserialize)]
80#[serde(rename_all = "camelCase")]
81pub struct Page<T> {
82    /// If present, the value to set as the `page_token` when making a subsequent `list` call
83    /// that causes the next page of results to be fetched.
84    #[serde(default)]
85    pub next_page_token: Option<String>,
86
87    /// The returned results.
88    #[serde(default = "Vec::new")]
89    pub items: Vec<T>,
90}
91
92/// A parsimonious representation of an object's metadata.
93#[derive(Debug, Clone, Default, Deserialize, Hash, PartialEq, Eq)]
94#[serde(rename_all = "camelCase")]
95pub struct Object {
96    /// The object's fully qualified ID.
97    pub id: String,
98
99    /// The object's name.
100    pub name: String,
101
102    /// The object's size in bytes.
103    #[serde(deserialize_with = "serde_with::rust::display_fromstr::deserialize")]
104    pub size: u64,
105}
106
107/// Options for filtering which objects are returned from a `list` call.
108#[serde_with::skip_serializing_none]
109#[derive(Debug, Default, Clone, Serialize)]
110#[serde(rename_all = "camelCase")]
111pub struct ListObjectOptions<'a> {
112    /// Return only objects having keys starting with this prefix.
113    pub prefix: Option<&'a str>,
114
115    /// The start offset of results.
116    pub start_offset: Option<&'a str>,
117
118    /// The end offset of results.
119    pub end_offset: Option<&'a str>,
120
121    /// The maximum number of results to return.
122    pub max_results: Option<u32>,
123
124    /// The page token.
125    pub page_token: Option<&'a str>,
126}
127
128impl<'a> From<&'a str> for ListObjectOptions<'a> {
129    fn from(prefix: &'a str) -> Self {
130        Self {
131            prefix: Some(prefix),
132            ..Default::default()
133        }
134    }
135}
136
137const PERCENT_ENCODE_SET: &percent_encoding::AsciiSet = &percent_encoding::NON_ALPHANUMERIC
138    .remove(b'*')
139    .remove(b'-')
140    .remove(b'.')
141    .remove(b'_');
142
143pub(crate) fn percent_encode(input: &str) -> std::borrow::Cow<'_, str> {
144    percent_encoding::utf8_percent_encode(input, PERCENT_ENCODE_SET).into()
145}