use crate::error::{Error, Result};
use crate::resource::{DriveId, ItemId};
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 {
pub fn me() -> Self {
Self {
inner: DriveLocationEnum::Me,
}
}
pub fn from_user(id_or_principal_name: String) -> Self {
Self {
inner: DriveLocationEnum::User(id_or_principal_name),
}
}
pub fn from_group(group_id: String) -> Self {
Self {
inner: DriveLocationEnum::Group(group_id),
}
}
pub fn from_site(site_id: String) -> Self {
Self {
inner: DriveLocationEnum::Site(site_id),
}
}
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),
}
impl<'a> ItemLocation<'a> {
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
}
}
pub fn from_id(item_id: &'a ItemId) -> Self {
Self {
inner: ItemLocationEnum::Id(item_id.as_ref()),
}
}
pub fn root() -> Self {
Self {
inner: ItemLocationEnum::Path("/"),
}
}
}
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
}
}
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) {
use self::DriveLocationEnum::*;
match &self.inner {
Me => buf.push("drive"),
User(id) => buf.extend(&["users", id, "drive"]),
Group(id) => buf.extend(&["groups", id, "drive"]),
Site(id) => buf.extend(&["sites", id, "drive"]),
Id(id) => buf.extend(&["drives", id.as_ref()]),
};
}
}
impl ApiPathComponent for ItemLocation<'_> {
fn extend_into(&self, buf: &mut PathSegmentsMut) {
use self::ItemLocationEnum::*;
match &self.inner {
Path("/") => buf.push("root"),
Path(path) => buf.push(&["root:", path, ":"].join("")),
Id(id) => buf.extend(&["items", id]),
};
}
}
impl ApiPathComponent for str {
fn extend_into(&self, buf: &mut PathSegmentsMut) {
buf.push(self);
}
}
pub(crate) trait RequestBuilderExt: Sized {
fn opt_header(self, key: impl AsRef<str>, value: Option<impl AsRef<str>>) -> Self;
}
impl RequestBuilderExt for reqwest::RequestBuilder {
fn opt_header(self, key: impl AsRef<str>, value: Option<impl AsRef<str>>) -> Self {
match value {
Some(v) => self.header(key.as_ref(), v.as_ref()),
None => self,
}
}
}
pub(crate) trait ResponseExt: Sized {
fn check_status(self) -> Result<Self>;
fn parse<T: de::DeserializeOwned>(self) -> Result<T>;
fn parse_optional<T: de::DeserializeOwned>(self) -> Result<Option<T>>;
fn parse_no_content(self) -> Result<()>;
}
impl ResponseExt for reqwest::Response {
fn check_status(mut self) -> Result<Self> {
match self.error_for_status_ref() {
Ok(_) => Ok(self),
Err(source) => {
#[derive(Deserialize)]
struct ErrorResponse {
error: crate::resource::ErrorObject,
}
let response: ErrorResponse = self.json()?; Err(Error::from_response(source, Some(response.error)))
}
}
}
fn parse<T: de::DeserializeOwned>(self) -> Result<T> {
Ok(self.check_status()?.json()?)
}
fn parse_optional<T: de::DeserializeOwned>(self) -> Result<Option<T>> {
use reqwest::StatusCode;
match self.status() {
StatusCode::NOT_MODIFIED | StatusCode::ACCEPTED => Ok(None),
_ => Ok(Some(self.parse()?)),
}
}
fn parse_no_content(self) -> Result<()> {
self.check_status()?;
Ok(())
}
}