cloud_storage_lite/
api.rs1use 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#[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 pub fn status_code(&self) -> StatusCode {
35 self.status
36 }
37
38 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#[derive(Debug, Default, Clone, Deserialize)]
80#[serde(rename_all = "camelCase")]
81pub struct Page<T> {
82 #[serde(default)]
85 pub next_page_token: Option<String>,
86
87 #[serde(default = "Vec::new")]
89 pub items: Vec<T>,
90}
91
92#[derive(Debug, Clone, Default, Deserialize, Hash, PartialEq, Eq)]
94#[serde(rename_all = "camelCase")]
95pub struct Object {
96 pub id: String,
98
99 pub name: String,
101
102 #[serde(deserialize_with = "serde_with::rust::display_fromstr::deserialize")]
104 pub size: u64,
105}
106
107#[serde_with::skip_serializing_none]
109#[derive(Debug, Default, Clone, Serialize)]
110#[serde(rename_all = "camelCase")]
111pub struct ListObjectOptions<'a> {
112 pub prefix: Option<&'a str>,
114
115 pub start_offset: Option<&'a str>,
117
118 pub end_offset: Option<&'a str>,
120
121 pub max_results: Option<u32>,
123
124 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}