feat(API): improved error handling

We are now able to decode detailed errors and pass them on. This allows
the CLI to provide more useful error responses.
Additionally, the CLI will only print debug responses in --debug mode.

Fixes #82
This commit is contained in:
Sebastian Thiel
2015-04-25 09:49:18 +02:00
parent f8689be451
commit a328942033
94 changed files with 35183 additions and 16 deletions

View File

@@ -0,0 +1,648 @@
// DO NOT EDIT !
// This file was generated automatically from 'src/mako/cli/main.rs.mako'
// DO NOT EDIT !
#![feature(plugin, exit_status)]
#![plugin(docopt_macros)]
#![allow(unused_variables, unused_imports, dead_code, unused_mut)]
extern crate docopt;
extern crate yup_oauth2 as oauth2;
extern crate yup_hyper_mock as mock;
extern crate rustc_serialize;
extern crate serde;
extern crate hyper;
extern crate mime;
extern crate google_taskqueue1_beta2 as api;
use std::env;
use std::io::{self, Write};
docopt!(Options derive Debug, "
Usage:
taskqueue1-beta2 [options] taskqueues get <project> <taskqueue> [-p <v>]... [-o <out>]
taskqueue1-beta2 [options] tasks delete <project> <taskqueue> <task> [-p <v>]...
taskqueue1-beta2 [options] tasks get <project> <taskqueue> <task> [-p <v>]... [-o <out>]
taskqueue1-beta2 [options] tasks insert <project> <taskqueue> -r <kv>... [-p <v>]... [-o <out>]
taskqueue1-beta2 [options] tasks lease <project> <taskqueue> <num-tasks> <lease-secs> [-p <v>]... [-o <out>]
taskqueue1-beta2 [options] tasks list <project> <taskqueue> [-p <v>]... [-o <out>]
taskqueue1-beta2 [options] tasks patch <project> <taskqueue> <task> <new-lease-seconds> -r <kv>... [-p <v>]... [-o <out>]
taskqueue1-beta2 [options] tasks update <project> <taskqueue> <task> <new-lease-seconds> -r <kv>... [-p <v>]... [-o <out>]
taskqueue1-beta2 --help
All documentation details can be found TODO: <URL to github.io docs here, see #51>
Configuration:
--scope <url>
Specify the authentication a method should be executed in. Each scope requires
the user to grant this application permission to use it.
If unset, it defaults to the shortest scope url for a particular method.
--config-dir <folder>
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: ~/.google-service-cli]
--debug
Output all server communication to standard error. `tx` and `rx` are placed into
the same stream.
--debug-auth
Output all communication related to authentication to standard error. `tx` and `rx` are placed into
the same stream.
");
mod cmn;
use cmn::{InvalidOptionsError, CLIError, JsonTokenStorage, arg_from_str, writer_from_opts, parse_kv_arg,
input_file_from_opts, input_mime_from_opts, FieldCursor, FieldError};
use std::default::Default;
use std::str::FromStr;
use oauth2::{Authenticator, DefaultAuthenticatorDelegate};
use rustc_serialize::json;
struct Engine {
opt: Options,
hub: api::Taskqueue<hyper::Client, Authenticator<DefaultAuthenticatorDelegate, JsonTokenStorage, hyper::Client>>,
}
impl Engine {
fn _taskqueues_get(&self, dry_run: bool, err: &mut InvalidOptionsError)
-> Option<api::Error> {
let mut call = self.hub.taskqueues().get(&self.opt.arg_project, &self.opt.arg_taskqueue);
for parg in self.opt.arg_v.iter() {
let (key, value) = parse_kv_arg(&*parg, err, false);
match key {
"get-stats" => {
call = call.get_stats(arg_from_str(value.unwrap_or("false"), err, "get-stats", "boolean"));
},
"alt"
|"fields"
|"key"
|"oauth-token"
|"pretty-print"
|"quota-user"
|"user-ip" => {
let map = [
("oauth-token", "oauth_token"),
("pretty-print", "prettyPrint"),
("quota-user", "quotaUser"),
("user-ip", "userIp"),
];
call = call.param(map.iter().find(|t| t.0 == key).unwrap_or(&("", key)).1, value.unwrap_or("unset"))
},
_ => err.issues.push(CLIError::UnknownParameter(key.to_string())),
}
}
let protocol = "standard-request";
if dry_run {
None
} else {
assert!(err.issues.len() == 0);
let mut ostream = writer_from_opts(self.opt.flag_o, &self.opt.arg_out);
match match protocol {
"standard-request" => call.doit(),
_ => unreachable!(),
} {
Err(api_err) => Some(api_err),
Ok((mut response, output_schema)) => {
serde::json::to_writer_pretty(&mut ostream, &output_schema).unwrap();
None
}
}
}
}
fn _tasks_delete(&self, dry_run: bool, err: &mut InvalidOptionsError)
-> Option<api::Error> {
let mut call = self.hub.tasks().delete(&self.opt.arg_project, &self.opt.arg_taskqueue, &self.opt.arg_task);
for parg in self.opt.arg_v.iter() {
let (key, value) = parse_kv_arg(&*parg, err, false);
match key {
"alt"
|"fields"
|"key"
|"oauth-token"
|"pretty-print"
|"quota-user"
|"user-ip" => {
let map = [
("oauth-token", "oauth_token"),
("pretty-print", "prettyPrint"),
("quota-user", "quotaUser"),
("user-ip", "userIp"),
];
call = call.param(map.iter().find(|t| t.0 == key).unwrap_or(&("", key)).1, value.unwrap_or("unset"))
},
_ => err.issues.push(CLIError::UnknownParameter(key.to_string())),
}
}
let protocol = "standard-request";
if dry_run {
None
} else {
assert!(err.issues.len() == 0);
match match protocol {
"standard-request" => call.doit(),
_ => unreachable!(),
} {
Err(api_err) => Some(api_err),
Ok(mut response) => {
None
}
}
}
}
fn _tasks_get(&self, dry_run: bool, err: &mut InvalidOptionsError)
-> Option<api::Error> {
let mut call = self.hub.tasks().get(&self.opt.arg_project, &self.opt.arg_taskqueue, &self.opt.arg_task);
for parg in self.opt.arg_v.iter() {
let (key, value) = parse_kv_arg(&*parg, err, false);
match key {
"alt"
|"fields"
|"key"
|"oauth-token"
|"pretty-print"
|"quota-user"
|"user-ip" => {
let map = [
("oauth-token", "oauth_token"),
("pretty-print", "prettyPrint"),
("quota-user", "quotaUser"),
("user-ip", "userIp"),
];
call = call.param(map.iter().find(|t| t.0 == key).unwrap_or(&("", key)).1, value.unwrap_or("unset"))
},
_ => err.issues.push(CLIError::UnknownParameter(key.to_string())),
}
}
let protocol = "standard-request";
if dry_run {
None
} else {
assert!(err.issues.len() == 0);
let mut ostream = writer_from_opts(self.opt.flag_o, &self.opt.arg_out);
match match protocol {
"standard-request" => call.doit(),
_ => unreachable!(),
} {
Err(api_err) => Some(api_err),
Ok((mut response, output_schema)) => {
serde::json::to_writer_pretty(&mut ostream, &output_schema).unwrap();
None
}
}
}
}
fn _tasks_insert(&self, dry_run: bool, err: &mut InvalidOptionsError)
-> Option<api::Error> {
let mut request = api::Task::default();
let mut call = self.hub.tasks().insert(&request, &self.opt.arg_project, &self.opt.arg_taskqueue);
for parg in self.opt.arg_v.iter() {
let (key, value) = parse_kv_arg(&*parg, err, false);
match key {
"alt"
|"fields"
|"key"
|"oauth-token"
|"pretty-print"
|"quota-user"
|"user-ip" => {
let map = [
("oauth-token", "oauth_token"),
("pretty-print", "prettyPrint"),
("quota-user", "quotaUser"),
("user-ip", "userIp"),
];
call = call.param(map.iter().find(|t| t.0 == key).unwrap_or(&("", key)).1, value.unwrap_or("unset"))
},
_ => err.issues.push(CLIError::UnknownParameter(key.to_string())),
}
}
let mut field_name = FieldCursor::default();
for kvarg in self.opt.arg_kv.iter() {
let (key, value) = parse_kv_arg(&*kvarg, err, false);
if let Err(field_err) = field_name.set(&*key) {
err.issues.push(field_err);
}
match &field_name.to_string()[..] {
"kind" => {
request.kind = Some(value.unwrap_or("").to_string());
},
"lease-timestamp" => {
request.lease_timestamp = Some(value.unwrap_or("").to_string());
},
"id" => {
request.id = Some(value.unwrap_or("").to_string());
},
"retry-count" => {
request.retry_count = Some(arg_from_str(value.unwrap_or("-0"), err, "retry-count", "integer"));
},
"tag" => {
request.tag = Some(value.unwrap_or("").to_string());
},
"payload-base64" => {
request.payload_base64 = Some(value.unwrap_or("").to_string());
},
"queue-name" => {
request.queue_name = Some(value.unwrap_or("").to_string());
},
"enqueue-timestamp" => {
request.enqueue_timestamp = Some(value.unwrap_or("").to_string());
},
_ => {
err.issues.push(CLIError::Field(FieldError::Unknown(field_name.to_string())));
}
}
}
let protocol = "standard-request";
if dry_run {
None
} else {
assert!(err.issues.len() == 0);
let mut ostream = writer_from_opts(self.opt.flag_o, &self.opt.arg_out);
match match protocol {
"standard-request" => call.doit(),
_ => unreachable!(),
} {
Err(api_err) => Some(api_err),
Ok((mut response, output_schema)) => {
serde::json::to_writer_pretty(&mut ostream, &output_schema).unwrap();
None
}
}
}
}
fn _tasks_lease(&self, dry_run: bool, err: &mut InvalidOptionsError)
-> Option<api::Error> {
let num_tasks: i32 = arg_from_str(&self.opt.arg_num_tasks, err, "<num-tasks>", "integer");
let lease_secs: i32 = arg_from_str(&self.opt.arg_lease_secs, err, "<lease-secs>", "integer");
let mut call = self.hub.tasks().lease(&self.opt.arg_project, &self.opt.arg_taskqueue, num_tasks, lease_secs);
for parg in self.opt.arg_v.iter() {
let (key, value) = parse_kv_arg(&*parg, err, false);
match key {
"tag" => {
call = call.tag(value.unwrap_or(""));
},
"group-by-tag" => {
call = call.group_by_tag(arg_from_str(value.unwrap_or("false"), err, "group-by-tag", "boolean"));
},
"alt"
|"fields"
|"key"
|"oauth-token"
|"pretty-print"
|"quota-user"
|"user-ip" => {
let map = [
("oauth-token", "oauth_token"),
("pretty-print", "prettyPrint"),
("quota-user", "quotaUser"),
("user-ip", "userIp"),
];
call = call.param(map.iter().find(|t| t.0 == key).unwrap_or(&("", key)).1, value.unwrap_or("unset"))
},
_ => err.issues.push(CLIError::UnknownParameter(key.to_string())),
}
}
let protocol = "standard-request";
if dry_run {
None
} else {
assert!(err.issues.len() == 0);
let mut ostream = writer_from_opts(self.opt.flag_o, &self.opt.arg_out);
match match protocol {
"standard-request" => call.doit(),
_ => unreachable!(),
} {
Err(api_err) => Some(api_err),
Ok((mut response, output_schema)) => {
serde::json::to_writer_pretty(&mut ostream, &output_schema).unwrap();
None
}
}
}
}
fn _tasks_list(&self, dry_run: bool, err: &mut InvalidOptionsError)
-> Option<api::Error> {
let mut call = self.hub.tasks().list(&self.opt.arg_project, &self.opt.arg_taskqueue);
for parg in self.opt.arg_v.iter() {
let (key, value) = parse_kv_arg(&*parg, err, false);
match key {
"alt"
|"fields"
|"key"
|"oauth-token"
|"pretty-print"
|"quota-user"
|"user-ip" => {
let map = [
("oauth-token", "oauth_token"),
("pretty-print", "prettyPrint"),
("quota-user", "quotaUser"),
("user-ip", "userIp"),
];
call = call.param(map.iter().find(|t| t.0 == key).unwrap_or(&("", key)).1, value.unwrap_or("unset"))
},
_ => err.issues.push(CLIError::UnknownParameter(key.to_string())),
}
}
let protocol = "standard-request";
if dry_run {
None
} else {
assert!(err.issues.len() == 0);
let mut ostream = writer_from_opts(self.opt.flag_o, &self.opt.arg_out);
match match protocol {
"standard-request" => call.doit(),
_ => unreachable!(),
} {
Err(api_err) => Some(api_err),
Ok((mut response, output_schema)) => {
serde::json::to_writer_pretty(&mut ostream, &output_schema).unwrap();
None
}
}
}
}
fn _tasks_patch(&self, dry_run: bool, err: &mut InvalidOptionsError)
-> Option<api::Error> {
let mut request = api::Task::default();
let new_lease_seconds: i32 = arg_from_str(&self.opt.arg_new_lease_seconds, err, "<new-lease-seconds>", "integer");
let mut call = self.hub.tasks().patch(&request, &self.opt.arg_project, &self.opt.arg_taskqueue, &self.opt.arg_task, new_lease_seconds);
for parg in self.opt.arg_v.iter() {
let (key, value) = parse_kv_arg(&*parg, err, false);
match key {
"alt"
|"fields"
|"key"
|"oauth-token"
|"pretty-print"
|"quota-user"
|"user-ip" => {
let map = [
("oauth-token", "oauth_token"),
("pretty-print", "prettyPrint"),
("quota-user", "quotaUser"),
("user-ip", "userIp"),
];
call = call.param(map.iter().find(|t| t.0 == key).unwrap_or(&("", key)).1, value.unwrap_or("unset"))
},
_ => err.issues.push(CLIError::UnknownParameter(key.to_string())),
}
}
let mut field_name = FieldCursor::default();
for kvarg in self.opt.arg_kv.iter() {
let (key, value) = parse_kv_arg(&*kvarg, err, false);
if let Err(field_err) = field_name.set(&*key) {
err.issues.push(field_err);
}
match &field_name.to_string()[..] {
"kind" => {
request.kind = Some(value.unwrap_or("").to_string());
},
"lease-timestamp" => {
request.lease_timestamp = Some(value.unwrap_or("").to_string());
},
"id" => {
request.id = Some(value.unwrap_or("").to_string());
},
"retry-count" => {
request.retry_count = Some(arg_from_str(value.unwrap_or("-0"), err, "retry-count", "integer"));
},
"tag" => {
request.tag = Some(value.unwrap_or("").to_string());
},
"payload-base64" => {
request.payload_base64 = Some(value.unwrap_or("").to_string());
},
"queue-name" => {
request.queue_name = Some(value.unwrap_or("").to_string());
},
"enqueue-timestamp" => {
request.enqueue_timestamp = Some(value.unwrap_or("").to_string());
},
_ => {
err.issues.push(CLIError::Field(FieldError::Unknown(field_name.to_string())));
}
}
}
let protocol = "standard-request";
if dry_run {
None
} else {
assert!(err.issues.len() == 0);
let mut ostream = writer_from_opts(self.opt.flag_o, &self.opt.arg_out);
match match protocol {
"standard-request" => call.doit(),
_ => unreachable!(),
} {
Err(api_err) => Some(api_err),
Ok((mut response, output_schema)) => {
serde::json::to_writer_pretty(&mut ostream, &output_schema).unwrap();
None
}
}
}
}
fn _tasks_update(&self, dry_run: bool, err: &mut InvalidOptionsError)
-> Option<api::Error> {
let mut request = api::Task::default();
let new_lease_seconds: i32 = arg_from_str(&self.opt.arg_new_lease_seconds, err, "<new-lease-seconds>", "integer");
let mut call = self.hub.tasks().update(&request, &self.opt.arg_project, &self.opt.arg_taskqueue, &self.opt.arg_task, new_lease_seconds);
for parg in self.opt.arg_v.iter() {
let (key, value) = parse_kv_arg(&*parg, err, false);
match key {
"alt"
|"fields"
|"key"
|"oauth-token"
|"pretty-print"
|"quota-user"
|"user-ip" => {
let map = [
("oauth-token", "oauth_token"),
("pretty-print", "prettyPrint"),
("quota-user", "quotaUser"),
("user-ip", "userIp"),
];
call = call.param(map.iter().find(|t| t.0 == key).unwrap_or(&("", key)).1, value.unwrap_or("unset"))
},
_ => err.issues.push(CLIError::UnknownParameter(key.to_string())),
}
}
let mut field_name = FieldCursor::default();
for kvarg in self.opt.arg_kv.iter() {
let (key, value) = parse_kv_arg(&*kvarg, err, false);
if let Err(field_err) = field_name.set(&*key) {
err.issues.push(field_err);
}
match &field_name.to_string()[..] {
"kind" => {
request.kind = Some(value.unwrap_or("").to_string());
},
"lease-timestamp" => {
request.lease_timestamp = Some(value.unwrap_or("").to_string());
},
"id" => {
request.id = Some(value.unwrap_or("").to_string());
},
"retry-count" => {
request.retry_count = Some(arg_from_str(value.unwrap_or("-0"), err, "retry-count", "integer"));
},
"tag" => {
request.tag = Some(value.unwrap_or("").to_string());
},
"payload-base64" => {
request.payload_base64 = Some(value.unwrap_or("").to_string());
},
"queue-name" => {
request.queue_name = Some(value.unwrap_or("").to_string());
},
"enqueue-timestamp" => {
request.enqueue_timestamp = Some(value.unwrap_or("").to_string());
},
_ => {
err.issues.push(CLIError::Field(FieldError::Unknown(field_name.to_string())));
}
}
}
let protocol = "standard-request";
if dry_run {
None
} else {
assert!(err.issues.len() == 0);
let mut ostream = writer_from_opts(self.opt.flag_o, &self.opt.arg_out);
match match protocol {
"standard-request" => call.doit(),
_ => unreachable!(),
} {
Err(api_err) => Some(api_err),
Ok((mut response, output_schema)) => {
serde::json::to_writer_pretty(&mut ostream, &output_schema).unwrap();
None
}
}
}
}
fn _doit(&self, dry_run: bool) -> (Option<api::Error>, Option<InvalidOptionsError>) {
let mut err = InvalidOptionsError::new();
let mut call_result: Option<api::Error>;
let mut err_opt: Option<InvalidOptionsError> = None;
if self.opt.cmd_taskqueues {
if self.opt.cmd_get {
call_result = self._taskqueues_get(dry_run, &mut err);
} else {
unreachable!();
}
}
else if self.opt.cmd_tasks {
if self.opt.cmd_delete {
call_result = self._tasks_delete(dry_run, &mut err);
} else if self.opt.cmd_get {
call_result = self._tasks_get(dry_run, &mut err);
} else if self.opt.cmd_insert {
call_result = self._tasks_insert(dry_run, &mut err);
} else if self.opt.cmd_lease {
call_result = self._tasks_lease(dry_run, &mut err);
} else if self.opt.cmd_list {
call_result = self._tasks_list(dry_run, &mut err);
} else if self.opt.cmd_patch {
call_result = self._tasks_patch(dry_run, &mut err);
} else if self.opt.cmd_update {
call_result = self._tasks_update(dry_run, &mut err);
} else {
unreachable!();
}
} else {
unreachable!();
}
if dry_run {
if err.issues.len() > 0 {
err_opt = Some(err);
}
}
(call_result, err_opt)
}
// Please note that this call will fail if any part of the opt can't be handled
fn new(opt: Options) -> Result<Engine, InvalidOptionsError> {
let (config_dir, secret) = {
let config_dir = match cmn::assure_config_dir_exists(&opt.flag_config_dir) {
Err(e) => return Err(InvalidOptionsError::single(e, 3)),
Ok(p) => p,
};
match cmn::application_secret_from_directory(&config_dir, "taskqueue1-beta2-secret.json",
"{\"installed\":{\"auth_uri\":\"https://accounts.google.com/o/oauth2/auth\",\"client_secret\":\"hCsslbCUyfehWMmbkG8vTYxG\",\"token_uri\":\"https://accounts.google.com/o/oauth2/token\",\"client_email\":\"\",\"redirect_uris\":[\"urn:ietf:wg:oauth:2.0:oob\",\"oob\"],\"client_x509_cert_url\":\"\",\"client_id\":\"620010449518-9ngf7o4dhs0dka470npqvor6dc5lqb9b.apps.googleusercontent.com\",\"auth_provider_x509_cert_url\":\"https://www.googleapis.com/oauth2/v1/certs\"}}") {
Ok(secret) => (config_dir, secret),
Err(e) => return Err(InvalidOptionsError::single(e, 4))
}
};
let auth = Authenticator::new( &secret, DefaultAuthenticatorDelegate,
if opt.flag_debug_auth {
hyper::Client::with_connector(mock::TeeConnector {
connector: hyper::net::HttpConnector(None)
})
} else {
hyper::Client::new()
},
JsonTokenStorage {
program_name: "taskqueue1-beta2",
db_dir: config_dir.clone(),
}, None);
let client =
if opt.flag_debug {
hyper::Client::with_connector(mock::TeeConnector {
connector: hyper::net::HttpConnector(None)
})
} else {
hyper::Client::new()
};
let engine = Engine {
opt: opt,
hub: api::Taskqueue::new(client, auth),
};
match engine._doit(true) {
(_, Some(err)) => Err(err),
_ => Ok(engine),
}
}
// Execute the call with all the bells and whistles, informing the caller only if there was an error.
// The absense of one indicates success.
fn doit(&self) -> Option<api::Error> {
self._doit(false).0
}
}
fn main() {
let opts: Options = Options::docopt().decode().unwrap_or_else(|e| e.exit());
match Engine::new(opts) {
Err(err) => {
writeln!(io::stderr(), "{}", err).ok();
env::set_exit_status(err.exit_code);
},
Ok(engine) => {
if let Some(err) = engine.doit() {
writeln!(io::stderr(), "{:?}", err).ok();
writeln!(io::stderr(), "{}", err).ok();
env::set_exit_status(1);
}
}
}
}