From abe57538f9d7fe67668343afd05f0a2733c0521c Mon Sep 17 00:00:00 2001 From: Bill Fraser Date: Mon, 13 Jan 2025 00:18:45 -0800 Subject: [PATCH] rustfmt everything except generated code I personally disagree with several of rustfmt's decisions, but it's better to be consistent. Generated code isn't being formatted because 1) it would have to get reformatted every time it gets generated; I'm not going to make the generator emit it so it matches rustfmt 2) rustfmt currently chokes on some of the generated code --- .github/workflows/cargo-test.yml | 22 +++- examples/demo-async.rs | 23 +++-- examples/demo.rs | 3 +- examples/large-file-upload.rs | 168 ++++++++++++++++++------------- src/async_client_trait.rs | 42 ++++---- src/client_helpers.rs | 154 +++++++++++++++------------- src/client_trait.rs | 11 +- src/error.rs | 38 +++++-- src/lib.rs | 15 ++- src/oauth2.rs | 148 ++++++++++++++++----------- tests/async_client_test.rs | 27 +++-- tests/benchmark.rs | 54 +++++----- tests/common/mod.rs | 54 +++++----- tests/json.rs | 17 ++-- tests/list_folder_recursive.rs | 33 ++++-- tests/noop_client.rs | 10 +- tests/path_root.rs | 12 ++- tests/sync_client_test.rs | 13 ++- 18 files changed, 503 insertions(+), 341 deletions(-) diff --git a/.github/workflows/cargo-test.yml b/.github/workflows/cargo-test.yml index d1e8f7f..20ac80e 100644 --- a/.github/workflows/cargo-test.yml +++ b/.github/workflows/cargo-test.yml @@ -12,6 +12,23 @@ jobs: with: submodules: true + - name: Install nightly toolchain + run: | + rustup install nightly --profile minimal + rustup component add clippy --toolchain nightly + rustup component add rustfmt --toolchain nightly + + - name: Run rustfmt + # Delete the generated code so it doesn't get formatted. + # We'll be re-running the generator later, so this is okay. + run: | + rm -rf src/generated + echo "// empty module for rustfmt" > src/generated.rs + echo "// empty module for rustfmt" > tests/generated.rs + rustup run nightly cargo fmt --check + rm src/generated.rs + rm tests/generated.rs + - name: Set up Python uses: actions/setup-python@v5.3.0 with: @@ -34,11 +51,6 @@ jobs: - name: Run cargo test run: rustup run 1.75.0 cargo test --all-features - - name: Install nightly toolchain - run: | - rustup install nightly --profile minimal - rustup component add clippy --toolchain nightly - - name: Run clippy run: rustup run nightly cargo clippy --all-targets --all-features -- --deny warnings diff --git a/examples/demo-async.rs b/examples/demo-async.rs index 722fe32..57b3955 100644 --- a/examples/demo-async.rs +++ b/examples/demo-async.rs @@ -3,9 +3,9 @@ //! This example illustrates a few basic Dropbox API operations: getting an OAuth2 token, listing //! the contents of a folder recursively, and fetching a file given its path. -use tokio_util::compat::FuturesAsyncReadCompatExt; -use dropbox_sdk::default_async_client::{NoauthDefaultClient, UserAuthDefaultClient}; use dropbox_sdk::async_routes::files; +use dropbox_sdk::default_async_client::{NoauthDefaultClient, UserAuthDefaultClient}; +use tokio_util::compat::FuturesAsyncReadCompatExt; enum Operation { Usage, @@ -68,7 +68,9 @@ async fn main() { let mut auth = dropbox_sdk::oauth2::get_auth_from_env_or_prompt(); if auth.save().is_none() { - auth.obtain_access_token_async(NoauthDefaultClient::default()).await.unwrap(); + auth.obtain_access_token_async(NoauthDefaultClient::default()) + .await + .unwrap(); eprintln!("Next time set these environment variables to reuse this authorization:"); eprintln!(" DBX_CLIENT_ID={}", auth.client_id()); eprintln!(" DBX_OAUTH={}", auth.save().unwrap()); @@ -84,10 +86,11 @@ async fn main() { match files::download(&client, &files::DownloadArg::new(path), None, None).await { Ok(result) => { match tokio::io::copy( - &mut result.body.expect("there must be a response body") - .compat(), + &mut result.body.expect("there must be a response body").compat(), &mut tokio::io::stdout(), - ).await { + ) + .await + { Ok(n) => { eprintln!("Downloaded {n} bytes"); } @@ -112,7 +115,9 @@ async fn main() { let mut result = match files::list_folder( &client, &files::ListFolderArg::new(path).with_recursive(true), - ).await { + ) + .await + { Ok(result) => result, Err(e) => { eprintln!("Error from files/list_folder: {e}"); @@ -145,7 +150,9 @@ async fn main() { result = match files::list_folder_continue( &client, &files::ListFolderContinueArg::new(result.cursor), - ).await { + ) + .await + { Ok(result) => { num_pages += 1; num_entries += result.entries.len(); diff --git a/examples/demo.rs b/examples/demo.rs index e50adaa..c804192 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -68,7 +68,8 @@ fn main() { let mut auth = dropbox_sdk::oauth2::get_auth_from_env_or_prompt(); if auth.save().is_none() { - auth.obtain_access_token(NoauthDefaultClient::default()).unwrap(); + auth.obtain_access_token(NoauthDefaultClient::default()) + .unwrap(); eprintln!("Next time set these environment variables to reuse this authorization:"); eprintln!(" DBX_CLIENT_ID={}", auth.client_id()); eprintln!(" DBX_OAUTH={}", auth.save().unwrap()); diff --git a/examples/large-file-upload.rs b/examples/large-file-upload.rs index c1e55d0..1e6ede9 100644 --- a/examples/large-file-upload.rs +++ b/examples/large-file-upload.rs @@ -4,16 +4,16 @@ //! files that would not fit in a single HTTP request, including allowing the user to resume //! interrupted uploads, and uploading blocks in parallel. -use dropbox_sdk::Error::Api; -use dropbox_sdk::files; use dropbox_sdk::default_client::UserAuthDefaultClient; +use dropbox_sdk::files; +use dropbox_sdk::Error::Api; use std::collections::HashMap; use std::fs::File; -use std::path::{Path, PathBuf}; use std::io::{Seek, SeekFrom}; +use std::path::{Path, PathBuf}; use std::process::exit; -use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicU64, Ordering::SeqCst}; +use std::sync::{Arc, Mutex}; use std::thread::sleep; use std::time::{Duration, Instant, SystemTime}; @@ -35,8 +35,10 @@ macro_rules! fatal { } fn usage() { - eprintln!("usage: {} [--resume ,]", - std::env::args().next().unwrap()); + eprintln!( + "usage: {} [--resume ,]", + std::env::args().next().unwrap() + ); } enum Operation { @@ -64,27 +66,26 @@ impl std::str::FromStr for Resume { let offset_str = parts.next().ok_or("missing session ID and file offset")?; let session_id = parts.next().ok_or("missing file offset")?.to_owned(); let start_offset = offset_str.parse().map_err(|_| "invalid file offset")?; - Ok(Self { start_offset, session_id }) + Ok(Self { + start_offset, + session_id, + }) } } fn parse_args() -> Operation { let mut a = std::env::args().skip(1); match (a.next(), a.next()) { - (Some(ref arg), _) if arg == "--help" || arg == "-h" => { - Operation::Usage - } + (Some(ref arg), _) if arg == "--help" || arg == "-h" => Operation::Usage, (Some(src), Some(dest)) => { let resume = match (a.next().as_deref(), a.next()) { - (Some("--resume"), Some(resume_str)) => { - match resume_str.parse() { - Ok(resume) => Some(resume), - Err(e) => { - eprintln!("Invalid --resume argument: {}", e); - return Operation::Usage; - } + (Some("--resume"), Some(resume_str)) => match resume_str.parse() { + Ok(resume) => Some(resume), + Err(e) => { + eprintln!("Invalid --resume argument: {}", e); + return Operation::Usage; } - } + }, (None, _) => None, _ => { return Operation::Usage; @@ -100,17 +101,18 @@ fn parse_args() -> Operation { eprintln!("missing destination path"); Operation::Usage } - (None, _) => { - Operation::Usage - } + (None, _) => Operation::Usage, } } /// Figure out if destination is a folder or not and change the destination path accordingly. -fn get_destination_path(client: &UserAuthDefaultClient, given_path: &str, source_path: &Path) - -> Result -{ - let filename = source_path.file_name() +fn get_destination_path( + client: &UserAuthDefaultClient, + given_path: &str, + source_path: &Path, +) -> Result { + let filename = source_path + .file_name() .ok_or_else(|| format!("invalid source path {:?} has no filename", source_path))? .to_string_lossy(); @@ -121,8 +123,8 @@ fn get_destination_path(client: &UserAuthDefaultClient, given_path: &str, source return Ok(path); } - let meta_result = files::get_metadata( - client, &files::GetMetadataArg::new(given_path.to_owned())); + let meta_result = + files::get_metadata(client, &files::GetMetadataArg::new(given_path.to_owned())); match meta_result { Ok(files::Metadata::File(_)) => { @@ -146,7 +148,7 @@ fn get_destination_path(client: &UserAuthDefaultClient, given_path: &str, source // automatically created as needed. Ok(given_path.to_owned()) } - Err(e) => Err(format!("Error looking up destination: {}", e)) + Err(e) => Err(format!("Error looking up destination: {}", e)), } } @@ -194,23 +196,23 @@ impl UploadSession { /// Generate the argument to append a block at the given offset. pub fn append_arg(&self, block_offset: u64) -> files::UploadSessionAppendArg { - files::UploadSessionAppendArg::new( - files::UploadSessionCursor::new( - self.session_id.clone(), - self.start_offset + block_offset)) + files::UploadSessionAppendArg::new(files::UploadSessionCursor::new( + self.session_id.clone(), + self.start_offset + block_offset, + )) } /// Generate the argument to commit the upload at the given path with the given modification /// time. - pub fn commit_arg(&self, dest_path: String, source_mtime: SystemTime) - -> files::UploadSessionFinishArg - { + pub fn commit_arg( + &self, + dest_path: String, + source_mtime: SystemTime, + ) -> files::UploadSessionFinishArg { files::UploadSessionFinishArg::new( - files::UploadSessionCursor::new( - self.session_id.clone(), - self.file_size), - files::CommitInfo::new(dest_path) - .with_client_modified(iso8601(source_mtime))) + files::UploadSessionCursor::new(self.session_id.clone(), self.file_size), + files::CommitInfo::new(dest_path).with_client_modified(iso8601(source_mtime)), + ) } /// Mark a block as uploaded. @@ -268,8 +270,12 @@ impl CompletionTracker { } fn get_file_mtime_and_size(f: &File) -> Result<(SystemTime, u64), String> { - let meta = f.metadata().map_err(|e| format!("Error getting source file metadata: {}", e))?; - let mtime = meta.modified().map_err(|e| format!("Error getting source file mtime: {}", e))?; + let meta = f + .metadata() + .map_err(|e| format!("Error getting source file metadata: {}", e))?; + let mtime = meta + .modified() + .map_err(|e| format!("Error getting source file mtime: {}", e))?; Ok((mtime, meta.len())) } @@ -280,11 +286,11 @@ fn upload_file( dest_path: String, resume: Option, ) -> Result<(), String> { - let (source_mtime, source_len) = get_file_mtime_and_size(&source_file)?; let session = Arc::new(if let Some(ref resume) = resume { - source_file.seek(SeekFrom::Start(resume.start_offset)) + source_file + .seek(SeekFrom::Start(resume.start_offset)) .map_err(|e| format!("Seek error: {}", e))?; UploadSession::resume(resume.clone(), source_len) } else { @@ -331,22 +337,35 @@ fn upload_file( session.mark_block_uploaded(block_offset, data.len() as u64); } result - })) + }), + ) }; if let Err(e) = upload_result { - return Err(format!("{}. To resume, use --resume {},{}", - e, session.session_id, session.complete_up_to())); + return Err(format!( + "{}. To resume, use --resume {},{}", + e, + session.session_id, + session.complete_up_to() + )); } let (last_block_offset, last_block_data) = unwrap_arcmutex(last_block); - eprintln!("closing session at {} with {}-byte block", - last_block_offset, last_block_data.len()); + eprintln!( + "closing session at {} with {}-byte block", + last_block_offset, + last_block_data.len() + ); let mut arg = session.append_arg(last_block_offset); arg.close = true; if let Err(e) = upload_block_with_retry( - client.as_ref(), &arg, &last_block_data, start_time, session.as_ref(), resume.as_ref()) - { + client.as_ref(), + &arg, + &last_block_data, + start_time, + session.as_ref(), + resume.as_ref(), + ) { eprintln!("failed to close session: {}", e); // But don't error out; try committing anyway. It could be we're resuming a file where we // already closed it out but failed to commit. @@ -371,8 +390,11 @@ fn upload_file( } } - Err(format!("Upload failed. To retry, use --resume {},{}", - session.session_id, session.complete_up_to())) + Err(format!( + "Upload failed. To retry, use --resume {},{}", + session.session_id, + session.complete_up_to() + )) } /// Upload a single block, retrying a few times if an error occurs. @@ -390,16 +412,21 @@ fn upload_block_with_retry( let mut errors = 0; loop { match files::upload_session_append_v2(client, arg, buf) { - Ok(()) => { break; } - Err(dropbox_sdk::Error::RateLimited { reason, retry_after_seconds }) => { - eprintln!("rate-limited ({}), waiting {} seconds", reason, retry_after_seconds); + Ok(()) => { + break; + } + Err(dropbox_sdk::Error::RateLimited { + reason, + retry_after_seconds, + }) => { + eprintln!("rate-limited ({reason}), waiting {retry_after_seconds} seconds"); if retry_after_seconds > 0 { sleep(Duration::from_secs(u64::from(retry_after_seconds))); } } Err(error) => { errors += 1; - let msg = format!("Error calling upload_session_append: {:?}", error); + let msg = format!("Error calling upload_session_append: {error:?}"); if errors == 3 { return Err(msg); } else { @@ -417,7 +444,8 @@ fn upload_block_with_retry( let bytes_sofar = session.bytes_transferred.fetch_add(block_bytes, SeqCst) + block_bytes; let percent = (resume.map(|r| r.start_offset).unwrap_or(0) + bytes_sofar) as f64 - / session.file_size as f64 * 100.; + / session.file_size as f64 + * 100.; // This assumes that we have `PARALLELISM` uploads going at the same time and at roughly the // same upload speed: @@ -425,19 +453,20 @@ fn upload_block_with_retry( let overall_rate = bytes_sofar as f64 / overall_dur.as_secs_f64(); - eprintln!("{:.01}%: {}Bytes uploaded, {}Bytes per second, {}Bytes per second average", + eprintln!( + "{:.01}%: {}Bytes uploaded, {}Bytes per second, {}Bytes per second average", percent, human_number(bytes_sofar), human_number(block_rate as u64), human_number(overall_rate as u64), - ); + ); Ok(()) } fn human_number(n: u64) -> String { let mut f = n as f64; - let prefixes = ['k','M','G','T','E']; + let prefixes = ['k', 'M', 'G', 'T', 'E']; let mut mag = 0; while mag < prefixes.len() { if f < 1000. { @@ -461,7 +490,8 @@ fn iso8601(t: SystemTime) -> String { chrono::DateTime::from_timestamp(timestamp, 0 /* nsecs */) .expect("invalid or out-of-range timestamp") - .format("%Y-%m-%dT%H:%M:%SZ").to_string() + .format("%Y-%m-%dT%H:%M:%SZ") + .to_string() } fn unwrap_arcmutex(x: Arc>) -> T { @@ -482,10 +512,9 @@ fn main() { Operation::Upload(args) => args, }; - let source_file = File::open(&args.source_path) - .unwrap_or_else(|e| { - fatal!("Source file {:?} not found: {}", args.source_path, e); - }); + let source_file = File::open(&args.source_path).unwrap_or_else(|e| { + fatal!("Source file {:?} not found: {}", args.source_path, e); + }); let auth = dropbox_sdk::oauth2::get_auth_from_env_or_prompt(); let client = Arc::new(UserAuthDefaultClient::new(auth)); @@ -498,8 +527,7 @@ fn main() { eprintln!("source = {:?}", args.source_path); eprintln!("dest = {:?}", dest_path); - upload_file(client, source_file, dest_path, args.resume) - .unwrap_or_else(|e| { - fatal!("{}", e); - }); + upload_file(client, source_file, dest_path, args.resume).unwrap_or_else(|e| { + fatal!("{}", e); + }); } diff --git a/src/async_client_trait.rs b/src/async_client_trait.rs index e9d8419..3336d36 100644 --- a/src/async_client_trait.rs +++ b/src/async_client_trait.rs @@ -1,11 +1,11 @@ //! Everything needed to implement your async HTTP client. -use std::future::{Future, ready}; -use std::sync::Arc; -use bytes::Bytes; -use futures::AsyncRead; pub use crate::client_trait_common::{HttpRequest, TeamSelect}; use crate::Error; +use bytes::Bytes; +use futures::AsyncRead; +use std::future::{ready, Future}; +use std::sync::Arc; /// The base HTTP asynchronous client trait. pub trait HttpClient: Sync { @@ -69,7 +69,9 @@ pub trait HttpClient: Sync { ) -> impl Future> + Send { unimplemented!(); #[allow(unreachable_code)] // otherwise it complains that `()` is not a future. - async move { unimplemented!() } + async move { + unimplemented!() + } } } @@ -110,32 +112,36 @@ pub struct HttpRequestResult { impl HttpClient for T { type Request = T::Request; - async fn execute(&self, request: Self::Request, body: Bytes) - -> Result - { + async fn execute( + &self, + request: Self::Request, + body: Bytes, + ) -> Result { self.execute_borrowed_body(request, &body).await } - async fn execute_borrowed_body(&self, request: Self::Request, body_slice: &[u8]) - -> Result - { - self.execute(request, body_slice).map(|r| { - HttpRequestResultRaw { + async fn execute_borrowed_body( + &self, + request: Self::Request, + body_slice: &[u8], + ) -> Result { + self.execute(request, body_slice) + .map(|r| HttpRequestResultRaw { status: r.status, result_header: r.result_header, content_length: r.content_length, body: Box::new(SyncReadAdapter { inner: r.body }), - } - }) + }) } fn new_request(&self, url: &str) -> Self::Request { self.new_request(url) } - fn update_token(&self, old_token: Arc) - -> impl Future> + Send - { + fn update_token( + &self, + old_token: Arc, + ) -> impl Future> + Send { ready(self.update_token(old_token)) } diff --git a/src/client_helpers.rs b/src/client_helpers.rs index f01aef1..bb17bd5 100644 --- a/src/client_helpers.rs +++ b/src/client_helpers.rs @@ -1,24 +1,23 @@ // Copyright (c) 2019-2025 Dropbox, Inc. -use std::error::Error as StdError; -use std::io::ErrorKind; -use std::sync::Arc; +use crate::async_client_trait::{HttpClient, HttpRequestResult, HttpRequestResultRaw}; +use crate::client_trait_common::{Endpoint, HttpRequest, ParamsType, Style, TeamSelect}; +use crate::types::auth::{AccessError, AuthError, RateLimitReason}; +use crate::Error; use bytes::Bytes; use futures::{AsyncRead, AsyncReadExt}; -use serde::{Deserialize}; use serde::de::DeserializeOwned; use serde::ser::Serialize; -use crate::Error; -use crate::async_client_trait::{HttpClient, HttpRequestResult, HttpRequestResultRaw}; -use crate::client_trait_common::{Endpoint, HttpRequest, ParamsType, Style, TeamSelect}; -use crate::types::auth::{AccessError, AuthError, RateLimitReason}; +use serde::Deserialize; +use std::error::Error as StdError; +use std::io::ErrorKind; +use std::sync::Arc; /// When Dropbox returns an error with HTTP 409 or 429, it uses an implicit JSON object with the /// following structure, which contains the actual error as a field. #[derive(Debug, Deserialize)] pub(crate) struct TopLevelError { pub error: T, - // It also has these fields, which we don't expose anywhere: //pub error_summary: String, //pub user_message: Option, @@ -52,7 +51,10 @@ pub(crate) fn prepare_request( let url = endpoint.url().to_owned() + function; let mut req = client.new_request(&url); - req = req.set_header("User-Agent", concat!("Dropbox-SDK-Rust/", env!("CARGO_PKG_VERSION"))); + req = req.set_header( + "User-Agent", + concat!("Dropbox-SDK-Rust/", env!("CARGO_PKG_VERSION")), + ); if let Some(token) = token { req = req.set_header("Authorization", &format!("Bearer {token}")); @@ -99,7 +101,9 @@ pub(crate) fn prepare_request( (req, params_body) } -pub(crate) async fn body_to_string(body: &mut (dyn AsyncRead + Send + Unpin)) -> Result { +pub(crate) async fn body_to_string( + body: &mut (dyn AsyncRead + Send + Unpin), +) -> Result { let mut s = String::new(); match body.read_to_string(&mut s).await { Ok(_) => Ok(s), @@ -140,7 +144,10 @@ where let token = client.token(); if token.is_none() && !retried - && client.update_token(Arc::new(String::new())).await.map_err(Error::typed)? + && client + .update_token(Arc::new(String::new())) + .await + .map_err(Error::typed)? { retried = true; continue 'auth_retry; @@ -166,7 +173,9 @@ where (None, Some(Body::Owned((body_bytes, ..)))) => client.execute(req, body_bytes).await, #[cfg(feature = "sync_routes")] - (None, Some(Body::Borrowed(body_slice))) => client.execute_borrowed_body(req, body_slice).await, + (None, Some(Body::Borrowed(body_slice))) => { + client.execute_borrowed_body(req, body_slice).await + } }; return match result { Ok(raw_resp) => { @@ -184,8 +193,8 @@ where } Err(e) => { error!("HTTP {status}: {e}"); - return Err(e.typed()) - }, + return Err(e.typed()); + } }; if status == 409 { @@ -195,7 +204,7 @@ where Ok(deserialized) => { error!("API error: {}", deserialized.error); Err(Error::Api(deserialized.error)) - }, + } Err(de_error) => { error!("Failed to deserialize JSON from API error: {}", de_error); Err(Error::Json(de_error)) @@ -210,25 +219,35 @@ where }) } Err(e) => Err(e.typed()), - } + }; } } -pub(crate) async fn parse_response(raw_resp: HttpRequestResultRaw, style: Style) - -> Result<(String, Option, Option>), Error> -{ +pub(crate) async fn parse_response( + raw_resp: HttpRequestResultRaw, + style: Style, +) -> Result< + ( + String, + Option, + Option>, + ), + Error, +> { let HttpRequestResultRaw { status, result_header, content_length, - mut body + mut body, } = raw_resp; if (200..300).contains(&status) { Ok(match style { Style::Rpc | Style::Upload => { // Read the response from the body. if let Some(header) = result_header { - return Err(Error::UnexpectedResponse(format!("unexpected response in header, expected it in the body: {header}"))); + return Err(Error::UnexpectedResponse(format!( + "unexpected response in header, expected it in the body: {header}" + ))); } else { (body_to_string(&mut body).await?, content_length, None) } @@ -238,7 +257,9 @@ pub(crate) async fn parse_response(raw_resp: HttpRequestResultRaw, style: Style) if let Some(header) = result_header { (header, content_length, Some(body)) } else { - return Err(Error::UnexpectedResponse("expected a Dropbox-API-Result header".to_owned())); + return Err(Error::UnexpectedResponse( + "expected a Dropbox-API-Result header".to_owned(), + )); } } }) @@ -246,54 +267,43 @@ pub(crate) async fn parse_response(raw_resp: HttpRequestResultRaw, style: Style) let response = body_to_string(&mut body).await?; debug!("HTTP {status}: {response}"); match status { - 400 => { - Err(Error::BadRequest(response)) - }, - 401 => { - match serde_json::from_str::>(&response) { - Ok(deserialized) => { - Err(Error::Authentication(deserialized.error)) - } - Err(de_error) => { - error!("Failed to deserialize JSON from API error: {response}"); - Err(Error::Json(de_error)) - } + 400 => Err(Error::BadRequest(response)), + 401 => match serde_json::from_str::>(&response) { + Ok(deserialized) => Err(Error::Authentication(deserialized.error)), + Err(de_error) => { + error!("Failed to deserialize JSON from API error: {response}"); + Err(Error::Json(de_error)) } }, - 403 => { - match serde_json::from_str::>(&response) { - Ok(deserialized) => { - Err(Error::AccessDenied(deserialized.error)) - } - Err(de_error) => { - error!("Failed to deserialize JSON from API error: {response}"); - Err(Error::Json(de_error)) - } + 403 => match serde_json::from_str::>(&response) { + Ok(deserialized) => Err(Error::AccessDenied(deserialized.error)), + Err(de_error) => { + error!("Failed to deserialize JSON from API error: {response}"); + Err(Error::Json(de_error)) } - } + }, 409 => { // Pretend it's okay for now; caller will parse it specially. Ok((response, None, None)) - }, - 429 => { - match serde_json::from_str::>(&response) { - Ok(deserialized) => { - let e = Error::RateLimited { - reason: deserialized.error.reason, - retry_after_seconds: deserialized.error.retry_after, - }; - Err(e) - } - Err(de_error) => { - error!("Failed to deserialize JSON from API error: {response}"); - Err(Error::Json(de_error)) - } + } + 429 => match serde_json::from_str::>(&response) { + Ok(deserialized) => { + let e = Error::RateLimited { + reason: deserialized.error.reason, + retry_after_seconds: deserialized.error.retry_after, + }; + Err(e) + } + Err(de_error) => { + error!("Failed to deserialize JSON from API error: {response}"); + Err(Error::Json(de_error)) } }, - 500..=599 => { - Err(Error::ServerError(response)) - }, - _ => Err(Error::UnexpectedHttpError { code: status, response }), + 500..=599 => Err(Error::ServerError(response)), + _ => Err(Error::UnexpectedHttpError { + code: status, + response, + }), } } } @@ -343,11 +353,11 @@ where #[cfg(feature = "sync_routes")] mod sync_helpers { - use std::future::Future; - use futures::{AsyncRead, FutureExt}; use crate::async_client_trait::{HttpRequestResult, SyncReadAdapter}; use crate::client_trait as sync; use crate::Error; + use futures::{AsyncRead, FutureExt}; + use std::future::Future; /// Given an async HttpRequestResult which was created from a *sync* HttpClient, convert it to the /// sync HttpRequestResult by cracking open the SyncReadAdapter in the body. @@ -365,9 +375,8 @@ mod sync_helpers { let p: *mut dyn AsyncRead = Box::into_raw(async_read); // SAFETY: the only body value an async HttpRequestResult created for a sync client // can be is a SyncReadAdapter. - let adapter = unsafe { - Box::::from_raw(p as *mut SyncReadAdapter) - }; + let adapter = + unsafe { Box::::from_raw(p as *mut SyncReadAdapter) }; sync::HttpRequestResult { result: r.result, content_length: r.content_length, @@ -378,7 +387,7 @@ mod sync_helpers { result: r.result, content_length: r.content_length, body: None, - } + }, } } @@ -388,7 +397,9 @@ mod sync_helpers { f: impl Future, Error>>, client: &impl sync::HttpClient, ) -> Result, Error> { - let r = f.now_or_never().expect("sync future should resolve immediately"); + let r = f + .now_or_never() + .expect("sync future should resolve immediately"); match r { Ok(v) => Ok(unwrap_async_result(v, client)), Err(e) => Err(e), @@ -400,7 +411,8 @@ mod sync_helpers { pub(crate) fn unwrap_async( f: impl Future>>, ) -> Result> { - f.now_or_never().expect("sync future should resolve immediately") + f.now_or_never() + .expect("sync future should resolve immediately") } } diff --git a/src/client_trait.rs b/src/client_trait.rs index 8c1ae27..e8641a7 100644 --- a/src/client_trait.rs +++ b/src/client_trait.rs @@ -2,10 +2,10 @@ //! Everything needed to implement your HTTP client. -use std::io::Read; -use std::sync::Arc; pub use crate::client_trait_common::{HttpRequest, TeamSelect}; use crate::Error; +use std::io::Read; +use std::sync::Arc; /// The base HTTP synchronous client trait. pub trait HttpClient: Sync { @@ -13,11 +13,7 @@ pub trait HttpClient: Sync { type Request: HttpRequest + Send; /// Make a HTTP request. - fn execute( - &self, - request: Self::Request, - body: &[u8], - ) -> Result; + fn execute(&self, request: Self::Request, body: &[u8]) -> Result; /// Create a new request instance for the given URL. It should be a POST request. fn new_request(&self, url: &str) -> Self::Request; @@ -92,4 +88,3 @@ pub struct HttpRequestResult { /// [`Style::Download`](crate::client_trait_common::Style::Download) endpoints. pub body: Option>, } - diff --git a/src/error.rs b/src/error.rs index b256215..be86dd8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -29,8 +29,10 @@ pub enum Error { Authentication(#[source] types::auth::AuthError), /// Your request was rejected due to rate-limiting. You can retry it later. - #[error("Dropbox API declined the request due to rate-limiting ({reason}), \ - retry after {retry_after_seconds}s")] + #[error( + "Dropbox API declined the request due to rate-limiting ({reason}), \ + retry after {retry_after_seconds}s" + )] RateLimited { /// The server-given reason for the rate-limiting. reason: types::auth::RateLimitReason, @@ -103,10 +105,18 @@ impl Error { Error::UnexpectedResponse(e) => Error::UnexpectedResponse(e), Error::BadRequest(e) => Error::BadRequest(e), Error::Authentication(e) => Error::Authentication(e), - Error::RateLimited { reason, retry_after_seconds } => Error::RateLimited { reason, retry_after_seconds }, + Error::RateLimited { + reason, + retry_after_seconds, + } => Error::RateLimited { + reason, + retry_after_seconds, + }, Error::AccessDenied(e) => Error::AccessDenied(e), Error::ServerError(e) => Error::ServerError(e), - Error::UnexpectedHttpError { code, response } => Error::UnexpectedHttpError { code, response }, + Error::UnexpectedHttpError { code, response } => { + Error::UnexpectedHttpError { code, response } + } } } } @@ -125,15 +135,22 @@ impl Error { Error::UnexpectedResponse(e) => Error::UnexpectedResponse(e), Error::BadRequest(e) => Error::BadRequest(e), Error::Authentication(e) => Error::Authentication(e), - Error::RateLimited { reason, retry_after_seconds } => Error::RateLimited { reason, retry_after_seconds }, + Error::RateLimited { + reason, + retry_after_seconds, + } => Error::RateLimited { + reason, + retry_after_seconds, + }, Error::AccessDenied(e) => Error::AccessDenied(e), Error::ServerError(e) => Error::ServerError(e), - Error::UnexpectedHttpError { code, response } => Error::UnexpectedHttpError { code, response }, + Error::UnexpectedHttpError { code, response } => { + Error::UnexpectedHttpError { code, response } + } } } } - /// A special error type for a method that doesn't have any defined error return. You can't /// actually encounter a value of this type in real life; it's here to satisfy type requirements. #[derive(Copy, Clone)] @@ -174,11 +191,10 @@ impl std::fmt::Display for NoError { // This is the reason we can't just use the otherwise-identical `void` crate's Void type: we need // to implement this trait. impl<'de> serde::de::Deserialize<'de> for NoError { - fn deserialize>(_: D) - -> Result - { + fn deserialize>(_: D) -> Result { Err(serde::de::Error::custom( - "method has no defined error type, but an error was returned")) + "method has no defined error type, but an error was returned", + )) } } diff --git a/src/lib.rs b/src/lib.rs index 9f3bbaa..4fd9943 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,14 @@ // Copyright (c) 2019-2025 Dropbox, Inc. -#![deny( - missing_docs, - rust_2018_idioms, -)] - +#![deny(missing_docs, rust_2018_idioms)] // Enable a nightly feature for docs.rs which enables decorating feature-gated items. // To enable this manually, run e.g. `cargo rustdoc --all-features -- --cfg docsrs`. #![cfg_attr(docsrs, feature(doc_cfg))] - #![cfg_attr(docsrs, doc = include_str!("../README.md"))] -#![cfg_attr(not(docsrs), doc = "Dropbox SDK for Rust. See README.md for more details.")] +#![cfg_attr( + not(docsrs), + doc = "Dropbox SDK for Rust. See README.md for more details." +)] /// Feature-gate something and also decorate it with the feature name on docs.rs. macro_rules! if_feature { @@ -30,7 +28,8 @@ macro_rules! if_feature { }; } -#[macro_use] extern crate log; +#[macro_use] +extern crate log; if_feature! { "default_client", pub mod default_client; diff --git a/src/oauth2.rs b/src/oauth2.rs index 94919e9..1481f56 100644 --- a/src/oauth2.rs +++ b/src/oauth2.rs @@ -12,19 +12,19 @@ //! [Dropbox OAuth Guide]: https://developers.dropbox.com/oauth-guide //! [OAuth types summary]: https://developers.dropbox.com/oauth-guide#summary -use std::env; -use std::io::{self, IsTerminal, Write}; -use std::sync::Arc; +use crate::async_client_trait::NoauthClient; +use crate::client_helpers::{parse_response, prepare_request}; +use crate::client_trait_common::{Endpoint, ParamsType, Style}; +use crate::Error; use async_lock::RwLock; -use base64::Engine; use base64::engine::general_purpose::{URL_SAFE, URL_SAFE_NO_PAD}; +use base64::Engine; use ring::rand::{SecureRandom, SystemRandom}; +use std::env; +use std::io::{self, IsTerminal, Write}; +use std::sync::Arc; use url::form_urlencoded::Serializer as UrlEncoder; use url::Url; -use crate::Error; -use crate::async_client_trait::NoauthClient; -use crate::client_helpers::{parse_response, prepare_request}; -use crate::client_trait_common::{Endpoint, ParamsType, Style}; /// Which type of OAuth2 flow to use. #[derive(Debug, Clone)] @@ -89,7 +89,8 @@ impl TokenType { match self { TokenType::ShortLivedAndRefresh => Some("offline"), TokenType::ShortLived => Some("online"), - #[allow(deprecated)] TokenType::LongLived => None, + #[allow(deprecated)] + TokenType::LongLived => None, } } } @@ -109,7 +110,9 @@ impl PkceCode { // A 93-byte input ends up as 125 base64 characters, so let's do that. let mut bytes = [0u8; 93]; // not expecting this to ever actually fail: - SystemRandom::new().fill(&mut bytes).expect("failed to get random bytes for PKCE"); + SystemRandom::new() + .fill(&mut bytes) + .expect("failed to get random bytes for PKCE"); let code = URL_SAFE.encode(bytes); Self { code } } @@ -333,7 +336,11 @@ impl Authorization { ) -> Self { Self { client_id, - state: AuthorizationState::InitialAuth { flow_type, auth_code, redirect_uri }, + state: AuthorizationState::InitialAuth { + flow_type, + auth_code, + redirect_uri, + }, } } @@ -343,13 +350,16 @@ impl Authorization { /// token yet). pub fn save(&self) -> Option { match &self.state { - AuthorizationState::AccessToken { token, client_secret } if client_secret.is_none() => { + AuthorizationState::AccessToken { + token, + client_secret, + } if client_secret.is_none() => { // Legacy long-lived access token. Some(format!("1&{}", token)) - }, + } AuthorizationState::Refresh { refresh_token, .. } => { Some(format!("2&{}", refresh_token)) - }, + } _ => None, } } @@ -364,13 +374,14 @@ impl Authorization { /// start the authorization procedure from scratch. pub fn load(client_id: String, saved: &str) -> Option { Some(match saved.get(0..2) { - Some("1&") => { + Some("1&") => + { #[allow(deprecated)] Self::from_long_lived_access_token(saved[2..].to_owned()) - }, + } Some("2&") => Self::from_refresh_token(client_id, saved[2..].to_owned()), _ => { - error!("unrecognized saved Authorization representation: {:?}", saved); + error!("unrecognized saved Authorization representation: {saved:?}"); return None; } }) @@ -378,10 +389,7 @@ impl Authorization { /// Recreate the authorization from a refresh token obtained using the [`Oauth2Type::PKCE`] /// flow. - pub fn from_refresh_token( - client_id: String, - refresh_token: String, - ) -> Self { + pub fn from_refresh_token(client_id: String, refresh_token: String) -> Self { Self { client_id, state: AuthorizationState::Refresh { @@ -414,12 +422,13 @@ impl Authorization { /// Long-lived tokens are deprecated and the ability to generate them will be removed in the /// future. #[deprecated] - pub fn from_long_lived_access_token( - access_token: String, - ) -> Self { + pub fn from_long_lived_access_token(access_token: String) -> Self { Self { client_id: String::new(), - state: AuthorizationState::AccessToken { token: access_token, client_secret: None }, + state: AuthorizationState::AccessToken { + token: access_token, + client_secret: None, + }, } } @@ -438,7 +447,10 @@ impl Authorization { /// Obtain an access token. Use this to complete the authorization process, or to obtain an /// updated token when a short-lived access token has expired. - pub async fn obtain_access_token_async(&mut self, client: impl NoauthClient) -> Result { + pub async fn obtain_access_token_async( + &mut self, + client: impl NoauthClient, + ) -> Result { let mut redirect_uri = None; let mut client_secret = None; let mut pkce_code = None; @@ -446,26 +458,36 @@ impl Authorization { let mut auth_code = None; match self.state.clone() { - AuthorizationState::AccessToken { token, client_secret: secret } => { + AuthorizationState::AccessToken { + token, + client_secret: secret, + } => { match secret { None => { // Long-lived token which cannot be refreshed - return Ok(token) - }, + return Ok(token); + } Some(secret) => { client_secret = Some(secret); } } } AuthorizationState::InitialAuth { - flow_type, auth_code: code, redirect_uri: uri } => - { + flow_type, + auth_code: code, + redirect_uri: uri, + } => { match flow_type { Oauth2Type::ImplicitGrant => { - self.state = AuthorizationState::AccessToken { client_secret: None, token: code.clone() }; + self.state = AuthorizationState::AccessToken { + client_secret: None, + token: code.clone(), + }; return Ok(code); } - Oauth2Type::AuthorizationCode { client_secret: secret } => { + Oauth2Type::AuthorizationCode { + client_secret: secret, + } => { client_secret = Some(secret); } Oauth2Type::PKCE(pkce) => { @@ -475,7 +497,10 @@ impl Authorization { auth_code = Some(code); redirect_uri = uri; } - AuthorizationState::Refresh { refresh_token: refresh, client_secret: secret } => { + AuthorizationState::Refresh { + refresh_token: refresh, + client_secret: secret, + } => { refresh_token = Some(refresh); if let Some(secret) = secret { client_secret = Some(secret); @@ -510,7 +535,10 @@ impl Authorization { } else { params.append_pair( "client_secret", - client_secret.as_ref().expect("need either PKCE code or client secret")); + client_secret + .as_ref() + .expect("need either PKCE code or client secret"), + ); } } @@ -550,24 +578,37 @@ impl Authorization { serde_json::Value::Object(mut map) => { match map.remove("access_token") { Some(serde_json::Value::String(token)) => access_token = token, - _ => return Err(Error::UnexpectedResponse("no access token in response!".to_owned())), + _ => { + return Err(Error::UnexpectedResponse( + "no access token in response!".to_owned(), + )) + } } match map.remove("refresh_token") { Some(serde_json::Value::String(refresh)) => refresh_token = Some(refresh), Some(_) => { - return Err(Error::UnexpectedResponse("refresh token is not a string!".to_owned())); - }, + return Err(Error::UnexpectedResponse( + "refresh token is not a string!".to_owned(), + )); + } None => refresh_token = None, } - }, - _ => return Err(Error::UnexpectedResponse("response is not a JSON object".to_owned())), + } + _ => { + return Err(Error::UnexpectedResponse( + "response is not a JSON object".to_owned(), + )) + } } match refresh_token { Some(refresh) => { - self.state = AuthorizationState::Refresh { refresh_token: refresh, client_secret }; + self.state = AuthorizationState::Refresh { + refresh_token: refresh, + client_secret, + }; } - None if !matches!(self.state, AuthorizationState::Refresh {..}) => { + None if !matches!(self.state, AuthorizationState::Refresh { .. }) => { self.state = AuthorizationState::AccessToken { token: access_token.clone(), client_secret, @@ -607,9 +648,11 @@ impl TokenCache { /// /// To avoid double-updating the token in a race, requires the token which is being replaced. /// For the case where no token is currently present, use the empty string as the token. - pub async fn update_token(&self, client: impl NoauthClient, old_token: Arc) - -> Result, Error> - { + pub async fn update_token( + &self, + client: impl NoauthClient, + old_token: Arc, + ) -> Result, Error> { let mut write = self.auth.write().await; // Check if the token changed while we were unlocked; only update it if it // didn't. @@ -651,9 +694,8 @@ pub fn get_auth_from_env_or_prompt() -> Authorization { return Authorization::from_long_lived_access_token(long_lived); } - if let (Ok(client_id), Ok(saved)) - = (env::var("DBX_CLIENT_ID"), env::var("DBX_OAUTH")) - // important! see the above warning about using environment variables for this + if let (Ok(client_id), Ok(saved)) = (env::var("DBX_CLIENT_ID"), env::var("DBX_OAUTH")) + // important! see the above warning about using environment variables for this { match Authorization::load(client_id, &saved) { Some(auth) => return auth, @@ -679,17 +721,11 @@ pub fn get_auth_from_env_or_prompt() -> Authorization { let client_id = prompt("Give me a Dropbox API app key"); let oauth2_flow = Oauth2Type::PKCE(PkceCode::new()); - let url = AuthorizeUrlBuilder::new(&client_id, &oauth2_flow) - .build(); + let url = AuthorizeUrlBuilder::new(&client_id, &oauth2_flow).build(); eprintln!("Open this URL in your browser:"); eprintln!("{}", url); eprintln!(); let auth_code = prompt("Then paste the code here"); - Authorization::from_auth_code( - client_id, - oauth2_flow, - auth_code.trim().to_owned(), - None, - ) + Authorization::from_auth_code(client_id, oauth2_flow, auth_code.trim().to_owned(), None) } diff --git a/tests/async_client_test.rs b/tests/async_client_test.rs index 682681b..c8a0036 100644 --- a/tests/async_client_test.rs +++ b/tests/async_client_test.rs @@ -1,8 +1,8 @@ use bytes::Bytes; -use futures::io::Cursor; -use dropbox_sdk::async_routes::check; use dropbox_sdk::async_client_trait::*; +use dropbox_sdk::async_routes::check; use dropbox_sdk::Error; +use futures::io::Cursor; struct TestAsyncClient; struct TestRequest { @@ -12,7 +12,11 @@ struct TestRequest { impl HttpClient for TestAsyncClient { type Request = TestRequest; - async fn execute(&self, request: Self::Request, body: Bytes) -> Result { + async fn execute( + &self, + request: Self::Request, + body: Bytes, + ) -> Result { match request.url.as_str() { "https://api.dropboxapi.com/2/check/user" => { let arg = serde_json::from_slice::(&body)?; @@ -24,15 +28,22 @@ impl HttpClient for TestAsyncClient { status: 200, result_header: None, content_length: None, - body: Box::new(Cursor::new(format!(r#"{{"result":"{}"}}"#, arg.query).into_bytes())), + body: Box::new(Cursor::new( + format!(r#"{{"result":"{}"}}"#, arg.query).into_bytes(), + )), }) } - _ => Err(Error::HttpClient(Box::new(std::io::Error::other(format!("unhandled URL {}", request.url))))), + _ => Err(Error::HttpClient(Box::new(std::io::Error::other(format!( + "unhandled URL {}", + request.url + ))))), } } fn new_request(&self, url: &str) -> Self::Request { - TestRequest{ url: url.to_owned() } + TestRequest { + url: url.to_owned(), + } } } @@ -48,7 +59,9 @@ impl HttpRequest for TestRequest { async fn test_sync_client() { let client = TestAsyncClient; let req = check::EchoArg::default().with_query("foobar".to_owned()); - let resp = check::user(&client, &req).await.expect("request must not fail"); + let resp = check::user(&client, &req) + .await + .expect("request must not fail"); if resp.result != req.query { panic!("response mismatch"); } diff --git a/tests/benchmark.rs b/tests/benchmark.rs index 05f71dc..dfce109 100644 --- a/tests/benchmark.rs +++ b/tests/benchmark.rs @@ -1,5 +1,5 @@ -use dropbox_sdk::files; use dropbox_sdk::default_client::UserAuthDefaultClient; +use dropbox_sdk::files; use std::sync::Arc; use std::thread; use std::time::{Duration, Instant}; @@ -24,41 +24,45 @@ fn fetch_files() { common::create_clean_folder(client.as_ref(), FOLDER); - let (file_path, file_bytes) = common::create_files( - client.clone(), FOLDER, NUM_FILES, FILE_SIZE); + let (file_path, file_bytes) = + common::create_files(client.clone(), FOLDER, NUM_FILES, FILE_SIZE); threadpool.join(); println!("Test setup complete. Starting benchmark."); let mut times = vec![]; - for _ in 0 .. NUM_TEST_RUNS { + for _ in 0..NUM_TEST_RUNS { println!("sleeping 10 seconds before run"); thread::sleep(Duration::from_secs(10)); let start = Instant::now(); - for i in 0 .. NUM_FILES { + for i in 0..NUM_FILES { let path = file_path(i); let expected_bytes = file_bytes(i); let c = client.clone(); - threadpool.execute(move || { - loop { - let arg = files::DownloadArg::new(path.clone()); - match files::download(c.as_ref(), &arg, None, None) { - Ok(result) => { - let mut read_bytes = Vec::new(); - result.body.expect("result should have a body") - .read_to_end(&mut read_bytes).expect("read_to_end"); - assert_eq!(&read_bytes, &expected_bytes); - } - Err(dropbox_sdk::Error::RateLimited { retry_after_seconds, .. }) => { - eprintln!("WARNING: rate-limited {} seconds", retry_after_seconds); - thread::sleep(Duration::from_secs(retry_after_seconds as u64)); - continue; - } - Err(e) => panic!("{}: download failed: {:?}", path, e), + threadpool.execute(move || loop { + let arg = files::DownloadArg::new(path.clone()); + match files::download(c.as_ref(), &arg, None, None) { + Ok(result) => { + let mut read_bytes = Vec::new(); + result + .body + .expect("result should have a body") + .read_to_end(&mut read_bytes) + .expect("read_to_end"); + assert_eq!(&read_bytes, &expected_bytes); + } + Err(dropbox_sdk::Error::RateLimited { + retry_after_seconds, + .. + }) => { + eprintln!("WARNING: rate-limited {} seconds", retry_after_seconds); + thread::sleep(Duration::from_secs(retry_after_seconds as u64)); + continue; } - break; + Err(e) => panic!("{}: download failed: {:?}", path, e), } + break; }); } @@ -69,6 +73,8 @@ fn fetch_files() { } println!("{:?}", times); - println!("average: {} seconds", - times.iter().map(Duration::as_secs_f64).sum::() / times.len() as f64) + println!( + "average: {} seconds", + times.iter().map(Duration::as_secs_f64).sum::() / times.len() as f64 + ) } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index db2e855..cdffece 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,6 +1,6 @@ -use dropbox_sdk::Error::Api; -use dropbox_sdk::files; use dropbox_sdk::client_trait::UserAuthClient; +use dropbox_sdk::files; +use dropbox_sdk::Error::Api; use std::sync::Arc; use std::thread; use std::time::Duration; @@ -14,34 +14,37 @@ pub fn create_files( ) -> (impl Fn(u32) -> String, impl Fn(u32) -> Vec) { let threadpool = ThreadPool::new(20); - let file_bytes = move |i| format!("This is file {}.\n", i) - .into_bytes() - .into_iter() - .cycle() - .take(size) - .collect::>(); - let file_path = move |i| format!("{}/file{}.txt", path, i); + let file_bytes = move |i| { + format!("This is file {i}.\n") + .into_bytes() + .into_iter() + .cycle() + .take(size) + .collect::>() + }; + let file_path = move |i| format!("{path}/file{i}.txt"); - println!("Creating {} files in {}", num_files, path); - for i in 0 .. num_files { + println!("Creating {num_files} files in {path}"); + for i in 0..num_files { let c = client.clone(); threadpool.execute(move || { let path = file_path(i); - let arg = files::UploadArg::new(path.clone()) - .with_mode(files::WriteMode::Overwrite); + let arg = files::UploadArg::new(path.clone()).with_mode(files::WriteMode::Overwrite); loop { - println!("{}: writing", path); + println!("{path}: writing"); match files::upload(c.as_ref(), &arg, &file_bytes(i)) { Ok(_) => (), - Err(dropbox_sdk::Error::RateLimited { retry_after_seconds, .. }) => { - println!("{}: rate limited; sleeping {} seconds", - path, retry_after_seconds); + Err(dropbox_sdk::Error::RateLimited { + retry_after_seconds, + .. + }) => { + println!("{path}: rate limited; sleeping {retry_after_seconds} seconds"); thread::sleep(Duration::from_secs(retry_after_seconds as u64)); continue; } - Err(e) => panic!("{}: upload failed: {:?}", path, e), + Err(e) => panic!("{path}: upload failed: {e:?}"), } - println!("{}: done", path); + println!("{path}: done"); break; } }); @@ -52,17 +55,18 @@ pub fn create_files( } pub fn create_clean_folder(client: &impl UserAuthClient, path: &str) { - println!("Deleting any existing {} folder", path); + println!("Deleting any existing {path} folder"); match files::delete_v2(client, &files::DeleteArg::new(path.to_owned())) { Ok(_) | Err(Api(files::DeleteError::PathLookup(files::LookupError::NotFound))) => (), - Err(e) => panic!("unexpected result when deleting {}: {:?}", path, e), + Err(e) => panic!("unexpected result when deleting {path}: {e:?}"), } - println!("Creating folder {}", path); + println!("Creating folder {path}"); match files::create_folder_v2( - client, &files::CreateFolderArg::new(path.to_owned()).with_autorename(false)) - { + client, + &files::CreateFolderArg::new(path.to_owned()).with_autorename(false), + ) { Ok(_) => (), - Err(e) => panic!("unexpected result when creating {}: {:?}", path, e), + Err(e) => panic!("unexpected result when creating {path}: {e:?}"), } } diff --git a/tests/json.rs b/tests/json.rs index c17711a..7b7373c 100644 --- a/tests/json.rs +++ b/tests/json.rs @@ -1,16 +1,15 @@ #[test] fn test_null_fields_elided() { // Struct fields with optional or default values don't need to be serialized. - let value = dropbox_sdk::files::Metadata::File( - dropbox_sdk::files::FileMetadata::new( - "name".to_owned(), - "id".to_owned(), - "client_modified".to_owned(), - "server_modified".to_owned(), - "rev".to_owned(), - 1337) + let value = dropbox_sdk::files::Metadata::File(dropbox_sdk::files::FileMetadata::new( + "name".to_owned(), + "id".to_owned(), + "client_modified".to_owned(), + "server_modified".to_owned(), + "rev".to_owned(), + 1337, // Many other optional fields not populated. - ); + )); // Serialized value should only contain these fields, and should not have the optional fields // included, like `"media_info": null`. diff --git a/tests/list_folder_recursive.rs b/tests/list_folder_recursive.rs index b7bec6c..12bfe9a 100644 --- a/tests/list_folder_recursive.rs +++ b/tests/list_folder_recursive.rs @@ -1,5 +1,5 @@ -use dropbox_sdk::files; use dropbox_sdk::default_client::UserAuthDefaultClient; +use dropbox_sdk::files; use std::collections::HashSet; use std::sync::Arc; @@ -23,7 +23,7 @@ fn list_folder_recursive() { let (files_b, _) = common::create_files(client.clone(), FOLDER_INNER, NUM_FILES, FILE_SIZE); let mut files = HashSet::new(); - for i in 0 .. NUM_FILES { + for i in 0..NUM_FILES { files.insert(files_a(i)); files.insert(files_b(i)); } @@ -35,7 +35,11 @@ fn list_folder_recursive() { match metadata { files::Metadata::File(files::FileMetadata { path_lower, .. }) => { let path_lower = path_lower.expect("missing path_lower in response"); - assert!(files.remove(&path_lower), "got unexpected path {}", path_lower); + assert!( + files.remove(&path_lower), + "got unexpected path {}", + path_lower + ); } files::Metadata::Folder(files::FolderMetadata { path_lower, .. }) => { let path_lower = path_lower.expect("missing path_lower in response"); @@ -51,9 +55,14 @@ fn list_folder_recursive() { client.as_ref(), &files::ListFolderArg::new(FOLDER.to_owned()) .with_recursive(true) - .with_limit(10)) - { - Ok(files::ListFolderResult { entries, cursor, has_more, .. }) => { + .with_limit(10), + ) { + Ok(files::ListFolderResult { + entries, + cursor, + has_more, + .. + }) => { println!("{} entries", entries.len()); process_entries(entries); assert!(has_more, "expected has_more from list_folder"); @@ -65,9 +74,15 @@ fn list_folder_recursive() { while has_more { println!("list_folder_continue"); let next = match files::list_folder_continue( - client.as_ref(), &files::ListFolderContinueArg::new(cursor.clone())) - { - Ok(files::ListFolderResult { entries, cursor, has_more, .. }) => { + client.as_ref(), + &files::ListFolderContinueArg::new(cursor.clone()), + ) { + Ok(files::ListFolderResult { + entries, + cursor, + has_more, + .. + }) => { println!("{} entries", entries.len()); process_entries(entries); (cursor, has_more) diff --git a/tests/noop_client.rs b/tests/noop_client.rs index df114a5..8c03e5c 100644 --- a/tests/noop_client.rs +++ b/tests/noop_client.rs @@ -1,5 +1,5 @@ -use std::fmt::{Debug, Display, Formatter}; use dropbox_sdk::client_trait::*; +use std::fmt::{Debug, Display, Formatter}; macro_rules! noop_client { ($name:ident) => { @@ -16,7 +16,9 @@ macro_rules! noop_client { _request: Self::Request, _body: &[u8], ) -> Result { - Err(dropbox_sdk::Error::HttpClient(Box::new(super::ErrMsg("noop client called".to_owned())))) + Err(dropbox_sdk::Error::HttpClient(Box::new(super::ErrMsg( + "noop client called".to_owned(), + )))) } fn new_request(&self, _url: &str) -> Self::Request { @@ -24,7 +26,7 @@ macro_rules! noop_client { } } } - } + }; } noop_client!(app); @@ -54,4 +56,4 @@ impl Display for ErrMsg { } } -impl std::error::Error for ErrMsg{} +impl std::error::Error for ErrMsg {} diff --git a/tests/path_root.rs b/tests/path_root.rs index bdfe533..a9da15f 100644 --- a/tests/path_root.rs +++ b/tests/path_root.rs @@ -11,17 +11,23 @@ fn invalid_path_root() { match files::list_folder(&client, &ListFolderArg::new("/".to_owned())) { // If the oauth token is for an app which only has access to its app folder, then the path // root cannot be specified. - Err(dropbox_sdk::Error::BadRequest(msg)) if msg.contains("Path root is not supported for sandbox app") => (), + Err(dropbox_sdk::Error::BadRequest(msg)) + if msg.contains("Path root is not supported for sandbox app") => {} // If the oauth token is for a "whole dropbox" app, then we should get this error, which // inside will have a "no_permission" error. // If the error is due to a change in the user's home nsid, then we get an "invalid_root" // error which includes the new nsid, but that's not what we expect here, where we're just // giving a bogus nsid. - Err(dropbox_sdk::Error::UnexpectedHttpError { code: 422, response, .. }) => { + Err(dropbox_sdk::Error::UnexpectedHttpError { + code: 422, + response, + .. + }) => { let error = serde_json::from_str::(&response) .unwrap_or_else(|e| panic!("invalid json {:?}: {}", response, e)); - let tag = error.as_object() + let tag = error + .as_object() .and_then(|map| map.get("error")) .and_then(|v| v.as_object()) .and_then(|map| map.get(".tag")) diff --git a/tests/sync_client_test.rs b/tests/sync_client_test.rs index 476a236..19b0e54 100644 --- a/tests/sync_client_test.rs +++ b/tests/sync_client_test.rs @@ -1,7 +1,7 @@ -use std::io::Cursor; -use dropbox_sdk::sync_routes::check; use dropbox_sdk::client_trait::*; +use dropbox_sdk::sync_routes::check; use dropbox_sdk::Error; +use std::io::Cursor; struct TestSyncClient; struct TestRequest { @@ -22,12 +22,17 @@ impl HttpClient for TestSyncClient { body: Box::new(Cursor::new(format!(r#"{{"result":"{}"}}"#, arg.query))), }) } - _ => Err(Error::HttpClient(Box::new(std::io::Error::other(format!("unhandled URL {}", request.url))))), + _ => Err(Error::HttpClient(Box::new(std::io::Error::other(format!( + "unhandled URL {}", + request.url + ))))), } } fn new_request(&self, url: &str) -> Self::Request { - TestRequest{ url: url.to_owned() } + TestRequest { + url: url.to_owned(), + } } }