clap/src/parse/validator.rs
Ed Page 9f12bfec47 fix!: Rename ArgValue to PossibleValue
In considering potential work for #2683, I realized we might need a type to carry data for
each of the `multiple_values`.  `ArgValue` works both for that and for
possible values, so we need to come up with a better name for one or
both.  Changing `ArgValue`s name now would be ideal since its new in
clap3 and by renaming it, we can reduce churn for users.

While thinking about this, I realized I regularly get these mixed
up, so renaming `ArgValue` to `PossibleValue` I think will help clear
things up, regardless of #2683.
2021-10-19 10:10:37 -05:00

704 lines
27 KiB
Rust

// Internal
use crate::{
build::{arg::PossibleValue, AppSettings as AS, Arg, ArgSettings},
output::Usage,
parse::{
errors::{Error, ErrorKind, Result as ClapResult},
ArgMatcher, MatchedArg, ParseState, Parser, ValueType,
},
util::{ChildGraph, Id},
INTERNAL_ERROR_MSG, INVALID_UTF8,
};
pub(crate) struct Validator<'help, 'app, 'parser> {
p: &'parser mut Parser<'help, 'app>,
c: ChildGraph<Id>,
}
impl<'help, 'app, 'parser> Validator<'help, 'app, 'parser> {
pub(crate) fn new(p: &'parser mut Parser<'help, 'app>) -> Self {
Validator {
p,
c: ChildGraph::with_capacity(5),
}
}
pub(crate) fn validate(
&mut self,
parse_state: ParseState,
is_subcmd: bool,
matcher: &mut ArgMatcher,
trailing_values: bool,
) -> ClapResult<()> {
debug!("Validator::validate");
let mut reqs_validated = false;
#[cfg(feature = "env")]
self.p.add_env(matcher, trailing_values)?;
self.p.add_defaults(matcher, trailing_values);
if let ParseState::Opt(a) = parse_state {
debug!("Validator::validate: needs_val_of={:?}", a);
self.validate_required(matcher)?;
let o = &self.p.app[&a];
reqs_validated = true;
let should_err = if let Some(v) = matcher.0.args.get(&o.id) {
v.is_vals_empty() && !(o.min_vals.is_some() && o.min_vals.unwrap() == 0)
} else {
true
};
if should_err {
return Err(Error::empty_value(
self.p.app,
o,
Usage::new(self.p).create_usage_with_title(&[]),
));
}
}
if matcher.is_empty()
&& matcher.subcommand_name().is_none()
&& self.p.is_set(AS::ArgRequiredElseHelp)
{
let message = self.p.write_help_err()?;
return Err(Error::new(
message,
ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand,
));
}
self.validate_conflicts(matcher)?;
if !(self.p.is_set(AS::SubcommandsNegateReqs) && is_subcmd || reqs_validated) {
self.validate_required(matcher)?;
self.validate_required_unless(matcher)?;
}
self.validate_matched_args(matcher)?;
Ok(())
}
fn validate_arg_values(
&self,
arg: &Arg,
ma: &MatchedArg,
matcher: &ArgMatcher,
) -> ClapResult<()> {
debug!("Validator::validate_arg_values: arg={:?}", arg.name);
for val in ma.vals_flatten() {
if !arg.is_set(ArgSettings::AllowInvalidUtf8) && val.to_str().is_none() {
debug!(
"Validator::validate_arg_values: invalid UTF-8 found in val {:?}",
val
);
return Err(Error::invalid_utf8(
self.p.app,
Usage::new(self.p).create_usage_with_title(&[]),
));
}
if !arg.possible_vals.is_empty() {
debug!(
"Validator::validate_arg_values: possible_vals={:?}",
arg.possible_vals
);
let val_str = val.to_string_lossy();
let ok = arg
.possible_vals
.iter()
.any(|pv| pv.matches(&val_str, arg.is_set(ArgSettings::IgnoreCase)));
if !ok {
let used: Vec<Id> = matcher
.arg_names()
.filter(|&n| {
self.p.app.find(n).map_or(true, |a| {
!(a.is_set(ArgSettings::Hidden) || self.p.required.contains(&a.id))
})
})
.cloned()
.collect();
return Err(Error::invalid_value(
self.p.app,
val_str.into_owned(),
&arg.possible_vals
.iter()
.filter_map(PossibleValue::get_visible_name)
.collect::<Vec<_>>(),
arg,
Usage::new(self.p).create_usage_with_title(&used),
));
}
}
if arg.is_set(ArgSettings::ForbidEmptyValues)
&& val.is_empty()
&& matcher.contains(&arg.id)
{
debug!("Validator::validate_arg_values: illegal empty val found");
return Err(Error::empty_value(
self.p.app,
arg,
Usage::new(self.p).create_usage_with_title(&[]),
));
}
if let Some(ref vtor) = arg.validator {
debug!("Validator::validate_arg_values: checking validator...");
let mut vtor = vtor.lock().unwrap();
if let Err(e) = vtor(&*val.to_string_lossy()) {
debug!("error");
return Err(Error::value_validation(
self.p.app,
arg.to_string(),
val.to_string_lossy().into_owned(),
e,
));
} else {
debug!("good");
}
}
if let Some(ref vtor) = arg.validator_os {
debug!("Validator::validate_arg_values: checking validator_os...");
let mut vtor = vtor.lock().unwrap();
if let Err(e) = vtor(val) {
debug!("error");
return Err(Error::value_validation(
self.p.app,
arg.to_string(),
val.to_string_lossy().into(),
e,
));
} else {
debug!("good");
}
}
}
Ok(())
}
fn build_conflict_err_usage(
&self,
matcher: &ArgMatcher,
retained_arg: &Arg,
conflicting_key: &Id,
) -> String {
let retained_blacklist = &retained_arg.blacklist;
let used_filtered: Vec<Id> = matcher
.arg_names()
.filter(|key| *key != conflicting_key && !retained_blacklist.contains(key))
.cloned()
.collect();
let required: Vec<Id> = used_filtered
.iter()
.filter_map(|key| self.p.app.find(key))
.flat_map(|arg| arg.requires.iter().map(|item| &item.1))
.filter(|key| {
!used_filtered.contains(key)
&& *key != conflicting_key
&& !retained_blacklist.contains(key)
})
.chain(used_filtered.iter())
.cloned()
.collect();
Usage::new(self.p).create_usage_with_title(&required)
}
fn build_conflict_err(&self, name: &Id, matcher: &ArgMatcher) -> ClapResult<()> {
debug!("Validator::build_conflict_err: name={:?}", name);
if let Some(checked_arg) = self.p.app.find(name) {
matcher
.arg_names()
.filter_map(|k| {
let arg = self.p.app.find(k);
// For an arg that blacklists `name`, this will return `Some((k, a))` to indicate a conflict.
arg.filter(|a| a.blacklist.contains(name)).map(|a| (k, a))
})
.try_for_each(|(k, a)| {
// The error will be then constructed according to the first conflict.
let (_former, former_arg, latter, latter_arg) = {
let name_pos = matcher.arg_names().position(|key| key == name);
let k_pos = matcher.arg_names().position(|key| key == k);
if name_pos < k_pos {
(name, checked_arg, k, a)
} else {
(k, a, name, checked_arg)
}
};
let usg = self.build_conflict_err_usage(matcher, former_arg, latter);
Err(Error::argument_conflict(
self.p.app,
latter_arg,
Some(former_arg.to_string()),
usg,
))
})
} else if let Some(g) = self.p.app.groups.iter().find(|x| x.id == *name) {
let usg = Usage::new(self.p).create_usage_with_title(&[]);
let args_in_group = self.p.app.unroll_args_in_group(&g.id);
let first = matcher
.arg_names()
.find(|x| args_in_group.contains(x))
.expect(INTERNAL_ERROR_MSG);
let c_with = matcher
.arg_names()
.find(|x| x != &first && args_in_group.contains(x))
.map(|x| self.p.app[x].to_string());
debug!("Validator::build_conflict_err: c_with={:?}:group", c_with);
Err(Error::argument_conflict(
self.p.app,
&self.p.app[first],
c_with,
usg,
))
} else {
Ok(())
}?;
panic!("{}", INTERNAL_ERROR_MSG);
}
fn validate_conflicts(&mut self, matcher: &mut ArgMatcher) -> ClapResult<()> {
debug!("Validator::validate_conflicts");
self.validate_exclusive(matcher)?;
self.gather_conflicts(matcher);
self.c
.iter()
.filter(|&name| {
debug!("Validator::validate_conflicts:iter:{:?}", name);
// Filter out the conflicting cases.
if let Some(g) = self
.p
.app
.groups
.iter()
.find(|g| !g.multiple && g.id == *name)
{
let conf_with_self = || {
self.p
.app
.unroll_args_in_group(&g.id)
.iter()
.filter(|&a| matcher.contains(a))
.count()
> 1
};
let conf_with_arg = || g.conflicts.iter().any(|x| matcher.contains(x));
let arg_conf_with_gr = || {
matcher
.arg_names()
.filter_map(|x| self.p.app.find(x))
.any(|x| x.blacklist.iter().any(|c| *c == g.id))
};
conf_with_self() || conf_with_arg() || arg_conf_with_gr()
} else if let Some(ma) = matcher.get(name) {
debug!(
"Validator::validate_conflicts:iter:{:?}: matcher contains it...",
name
);
ma.occurs > 0
} else {
false
}
})
// Throw an error for the first conflict found.
.try_for_each(|odd| self.build_conflict_err(odd, matcher))
}
fn validate_exclusive(&mut self, matcher: &mut ArgMatcher) -> ClapResult<()> {
debug!("Validator::validate_exclusive");
let args_count = matcher.arg_names().count();
matcher
.arg_names()
.filter_map(|name| {
debug!("Validator::validate_exclusive:iter:{:?}", name);
self.p
.app
.find(name)
// Find `arg`s which are exclusive but also appear with other args.
.filter(|&arg| arg.exclusive && args_count > 1)
})
// Throw an error for the first conflict found.
.try_for_each(|arg| {
Err(Error::argument_conflict(
self.p.app,
arg,
None,
Usage::new(self.p).create_usage_with_title(&[]),
))
})
}
// Gathers potential conflicts based on used argument, but without considering requirements
// and such
fn gather_conflicts(&mut self, matcher: &mut ArgMatcher) {
debug!("Validator::gather_conflicts");
matcher
.arg_names()
.filter(|name| {
debug!("Validator::gather_conflicts:iter: id={:?}", name);
// if arg is "present" only because it got default value
// it doesn't conflict with anything and should be skipped
let skip = matcher
.get(name)
.map_or(false, |a| a.ty == ValueType::DefaultValue);
if skip {
debug!("Validator::gather_conflicts:iter: This is default value, skipping.",);
}
!skip
})
.for_each(|name| {
if let Some(arg) = self.p.app.find(name) {
// Since an arg was used, every arg it conflicts with is added to the conflicts
for conf in &arg.blacklist {
/*
for g_arg in self.p.app.unroll_args_in_group(&g.name) {
if &g_arg != name {
self.c.insert(g.id.clone()); // TODO ERROR is here - groups allow one arg but this line disallows all group args
}
}
*/
self.c.insert(conf.clone());
}
// Now we need to know which groups this arg was a member of, to add all other
// args in that group to the conflicts, as well as any args those args conflict
// with
for grp in self.p.app.groups_for_arg(name) {
if let Some(g) = self
.p
.app
.groups
.iter()
.find(|g| !g.multiple && g.id == grp)
{
/*
for g_arg in self.p.app.unroll_args_in_group(&g.name) {
if &g_arg != name {
self.c.insert(g.id.clone());
}
}
*/
self.c.insert(g.id.clone());
}
}
} else if let Some(g) = self
.p
.app
.groups
.iter()
.find(|g| !g.multiple && g.id == *name)
{
debug!("Validator::gather_conflicts:iter:{:?}:group", name);
self.c.insert(g.id.clone());
}
});
}
fn gather_requirements(&mut self, matcher: &ArgMatcher) {
debug!("Validator::gather_requirements");
for name in matcher.arg_names() {
debug!("Validator::gather_requirements:iter:{:?}", name);
if let Some(arg) = self.p.app.find(name) {
for req in self.p.app.unroll_requirements_for_arg(&arg.id, matcher) {
self.p.required.insert(req);
}
} else if let Some(g) = self.p.app.groups.iter().find(|grp| grp.id == *name) {
debug!("Validator::gather_conflicts:iter:{:?}:group", name);
for r in &g.requires {
self.p.required.insert(r.clone());
}
}
}
}
fn validate_matched_args(&self, matcher: &mut ArgMatcher) -> ClapResult<()> {
debug!("Validator::validate_matched_args");
matcher.iter().try_for_each(|(name, ma)| {
debug!(
"Validator::validate_matched_args:iter:{:?}: vals={:#?}",
name,
ma.vals_flatten()
);
if let Some(arg) = self.p.app.find(name) {
self.validate_arg_num_vals(arg, ma)?;
self.validate_arg_values(arg, ma, matcher)?;
self.validate_arg_requires(arg, ma, matcher)?;
self.validate_arg_num_occurs(arg, ma)?;
} else {
let grp = self
.p
.app
.groups
.iter()
.find(|g| g.id == *name)
.expect(INTERNAL_ERROR_MSG);
if grp.requires.iter().any(|n| !matcher.contains(n)) {
return self.missing_required_error(matcher, vec![name.clone()]);
}
}
Ok(())
})
}
fn validate_arg_num_occurs(&self, a: &Arg, ma: &MatchedArg) -> ClapResult<()> {
debug!(
"Validator::validate_arg_num_occurs: {:?}={}",
a.name, ma.occurs
);
// Occurrence of positional argument equals to number of values rather
// than number of grouped values.
if ma.occurs > 1 && !a.is_set(ArgSettings::MultipleOccurrences) && !a.is_positional() {
// Not the first time, and we don't allow multiples
return Err(Error::unexpected_multiple_usage(
self.p.app,
a,
Usage::new(self.p).create_usage_with_title(&[]),
));
}
if let Some(max_occurs) = a.max_occurs {
debug!(
"Validator::validate_arg_num_occurs: max_occurs set...{}",
max_occurs
);
let occurs = ma.occurs as usize;
if occurs > max_occurs {
return Err(Error::too_many_occurrences(
self.p.app,
a,
max_occurs,
occurs,
Usage::new(self.p).create_usage_with_title(&[]),
));
}
}
Ok(())
}
fn validate_arg_num_vals(&self, a: &Arg, ma: &MatchedArg) -> ClapResult<()> {
debug!("Validator::validate_arg_num_vals");
if let Some(num) = a.num_vals {
let total_num = ma.num_vals();
debug!("Validator::validate_arg_num_vals: num_vals set...{}", num);
let should_err = if a.is_set(ArgSettings::MultipleOccurrences) {
total_num % num != 0
} else {
num != total_num
};
if should_err {
debug!("Validator::validate_arg_num_vals: Sending error WrongNumberOfValues");
return Err(Error::wrong_number_of_values(
self.p.app,
a,
num,
if a.is_set(ArgSettings::MultipleOccurrences) {
total_num % num
} else {
total_num
},
Usage::new(self.p).create_usage_with_title(&[]),
));
}
}
if let Some(num) = a.max_vals {
debug!("Validator::validate_arg_num_vals: max_vals set...{}", num);
if ma.num_vals() > num {
debug!("Validator::validate_arg_num_vals: Sending error TooManyValues");
return Err(Error::too_many_values(
self.p.app,
ma.vals_flatten()
.last()
.expect(INTERNAL_ERROR_MSG)
.to_str()
.expect(INVALID_UTF8)
.to_string(),
a.to_string(),
Usage::new(self.p).create_usage_with_title(&[]),
));
}
}
let min_vals_zero = if let Some(num) = a.min_vals {
debug!("Validator::validate_arg_num_vals: min_vals set: {}", num);
if ma.num_vals() < num && num != 0 {
debug!("Validator::validate_arg_num_vals: Sending error TooFewValues");
return Err(Error::too_few_values(
self.p.app,
a,
num,
ma.num_vals(),
Usage::new(self.p).create_usage_with_title(&[]),
));
}
num == 0
} else {
false
};
// Issue 665 (https://github.com/kbknapp/clap-rs/issues/665)
// Issue 1105 (https://github.com/kbknapp/clap-rs/issues/1105)
if a.is_set(ArgSettings::TakesValue) && !min_vals_zero && ma.is_vals_empty() {
return Err(Error::empty_value(
self.p.app,
a,
Usage::new(self.p).create_usage_with_title(&[]),
));
}
Ok(())
}
fn validate_arg_requires(
&self,
a: &Arg<'help>,
ma: &MatchedArg,
matcher: &ArgMatcher,
) -> ClapResult<()> {
debug!("Validator::validate_arg_requires:{:?}", a.name);
for (val, name) in &a.requires {
if let Some(val) = val {
let missing_req = |v| v == val && !matcher.contains(name);
if ma.vals_flatten().any(missing_req) {
return self.missing_required_error(matcher, vec![a.id.clone()]);
}
} else if !matcher.contains(name) {
return self.missing_required_error(matcher, vec![name.clone()]);
}
}
Ok(())
}
fn validate_required(&mut self, matcher: &ArgMatcher) -> ClapResult<()> {
debug!(
"Validator::validate_required: required={:?}",
self.p.required
);
self.gather_requirements(matcher);
for arg_or_group in self.p.required.iter().filter(|r| !matcher.contains(r)) {
debug!("Validator::validate_required:iter:aog={:?}", arg_or_group);
if let Some(arg) = self.p.app.find(arg_or_group) {
debug!("Validator::validate_required:iter: This is an arg");
if !self.is_missing_required_ok(arg, matcher) {
return self.missing_required_error(matcher, vec![]);
}
} else if let Some(group) = self.p.app.groups.iter().find(|g| g.id == *arg_or_group) {
debug!("Validator::validate_required:iter: This is a group");
if !self
.p
.app
.unroll_args_in_group(&group.id)
.iter()
.any(|a| matcher.contains(a))
{
return self.missing_required_error(matcher, vec![]);
}
}
}
// Validate the conditionally required args
for a in self.p.app.args.args() {
for (other, val) in &a.r_ifs {
if let Some(ma) = matcher.get(other) {
if ma.contains_val(val) && !matcher.contains(&a.id) {
return self.missing_required_error(matcher, vec![a.id.clone()]);
}
}
}
let match_all = a
.r_ifs_all
.iter()
.all(|(other, val)| matcher.get(other).map_or(false, |ma| ma.contains_val(val)));
if match_all && !a.r_ifs_all.is_empty() && !matcher.contains(&a.id) {
return self.missing_required_error(matcher, vec![a.id.clone()]);
}
}
Ok(())
}
fn is_missing_required_ok(&self, a: &Arg<'help>, matcher: &ArgMatcher) -> bool {
debug!("Validator::is_missing_required_ok: {}", a.name);
self.validate_arg_conflicts(a, matcher) || self.p.overridden.contains(&a.id)
}
fn validate_arg_conflicts(&self, a: &Arg<'help>, matcher: &ArgMatcher) -> bool {
debug!("Validator::validate_arg_conflicts: a={:?}", a.name);
a.blacklist.iter().any(|conf| {
matcher.contains(conf)
|| self
.p
.app
.groups
.iter()
.find(|g| g.id == *conf)
.map_or(false, |g| g.args.iter().any(|arg| matcher.contains(arg)))
})
}
fn validate_required_unless(&self, matcher: &ArgMatcher) -> ClapResult<()> {
debug!("Validator::validate_required_unless");
let failed_args: Vec<_> = self
.p
.app
.args
.args()
.filter(|&a| {
!a.r_unless.is_empty()
&& !matcher.contains(&a.id)
&& self.fails_arg_required_unless(a, matcher)
})
.map(|a| a.id.clone())
.collect();
if failed_args.is_empty() {
Ok(())
} else {
self.missing_required_error(matcher, failed_args)
}
}
// Failing a required unless means, the arg's "unless" wasn't present, and neither were they
fn fails_arg_required_unless(&self, a: &Arg<'help>, matcher: &ArgMatcher) -> bool {
debug!("Validator::fails_arg_required_unless: a={:?}", a.name);
if a.is_set(ArgSettings::RequiredUnlessAll) {
debug!("Validator::fails_arg_required_unless:{}:All", a.name);
!a.r_unless.iter().all(|id| matcher.contains(id))
} else {
debug!("Validator::fails_arg_required_unless:{}:Any", a.name);
!a.r_unless.iter().any(|id| matcher.contains(id))
}
}
// `incl`: an arg to include in the error even if not used
fn missing_required_error(&self, matcher: &ArgMatcher, incl: Vec<Id>) -> ClapResult<()> {
debug!("Validator::missing_required_error; incl={:?}", incl);
debug!(
"Validator::missing_required_error: reqs={:?}",
self.p.required
);
let usg = Usage::new(self.p);
let req_args = usg.get_required_usage_from(&incl, Some(matcher), true);
debug!(
"Validator::missing_required_error: req_args={:#?}",
req_args
);
let used: Vec<Id> = matcher
.arg_names()
.filter(|n| {
// Filter out the args we don't want to specify.
self.p.app.find(n).map_or(true, |a| {
!a.is_set(ArgSettings::Hidden)
&& a.default_vals.is_empty()
&& !self.p.required.contains(&a.id)
})
})
.cloned()
.chain(incl)
.collect();
Err(Error::missing_required_argument(
self.p.app,
req_args,
usg.create_usage_with_title(&used),
))
}
}