use crate::{
error::{Error, Result},
resource::{DriveId, ErrorResponse, ItemId, OAuth2ErrorResponse},
};
use reqwest::{header, RequestBuilder, Response, StatusCode};
use serde::{de, Deserialize};
use url::PathSegmentsMut;
#[derive(Clone, Debug)]
pub struct DriveLocation {
inner: DriveLocationEnum,
}
#[derive(Clone, Debug)]
enum DriveLocationEnum {
Me,
User(String),
Group(String),
Site(String),
Id(DriveId),
}
impl DriveLocation {
#[must_use]
pub fn me() -> Self {
Self {
inner: DriveLocationEnum::Me,
}
}
pub fn from_user(id_or_principal_name: impl Into<String>) -> Self {
Self {
inner: DriveLocationEnum::User(id_or_principal_name.into()),
}
}
pub fn from_group(group_id: impl Into<String>) -> Self {
Self {
inner: DriveLocationEnum::Group(group_id.into()),
}
}
pub fn from_site(site_id: impl Into<String>) -> Self {
Self {
inner: DriveLocationEnum::Site(site_id.into()),
}
}
#[must_use]
pub fn from_id(drive_id: DriveId) -> Self {
Self {
inner: DriveLocationEnum::Id(drive_id),
}
}
}
impl From<DriveId> for DriveLocation {
fn from(id: DriveId) -> Self {
Self::from_id(id)
}
}
#[derive(Clone, Copy, Debug)]
pub struct ItemLocation<'a> {
inner: ItemLocationEnum<'a>,
}
#[derive(Clone, Copy, Debug)]
enum ItemLocationEnum<'a> {
Path(&'a str),
Id(&'a str),
ChildOfId {
parent_id: &'a str,
child_name: &'a str,
},
}
impl<'a> ItemLocation<'a> {
#[must_use]
pub fn from_path(path: &'a str) -> Option<Self> {
if path == "/" {
Some(Self::root())
} else if path.starts_with('/')
&& path[1..]
.split_terminator('/')
.all(|comp| !comp.is_empty() && FileName::new(comp).is_some())
{
Some(Self {
inner: ItemLocationEnum::Path(path),
})
} else {
None
}
}
#[must_use]
pub fn from_id(item_id: &'a ItemId) -> Self {
Self {
inner: ItemLocationEnum::Id(item_id.as_str()),
}
}
#[must_use]
pub fn root() -> Self {
Self {
inner: ItemLocationEnum::Path("/"),
}
}
#[must_use]
pub fn child_of_id(parent_id: &'a ItemId, child_name: &'a FileName) -> Self {
Self {
inner: ItemLocationEnum::ChildOfId {
parent_id: parent_id.as_str(),
child_name: child_name.as_str(),
},
}
}
}
impl<'a> From<&'a ItemId> for ItemLocation<'a> {
fn from(id: &'a ItemId) -> Self {
Self::from_id(id)
}
}
#[derive(Debug)]
pub struct FileName(str);
impl FileName {
pub fn new<S: AsRef<str> + ?Sized>(name: &S) -> Option<&Self> {
const INVALID_CHARS: &str = r#""*:<>?/\|"#;
let name = name.as_ref();
if !name.is_empty() && !name.contains(|c| INVALID_CHARS.contains(c)) {
Some(unsafe { &*(name as *const str as *const Self) })
} else {
None
}
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl AsRef<str> for FileName {
fn as_ref(&self) -> &str {
self.as_str()
}
}
pub(crate) trait ApiPathComponent {
fn extend_into(&self, buf: &mut PathSegmentsMut);
}
impl ApiPathComponent for DriveLocation {
fn extend_into(&self, buf: &mut PathSegmentsMut) {
match &self.inner {
DriveLocationEnum::Me => buf.extend(&["me", "drive"]),
DriveLocationEnum::User(id) => buf.extend(&["users", id, "drive"]),
DriveLocationEnum::Group(id) => buf.extend(&["groups", id, "drive"]),
DriveLocationEnum::Site(id) => buf.extend(&["sites", id, "drive"]),
DriveLocationEnum::Id(id) => buf.extend(&["drives", id.as_str()]),
};
}
}
impl ApiPathComponent for ItemLocation<'_> {
fn extend_into(&self, buf: &mut PathSegmentsMut) {
match &self.inner {
ItemLocationEnum::Path("/") => buf.push("root"),
ItemLocationEnum::Path(path) => buf.push(&["root:", path, ":"].join("")),
ItemLocationEnum::Id(id) => buf.extend(&["items", id]),
ItemLocationEnum::ChildOfId {
parent_id,
child_name,
} => buf.extend(&["items", parent_id, "children", child_name]),
};
}
}
impl ApiPathComponent for str {
fn extend_into(&self, buf: &mut PathSegmentsMut) {
buf.push(self);
}
}
pub(crate) trait RequestBuilderTransformer {
fn trans(self, req: RequestBuilder) -> RequestBuilder;
}
pub(crate) trait RequestBuilderExt: Sized {
fn apply(self, trans: impl RequestBuilderTransformer) -> Self;
}
impl RequestBuilderExt for RequestBuilder {
fn apply(self, trans: impl RequestBuilderTransformer) -> Self {
trans.trans(self)
}
}
type BoxFuture<T> = std::pin::Pin<Box<dyn std::future::Future<Output = T> + Send + 'static>>;
pub(crate) trait ResponseExt: Sized {
fn parse<T: de::DeserializeOwned>(self) -> BoxFuture<Result<T>>;
fn parse_optional<T: de::DeserializeOwned>(self) -> BoxFuture<Result<Option<T>>>;
fn parse_no_content(self) -> BoxFuture<Result<()>>;
}
impl ResponseExt for Response {
fn parse<T: de::DeserializeOwned>(self) -> BoxFuture<Result<T>> {
Box::pin(async move { Ok(handle_error_response(self).await?.json().await?) })
}
fn parse_optional<T: de::DeserializeOwned>(self) -> BoxFuture<Result<Option<T>>> {
Box::pin(async move {
match self.status() {
StatusCode::NOT_MODIFIED | StatusCode::ACCEPTED => Ok(None),
_ => Ok(Some(handle_error_response(self).await?.json().await?)),
}
})
}
fn parse_no_content(self) -> BoxFuture<Result<()>> {
Box::pin(async move {
handle_error_response(self).await?;
Ok(())
})
}
}
pub(crate) async fn handle_error_response(resp: Response) -> Result<Response> {
#[derive(Deserialize)]
struct Resp {
error: ErrorResponse,
}
let status = resp.status();
if status.is_success() || status.is_redirection() {
Ok(resp)
} else {
let retry_after = parse_retry_after_sec(&resp);
let resp: Resp = resp.json().await?;
Err(Error::from_error_response(status, resp.error, retry_after))
}
}
pub(crate) async fn handle_oauth2_error_response(resp: Response) -> Result<Response> {
let status = resp.status();
if status.is_success() {
Ok(resp)
} else {
let retry_after = parse_retry_after_sec(&resp);
let resp: OAuth2ErrorResponse = resp.json().await?;
Err(Error::from_oauth2_error_response(status, resp, retry_after))
}
}
fn parse_retry_after_sec(resp: &Response) -> Option<u32> {
resp.headers()
.get(header::RETRY_AFTER)?
.to_str()
.ok()?
.parse()
.ok()
}