From b2e63b9efcda00002c6259f158e811386c2c391d Mon Sep 17 00:00:00 2001 From: timvisee Date: Wed, 28 Mar 2018 01:25:34 +0200 Subject: [PATCH] Add CLI error structs, improve routing and error reporting from API --- .travis.yml | 1 - Cargo.lock | 1 + cli/Cargo.toml | 1 + cli/src/action/download.rs | 12 ++++++------ cli/src/action/upload.rs | 5 ++++- cli/src/error.rs | 20 ++++++++++++++++++++ cli/src/main.rs | 19 +++++++++++++++---- cli/src/util.rs | 21 ++++++++++++++------- 8 files changed, 61 insertions(+), 19 deletions(-) create mode 100644 cli/src/error.rs diff --git a/.travis.yml b/.travis.yml index 2ac579f..dfa06c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,5 @@ addons: script: - cargo build --verbose --all - cargo build --no-default-features --verbose --all -- cargo build --features no-color --verbose --all - cargo test --verbose --all - cargo doc diff --git a/Cargo.lock b/Cargo.lock index d89f7bd..0f99008 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -341,6 +341,7 @@ dependencies = [ "clipboard 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "colored 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "ffsend-api 0.1.0", "open 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "pbr 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 5122036..4da6def 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -17,6 +17,7 @@ clap = "2.31" clipboard = { version = "0.4", optional = true } colored = "1.6" failure = "0.1" +failure_derive = "0.1" ffsend-api = { version = "*", path = "../api" } open = "1" pbr = "1" diff --git a/cli/src/action/download.rs b/cli/src/action/download.rs index 0ee177e..a6172db 100644 --- a/cli/src/action/download.rs +++ b/cli/src/action/download.rs @@ -1,13 +1,12 @@ use std::sync::{Arc, Mutex}; -use failure::Fail; use ffsend_api::action::download::Download as ApiDownload; use ffsend_api::file::file::DownloadFile; use ffsend_api::reqwest::Client; use cmd::cmd_download::CmdDownload; +use error::ActionError; use progress::ProgressBar; -use util::quit_error; /// A file download action. pub struct Download<'a> { @@ -24,7 +23,7 @@ impl<'a> Download<'a> { /// Invoke the download action. // TODO: create a trait for this method - pub fn invoke(&self) { + pub fn invoke(&self) -> Result<(), ActionError> { // Get the download URL let url = self.cmd.url(); @@ -40,11 +39,12 @@ impl<'a> Download<'a> { // Execute an download action // TODO: do not unwrap, but return an error - if let Err(err) = ApiDownload::new(&file).invoke(&client, bar) { - quit_error(err.context("Failed to download the requested file")); - } + ApiDownload::new(&file).invoke(&client, bar) + .map_err(|err| ActionError::Download(err))?; // TODO: open the file, or it's location // TODO: copy the file location + + Ok(()) } } diff --git a/cli/src/action/upload.rs b/cli/src/action/upload.rs index c3c3b2b..f3007e7 100644 --- a/cli/src/action/upload.rs +++ b/cli/src/action/upload.rs @@ -5,6 +5,7 @@ use ffsend_api::action::upload::Upload as ApiUpload; use ffsend_api::reqwest::Client; use cmd::cmd_upload::CmdUpload; +use error::ActionError; use progress::ProgressBar; use util::open_url; #[cfg(feature = "clipboard")] @@ -25,7 +26,7 @@ impl<'a> Upload<'a> { /// Invoke the upload action. // TODO: create a trait for this method - pub fn invoke(&self) { + pub fn invoke(&self) -> Result<(), ActionError> { // Get API parameters let path = Path::new(self.cmd.file()).to_path_buf(); let host = self.cmd.host(); @@ -59,5 +60,7 @@ impl<'a> Upload<'a> { .expect("failed to put download URL in user clipboard"); } } + + Ok(()) } } diff --git a/cli/src/error.rs b/cli/src/error.rs new file mode 100644 index 0000000..cee47b2 --- /dev/null +++ b/cli/src/error.rs @@ -0,0 +1,20 @@ +use ffsend_api::action::download::Error as DownloadError; + +#[derive(Fail, Debug)] +pub enum Error { + /// An error occurred while invoking an action. + #[fail(display = "")] + Action(#[cause] ActionError), +} + +#[derive(Debug, Fail)] +pub enum ActionError { + /// An error occurred while invoking the upload action. + // TODO: bind the upload cause here + #[fail(display = "Failed to upload the specified file")] + Upload, + + /// An error occurred while invoking the download action. + #[fail(display = "Failed to download the requested file")] + Download(#[cause] DownloadError), +} diff --git a/cli/src/main.rs b/cli/src/main.rs index 9196f1a..e7b0927 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,15 +1,20 @@ extern crate failure; +#[macro_use] +extern crate failure_derive; extern crate ffsend_api; mod action; mod app; mod cmd; +mod error; mod progress; mod util; use action::download::Download; use action::upload::Upload; use cmd::Handler; +use error::Error; +use util::quit_error; /// Application entrypoint. fn main() { @@ -17,26 +22,32 @@ fn main() { let cmd_handler = Handler::parse(); // Invoke the proper action - invoke_action(&cmd_handler); + if let Err(err) = invoke_action(&cmd_handler) { + quit_error(err); + }; } /// Invoke the proper action based on the CLI input. /// /// If no proper action is selected, the program will quit with an error /// message. -fn invoke_action(handler: &Handler) { +fn invoke_action(handler: &Handler) -> Result<(), Error> { // Match the upload command if let Some(cmd) = handler.upload() { - return Upload::new(&cmd).invoke(); + return Upload::new(&cmd).invoke() + .map_err(|err| Error::Action(err)); } // Match the download command if let Some(cmd) = handler.download() { - return Download::new(&cmd).invoke(); + return Download::new(&cmd).invoke() + .map_err(|err| Error::Action(err)); } // No subcommand was selected, show general help Handler::build() .print_help() .expect("failed to print command help"); + + Ok(()) } diff --git a/cli/src/util.rs b/cli/src/util.rs index 266b31e..ef86963 100644 --- a/cli/src/util.rs +++ b/cli/src/util.rs @@ -18,14 +18,21 @@ use ffsend_api::url::Url; /// Print the given error in a proper format for the user, /// with it's causes. pub fn print_error(err: E) { - // Print the main error - eprintln!("{} {}", "error:".red().bold(), err); + // Report each printable error, count them + let count = err.causes() + .map(|err| format!("{}", err)) + .filter(|err| !err.is_empty()) + .enumerate() + .map(|(i, err)| if i == 0 { + eprintln!("{} {}", "error:".red().bold(), err); + } else { + eprintln!("{} {}", "caused by:".red().bold(), err); + }) + .count(); - // Print the causes - let mut cause = err.cause(); - while let Some(err) = cause { - eprintln!("{} {}", "caused by:".red().bold(), err); - cause = err.cause(); + // Fall back to a basic message + if count == 0 { + eprintln!("{} {}", "error:".red().bold(), "An undefined error occurred"); } }