use reqwest::StatusCode;
use serde::{de, Deserialize, Serialize};
#[derive(Debug, thiserror::Error)]
pub(crate) enum Error {
#[error("http error: {0}")]
Http(#[from] reqwest::Error),
#[error("Google error response: {0}")]
Google(#[from] GoogleError),
}
#[derive(Deserialize)]
#[serde(untagged)]
pub(crate) enum GoogleErrorReponse<T> {
Error { error: GoogleError },
Ok(T),
}
#[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,
}
impl GoogleError {
pub fn status_code(&self) -> StatusCode {
self.status
}
pub fn message(&self) -> &str {
&self.message
}
}
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()),
}
}
}
#[derive(Debug, Default, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Page<T> {
#[serde(default)]
pub next_page_token: Option<String>,
#[serde(default = "Vec::new")]
pub items: Vec<T>,
}
#[derive(Debug, Clone, Default, Deserialize, Hash, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Object {
pub id: String,
pub name: String,
#[serde(deserialize_with = "serde_with::rust::display_fromstr::deserialize")]
pub size: u64,
}
#[serde_with::skip_serializing_none]
#[derive(Debug, Default, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ListObjectOptions<'a> {
pub prefix: Option<&'a str>,
pub start_offset: Option<&'a str>,
pub end_offset: Option<&'a str>,
pub max_results: Option<u32>,
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()
}