dropbox_sdk/
async_client_trait.rs

1//! Everything needed to implement your async HTTP client.
2
3pub use crate::client_trait_common::{HttpRequest, TeamSelect};
4use crate::Error;
5use bytes::Bytes;
6use futures::AsyncRead;
7use std::future::{ready, Future};
8use std::sync::Arc;
9
10/// The base HTTP asynchronous client trait.
11pub trait HttpClient: Sync {
12    /// The concrete type of request supported by the client.
13    type Request: HttpRequest + Send;
14
15    /// Make a HTTP request.
16    fn execute(
17        &self,
18        request: Self::Request,
19        body: Bytes,
20    ) -> impl Future<Output = Result<HttpRequestResultRaw, Error>> + Send;
21
22    /// Create a new request instance for the given URL. It should be a POST request.
23    fn new_request(&self, url: &str) -> Self::Request;
24
25    /// Attempt to update the current authentication token. The previously fetched token is given
26    /// as a way to avoid repeat updates in case of a race. If the update is successful, return
27    /// `true` and the current request will be retried with a newly-fetched token. Return `false` if
28    /// authentication is not supported, or return an error if the update operation fails.
29    fn update_token(
30        &self,
31        _old_token: Arc<String>,
32    ) -> impl Future<Output = Result<bool, Error>> + Send {
33        ready(Ok(false))
34    }
35
36    /// The client's current authentication token, if any.
37    fn token(&self) -> Option<Arc<String>> {
38        None
39    }
40
41    /// The currently set path root, if any.
42    fn path_root(&self) -> Option<&str> {
43        None
44    }
45
46    /// The alternate user or team context currently set, if any.
47    fn team_select(&self) -> Option<&TeamSelect> {
48        None
49    }
50
51    /// This should only be implemented by (or called on) the blanket impl for sync HTTP clients
52    /// implemented in this module.
53    ///
54    /// It's necessary because
55    ///   * there's no efficient way to implement an async client which takes a request body slice
56    ///     (making a Bytes involves a copy)
57    ///   * there IS a way to do it for sync clients
58    ///   * the signature of the sync upload routes takes the body this way
59    ///   * we don't want to break compatibility
60    ///
61    /// Only the sync routes take a body arg this way, and this logic only gets invoked for those,
62    /// so only the sync HTTP client wrapper needs to implement it.
63    #[doc(hidden)]
64    #[cfg(feature = "sync_routes")]
65    fn execute_borrowed_body(
66        &self,
67        _request: Self::Request,
68        _body_slice: &[u8],
69    ) -> impl Future<Output = Result<HttpRequestResultRaw, Error>> + Send {
70        unimplemented!();
71        #[allow(unreachable_code)] // otherwise it complains that `()` is not a future.
72        async move {
73            unimplemented!()
74        }
75    }
76}
77
78/// The raw response from the server, including an async streaming response body.
79pub struct HttpRequestResultRaw {
80    /// HTTP response code.
81    pub status: u16,
82
83    /// The value of the `Dropbox-API-Result` header, if present.
84    pub result_header: Option<String>,
85
86    /// The value of the `Content-Length` header, if present.
87    pub content_length: Option<u64>,
88
89    /// The response body stream.
90    pub body: Box<dyn AsyncRead + Send + Unpin>,
91}
92
93/// The response from the server, parsed into a given type, including a body stream if it is from
94/// a Download style request.
95pub struct HttpRequestResult<T> {
96    /// The API result, parsed into the given type.
97    pub result: T,
98
99    /// The value of the `Content-Length` header in the response, if any. Only expected to not be
100    /// `None` if `body` is also not `None`.
101    pub content_length: Option<u64>,
102
103    /// The response body stream, if any. Only expected to not be `None` for
104    /// [`Style::Download`](crate::client_trait_common::Style::Download) endpoints.
105    pub body: Option<Box<dyn AsyncRead + Unpin + Send>>,
106}
107
108/// Blanket implementation of the async interface for all sync clients.
109/// This is necessary because all the machinery is actually implemented in terms of the async
110/// client.
111#[cfg(feature = "sync_routes")]
112impl<T: crate::client_trait::HttpClient + Sync> HttpClient for T {
113    type Request = T::Request;
114
115    async fn execute(
116        &self,
117        request: Self::Request,
118        body: Bytes,
119    ) -> Result<HttpRequestResultRaw, Error> {
120        self.execute_borrowed_body(request, &body).await
121    }
122
123    async fn execute_borrowed_body(
124        &self,
125        request: Self::Request,
126        body_slice: &[u8],
127    ) -> Result<HttpRequestResultRaw, Error> {
128        self.execute(request, body_slice)
129            .map(|r| HttpRequestResultRaw {
130                status: r.status,
131                result_header: r.result_header,
132                content_length: r.content_length,
133                body: Box::new(SyncReadAdapter { inner: r.body }),
134            })
135    }
136
137    fn new_request(&self, url: &str) -> Self::Request {
138        self.new_request(url)
139    }
140
141    fn update_token(
142        &self,
143        old_token: Arc<String>,
144    ) -> impl Future<Output = Result<bool, Error>> + Send {
145        ready(self.update_token(old_token))
146    }
147
148    fn token(&self) -> Option<Arc<String>> {
149        self.token()
150    }
151
152    fn path_root(&self) -> Option<&str> {
153        self.path_root()
154    }
155
156    fn team_select(&self) -> Option<&TeamSelect> {
157        self.team_select()
158    }
159}
160
161/// Marker trait to indicate that a HTTP client supports unauthenticated routes.
162pub trait NoauthClient: HttpClient {}
163
164/// Marker trait to indicate that a HTTP client supports User authentication.
165/// User authentication works by adding a `Authorization: Bearer <TOKEN>` header.
166pub trait UserAuthClient: HttpClient {}
167
168/// Marker trait to indicate that a HTTP client supports Team authentication.
169/// Team authentication works by adding a `Authorization: Bearer <TOKEN>` header, and optionally a
170/// `Dropbox-API-Select-Admin` or `Dropbox-API-Select-User` header.
171pub trait TeamAuthClient: HttpClient {}
172
173/// Marker trait to indicate that a HTTP client supports App authentication.
174/// App authentication works by adding a `Authorization: Basic <base64(APP_KEY:APP_SECRET)>` header
175/// to the HTTP request.
176pub trait AppAuthClient: HttpClient {}
177
178// blanket impls to convert the sync marker traits to the async ones:
179#[cfg(feature = "sync_routes")]
180impl<T: crate::client_trait::NoauthClient + Sync> NoauthClient for T {}
181#[cfg(feature = "sync_routes")]
182impl<T: crate::client_trait::UserAuthClient + Sync> UserAuthClient for T {}
183#[cfg(feature = "sync_routes")]
184impl<T: crate::client_trait::TeamAuthClient + Sync> TeamAuthClient for T {}
185#[cfg(feature = "sync_routes")]
186impl<T: crate::client_trait::AppAuthClient + Sync> AppAuthClient for T {}
187
188#[cfg(feature = "sync_routes")]
189pub(crate) struct SyncReadAdapter {
190    pub inner: Box<dyn std::io::Read + Send>,
191}
192
193#[cfg(feature = "sync_routes")]
194impl AsyncRead for SyncReadAdapter {
195    fn poll_read(
196        mut self: std::pin::Pin<&mut Self>,
197        _cx: &mut std::task::Context<'_>,
198        buf: &mut [u8],
199    ) -> std::task::Poll<std::io::Result<usize>> {
200        std::task::Poll::Ready(std::io::Read::read(&mut self.inner, buf))
201    }
202
203    fn poll_read_vectored(
204        mut self: std::pin::Pin<&mut Self>,
205        _cx: &mut std::task::Context<'_>,
206        bufs: &mut [std::io::IoSliceMut<'_>],
207    ) -> std::task::Poll<std::io::Result<usize>> {
208        std::task::Poll::Ready(std::io::Read::read_vectored(&mut self.inner, bufs))
209    }
210}