// Internal use crate::builder::StyledStr; use crate::builder::{Arg, ArgGroup, ArgPredicate, Command, PossibleValue}; use crate::error::{Error, Result as ClapResult}; use crate::output::Usage; use crate::parser::{ArgMatcher, ParseState}; use crate::util::ChildGraph; use crate::util::FlatMap; use crate::util::FlatSet; use crate::util::Id; use crate::INTERNAL_ERROR_MSG; pub(crate) struct Validator<'cmd> { cmd: &'cmd Command, required: ChildGraph, } impl<'cmd> Validator<'cmd> { pub(crate) fn new(cmd: &'cmd Command) -> Self { let required = cmd.required_graph(); Validator { cmd, required } } pub(crate) fn validate( &mut self, parse_state: ParseState, matcher: &mut ArgMatcher, ) -> ClapResult<()> { debug!("Validator::validate"); let conflicts = Conflicts::with_args(self.cmd, matcher); let has_subcmd = matcher.subcommand_name().is_some(); if let ParseState::Opt(a) = parse_state { debug!("Validator::validate: needs_val_of={a:?}"); let o = &self.cmd[&a]; let should_err = if let Some(v) = matcher.args.get(o.get_id()) { v.all_val_groups_empty() && o.get_min_vals() != 0 } else { true }; if should_err { return Err(Error::empty_value( self.cmd, &get_possible_values_cli(o) .iter() .filter(|pv| !pv.is_hide_set()) .map(|n| n.get_name().to_owned()) .collect::>(), o.to_string(), )); } } if !has_subcmd && self.cmd.is_arg_required_else_help_set() { let num_user_values = matcher .args() .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent)) .count(); if num_user_values == 0 { let message = self.cmd.write_help_err(false); return Err(Error::display_help_error(self.cmd, message)); } } if !has_subcmd && self.cmd.is_subcommand_required_set() { let bn = self.cmd.get_bin_name_fallback(); return Err(Error::missing_subcommand( self.cmd, bn.to_string(), self.cmd .all_subcommand_names() .map(|s| s.to_owned()) .collect::>(), Usage::new(self.cmd) .required(&self.required) .create_usage_with_title(&[]), )); } ok!(self.validate_conflicts(matcher, &conflicts)); if !(self.cmd.is_subcommand_negates_reqs_set() && has_subcmd) { ok!(self.validate_required(matcher, &conflicts)); } Ok(()) } fn validate_conflicts( &mut self, matcher: &ArgMatcher, conflicts: &Conflicts, ) -> ClapResult<()> { debug!("Validator::validate_conflicts"); ok!(self.validate_exclusive(matcher)); for (arg_id, _) in matcher .args() .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent)) .filter(|(arg_id, _)| self.cmd.find(arg_id).is_some()) { debug!("Validator::validate_conflicts::iter: id={arg_id:?}"); let conflicts = conflicts.gather_conflicts(self.cmd, arg_id); ok!(self.build_conflict_err(arg_id, &conflicts, matcher)); } Ok(()) } fn validate_exclusive(&self, matcher: &ArgMatcher) -> ClapResult<()> { debug!("Validator::validate_exclusive"); let args_count = matcher .args() .filter(|(arg_id, matched)| { matched.check_explicit(&crate::builder::ArgPredicate::IsPresent) // Avoid including our own groups by checking none of them. If a group is present, the // args for the group will be. && self.cmd.find(arg_id).is_some() }) .count(); if args_count <= 1 { // Nothing present to conflict with return Ok(()); } matcher .args() .filter(|(_, matched)| matched.check_explicit(&crate::builder::ArgPredicate::IsPresent)) .filter_map(|(id, _)| { debug!("Validator::validate_exclusive:iter:{id:?}"); self.cmd .find(id) // Find `arg`s which are exclusive but also appear with other args. .filter(|&arg| arg.is_exclusive_set() && args_count > 1) }) .next() .map(|arg| { // Throw an error for the first conflict found. Err(Error::argument_conflict( self.cmd, arg.to_string(), Vec::new(), Usage::new(self.cmd) .required(&self.required) .create_usage_with_title(&[]), )) }) .unwrap_or(Ok(())) } fn build_conflict_err( &self, name: &Id, conflict_ids: &[Id], matcher: &ArgMatcher, ) -> ClapResult<()> { if conflict_ids.is_empty() { return Ok(()); } debug!("Validator::build_conflict_err: name={name:?}"); let mut seen = FlatSet::new(); let conflicts = conflict_ids .iter() .flat_map(|c_id| { if self.cmd.find_group(c_id).is_some() { self.cmd.unroll_args_in_group(c_id) } else { vec![c_id.clone()] } }) .filter_map(|c_id| { seen.insert(c_id.clone()).then(|| { let c_arg = self.cmd.find(&c_id).expect(INTERNAL_ERROR_MSG); c_arg.to_string() }) }) .collect(); let former_arg = self.cmd.find(name).expect(INTERNAL_ERROR_MSG); let usg = self.build_conflict_err_usage(matcher, conflict_ids); Err(Error::argument_conflict( self.cmd, former_arg.to_string(), conflicts, usg, )) } fn build_conflict_err_usage( &self, matcher: &ArgMatcher, conflicting_keys: &[Id], ) -> Option { let used_filtered: Vec = matcher .args() .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent)) .map(|(n, _)| n) .filter(|n| { // Filter out the args we don't want to specify. self.cmd .find(n) .map(|a| !a.is_hide_set()) .unwrap_or_default() }) .filter(|key| !conflicting_keys.contains(key)) .cloned() .collect(); let required: Vec = used_filtered .iter() .filter_map(|key| self.cmd.find(key)) .flat_map(|arg| arg.requires.iter().map(|item| &item.1)) .filter(|key| !used_filtered.contains(key) && !conflicting_keys.contains(key)) .chain(used_filtered.iter()) .cloned() .collect(); Usage::new(self.cmd) .required(&self.required) .create_usage_with_title(&required) } fn gather_requires(&mut self, matcher: &ArgMatcher) { debug!("Validator::gather_requires"); for (name, matched) in matcher .args() .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent)) { debug!("Validator::gather_requires:iter:{name:?}"); if let Some(arg) = self.cmd.find(name) { let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option { let required = matched.check_explicit(val); required.then(|| req_arg.clone()) }; for req in self.cmd.unroll_arg_requires(is_relevant, arg.get_id()) { self.required.insert(req); } } else if let Some(g) = self.cmd.find_group(name) { debug!("Validator::gather_requires:iter:{name:?}:group"); for r in &g.requires { self.required.insert(r.clone()); } } } } fn validate_required(&mut self, matcher: &ArgMatcher, conflicts: &Conflicts) -> ClapResult<()> { debug!("Validator::validate_required: required={:?}", self.required); self.gather_requires(matcher); let mut missing_required = Vec::new(); let mut highest_index = 0; let is_exclusive_present = matcher .args() .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent)) .any(|(id, _)| { self.cmd .find(id) .map(|arg| arg.is_exclusive_set()) .unwrap_or_default() }); debug!("Validator::validate_required: is_exclusive_present={is_exclusive_present}"); for arg_or_group in self .required .iter() .filter(|r| !matcher.check_explicit(r, &ArgPredicate::IsPresent)) { debug!("Validator::validate_required:iter:aog={arg_or_group:?}"); if let Some(arg) = self.cmd.find(arg_or_group) { debug!("Validator::validate_required:iter: This is an arg"); if !is_exclusive_present && !self.is_missing_required_ok(arg, conflicts) { debug!( "Validator::validate_required:iter: Missing {:?}", arg.get_id() ); missing_required.push(arg.get_id().clone()); if !arg.is_last_set() { highest_index = highest_index.max(arg.get_index().unwrap_or(0)); } } } else if let Some(group) = self.cmd.find_group(arg_or_group) { debug!("Validator::validate_required:iter: This is a group"); if !self .cmd .unroll_args_in_group(&group.id) .iter() .any(|a| matcher.check_explicit(a, &ArgPredicate::IsPresent)) { debug!( "Validator::validate_required:iter: Missing {:?}", group.get_id() ); missing_required.push(group.get_id().clone()); } } } // Validate the conditionally required args for a in self .cmd .get_arguments() .filter(|a| !matcher.check_explicit(a.get_id(), &ArgPredicate::IsPresent)) { let mut required = false; for (other, val) in &a.r_ifs { if matcher.check_explicit(other, &ArgPredicate::Equals(val.into())) { debug!( "Validator::validate_required:iter: Missing {:?}", a.get_id() ); required = true; } } let match_all = a.r_ifs_all.iter().all(|(other, val)| { matcher.check_explicit(other, &ArgPredicate::Equals(val.into())) }); if match_all && !a.r_ifs_all.is_empty() { debug!( "Validator::validate_required:iter: Missing {:?}", a.get_id() ); required = true; } if (!a.r_unless.is_empty() || !a.r_unless_all.is_empty()) && self.fails_arg_required_unless(a, matcher) { debug!( "Validator::validate_required:iter: Missing {:?}", a.get_id() ); required = true; } if required { missing_required.push(a.get_id().clone()); if !a.is_last_set() { highest_index = highest_index.max(a.get_index().unwrap_or(0)); } } } // For display purposes, include all of the preceding positional arguments if !self.cmd.is_allow_missing_positional_set() { for pos in self .cmd .get_positionals() .filter(|a| !matcher.check_explicit(a.get_id(), &ArgPredicate::IsPresent)) { if pos.get_index() < Some(highest_index) { debug!( "Validator::validate_required:iter: Missing {:?}", pos.get_id() ); missing_required.push(pos.get_id().clone()); } } } if !missing_required.is_empty() { ok!(self.missing_required_error(matcher, missing_required)); } Ok(()) } fn is_missing_required_ok(&self, a: &Arg, conflicts: &Conflicts) -> bool { debug!("Validator::is_missing_required_ok: {}", a.get_id()); if !conflicts.gather_conflicts(self.cmd, a.get_id()).is_empty() { debug!("Validator::is_missing_required_ok: true (self)"); return true; } for group_id in self.cmd.groups_for_arg(a.get_id()) { if !conflicts.gather_conflicts(self.cmd, &group_id).is_empty() { debug!("Validator::is_missing_required_ok: true ({group_id})"); return true; } } false } // Failing a required unless means, the arg's "unless" wasn't present, and neither were they fn fails_arg_required_unless(&self, a: &Arg, matcher: &ArgMatcher) -> bool { debug!("Validator::fails_arg_required_unless: a={:?}", a.get_id()); let exists = |id| matcher.check_explicit(id, &ArgPredicate::IsPresent); (a.r_unless_all.is_empty() || !a.r_unless_all.iter().all(exists)) && !a.r_unless.iter().any(exists) } // `req_args`: an arg to include in the error even if not used fn missing_required_error( &self, matcher: &ArgMatcher, raw_req_args: Vec, ) -> ClapResult<()> { debug!("Validator::missing_required_error; incl={raw_req_args:?}"); debug!( "Validator::missing_required_error: reqs={:?}", self.required ); let usg = Usage::new(self.cmd).required(&self.required); let req_args = { #[cfg(feature = "usage")] { usg.get_required_usage_from(&raw_req_args, Some(matcher), true) .into_iter() .map(|s| s.to_string()) .collect::>() } #[cfg(not(feature = "usage"))] { raw_req_args .iter() .map(|id| { if let Some(arg) = self.cmd.find(id) { arg.to_string() } else if let Some(_group) = self.cmd.find_group(id) { self.cmd.format_group(id).to_string() } else { debug_assert!(false, "id={id:?} is unknown"); "".to_owned() } }) .collect::>() } }; debug!("Validator::missing_required_error: req_args={req_args:#?}"); let used: Vec = matcher .args() .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent)) .map(|(n, _)| n) .filter(|n| { // Filter out the args we don't want to specify. self.cmd .find(n) .map(|a| !a.is_hide_set()) .unwrap_or_default() }) .cloned() .chain(raw_req_args) .collect(); Err(Error::missing_required_argument( self.cmd, req_args, usg.create_usage_with_title(&used), )) } } #[derive(Default, Clone, Debug)] struct Conflicts { potential: FlatMap>, } impl Conflicts { fn with_args(cmd: &Command, matcher: &ArgMatcher) -> Self { let mut potential = FlatMap::new(); potential.extend_unchecked( matcher .args() .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent)) .map(|(id, _)| { let conf = gather_direct_conflicts(cmd, id); (id.clone(), conf) }), ); Self { potential } } fn gather_conflicts(&self, cmd: &Command, arg_id: &Id) -> Vec { debug!("Conflicts::gather_conflicts: arg={arg_id:?}"); let mut conflicts = Vec::new(); let arg_id_conflicts_storage; let arg_id_conflicts = if let Some(arg_id_conflicts) = self.get_direct_conflicts(arg_id) { arg_id_conflicts } else { // `is_missing_required_ok` is a case where we check not-present args for conflicts arg_id_conflicts_storage = gather_direct_conflicts(cmd, arg_id); &arg_id_conflicts_storage }; for (other_arg_id, other_arg_id_conflicts) in self.potential.iter() { if arg_id == other_arg_id { continue; } if arg_id_conflicts.contains(other_arg_id) { conflicts.push(other_arg_id.clone()); } if other_arg_id_conflicts.contains(arg_id) { conflicts.push(other_arg_id.clone()); } } debug!("Conflicts::gather_conflicts: conflicts={conflicts:?}"); conflicts } fn get_direct_conflicts(&self, arg_id: &Id) -> Option<&[Id]> { self.potential.get(arg_id).map(Vec::as_slice) } } fn gather_direct_conflicts(cmd: &Command, id: &Id) -> Vec { let conf = if let Some(arg) = cmd.find(id) { gather_arg_direct_conflicts(cmd, arg) } else if let Some(group) = cmd.find_group(id) { gather_group_direct_conflicts(group) } else { debug_assert!(false, "id={id:?} is unknown"); Vec::new() }; debug!("Conflicts::gather_direct_conflicts id={id:?}, conflicts={conf:?}",); conf } fn gather_arg_direct_conflicts(cmd: &Command, arg: &Arg) -> Vec { let mut conf = arg.blacklist.clone(); for group_id in cmd.groups_for_arg(arg.get_id()) { let group = cmd.find_group(&group_id).expect(INTERNAL_ERROR_MSG); conf.extend(group.conflicts.iter().cloned()); if !group.multiple { for member_id in &group.args { if member_id != arg.get_id() { conf.push(member_id.clone()); } } } } // Overrides are implicitly conflicts conf.extend(arg.overrides.iter().cloned()); conf } fn gather_group_direct_conflicts(group: &ArgGroup) -> Vec { group.conflicts.clone() } pub(crate) fn get_possible_values_cli(a: &Arg) -> Vec { if !a.is_takes_value_set() { vec![] } else { a.get_value_parser() .possible_values() .map(|pvs| pvs.collect()) .unwrap_or_default() } }