use crate::Error;
use crate::client_trait::*;
const USER_AGENT: &str = concat!("Dropbox-APIv2-Rust/", env!("CARGO_PKG_VERSION"));
macro_rules! forward_request {
($self:ident, $inner:expr, $token:expr, $path_root:expr, $team_select:expr) => {
fn request(
&$self,
endpoint: Endpoint,
style: Style,
function: &str,
params: String,
params_type: ParamsType,
body: Option<&[u8]>,
range_start: Option<u64>,
range_end: Option<u64>,
) -> crate::Result<HttpRequestResultRaw> {
$inner.request(endpoint, style, function, params, params_type, body, range_start,
range_end, $token, $path_root, $team_select)
}
}
}
macro_rules! impl_set_path_root {
($self:ident) => {
#[cfg(feature = "dbx_common")]
pub fn set_path_root(&mut $self, path_root: &crate::common::PathRoot) {
$self.path_root = Some(serde_json::to_string(path_root).expect("invalid path root"));
}
}
}
pub struct UserAuthDefaultClient {
inner: UreqClient,
token: String,
path_root: Option<String>,
}
impl UserAuthDefaultClient {
pub fn new(token: String) -> Self {
Self {
inner: UreqClient::default(),
token,
path_root: None,
}
}
impl_set_path_root!(self);
}
impl HttpClient for UserAuthDefaultClient {
forward_request! { self, self.inner, Some(&self.token), self.path_root.as_deref(), None }
}
impl UserAuthClient for UserAuthDefaultClient {}
pub struct TeamAuthDefaultClient {
inner: UreqClient,
token: String,
path_root: Option<String>,
team_select: Option<TeamSelect>,
}
impl TeamAuthDefaultClient {
pub fn new(token: String) -> Self {
Self {
inner: UreqClient::default(),
token,
path_root: None,
team_select: None,
}
}
pub fn select(&mut self, team_select: Option<TeamSelect>) {
self.team_select = team_select;
}
impl_set_path_root!(self);
}
impl HttpClient for TeamAuthDefaultClient {
forward_request! { self, self.inner, Some(&self.token), self.path_root.as_deref(), self.team_select.as_ref() }
}
impl TeamAuthClient for TeamAuthDefaultClient {}
#[derive(Debug, Default)]
pub struct NoauthDefaultClient {
inner: UreqClient,
path_root: Option<String>,
}
impl NoauthDefaultClient {
impl_set_path_root!(self);
}
impl HttpClient for NoauthDefaultClient {
forward_request! { self, self.inner, None, self.path_root.as_deref(), None }
}
impl NoauthClient for NoauthDefaultClient {}
#[derive(Debug, Default)]
struct UreqClient {}
impl UreqClient {
#[allow(clippy::too_many_arguments)]
fn request(
&self,
endpoint: Endpoint,
style: Style,
function: &str,
params: String,
params_type: ParamsType,
body: Option<&[u8]>,
range_start: Option<u64>,
range_end: Option<u64>,
token: Option<&str>,
path_root: Option<&str>,
team_select: Option<&TeamSelect>,
) -> crate::Result<HttpRequestResultRaw> {
let url = endpoint.url().to_owned() + function;
debug!("request for {:?}", url);
let mut req = ureq::post(&url)
.set("User-Agent", USER_AGENT);
if let Some(token) = token {
req = req.set("Authorization", &format!("Bearer {}", token));
}
if let Some(path_root) = path_root {
req = req.set("Dropbox-API-Path-Root", path_root);
}
if let Some(team_select) = team_select {
req = match team_select {
TeamSelect::User(id) => req.set("Dropbox-API-Select-User", id),
TeamSelect::Admin(id) => req.set("Dropbox-API-Select-Admin", id),
};
}
req = match (range_start, range_end) {
(Some(start), Some(end)) => req.set("Range", &format!("bytes={}-{}", start, end)),
(Some(start), None) => req.set("Range", &format!("bytes={}-", start)),
(None, Some(end)) => req.set("Range", &format!("bytes=-{}", end)),
(None, None) => req,
};
let result = if params.is_empty() {
req.call()
} else {
match style {
Style::Rpc => {
req = req.set("Content-Type", params_type.content_type());
req.send_string(¶ms)
}
Style::Upload | Style::Download => {
req = req.set("Dropbox-API-Arg", ¶ms);
if style == Style::Upload {
req = req.set("Content-Type", "application/octet-stream");
if let Some(body) = body {
req.send_bytes(body)
} else {
req.send_bytes(&[])
}
} else {
assert!(body.is_none(), "body can only be set for Style::Upload request");
req.call()
}
}
}
};
let resp = match result {
Ok(resp) => resp,
Err(e @ ureq::Error::Transport(_)) => {
error!("request failed: {}", e);
return Err(RequestError { inner: e }.into());
}
Err(ureq::Error::Status(code, resp)) => {
let status = resp.status_text().to_owned();
let json = resp.into_string()?;
return Err(Error::UnexpectedHttpError {
code,
status,
json,
});
}
};
match style {
Style::Rpc | Style::Upload => {
let result_json = resp.into_string()?;
Ok(HttpRequestResultRaw {
result_json,
content_length: None,
body: None,
})
}
Style::Download => {
let result_json = resp.header("Dropbox-API-Result")
.ok_or(Error::UnexpectedResponse("missing Dropbox-API-Result header"))?
.to_owned();
let content_length = match resp.header("Content-Length") {
Some(s) => Some(s.parse()
.map_err(|_| Error::UnexpectedResponse("invalid Content-Length header"))?),
None => None,
};
Ok(HttpRequestResultRaw {
result_json,
content_length,
body: Some(Box::new(resp.into_reader())),
})
}
}
}
}
#[derive(thiserror::Error, Debug)]
#[allow(clippy::large_enum_variant)]
pub enum DefaultClientError {
#[error("invalid UTF-8 string")]
Utf8(#[from] std::string::FromUtf8Error),
#[error("I/O error: {0}")]
IO(#[from] std::io::Error),
#[error(transparent)]
Request(#[from] RequestError),
}
macro_rules! wrap_error {
($e:ty) => {
impl From<$e> for crate::Error {
fn from(e: $e) -> Self {
Self::HttpClient(Box::new(DefaultClientError::from(e)))
}
}
}
}
wrap_error!(std::io::Error);
wrap_error!(std::string::FromUtf8Error);
wrap_error!(RequestError);
pub struct RequestError {
inner: ureq::Error,
}
impl std::fmt::Display for RequestError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
<ureq::Error as std::fmt::Display>::fmt(&self.inner, f)
}
}
impl std::fmt::Debug for RequestError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
<ureq::Error as std::fmt::Debug>::fmt(&self.inner, f)
}
}
impl std::error::Error for RequestError {
fn cause(&self) -> Option<&dyn std::error::Error> {
Some(&self.inner)
}
}