rust_dropbox/
client.rs

1use crate::{
2    DbxRequestErrorSummary, DbxRequestLimitsErrorSummary, DropboxError, DropboxResult,
3    MoveCopyOption, UploadMode, UploadOption,
4};
5#[cfg(feature = "non-blocking")]
6use async_trait::async_trait;
7#[cfg(feature = "non-blocking")]
8use reqwest::{header, header::HeaderMap, StatusCode};
9use serde_json::json;
10#[cfg(feature = "blocking")]
11use std::io::Read;
12use std::time;
13
14const CONTENT_END_POINT: &str = "https://content.dropboxapi.com";
15const OPERATION_END_POINT: &str = "https://api.dropboxapi.com";
16
17#[cfg(feature = "non-blocking")]
18#[derive(Debug, Clone)]
19pub struct AsyncDBXClient {
20    client: reqwest::Client,
21}
22
23#[cfg(feature = "non-blocking")]
24impl AsyncDBXClient {
25    pub fn new(token: &str) -> Self {
26        let mut auth_value = header::HeaderValue::from_str(&format!("Bearer {}", token)).unwrap();
27        auth_value.set_sensitive(true);
28        let mut headers = HeaderMap::new();
29        headers.insert(header::AUTHORIZATION, auth_value);
30        let client = reqwest::ClientBuilder::new()
31            .connect_timeout(time::Duration::from_secs(100))
32            .default_headers(headers)
33            .build()
34            .unwrap();
35        Self { client }
36    }
37
38    pub async fn check_user(&self, ping_str: &str) -> DropboxResult<()> {
39        let url = format!("{}{}", OPERATION_END_POINT, "/2/check/user");
40        let res = self
41            .client
42            .post(&url)
43            .header("Content-Type", "application/json")
44            .body(
45                json!(
46                {
47                    "query":ping_str,
48                }
49                )
50                .to_string(),
51            )
52            .send()
53            .await?;
54        match res.status() {
55            reqwest::StatusCode::BAD_REQUEST => {
56                let text = res.text().await?;
57                return Err(DropboxError::DbxInvalidTokenError(text));
58            }
59            _ => handle_async_dbx_request_response(res).await,
60        }
61    }
62    ///binding /upload
63    pub async fn upload(
64        &self,
65        file: Vec<u8>,
66        path: &str,
67        option: UploadOption,
68    ) -> DropboxResult<()> {
69        let mode = match option.mode {
70            UploadMode::Add => "add".to_string(),
71            UploadMode::Overwrite => "overwrite".to_string(),
72            UploadMode::Update(ref rev) => json!({
73                ".tag":"update",
74                "update":rev
75            })
76            .to_string(),
77        };
78        let url = format!("{}{}", CONTENT_END_POINT, "/2/files/upload");
79        let res = self
80            .client
81            .post(&url)
82            .header("Content-Type", "application/octet-stream")
83            .header(
84                "Dropbox-API-Arg",
85                json!({
86                    "path":path,
87                    "mode":mode,
88                    "autorename":option.allow_auto_rename,
89                    "mute":option.mute_notification,
90                    "strict_conflict":option.allow_strict_conflict
91                })
92                .to_string(),
93            )
94            .body(file)
95            .send()
96            .await?;
97        handle_async_dbx_request_response(res).await
98    }
99
100    ///binding /download
101    pub async fn download(&self, path: &str) -> DropboxResult<Vec<u8>> {
102        let url = format!("{}{}", CONTENT_END_POINT, "/2/files/download");
103        let res = self
104            .client
105            .post(&url)
106            .header("Dropbox-API-Arg", json!({ "path": path }).to_string())
107            .send()
108            .await?;
109        handle_async_dbx_request_response(res).await
110    }
111
112    // binding /move_v2
113    pub async fn move_file(
114        &self,
115        from_path: &str,
116        to_path: &str,
117        option: MoveCopyOption,
118    ) -> DropboxResult<()> {
119        let url = format!("{}{}", OPERATION_END_POINT, "/2/files/move_v2");
120        let res = self
121            .client
122            .post(&url)
123            .header("Content-Type", "application/json")
124            .body(
125                json!(
126                {
127                    "from_path":from_path,
128                    "to_path":to_path,
129                    "allow_shared_folder": option.allow_shared_folder,
130                    "autorename": option.auto_rename,
131                    "allow_ownership_transfer": option.allow_ownership_transfer
132                }
133                )
134                .to_string(),
135            )
136            .send()
137            .await?;
138        handle_async_dbx_request_response(res).await
139    }
140
141    pub async fn copy(
142        &self,
143        from_path: &str,
144        to_path: &str,
145        option: MoveCopyOption,
146    ) -> DropboxResult<()> {
147        let url = format!("{}{}", OPERATION_END_POINT, "/2/files/copy_v2");
148        let res = self
149            .client
150            .post(&url)
151            .header("Content-Type", "application/json")
152            .body(
153                json!(
154                {
155                    "from_path":from_path,
156                    "to_path":to_path,
157                    "allow_shared_folder": option.allow_shared_folder,
158                    "autorename": option.auto_rename,
159                    "allow_ownership_transfer": option.allow_ownership_transfer
160                }
161                )
162                .to_string(),
163            )
164            .send()
165            .await?;
166        handle_async_dbx_request_response(res).await
167    }
168}
169
170#[inline]
171#[cfg(feature = "non-blocking")]
172async fn handle_async_dbx_request_response<T: AsyncFrom<reqwest::Response>>(
173    res: reqwest::Response,
174) -> DropboxResult<T> {
175    if res.status() != StatusCode::OK {
176        match res.status() {
177            StatusCode::BAD_REQUEST => {
178                let text = res.text().await?;
179                return Err(DropboxError::DbxPathError(text));
180            }
181            StatusCode::UNAUTHORIZED => {
182                let error_summary = res.json::<DbxRequestErrorSummary>().await?;
183                return Err(DropboxError::DbxInvalidTokenError(
184                    error_summary.error_summary,
185                ));
186            }
187            StatusCode::FORBIDDEN => {
188                let error_summary = res.json::<DbxRequestErrorSummary>().await?;
189                return Err(DropboxError::DbxAccessError(error_summary.error_summary));
190            }
191            StatusCode::CONFLICT => {
192                let error_summary = res.json::<DbxRequestErrorSummary>().await?;
193                let content: Vec<&str> = error_summary.error_summary.split("/").collect();
194                match content[0] == "path" {
195                    true => return Err(DropboxError::DbxPathError(content[1].to_string())),
196                    false => match content[0] == "from_lookup" {
197                        true => {
198                            return Err(DropboxError::DbxFromLookUpError(content[1].to_string()))
199                        }
200                        false => match content[0] == "to" {
201                            true => {
202                                return Err(DropboxError::DbxExistedError(content[1].to_string()))
203                            }
204                            false => {
205                                return Err(DropboxError::DbxConflictError(
206                                    error_summary.error_summary,
207                                ))
208                            }
209                        },
210                    },
211                }
212            }
213            StatusCode::TOO_MANY_REQUESTS => {
214                let text = res.text().await?;
215                match serde_json::from_str::<DbxRequestLimitsErrorSummary>(&text) {
216                    Ok(error_summary) => {
217                        return Err(DropboxError::DbxRequestLimitsError(format!(
218                            "{} , retry after {}",
219                            error_summary.error_summary, error_summary.error.retry_after
220                        )));
221                    }
222                    Err(_) => {
223                        return Err(DropboxError::DbxRequestLimitsError(text));
224                    }
225                }
226            }
227            StatusCode::INTERNAL_SERVER_ERROR | StatusCode::SERVICE_UNAVAILABLE => {
228                let text = res.text().await?;
229                return Err(DropboxError::DbxServerError(text));
230            }
231            _ => {
232                let text = res.text().await?;
233                match serde_json::from_str::<DbxRequestErrorSummary>(&text) {
234                    Ok(error_summary) => {
235                        return Err(DropboxError::OtherError(error_summary.error_summary));
236                    }
237                    Err(_) => {
238                        return Err(DropboxError::OtherError(text));
239                    }
240                }
241            }
242        }
243    }
244    T::from(res).await.map(|i| *i)
245}
246
247#[cfg(feature = "non-blocking")]
248#[async_trait]
249trait AsyncFrom<T> {
250    async fn from(t: T) -> DropboxResult<Box<Self>>;
251}
252
253#[cfg(feature = "non-blocking")]
254#[async_trait]
255impl AsyncFrom<reqwest::Response> for Vec<u8> {
256    async fn from(res: reqwest::Response) -> DropboxResult<Box<Self>> {
257        res.bytes()
258            .await
259            .map(|b| Box::new(b.to_vec()))
260            .map_err(|e| DropboxError::NonBlockingRequestError(e))
261    }
262}
263
264#[cfg(feature = "non-blocking")]
265#[async_trait]
266impl AsyncFrom<reqwest::Response> for () {
267    async fn from(_res: reqwest::Response) -> DropboxResult<Box<Self>> {
268        DropboxResult::Ok(Box::new(()))
269    }
270}
271
272/////////////////////////////////////////////////////////////////////
273#[cfg(feature = "blocking")]
274//the blocking-io client
275pub struct DBXClient {
276    client: ureq::Agent,
277    token: String,
278}
279
280#[cfg(feature = "blocking")]
281impl DBXClient {
282    pub fn new(token: &str) -> Self {
283        let client = ureq::AgentBuilder::new()
284            .timeout(time::Duration::from_secs(10))
285            .build();
286        let token = token.to_string();
287        Self { client, token }
288    }
289
290    pub fn check_user(&self, ping_str: &str) -> DropboxResult<()> {
291        let url = format!("{}{}", OPERATION_END_POINT, "/2/check/user");
292        let res = self
293            .client
294            .post(&url)
295            .set("Authorization", &format!("Bearer {}", self.token))
296            .set("Content-Type", "application/json")
297            .send_json(json!(
298            {
299                "query":ping_str,
300            }
301            ))?;
302        match res.status() {
303            400 => {
304                let text = res.into_string()?;
305                return Err(DropboxError::DbxInvalidTokenError(text));
306            }
307            _ => handle_dbx_request_response(res),
308        }
309    }
310    ///binding /upload
311    pub fn upload(&self, file: Vec<u8>, path: &str, option: UploadOption) -> DropboxResult<()> {
312        let mode = match option.mode {
313            UploadMode::Add => "add".to_string(),
314            UploadMode::Overwrite => "overwrite".to_string(),
315            UploadMode::Update(ref rev) => json!({
316                ".tag":"update",
317                "update":rev
318            })
319            .to_string(),
320        };
321        let url = format!("{}{}", CONTENT_END_POINT, "/2/files/upload");
322        let res = self
323            .client
324            .post(&url)
325            .set("Authorization", &format!("Bearer {}", self.token))
326            .set("Content-Type", "application/octet-stream")
327            .set(
328                "Dropbox-API-Arg",
329                json!({
330                    "path":path,
331                    "mode":mode,
332                    "autorename":option.allow_auto_rename,
333                    "mute":option.mute_notification,
334                    "strict_conflict":option.allow_strict_conflict
335                })
336                .to_string()
337                .as_str(),
338            )
339            .send_bytes(&file)?;
340
341        handle_dbx_request_response(res)
342    }
343
344    ///binding /download
345    pub fn download(&self, path: &str) -> DropboxResult<Vec<u8>> {
346        let url = format!("{}{}", CONTENT_END_POINT, "/2/files/download");
347        let res = self
348            .client
349            .post(&url)
350            .set("Authorization", &format!("Bearer {}", self.token))
351            .set(
352                "Dropbox-API-Arg",
353                json!({ "path": path }).to_string().as_str(),
354            )
355            .call()?;
356
357        handle_dbx_request_response(res)
358    }
359
360    // binding /move_v2
361    pub fn move_file(
362        &self,
363        from_path: &str,
364        to_path: &str,
365        option: MoveCopyOption,
366    ) -> DropboxResult<()> {
367        let url = format!("{}{}", OPERATION_END_POINT, "/2/files/move_v2");
368        let res = self
369            .client
370            .post(&url)
371            .set("Content-Type", "application/json")
372            .set("Authorization", &format!("Bearer {}", self.token))
373            .send_json(json!(
374            {
375                "from_path":from_path,
376                "to_path":to_path,
377                "allow_shared_folder": option.allow_shared_folder,
378                "autorename": option.auto_rename,
379                "allow_ownership_transfer": option.allow_ownership_transfer
380            }
381            ))?;
382        handle_dbx_request_response(res)
383    }
384
385    pub fn copy(
386        &self,
387        from_path: &str,
388        to_path: &str,
389        option: MoveCopyOption,
390    ) -> DropboxResult<()> {
391        let url = format!("{}{}", OPERATION_END_POINT, "/2/files/copy_v2");
392        let res = self
393            .client
394            .post(&url)
395            .set("Content-Type", "application/json")
396            .set("Authorization", &format!("Bearer {}", self.token))
397            .send_json(json!(
398            {
399                "from_path":from_path,
400                "to_path":to_path,
401                "allow_shared_folder": option.allow_shared_folder,
402                "autorename": option.auto_rename,
403                "allow_ownership_transfer": option.allow_ownership_transfer
404            }
405            ))?;
406        handle_dbx_request_response(res)
407    }
408}
409
410#[cfg(feature = "blocking")]
411trait FromRes<T> {
412    fn from_res(t: T) -> DropboxResult<Box<Self>>;
413}
414
415#[cfg(feature = "blocking")]
416impl FromRes<ureq::Response> for Vec<u8> {
417    fn from_res(res: ureq::Response) -> DropboxResult<Box<Self>> {
418        let len = res
419            .header("Content-Length")
420            .and_then(|s| s.parse::<usize>().ok())
421            .unwrap();
422        let mut bytes: Vec<u8> = Vec::with_capacity(len);
423        res.into_reader().read_to_end(&mut bytes)?;
424        Ok(Box::new(bytes))
425    }
426}
427
428#[cfg(feature = "blocking")]
429impl FromRes<ureq::Response> for () {
430    fn from_res(_res: ureq::Response) -> DropboxResult<Box<Self>> {
431        DropboxResult::Ok(Box::new(()))
432    }
433}
434
435#[inline]
436#[cfg(feature = "blocking")]
437fn handle_dbx_request_response<T: FromRes<ureq::Response>>(
438    res: ureq::Response,
439) -> DropboxResult<T> {
440    if res.status() != 200 {
441        match res.status() {
442            400 => {
443                let text = res.into_string()?;
444                return Err(DropboxError::DbxPathError(text));
445            }
446            401 => {
447                let error_summary = res.into_json::<DbxRequestErrorSummary>()?;
448                return Err(DropboxError::DbxInvalidTokenError(
449                    error_summary.error_summary,
450                ));
451            }
452            403 => {
453                let error_summary = res.into_json::<DbxRequestErrorSummary>()?;
454                return Err(DropboxError::DbxAccessError(error_summary.error_summary));
455            }
456            409 => {
457                let error_summary = res.into_json::<DbxRequestErrorSummary>()?;
458                let content: Vec<&str> = error_summary.error_summary.split("/").collect();
459                match content[0] == "path" {
460                    true => return Err(DropboxError::DbxPathError(content[1].to_string())),
461                    false => match content[0] == "from_lookup" {
462                        true => {
463                            return Err(DropboxError::DbxFromLookUpError(content[1].to_string()))
464                        }
465                        false => match content[0] == "to" {
466                            true => {
467                                return Err(DropboxError::DbxExistedError(content[1].to_string()))
468                            }
469                            false => {
470                                return Err(DropboxError::DbxConflictError(
471                                    error_summary.error_summary,
472                                ))
473                            }
474                        },
475                    },
476                }
477            }
478            429 => {
479                let text = res.into_string()?;
480                match serde_json::from_str::<DbxRequestLimitsErrorSummary>(&text) {
481                    Ok(error_summary) => {
482                        return Err(DropboxError::DbxRequestLimitsError(format!(
483                            "{} , retry after {}",
484                            error_summary.error_summary, error_summary.error.retry_after
485                        )));
486                    }
487                    Err(_) => {
488                        return Err(DropboxError::DbxRequestLimitsError(text));
489                    }
490                }
491            }
492            500 | 503 => {
493                let text = res.into_string()?;
494                return Err(DropboxError::DbxServerError(text));
495            }
496            _ => {
497                let text = res.into_string()?;
498                match serde_json::from_str::<DbxRequestErrorSummary>(&text) {
499                    Ok(error_summary) => {
500                        return Err(DropboxError::OtherError(error_summary.error_summary));
501                    }
502                    Err(_) => {
503                        return Err(DropboxError::OtherError(text));
504                    }
505                }
506            }
507        }
508    }
509    T::from_res(res).map(|i| *i)
510}