Add CLI error structs, improve routing and error reporting from API

This commit is contained in:
timvisee 2018-03-28 01:25:34 +02:00
parent affa6e65d2
commit b2e63b9efc
No known key found for this signature in database
GPG key ID: 109CBA0BF74036C2
8 changed files with 61 additions and 19 deletions

View file

@ -15,6 +15,5 @@ addons:
script: script:
- cargo build --verbose --all - cargo build --verbose --all
- cargo build --no-default-features --verbose --all - cargo build --no-default-features --verbose --all
- cargo build --features no-color --verbose --all
- cargo test --verbose --all - cargo test --verbose --all
- cargo doc - cargo doc

1
Cargo.lock generated
View file

@ -341,6 +341,7 @@ dependencies = [
"clipboard 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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 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", "ffsend-api 0.1.0",
"open 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "pbr 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -17,6 +17,7 @@ clap = "2.31"
clipboard = { version = "0.4", optional = true } clipboard = { version = "0.4", optional = true }
colored = "1.6" colored = "1.6"
failure = "0.1" failure = "0.1"
failure_derive = "0.1"
ffsend-api = { version = "*", path = "../api" } ffsend-api = { version = "*", path = "../api" }
open = "1" open = "1"
pbr = "1" pbr = "1"

View file

@ -1,13 +1,12 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use failure::Fail;
use ffsend_api::action::download::Download as ApiDownload; use ffsend_api::action::download::Download as ApiDownload;
use ffsend_api::file::file::DownloadFile; use ffsend_api::file::file::DownloadFile;
use ffsend_api::reqwest::Client; use ffsend_api::reqwest::Client;
use cmd::cmd_download::CmdDownload; use cmd::cmd_download::CmdDownload;
use error::ActionError;
use progress::ProgressBar; use progress::ProgressBar;
use util::quit_error;
/// A file download action. /// A file download action.
pub struct Download<'a> { pub struct Download<'a> {
@ -24,7 +23,7 @@ impl<'a> Download<'a> {
/// Invoke the download action. /// Invoke the download action.
// TODO: create a trait for this method // TODO: create a trait for this method
pub fn invoke(&self) { pub fn invoke(&self) -> Result<(), ActionError> {
// Get the download URL // Get the download URL
let url = self.cmd.url(); let url = self.cmd.url();
@ -40,11 +39,12 @@ impl<'a> Download<'a> {
// Execute an download action // Execute an download action
// TODO: do not unwrap, but return an error // TODO: do not unwrap, but return an error
if let Err(err) = ApiDownload::new(&file).invoke(&client, bar) { ApiDownload::new(&file).invoke(&client, bar)
quit_error(err.context("Failed to download the requested file")); .map_err(|err| ActionError::Download(err))?;
}
// TODO: open the file, or it's location // TODO: open the file, or it's location
// TODO: copy the file location // TODO: copy the file location
Ok(())
} }
} }

View file

@ -5,6 +5,7 @@ use ffsend_api::action::upload::Upload as ApiUpload;
use ffsend_api::reqwest::Client; use ffsend_api::reqwest::Client;
use cmd::cmd_upload::CmdUpload; use cmd::cmd_upload::CmdUpload;
use error::ActionError;
use progress::ProgressBar; use progress::ProgressBar;
use util::open_url; use util::open_url;
#[cfg(feature = "clipboard")] #[cfg(feature = "clipboard")]
@ -25,7 +26,7 @@ impl<'a> Upload<'a> {
/// Invoke the upload action. /// Invoke the upload action.
// TODO: create a trait for this method // TODO: create a trait for this method
pub fn invoke(&self) { pub fn invoke(&self) -> Result<(), ActionError> {
// Get API parameters // Get API parameters
let path = Path::new(self.cmd.file()).to_path_buf(); let path = Path::new(self.cmd.file()).to_path_buf();
let host = self.cmd.host(); let host = self.cmd.host();
@ -59,5 +60,7 @@ impl<'a> Upload<'a> {
.expect("failed to put download URL in user clipboard"); .expect("failed to put download URL in user clipboard");
} }
} }
Ok(())
} }
} }

20
cli/src/error.rs Normal file
View file

@ -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),
}

View file

@ -1,15 +1,20 @@
extern crate failure; extern crate failure;
#[macro_use]
extern crate failure_derive;
extern crate ffsend_api; extern crate ffsend_api;
mod action; mod action;
mod app; mod app;
mod cmd; mod cmd;
mod error;
mod progress; mod progress;
mod util; mod util;
use action::download::Download; use action::download::Download;
use action::upload::Upload; use action::upload::Upload;
use cmd::Handler; use cmd::Handler;
use error::Error;
use util::quit_error;
/// Application entrypoint. /// Application entrypoint.
fn main() { fn main() {
@ -17,26 +22,32 @@ fn main() {
let cmd_handler = Handler::parse(); let cmd_handler = Handler::parse();
// Invoke the proper action // 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. /// Invoke the proper action based on the CLI input.
/// ///
/// If no proper action is selected, the program will quit with an error /// If no proper action is selected, the program will quit with an error
/// message. /// message.
fn invoke_action(handler: &Handler) { fn invoke_action(handler: &Handler) -> Result<(), Error> {
// Match the upload command // Match the upload command
if let Some(cmd) = handler.upload() { 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 // Match the download command
if let Some(cmd) = handler.download() { 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 // No subcommand was selected, show general help
Handler::build() Handler::build()
.print_help() .print_help()
.expect("failed to print command help"); .expect("failed to print command help");
Ok(())
} }

View file

@ -18,14 +18,21 @@ use ffsend_api::url::Url;
/// Print the given error in a proper format for the user, /// Print the given error in a proper format for the user,
/// with it's causes. /// with it's causes.
pub fn print_error<E: Fail>(err: E) { pub fn print_error<E: Fail>(err: E) {
// Print the main error // Report each printable error, count them
eprintln!("{} {}", "error:".red().bold(), err); 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 // Fall back to a basic message
let mut cause = err.cause(); if count == 0 {
while let Some(err) = cause { eprintln!("{} {}", "error:".red().bold(), "An undefined error occurred");
eprintln!("{} {}", "caused by:".red().bold(), err);
cause = err.cause();
} }
} }