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:
- cargo build --verbose --all
- cargo build --no-default-features --verbose --all
- cargo build --features no-color --verbose --all
- cargo test --verbose --all
- 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)",
"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)",

View file

@ -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"

View file

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

View file

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

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;
#[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(())
}

View file

@ -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<E: Fail>(err: E) {
// Print the main error
// 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);
// Print the causes
let mut cause = err.cause();
while let Some(err) = cause {
} else {
eprintln!("{} {}", "caused by:".red().bold(), err);
cause = err.cause();
})
.count();
// Fall back to a basic message
if count == 0 {
eprintln!("{} {}", "error:".red().bold(), "An undefined error occurred");
}
}