Merge pull request #1388 from scampi/issue-861

feat(Error): add a cause field to the Error struct
This commit is contained in:
Kevin K 2019-11-03 14:17:38 -05:00 committed by GitHub
commit 2a50c2ef80
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 140 additions and 44 deletions

View file

@ -374,7 +374,9 @@ pub enum ErrorKind {
/// Command Line Argument Parser Error /// Command Line Argument Parser Error
#[derive(Debug)] #[derive(Debug)]
pub struct Error { pub struct Error {
/// Formatted error message /// The cause of the error
pub cause: String,
/// Formatted error message, enhancing the cause message with extra information
pub message: String, pub message: String,
/// The type of error /// The type of error
pub kind: ErrorKind, pub kind: ErrorKind,
@ -383,6 +385,15 @@ pub struct Error {
} }
impl Error { impl Error {
/// Returns the singular or plural form on the verb to be based on the argument's value.
fn singular_or_plural(n: usize) -> String {
if n > 1 {
String::from("were")
} else {
String::from("was")
}
}
/// Should the message be written to `stdout` or not /// Should the message be written to `stdout` or not
pub fn use_stderr(&self) -> bool { pub fn use_stderr(&self) -> bool {
match self.kind { match self.kind {
@ -421,21 +432,37 @@ impl Error {
use_stderr: true, use_stderr: true,
when: color, when: color,
}); });
Error { let (plain_cause, colored_cause) = match other {
message: format!(
"{} The argument '{}' cannot be used with {}\n\n\
{}\n\n\
For more information try {}",
c.error("error:"),
c.warning(group.name),
match other {
Some(name) => { Some(name) => {
let n = name.into(); let n = name.into();
v.push(n.clone()); v.push(n.clone());
c.warning(format!("'{}'", n)) (
format!("The argument '{}' cannot be used with '{}'", group.name, n),
format!(
"The argument '{}' cannot be used with '{}'",
group.name,
c.warning(n)
),
)
} }
None => c.none("one or more of the other specified arguments".to_owned()), None => {
}, let n = "one or more of the other specified arguments";
(
format!("The argument '{}' cannot be used with {}", group.name, n),
format!(
"The argument '{}' cannot be used with {}",
group.name,
c.none(n)
),
)
}
};
Error {
cause: plain_cause,
message: format!(
"{} {}\n\n{}\n\nFor more information try {}",
c.error("error:"),
colored_cause,
usage, usage,
c.good("--help") c.good("--help")
), ),
@ -443,6 +470,7 @@ impl Error {
info: Some(v), info: Some(v),
} }
} }
#[doc(hidden)] #[doc(hidden)]
pub fn argument_conflict<O, U>(arg: &Arg, other: Option<O>, usage: U, color: ColorWhen) -> Self pub fn argument_conflict<O, U>(arg: &Arg, other: Option<O>, usage: U, color: ColorWhen) -> Self
where where
@ -454,21 +482,37 @@ impl Error {
use_stderr: true, use_stderr: true,
when: color, when: color,
}); });
Error { let (plain_cause, colored_cause) = match other {
message: format!(
"{} The argument '{}' cannot be used with {}\n\n\
{}\n\n\
For more information try {}",
c.error("error:"),
c.warning(&*arg.to_string()),
match other {
Some(name) => { Some(name) => {
let n = name.into(); let n = name.into();
v.push(n.clone()); v.push(n.clone());
(
format!("The argument '{}' cannot be used with '{}'", arg, n),
format!(
"The argument '{}' cannot be used with {}",
c.warning(arg.to_string()),
c.warning(format!("'{}'", n)) c.warning(format!("'{}'", n))
),
)
} }
None => c.none("one or more of the other specified arguments".to_owned()), None => {
}, let n = "one or more of the other specified arguments";
(
format!("The argument '{}' cannot be used with {}", arg, n),
format!(
"The argument '{}' cannot be used with {}",
c.warning(arg.to_string()),
c.none(n)
),
)
}
};
Error {
cause: plain_cause,
message: format!(
"{} {}\n\n{}\n\nFor more information try {}",
c.error("error:"),
colored_cause,
usage, usage,
c.good("--help") c.good("--help")
), ),
@ -487,6 +531,10 @@ impl Error {
when: color, when: color,
}); });
Error { Error {
cause: format!(
"The argument '{}' requires a value but none was supplied",
arg
),
message: format!( message: format!(
"{} The argument '{}' requires a value but none was supplied\ "{} The argument '{}' requires a value but none was supplied\
\n\n\ \n\n\
@ -529,6 +577,13 @@ impl Error {
sorted.sort(); sorted.sort();
let valid_values = sorted.join(", "); let valid_values = sorted.join(", ");
Error { Error {
cause: format!(
"'{}' isn't a valid value for '{}'\n\t\
[possible values: {}]",
bad_val.as_ref(),
arg,
valid_values
),
message: format!( message: format!(
"{} '{}' isn't a valid value for '{}'\n\t\ "{} '{}' isn't a valid value for '{}'\n\t\
[possible values: {}]\n\ [possible values: {}]\n\
@ -568,6 +623,7 @@ impl Error {
when: color, when: color,
}); });
Error { Error {
cause: format!("The subcommand '{}' wasn't recognized", s),
message: format!( message: format!(
"{} The subcommand '{}' wasn't recognized\n\t\ "{} The subcommand '{}' wasn't recognized\n\t\
Did you mean '{}'?\n\n\ Did you mean '{}'?\n\n\
@ -601,6 +657,7 @@ impl Error {
when: color, when: color,
}); });
Error { Error {
cause: format!("The subcommand '{}' wasn't recognized", s),
message: format!( message: format!(
"{} The subcommand '{}' wasn't recognized\n\n\ "{} The subcommand '{}' wasn't recognized\n\n\
{}\n\t\ {}\n\t\
@ -628,6 +685,10 @@ impl Error {
when: color, when: color,
}); });
Error { Error {
cause: format!(
"The following required arguments were not provided:{}",
required
),
message: format!( message: format!(
"{} The following required arguments were not provided:{}\n\n\ "{} The following required arguments were not provided:{}\n\n\
{}\n\n\ {}\n\n\
@ -653,6 +714,7 @@ impl Error {
when: color, when: color,
}); });
Error { Error {
cause: format!("'{}' requires a subcommand, but one was not provided", name),
message: format!( message: format!(
"{} '{}' requires a subcommand, but one was not provided\n\n\ "{} '{}' requires a subcommand, but one was not provided\n\n\
{}\n\n\ {}\n\n\
@ -677,6 +739,7 @@ impl Error {
when: color, when: color,
}); });
Error { Error {
cause: "Invalid UTF-8 was detected in one or more arguments".to_string(),
message: format!( message: format!(
"{} Invalid UTF-8 was detected in one or more arguments\n\n\ "{} Invalid UTF-8 was detected in one or more arguments\n\n\
{}\n\n\ {}\n\n\
@ -702,6 +765,10 @@ impl Error {
when: color, when: color,
}); });
Error { Error {
cause: format!(
"The value '{}' was provided to '{}', but it wasn't expecting any more values",
v, arg
),
message: format!( message: format!(
"{} The value '{}' was provided to '{}', but it wasn't expecting \ "{} The value '{}' was provided to '{}', but it wasn't expecting \
any more values\n\n\ any more values\n\n\
@ -733,9 +800,14 @@ impl Error {
use_stderr: true, use_stderr: true,
when: color, when: color,
}); });
let verb = Error::singular_or_plural(curr_vals);
Error { Error {
cause: format!(
"The argument '{}' requires at least {} values, but only {} {} provided",
arg, min_vals, curr_vals, verb
),
message: format!( message: format!(
"{} The argument '{}' requires at least {} values, but only {} w{} \ "{} The argument '{}' requires at least {} values, but only {} {} \
provided\n\n\ provided\n\n\
{}\n\n\ {}\n\n\
For more information try {}", For more information try {}",
@ -743,7 +815,7 @@ impl Error {
c.warning(arg.to_string()), c.warning(arg.to_string()),
c.warning(min_vals.to_string()), c.warning(min_vals.to_string()),
c.warning(curr_vals.to_string()), c.warning(curr_vals.to_string()),
if curr_vals > 1 { "ere" } else { "as" }, verb,
usage, usage,
c.good("--help") c.good("--help")
), ),
@ -759,6 +831,15 @@ impl Error {
when: color, when: color,
}); });
Error { Error {
cause: format!(
"Invalid value{}: {}",
if let Some(a) = arg {
format!(" for '{}'", a)
} else {
String::new()
},
err
),
message: format!( message: format!(
"{} Invalid value{}: {}", "{} Invalid value{}: {}",
c.error("error:"), c.error("error:"),
@ -781,25 +862,28 @@ impl Error {
} }
#[doc(hidden)] #[doc(hidden)]
pub fn wrong_number_of_values<S, U>( pub fn wrong_number_of_values<U>(
arg: &Arg, arg: &Arg,
num_vals: u64, num_vals: u64,
curr_vals: usize, curr_vals: usize,
suffix: S,
usage: U, usage: U,
color: ColorWhen, color: ColorWhen,
) -> Self ) -> Self
where where
S: Display,
U: Display, U: Display,
{ {
let c = Colorizer::new(&ColorizerOption { let c = Colorizer::new(&ColorizerOption {
use_stderr: true, use_stderr: true,
when: color, when: color,
}); });
let verb = Error::singular_or_plural(curr_vals);
Error { Error {
cause: format!(
"The argument '{}' requires {} values, but {} {} provided",
arg, num_vals, curr_vals, verb
),
message: format!( message: format!(
"{} The argument '{}' requires {} values, but {} w{} \ "{} The argument '{}' requires {} values, but {} {}
provided\n\n\ provided\n\n\
{}\n\n\ {}\n\n\
For more information try {}", For more information try {}",
@ -807,7 +891,7 @@ impl Error {
c.warning(arg.to_string()), c.warning(arg.to_string()),
c.warning(num_vals.to_string()), c.warning(num_vals.to_string()),
c.warning(curr_vals.to_string()), c.warning(curr_vals.to_string()),
suffix, verb,
usage, usage,
c.good("--help") c.good("--help")
), ),
@ -826,6 +910,10 @@ impl Error {
when: color, when: color,
}); });
Error { Error {
cause: format!(
"The argument '{}' was provided more than once, but cannot be used multiple times",
arg
),
message: format!( message: format!(
"{} The argument '{}' was provided more than once, but cannot \ "{} The argument '{}' was provided more than once, but cannot \
be used multiple times\n\n\ be used multiple times\n\n\
@ -842,7 +930,12 @@ impl Error {
} }
#[doc(hidden)] #[doc(hidden)]
pub fn unknown_argument<A, U>(arg: A, did_you_mean: Option<String>, usage: U, color: ColorWhen) -> Self pub fn unknown_argument<A, U>(
arg: A,
did_you_mean: Option<String>,
usage: U,
color: ColorWhen,
) -> Self
where where
A: Into<String>, A: Into<String>,
U: Display, U: Display,
@ -857,10 +950,14 @@ impl Error {
let did_you_mean_message = match did_you_mean { let did_you_mean_message = match did_you_mean {
Some(s) => format!("{}\n", s), Some(s) => format!("{}\n", s),
_ => "\n".to_owned() _ => "\n".to_owned(),
}; };
Error { Error {
cause: format!(
"Found argument '{}' which wasn't expected, or isn't valid in this context{}",
a, did_you_mean_message
),
message: format!( message: format!(
"{} Found argument '{}' which wasn't expected, or isn't valid in \ "{} Found argument '{}' which wasn't expected, or isn't valid in \
this context{}\ this context{}\
@ -886,6 +983,7 @@ impl Error {
when: color, when: color,
}); });
Error { Error {
cause: e.description().to_string(),
message: format!("{} {}", c.error("error:"), e.description()), message: format!("{} {}", c.error("error:"), e.description()),
kind: ErrorKind::Io, kind: ErrorKind::Io,
info: None, info: None,
@ -903,6 +1001,7 @@ impl Error {
when: ColorWhen::Auto, when: ColorWhen::Auto,
}); });
Error { Error {
cause: format!("The argument '{}' wasn't found", a),
message: format!( message: format!(
"{} The argument '{}' wasn't found", "{} The argument '{}' wasn't found",
c.error("error:"), c.error("error:"),
@ -923,6 +1022,7 @@ impl Error {
when: ColorWhen::Auto, when: ColorWhen::Auto,
}); });
Error { Error {
cause: description.to_string(),
message: format!("{} {}", c.error("error:"), description), message: format!("{} {}", c.error("error:"), description),
kind, kind,
info: None, info: None,

View file

@ -700,6 +700,7 @@ where
let mut out = vec![]; let mut out = vec![];
self.write_help_err(&mut out)?; self.write_help_err(&mut out)?;
return Err(ClapError { return Err(ClapError {
cause: String::new(),
message: String::from_utf8_lossy(&*out).into_owned(), message: String::from_utf8_lossy(&*out).into_owned(),
kind: ErrorKind::MissingArgumentOrSubcommand, kind: ErrorKind::MissingArgumentOrSubcommand,
info: None, info: None,
@ -1534,6 +1535,7 @@ where
match Help::new(&mut buf, self, use_long, false).write_help() { match Help::new(&mut buf, self, use_long, false).write_help() {
Err(e) => e, Err(e) => e,
_ => ClapError { _ => ClapError {
cause: String::new(),
message: String::from_utf8(buf).unwrap_or_default(), message: String::from_utf8(buf).unwrap_or_default(),
kind: ErrorKind::HelpDisplayed, kind: ErrorKind::HelpDisplayed,
info: None, info: None,
@ -1548,6 +1550,7 @@ where
match self.print_version(&mut buf_w, use_long) { match self.print_version(&mut buf_w, use_long) {
Err(e) => e, Err(e) => e,
_ => ClapError { _ => ClapError {
cause: String::new(),
message: String::new(), message: String::new(),
kind: ErrorKind::VersionDisplayed, kind: ErrorKind::VersionDisplayed,
info: None, info: None,

View file

@ -67,6 +67,7 @@ impl<'b, 'c, 'z> Validator<'b, 'c, 'z> {
let mut out = vec![]; let mut out = vec![];
self.p.write_help_err(&mut out)?; self.p.write_help_err(&mut out)?;
return Err(Error { return Err(Error {
cause: String::new(),
message: String::from_utf8_lossy(&*out).into_owned(), message: String::from_utf8_lossy(&*out).into_owned(),
kind: ErrorKind::MissingArgumentOrSubcommand, kind: ErrorKind::MissingArgumentOrSubcommand,
info: None, info: None,
@ -398,14 +399,6 @@ impl<'b, 'c, 'z> Validator<'b, 'c, 'z> {
} else { } else {
ma.vals.len() ma.vals.len()
}, },
if ma.vals.len() == 1
|| (a.is_set(ArgSettings::MultipleValues)
&& (ma.vals.len() % num as usize) == 1)
{
"as"
} else {
"ere"
},
&*Usage::new(self.p).create_usage_with_title(&[]), &*Usage::new(self.p).create_usage_with_title(&[]),
self.p.app.color(), self.p.app.color(),
)); ));