use crate::async_client_trait::{
AppAuthClient, HttpClient, HttpRequest, HttpRequestResultRaw, NoauthClient, TeamAuthClient,
TeamSelect, UserAuthClient,
};
use crate::default_client_common::impl_set_path_root;
use crate::oauth2::{Authorization, TokenCache};
use crate::Error;
use bytes::Bytes;
use futures::{FutureExt, TryFutureExt, TryStreamExt};
use std::future::{ready, Future};
use std::str::FromStr;
use std::sync::Arc;
macro_rules! impl_update_token {
($self:ident) => {
fn update_token(&$self, old_token: Arc<String>)
-> impl Future<Output = Result<bool, Error>> + Send
{
info!("refreshing auth token");
$self.tokens
.update_token(
TokenUpdateClient { inner: &$self.inner },
old_token,
)
.map(|r| match r {
Ok(_) => Ok(true),
Err(e) => {
error!("failed to update auth token: {e}");
Err(e.into())
}
})
}
};
}
pub struct UserAuthDefaultClient {
inner: ReqwestClient,
tokens: Arc<TokenCache>,
path_root: Option<String>, }
impl UserAuthDefaultClient {
pub fn new(auth: Authorization) -> Self {
Self::from_token_cache(Arc::new(TokenCache::new(auth)))
}
pub fn from_token_cache(tokens: Arc<TokenCache>) -> Self {
Self {
inner: Default::default(),
tokens,
path_root: None,
}
}
impl_set_path_root!(self);
}
impl HttpClient for UserAuthDefaultClient {
type Request = ReqwestRequest;
fn execute(
&self,
request: Self::Request,
body: Bytes,
) -> impl Future<Output = Result<HttpRequestResultRaw, Error>> + Send {
self.inner.execute(request, body)
}
fn new_request(&self, url: &str) -> Self::Request {
self.inner.new_request(url)
}
impl_update_token!(self);
fn token(&self) -> Option<Arc<String>> {
self.tokens.get_token()
}
fn path_root(&self) -> Option<&str> {
self.path_root.as_deref()
}
}
impl UserAuthClient for UserAuthDefaultClient {}
pub struct TeamAuthDefaultClient {
inner: ReqwestClient,
tokens: Arc<TokenCache>,
path_root: Option<String>, team_select: Option<TeamSelect>,
}
impl TeamAuthDefaultClient {
pub fn new(tokens: impl Into<Arc<TokenCache>>) -> Self {
Self {
inner: Default::default(),
tokens: tokens.into(),
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 {
type Request = ReqwestRequest;
fn execute(
&self,
request: Self::Request,
body: Bytes,
) -> impl Future<Output = Result<HttpRequestResultRaw, Error>> + Send {
self.inner.execute(request, body)
}
fn new_request(&self, url: &str) -> Self::Request {
self.inner.new_request(url)
}
fn token(&self) -> Option<Arc<String>> {
self.tokens.get_token()
}
impl_update_token!(self);
fn path_root(&self) -> Option<&str> {
self.path_root.as_deref()
}
fn team_select(&self) -> Option<&TeamSelect> {
self.team_select.as_ref()
}
}
impl TeamAuthClient for TeamAuthDefaultClient {}
#[derive(Debug)]
pub struct AppAuthDefaultClient {
inner: ReqwestClient,
path_root: Option<String>,
auth: String,
}
impl AppAuthDefaultClient {
pub fn new(app_key: &str, app_secret: &str) -> Self {
use base64::prelude::*;
let encoded = BASE64_STANDARD.encode(format!("{app_key}:{app_secret}"));
Self {
inner: ReqwestClient::default(),
path_root: None,
auth: format!("Basic {encoded}"),
}
}
impl_set_path_root!(self);
}
impl HttpClient for AppAuthDefaultClient {
type Request = ReqwestRequest;
fn execute(
&self,
request: Self::Request,
body: Bytes,
) -> impl Future<Output = Result<HttpRequestResultRaw, Error>> + Send {
self.inner.execute(request, body)
}
fn new_request(&self, url: &str) -> Self::Request {
self.inner
.new_request(url)
.set_header("Authorization", &self.auth)
}
}
impl AppAuthClient for AppAuthDefaultClient {}
#[derive(Debug, Default)]
pub struct NoauthDefaultClient {
inner: ReqwestClient,
path_root: Option<String>,
}
impl NoauthDefaultClient {
impl_set_path_root!(self);
}
impl HttpClient for NoauthDefaultClient {
type Request = ReqwestRequest;
fn execute(
&self,
request: Self::Request,
body: Bytes,
) -> impl Future<Output = Result<HttpRequestResultRaw, Error>> + Send {
self.inner.execute(request, body)
}
fn new_request(&self, url: &str) -> Self::Request {
self.inner.new_request(url)
}
fn path_root(&self) -> Option<&str> {
self.path_root.as_deref()
}
}
impl NoauthClient for NoauthDefaultClient {}
struct TokenUpdateClient<'a> {
inner: &'a ReqwestClient,
}
impl HttpClient for TokenUpdateClient<'_> {
type Request = ReqwestRequest;
fn execute(
&self,
request: Self::Request,
body: Bytes,
) -> impl Future<Output = Result<HttpRequestResultRaw, Error>> + Send {
self.inner.execute(request, body)
}
fn new_request(&self, url: &str) -> Self::Request {
self.inner.new_request(url)
}
}
impl NoauthClient for TokenUpdateClient<'_> {}
#[derive(Debug)]
struct ReqwestClient {
inner: reqwest::Client,
}
impl Default for ReqwestClient {
fn default() -> Self {
Self {
inner: reqwest::Client::builder()
.https_only(true)
.http2_prior_knowledge()
.build()
.unwrap(),
}
}
}
fn unexpected<T: std::error::Error + Send + Sync>(e: T, msg: &str) -> Error {
Error::UnexpectedResponse(format!("{msg}: {e}"))
}
impl HttpClient for ReqwestClient {
type Request = ReqwestRequest;
fn execute(
&self,
request: Self::Request,
body: Bytes,
) -> impl Future<Output = Result<HttpRequestResultRaw, Error>> + Send {
let mut req = match request.req.build() {
Ok(req) => req,
Err(e) => {
return ready(Err(Error::HttpClient(Box::new(e)))).boxed();
}
};
debug!("request for {}", req.url());
if !body.is_empty() {
*req.body_mut() = Some(reqwest::Body::from(body));
}
self.inner
.execute(req)
.map_ok_or_else(
|e| Err(Error::HttpClient(Box::new(e))),
|resp| {
let status = resp.status().as_u16();
let result_header = resp
.headers()
.get("Dropbox-API-Result")
.map(|v| v.to_str())
.transpose()
.map_err(|e| unexpected(e, "invalid Dropbox-API-Result header"))?
.map(ToOwned::to_owned);
let content_length = resp
.headers()
.get("Content-Length")
.map(|v| {
v.to_str()
.map_err(|e| unexpected(e, "invalid Content-Length"))
.and_then(|s| {
u64::from_str(s)
.map_err(|e| unexpected(e, "invalid Content-Length"))
})
})
.transpose()?;
let body = resp
.bytes_stream()
.map_err(futures::io::Error::other)
.into_async_read();
Ok(HttpRequestResultRaw {
status,
result_header,
content_length,
body: Box::new(body),
})
},
)
.boxed()
}
fn new_request(&self, url: &str) -> Self::Request {
ReqwestRequest {
req: self.inner.post(url),
}
}
}
pub struct ReqwestRequest {
req: reqwest::RequestBuilder,
}
impl HttpRequest for ReqwestRequest {
fn set_header(mut self, name: &str, value: &str) -> Self {
self.req = self.req.header(name, value);
self
}
}