From e45eb053d52db016342bd568d10bc368495dad86 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 14 Apr 2015 08:34:53 +0200 Subject: [PATCH] fix(CLI): Display for Errors + refactor * refactored errors into a hierarchy * implemented `Display` trait for all error types, including some 'hierarchy-aware' printing. Fixes #54 --- src/mako/cli/lib/cli.py | 1 + src/mako/cli/lib/docopt.mako | 5 +- src/mako/cli/lib/engine.mako | 2 +- src/mako/cli/main.rs.mako | 2 +- src/rust/cli/cmn.rs | 101 ++++++++++++++++++++++++++++------- 5 files changed, 89 insertions(+), 22 deletions(-) diff --git a/src/mako/cli/lib/cli.py b/src/mako/cli/lib/cli.py index 0d954673286..b92074c0bfa 100644 --- a/src/mako/cli/lib/cli.py +++ b/src/mako/cli/lib/cli.py @@ -15,6 +15,7 @@ OUTPUT_FLAG = 'o' VALUE_ARG = 'v' SCOPE_FLAG = 'scope' +CONFIG_DIR_FLAG = 'config-dir' FILE_ARG = '' MIME_ARG = '' diff --git a/src/mako/cli/lib/docopt.mako b/src/mako/cli/lib/docopt.mako index 7ce6b4234d1..7d0c77f376b 100644 --- a/src/mako/cli/lib/docopt.mako +++ b/src/mako/cli/lib/docopt.mako @@ -2,7 +2,8 @@ <%! from util import (put_and, supports_scopes) from cli import (mangle_subcommand, new_method_context, PARAM_FLAG, STRUCT_FLAG, UPLOAD_FLAG, OUTPUT_FLAG, VALUE_ARG, - CONFIG_DIR, SCOPE_FLAG, is_request_value_property, FIELD_SEP, docopt_mode, FILE_ARG, MIME_ARG, OUT_ARG) + CONFIG_DIR, SCOPE_FLAG, is_request_value_property, FIELD_SEP, docopt_mode, FILE_ARG, MIME_ARG, OUT_ARG, + CONFIG_DIR_FLAG) v_arg = '<%s>' % VALUE_ARG %>\ @@ -63,7 +64,7 @@ Configuration: the user to grant this application permission to use it. If unset, it defaults to the shortest scope url for a particular method. % endif scopes - --config-dir + --${CONFIG_DIR_FLAG} A directory into which we will store our persistent data. Defaults to a user-writable directory that we will create during the first invocation. [default: ${CONFIG_DIR}] diff --git a/src/mako/cli/lib/engine.mako b/src/mako/cli/lib/engine.mako index 8d782f10f0a..23278d9772d 100644 --- a/src/mako/cli/lib/engine.mako +++ b/src/mako/cli/lib/engine.mako @@ -7,7 +7,7 @@ %>\ <%def name="new(c)">\ mod cmn; -use cmn::{InvalidOptionsError}; +use cmn::InvalidOptionsError; use oauth2::ApplicationSecret; diff --git a/src/mako/cli/main.rs.mako b/src/mako/cli/main.rs.mako index e20ca7622b8..7630ea3d431 100644 --- a/src/mako/cli/main.rs.mako +++ b/src/mako/cli/main.rs.mako @@ -30,7 +30,7 @@ fn main() { println!("{:?}", opts); match Engine::new(opts) { Err(e) => { - write!(io::stderr(), "{:?}", e).ok(); + write!(io::stderr(), "{}", e).ok(); env::set_exit_status(e.exit_code); }, Ok(mut engine) => { diff --git a/src/rust/cli/cmn.rs b/src/rust/cli/cmn.rs index c9f90339985..7a570965e24 100644 --- a/src/rust/cli/cmn.rs +++ b/src/rust/cli/cmn.rs @@ -4,20 +4,70 @@ use rustc_serialize::json; use std::fs; use std::env; use std::io; +use std::fmt; use std::path::Path; +use std::cell::RefCell; use std::io::{Write, Read}; use std::default::Default; #[derive(Debug)] -pub enum ArgumentError { - ConfigurationDirectoryInaccessible((String, io::Error)), - ConfigurationDirectoryUnset, - UsernameExpansionFailed(String), +pub enum ApplicationSecretError { + DecoderError((String, json::DecoderError)), + FormatError(String), +} + +impl fmt::Display for ApplicationSecretError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + ApplicationSecretError::DecoderError((ref path, ref err)) + => writeln!(f, "Could not decode file at '{}' with error: {}" + , path, err), + ApplicationSecretError::FormatError(ref path) + => writeln!(f, "'installed' field is unset in secret file at '{}'" + , path), + } + } +} + +#[derive(Debug)] +pub enum ConfigurationError { + DirectoryCreationFailed((String, io::Error)), + DirectoryUnset, + HomeExpansionFailed(String), + Secret(ApplicationSecretError), IOError((String, io::Error)), - SecretDecoderError((String, json::DecoderError)), - SecretFormatError(String), +} + +impl fmt::Display for ConfigurationError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + ConfigurationError::DirectoryCreationFailed((ref dir, ref err)) + => writeln!(f, "Directory '{}' could not be created with error: {}", dir, err), + ConfigurationError::DirectoryUnset + => writeln!(f, "--config-dir was unset or empty"), + ConfigurationError::HomeExpansionFailed(ref dir) + => writeln!(f, "Couldn't find HOME directory of current user, failed to expand '{}'", dir), + ConfigurationError::Secret(ref err) + => writeln!(f, "Secret -> {}", err), + ConfigurationError::IOError((ref path, ref err)) + => writeln!(f, "IO operation failed on path '{}' with error: {}", path, err), + } + } +} + +#[derive(Debug)] +pub enum ArgumentError { + Configuration(ConfigurationError), +} + +impl fmt::Display for ArgumentError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + ArgumentError::Configuration(ref err) => writeln!(f, "Configuration -> {}", err) + } + } } #[derive(Debug)] @@ -26,6 +76,15 @@ pub struct InvalidOptionsError { pub exit_code: i32, } +impl fmt::Display for InvalidOptionsError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + for issue in &self.issues { + try!(issue.fmt(f)); + } + Ok(()) + } +} + impl InvalidOptionsError { pub fn single(err: ArgumentError, exit_code: i32) -> InvalidOptionsError { InvalidOptionsError { @@ -38,13 +97,13 @@ impl InvalidOptionsError { pub fn assure_config_dir_exists(dir: &str) -> Result { let trdir = dir.trim(); if trdir.len() == 0 { - return Err(ArgumentError::ConfigurationDirectoryUnset) + return Err(ArgumentError::Configuration(ConfigurationError::DirectoryUnset)) } let expanded_config_dir = if trdir.as_bytes()[0] == b'~' { match env::var("HOME").ok().or(env::var("UserProfile").ok()) { - None => return Err(ArgumentError::UsernameExpansionFailed(trdir.to_string())), + None => return Err(ArgumentError::Configuration(ConfigurationError::HomeExpansionFailed(trdir.to_string()))), Some(mut user) => { user.push_str(&trdir[1..]); user @@ -56,7 +115,8 @@ pub fn assure_config_dir_exists(dir: &str) -> Result { if let Err(err) = fs::create_dir(&expanded_config_dir) { if err.kind() != io::ErrorKind::AlreadyExists { - return Err(ArgumentError::ConfigurationDirectoryInaccessible((expanded_config_dir, err))) + return Err(ArgumentError::Configuration( + ConfigurationError::DirectoryCreationFailed((expanded_config_dir, err)))) } } @@ -67,15 +127,15 @@ pub fn application_secret_from_directory(dir: &str, secret_basename: &str) -> Re let secret_path = Path::new(dir).join(secret_basename); let secret_str = || secret_path.as_path().to_str().unwrap().to_string(); let secret_io_error = |io_err: io::Error| { - Err(ArgumentError::IOError( + Err(ArgumentError::Configuration(ConfigurationError::IOError( (secret_str(), io_err) - )) + ))) }; for _ in 0..2 { match fs::File::open(&secret_path) { - Err(mut e) => { - if e.kind() == io::ErrorKind::NotFound { + Err(mut err) => { + if err.kind() == io::ErrorKind::NotFound { // Write our built-in one - user may adjust the written file at will let secret = ApplicationSecret { client_id: "14070749909-vgip2f1okm7bkvajhi9jugan6126io9v.apps.googleusercontent.com".to_string(), @@ -94,7 +154,7 @@ pub fn application_secret_from_directory(dir: &str, secret_basename: &str) -> Re }; let json_enocded_secret = json::encode(&app_secret).unwrap(); - e = match fs::OpenOptions::new().create(true).write(true).open(&secret_path) { + err = match fs::OpenOptions::new().create(true).write(true).open(&secret_path) { Err(cfe) => cfe, Ok(mut f) => { match f.write(json_enocded_secret.as_bytes()) { @@ -105,7 +165,7 @@ pub fn application_secret_from_directory(dir: &str, secret_basename: &str) -> Re }; // fall through to IO error handling } - return secret_io_error(e) + return secret_io_error(err) }, Ok(mut f) => { let mut json_encoded_secret = String::new(); @@ -113,12 +173,17 @@ pub fn application_secret_from_directory(dir: &str, secret_basename: &str) -> Re return secret_io_error(io_err) } match json::decode::(&json_encoded_secret) { - Err(json_decode_error) => return Err(ArgumentError::SecretDecoderError( + Err(json_decode_error) => return Err(ArgumentError::Configuration( + ConfigurationError::Secret(ApplicationSecretError::DecoderError( (secret_str(), json_decode_error) - )), + )))), Ok(console_secret) => match console_secret.installed { Some(secret) => return Ok(secret), - None => return Err(ArgumentError::SecretFormatError(secret_str())) + None => return Err( + ArgumentError::Configuration( + ConfigurationError::Secret( + ApplicationSecretError::FormatError(secret_str()) + ))) }, } }