clap_builder/parser/
validator.rs

1// Internal
2use crate::builder::StyledStr;
3use crate::builder::{Arg, ArgGroup, ArgPredicate, Command, PossibleValue};
4use crate::error::{Error, Result as ClapResult};
5use crate::output::Usage;
6use crate::parser::{ArgMatcher, ParseState};
7use crate::util::ChildGraph;
8use crate::util::FlatMap;
9use crate::util::FlatSet;
10use crate::util::Id;
11use crate::INTERNAL_ERROR_MSG;
12
13pub(crate) struct Validator<'cmd> {
14    cmd: &'cmd Command,
15    required: ChildGraph<Id>,
16}
17
18impl<'cmd> Validator<'cmd> {
19    pub(crate) fn new(cmd: &'cmd Command) -> Self {
20        let required = cmd.required_graph();
21        Validator { cmd, required }
22    }
23
24    pub(crate) fn validate(
25        &mut self,
26        parse_state: ParseState,
27        matcher: &mut ArgMatcher,
28    ) -> ClapResult<()> {
29        debug!("Validator::validate");
30        let conflicts = Conflicts::with_args(self.cmd, matcher);
31        let has_subcmd = matcher.subcommand_name().is_some();
32
33        if let ParseState::Opt(a) = parse_state {
34            debug!("Validator::validate: needs_val_of={a:?}");
35
36            let o = &self.cmd[&a];
37            let should_err = if let Some(v) = matcher.args.get(o.get_id()) {
38                v.all_val_groups_empty() && o.get_min_vals() != 0
39            } else {
40                true
41            };
42            if should_err {
43                return Err(Error::empty_value(
44                    self.cmd,
45                    &get_possible_values_cli(o)
46                        .iter()
47                        .filter(|pv| !pv.is_hide_set())
48                        .map(|n| n.get_name().to_owned())
49                        .collect::<Vec<_>>(),
50                    o.to_string(),
51                ));
52            }
53        }
54
55        if !has_subcmd && self.cmd.is_arg_required_else_help_set() {
56            let num_user_values = matcher
57                .args()
58                .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
59                .count();
60            if num_user_values == 0 {
61                let message = self.cmd.write_help_err(false);
62                return Err(Error::display_help_error(self.cmd, message));
63            }
64        }
65        if !has_subcmd && self.cmd.is_subcommand_required_set() {
66            let bn = self.cmd.get_bin_name_fallback();
67            return Err(Error::missing_subcommand(
68                self.cmd,
69                bn.to_string(),
70                self.cmd
71                    .all_subcommand_names()
72                    .map(|s| s.to_owned())
73                    .collect::<Vec<_>>(),
74                Usage::new(self.cmd)
75                    .required(&self.required)
76                    .create_usage_with_title(&[]),
77            ));
78        }
79
80        ok!(self.validate_conflicts(matcher, &conflicts));
81        if !(self.cmd.is_subcommand_negates_reqs_set() && has_subcmd) {
82            ok!(self.validate_required(matcher, &conflicts));
83        }
84
85        Ok(())
86    }
87
88    fn validate_conflicts(
89        &mut self,
90        matcher: &ArgMatcher,
91        conflicts: &Conflicts,
92    ) -> ClapResult<()> {
93        debug!("Validator::validate_conflicts");
94
95        ok!(self.validate_exclusive(matcher));
96
97        for (arg_id, _) in matcher
98            .args()
99            .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
100            .filter(|(arg_id, _)| self.cmd.find(arg_id).is_some())
101        {
102            debug!("Validator::validate_conflicts::iter: id={arg_id:?}");
103            let conflicts = conflicts.gather_conflicts(self.cmd, arg_id);
104            ok!(self.build_conflict_err(arg_id, &conflicts, matcher));
105        }
106
107        Ok(())
108    }
109
110    fn validate_exclusive(&self, matcher: &ArgMatcher) -> ClapResult<()> {
111        debug!("Validator::validate_exclusive");
112        let args_count = matcher
113            .args()
114            .filter(|(arg_id, matched)| {
115                matched.check_explicit(&ArgPredicate::IsPresent)
116                    // Avoid including our own groups by checking none of them.  If a group is present, the
117                    // args for the group will be.
118                    && self.cmd.find(arg_id).is_some()
119            })
120            .count();
121        if args_count <= 1 {
122            // Nothing present to conflict with
123            return Ok(());
124        }
125
126        matcher
127            .args()
128            .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
129            .find_map(|(id, _)| {
130                debug!("Validator::validate_exclusive:iter:{id:?}");
131                self.cmd
132                    .find(id)
133                    // Find `arg`s which are exclusive but also appear with other args.
134                    .filter(|&arg| arg.is_exclusive_set() && args_count > 1)
135            })
136            .map(|arg| {
137                // Throw an error for the first conflict found.
138                Err(Error::argument_conflict(
139                    self.cmd,
140                    arg.to_string(),
141                    Vec::new(),
142                    Usage::new(self.cmd)
143                        .required(&self.required)
144                        .create_usage_with_title(&[]),
145                ))
146            })
147            .unwrap_or(Ok(()))
148    }
149
150    fn build_conflict_err(
151        &self,
152        name: &Id,
153        conflict_ids: &[Id],
154        matcher: &ArgMatcher,
155    ) -> ClapResult<()> {
156        if conflict_ids.is_empty() {
157            return Ok(());
158        }
159
160        debug!("Validator::build_conflict_err: name={name:?}");
161        let mut seen = FlatSet::new();
162        let conflicts = conflict_ids
163            .iter()
164            .flat_map(|c_id| {
165                if self.cmd.find_group(c_id).is_some() {
166                    self.cmd.unroll_args_in_group(c_id)
167                } else {
168                    vec![c_id.clone()]
169                }
170            })
171            .filter_map(|c_id| {
172                seen.insert(c_id.clone()).then(|| {
173                    let c_arg = self.cmd.find(&c_id).expect(INTERNAL_ERROR_MSG);
174                    c_arg.to_string()
175                })
176            })
177            .collect();
178
179        let former_arg = self.cmd.find(name).expect(INTERNAL_ERROR_MSG);
180        let usg = self.build_conflict_err_usage(matcher, conflict_ids);
181        Err(Error::argument_conflict(
182            self.cmd,
183            former_arg.to_string(),
184            conflicts,
185            usg,
186        ))
187    }
188
189    fn build_conflict_err_usage(
190        &self,
191        matcher: &ArgMatcher,
192        conflicting_keys: &[Id],
193    ) -> Option<StyledStr> {
194        let used_filtered: Vec<Id> = matcher
195            .args()
196            .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
197            .map(|(n, _)| n)
198            .filter(|n| {
199                // Filter out the args we don't want to specify.
200                self.cmd
201                    .find(n)
202                    .map(|a| !a.is_hide_set())
203                    .unwrap_or_default()
204            })
205            .filter(|key| !conflicting_keys.contains(key))
206            .cloned()
207            .collect();
208        let required: Vec<Id> = used_filtered
209            .iter()
210            .filter_map(|key| self.cmd.find(key))
211            .flat_map(|arg| arg.requires.iter().map(|item| &item.1))
212            .filter(|key| !used_filtered.contains(key) && !conflicting_keys.contains(key))
213            .chain(used_filtered.iter())
214            .cloned()
215            .collect();
216        Usage::new(self.cmd)
217            .required(&self.required)
218            .create_usage_with_title(&required)
219    }
220
221    fn gather_requires(&mut self, matcher: &ArgMatcher) {
222        debug!("Validator::gather_requires");
223        for (name, matched) in matcher
224            .args()
225            .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
226        {
227            debug!("Validator::gather_requires:iter:{name:?}");
228            if let Some(arg) = self.cmd.find(name) {
229                let is_relevant = |(val, req_arg): &(ArgPredicate, Id)| -> Option<Id> {
230                    let required = matched.check_explicit(val);
231                    required.then(|| req_arg.clone())
232                };
233
234                for req in self.cmd.unroll_arg_requires(is_relevant, arg.get_id()) {
235                    self.required.insert(req);
236                }
237            } else if let Some(g) = self.cmd.find_group(name) {
238                debug!("Validator::gather_requires:iter:{name:?}:group");
239                for r in &g.requires {
240                    self.required.insert(r.clone());
241                }
242            }
243        }
244    }
245
246    fn validate_required(&mut self, matcher: &ArgMatcher, conflicts: &Conflicts) -> ClapResult<()> {
247        debug!("Validator::validate_required: required={:?}", self.required);
248        self.gather_requires(matcher);
249
250        let mut missing_required = Vec::new();
251        let mut highest_index = 0;
252
253        let is_exclusive_present = matcher
254            .args()
255            .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
256            .any(|(id, _)| {
257                self.cmd
258                    .find(id)
259                    .map(|arg| arg.is_exclusive_set())
260                    .unwrap_or_default()
261            });
262        debug!("Validator::validate_required: is_exclusive_present={is_exclusive_present}");
263
264        for arg_or_group in self
265            .required
266            .iter()
267            .filter(|r| !matcher.check_explicit(r, &ArgPredicate::IsPresent))
268        {
269            debug!("Validator::validate_required:iter:aog={arg_or_group:?}");
270            if let Some(arg) = self.cmd.find(arg_or_group) {
271                debug!("Validator::validate_required:iter: This is an arg");
272                if !is_exclusive_present && !self.is_missing_required_ok(arg, conflicts) {
273                    debug!(
274                        "Validator::validate_required:iter: Missing {:?}",
275                        arg.get_id()
276                    );
277                    missing_required.push(arg.get_id().clone());
278                    if !arg.is_last_set() {
279                        highest_index = highest_index.max(arg.get_index().unwrap_or(0));
280                    }
281                }
282            } else if let Some(group) = self.cmd.find_group(arg_or_group) {
283                debug!("Validator::validate_required:iter: This is a group");
284                if !self
285                    .cmd
286                    .unroll_args_in_group(&group.id)
287                    .iter()
288                    .any(|a| matcher.check_explicit(a, &ArgPredicate::IsPresent))
289                {
290                    debug!(
291                        "Validator::validate_required:iter: Missing {:?}",
292                        group.get_id()
293                    );
294                    missing_required.push(group.get_id().clone());
295                }
296            }
297        }
298
299        // Validate the conditionally required args
300        for a in self
301            .cmd
302            .get_arguments()
303            .filter(|a| !matcher.check_explicit(a.get_id(), &ArgPredicate::IsPresent))
304        {
305            let mut required = false;
306
307            for (other, val) in &a.r_ifs {
308                if matcher.check_explicit(other, &ArgPredicate::Equals(val.into())) {
309                    debug!(
310                        "Validator::validate_required:iter: Missing {:?}",
311                        a.get_id()
312                    );
313                    required = true;
314                }
315            }
316
317            let match_all = a.r_ifs_all.iter().all(|(other, val)| {
318                matcher.check_explicit(other, &ArgPredicate::Equals(val.into()))
319            });
320            if match_all && !a.r_ifs_all.is_empty() {
321                debug!(
322                    "Validator::validate_required:iter: Missing {:?}",
323                    a.get_id()
324                );
325                required = true;
326            }
327
328            if (!a.r_unless.is_empty() || !a.r_unless_all.is_empty())
329                && self.fails_arg_required_unless(a, matcher)
330            {
331                debug!(
332                    "Validator::validate_required:iter: Missing {:?}",
333                    a.get_id()
334                );
335                required = true;
336            }
337
338            if !is_exclusive_present && required {
339                missing_required.push(a.get_id().clone());
340                if !a.is_last_set() {
341                    highest_index = highest_index.max(a.get_index().unwrap_or(0));
342                }
343            }
344        }
345
346        // For display purposes, include all of the preceding positional arguments
347        if !self.cmd.is_allow_missing_positional_set() {
348            for pos in self
349                .cmd
350                .get_positionals()
351                .filter(|a| !matcher.check_explicit(a.get_id(), &ArgPredicate::IsPresent))
352            {
353                if pos.get_index() < Some(highest_index) {
354                    debug!(
355                        "Validator::validate_required:iter: Missing {:?}",
356                        pos.get_id()
357                    );
358                    missing_required.push(pos.get_id().clone());
359                }
360            }
361        }
362
363        if !missing_required.is_empty() {
364            ok!(self.missing_required_error(matcher, missing_required));
365        }
366
367        Ok(())
368    }
369
370    fn is_missing_required_ok(&self, a: &Arg, conflicts: &Conflicts) -> bool {
371        debug!("Validator::is_missing_required_ok: {}", a.get_id());
372        if !conflicts.gather_conflicts(self.cmd, a.get_id()).is_empty() {
373            debug!("Validator::is_missing_required_ok: true (self)");
374            return true;
375        }
376        for group_id in self.cmd.groups_for_arg(a.get_id()) {
377            if !conflicts.gather_conflicts(self.cmd, &group_id).is_empty() {
378                debug!("Validator::is_missing_required_ok: true ({group_id})");
379                return true;
380            }
381        }
382        false
383    }
384
385    // Failing a required unless means, the arg's "unless" wasn't present, and neither were they
386    fn fails_arg_required_unless(&self, a: &Arg, matcher: &ArgMatcher) -> bool {
387        debug!("Validator::fails_arg_required_unless: a={:?}", a.get_id());
388        let exists = |id| matcher.check_explicit(id, &ArgPredicate::IsPresent);
389
390        (a.r_unless_all.is_empty() || !a.r_unless_all.iter().all(exists))
391            && !a.r_unless.iter().any(exists)
392    }
393
394    // `req_args`: an arg to include in the error even if not used
395    fn missing_required_error(
396        &self,
397        matcher: &ArgMatcher,
398        raw_req_args: Vec<Id>,
399    ) -> ClapResult<()> {
400        debug!("Validator::missing_required_error; incl={raw_req_args:?}");
401        debug!(
402            "Validator::missing_required_error: reqs={:?}",
403            self.required
404        );
405
406        let usg = Usage::new(self.cmd).required(&self.required);
407
408        let req_args = {
409            #[cfg(feature = "usage")]
410            {
411                usg.get_required_usage_from(&raw_req_args, Some(matcher), true)
412                    .into_iter()
413                    .map(|s| s.to_string())
414                    .collect::<Vec<_>>()
415            }
416
417            #[cfg(not(feature = "usage"))]
418            {
419                raw_req_args
420                    .iter()
421                    .map(|id| {
422                        if let Some(arg) = self.cmd.find(id) {
423                            arg.to_string()
424                        } else if let Some(_group) = self.cmd.find_group(id) {
425                            self.cmd.format_group(id).to_string()
426                        } else {
427                            debug_assert!(false, "id={id:?} is unknown");
428                            "".to_owned()
429                        }
430                    })
431                    .collect::<FlatSet<_>>()
432                    .into_iter()
433                    .collect::<Vec<_>>()
434            }
435        };
436
437        debug!("Validator::missing_required_error: req_args={req_args:#?}");
438
439        let used: Vec<Id> = matcher
440            .args()
441            .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
442            .map(|(n, _)| n)
443            .filter(|n| {
444                // Filter out the args we don't want to specify.
445                self.cmd
446                    .find(n)
447                    .map(|a| !a.is_hide_set())
448                    .unwrap_or_default()
449            })
450            .cloned()
451            .chain(raw_req_args)
452            .collect();
453
454        Err(Error::missing_required_argument(
455            self.cmd,
456            req_args,
457            usg.create_usage_with_title(&used),
458        ))
459    }
460}
461
462#[derive(Default, Clone, Debug)]
463struct Conflicts {
464    potential: FlatMap<Id, Vec<Id>>,
465}
466
467impl Conflicts {
468    fn with_args(cmd: &Command, matcher: &ArgMatcher) -> Self {
469        let mut potential = FlatMap::new();
470        potential.extend_unchecked(
471            matcher
472                .args()
473                .filter(|(_, matched)| matched.check_explicit(&ArgPredicate::IsPresent))
474                .map(|(id, _)| {
475                    let conf = gather_direct_conflicts(cmd, id);
476                    (id.clone(), conf)
477                }),
478        );
479        Self { potential }
480    }
481
482    fn gather_conflicts(&self, cmd: &Command, arg_id: &Id) -> Vec<Id> {
483        debug!("Conflicts::gather_conflicts: arg={arg_id:?}");
484        let mut conflicts = Vec::new();
485
486        let arg_id_conflicts_storage;
487        let arg_id_conflicts = if let Some(arg_id_conflicts) = self.get_direct_conflicts(arg_id) {
488            arg_id_conflicts
489        } else {
490            // `is_missing_required_ok` is a case where we check not-present args for conflicts
491            arg_id_conflicts_storage = gather_direct_conflicts(cmd, arg_id);
492            &arg_id_conflicts_storage
493        };
494        for (other_arg_id, other_arg_id_conflicts) in self.potential.iter() {
495            if arg_id == other_arg_id {
496                continue;
497            }
498
499            if arg_id_conflicts.contains(other_arg_id) {
500                conflicts.push(other_arg_id.clone());
501            }
502            if other_arg_id_conflicts.contains(arg_id) {
503                conflicts.push(other_arg_id.clone());
504            }
505        }
506
507        debug!("Conflicts::gather_conflicts: conflicts={conflicts:?}");
508        conflicts
509    }
510
511    fn get_direct_conflicts(&self, arg_id: &Id) -> Option<&[Id]> {
512        self.potential.get(arg_id).map(Vec::as_slice)
513    }
514}
515
516fn gather_direct_conflicts(cmd: &Command, id: &Id) -> Vec<Id> {
517    let conf = if let Some(arg) = cmd.find(id) {
518        gather_arg_direct_conflicts(cmd, arg)
519    } else if let Some(group) = cmd.find_group(id) {
520        gather_group_direct_conflicts(group)
521    } else {
522        debug_assert!(false, "id={id:?} is unknown");
523        Vec::new()
524    };
525    debug!("Conflicts::gather_direct_conflicts id={id:?}, conflicts={conf:?}",);
526    conf
527}
528
529fn gather_arg_direct_conflicts(cmd: &Command, arg: &Arg) -> Vec<Id> {
530    let mut conf = arg.blacklist.clone();
531    for group_id in cmd.groups_for_arg(arg.get_id()) {
532        let group = cmd.find_group(&group_id).expect(INTERNAL_ERROR_MSG);
533        conf.extend(group.conflicts.iter().cloned());
534        if !group.multiple {
535            for member_id in &group.args {
536                if member_id != arg.get_id() {
537                    conf.push(member_id.clone());
538                }
539            }
540        }
541    }
542
543    // Overrides are implicitly conflicts
544    conf.extend(arg.overrides.iter().cloned());
545
546    conf
547}
548
549fn gather_group_direct_conflicts(group: &ArgGroup) -> Vec<Id> {
550    group.conflicts.clone()
551}
552
553pub(crate) fn get_possible_values_cli(a: &Arg) -> Vec<PossibleValue> {
554    if !a.is_takes_value_set() {
555        vec![]
556    } else {
557        a.get_value_parser()
558            .possible_values()
559            .map(|pvs| pvs.collect())
560            .unwrap_or_default()
561    }
562}