utils/
arg_parser.rs

1// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::BTreeMap;
5use std::{env, fmt, result};
6
7pub type Result<T> = result::Result<T, UtilsArgParserError>;
8
9const ARG_PREFIX: &str = "--";
10const ARG_SEPARATOR: &str = "--";
11const HELP_ARG: &str = "--help";
12const SHORT_HELP_ARG: &str = "-h";
13const VERSION_ARG: &str = "--version";
14
15/// Errors associated with parsing and validating arguments.
16#[derive(Debug, PartialEq, Eq, thiserror::Error, displaydoc::Display)]
17pub enum UtilsArgParserError {
18    /// Argument '{1}' cannot be used together with argument '{0}'.
19    ForbiddenArgument(String, String),
20    /// Argument '{0}' required, but not found.
21    MissingArgument(String),
22    /// The argument '{0}' requires a value, but none was supplied.
23    MissingValue(String),
24    /// Found argument '{0}' which wasn't expected, or isn't valid in this context.
25    UnexpectedArgument(String),
26    /// The argument '{0}' was provided more than once.
27    DuplicateArgument(String),
28}
29
30/// Keep information about the argument parser.
31#[derive(Debug, Clone, Default)]
32pub struct ArgParser<'a> {
33    arguments: Arguments<'a>,
34}
35
36impl<'a> ArgParser<'a> {
37    /// Create a new ArgParser instance.
38    pub fn new() -> Self {
39        ArgParser::default()
40    }
41
42    /// Add an argument with its associated `Argument` in `arguments`.
43    pub fn arg(mut self, argument: Argument<'a>) -> Self {
44        self.arguments.insert_arg(argument);
45        self
46    }
47
48    /// Parse the command line arguments.
49    pub fn parse_from_cmdline(&mut self) -> Result<()> {
50        self.arguments.parse_from_cmdline()
51    }
52
53    /// Concatenate the `help` information of every possible argument
54    /// in a message that represents the correct command line usage
55    /// for the application.
56    pub fn formatted_help(&self) -> String {
57        let mut help_builder = vec![];
58
59        let required_arguments = self.format_arguments(true);
60        if !required_arguments.is_empty() {
61            help_builder.push("required arguments:".to_string());
62            help_builder.push(required_arguments);
63        }
64
65        let optional_arguments = self.format_arguments(false);
66        if !optional_arguments.is_empty() {
67            // Add line break if `required_arguments` is pushed.
68            if !help_builder.is_empty() {
69                help_builder.push("".to_string());
70            }
71
72            help_builder.push("optional arguments:".to_string());
73            help_builder.push(optional_arguments);
74        }
75
76        help_builder.join("\n")
77    }
78
79    /// Return a reference to `arguments` field.
80    pub fn arguments(&self) -> &Arguments<'_> {
81        &self.arguments
82    }
83
84    // Filter arguments by whether or not it is required.
85    // Align arguments by setting width to length of the longest argument.
86    fn format_arguments(&self, is_required: bool) -> String {
87        let filtered_arguments = self
88            .arguments
89            .args
90            .values()
91            .filter(|arg| is_required == arg.required)
92            .collect::<Vec<_>>();
93
94        let max_arg_width = filtered_arguments
95            .iter()
96            .map(|arg| arg.format_name().len())
97            .max()
98            .unwrap_or(0);
99
100        filtered_arguments
101            .into_iter()
102            .map(|arg| arg.format_help(max_arg_width))
103            .collect::<Vec<_>>()
104            .join("\n")
105    }
106}
107
108/// Stores the characteristics of the `name` command line argument.
109#[derive(Clone, Debug, PartialEq, Eq)]
110pub struct Argument<'a> {
111    name: &'a str,
112    required: bool,
113    requires: Option<&'a str>,
114    forbids: Vec<&'a str>,
115    takes_value: bool,
116    allow_multiple: bool,
117    default_value: Option<Value>,
118    help: Option<&'a str>,
119    user_value: Option<Value>,
120}
121
122impl<'a> Argument<'a> {
123    /// Create a new `Argument` that keeps the necessary information for an argument.
124    pub fn new(name: &'a str) -> Argument<'a> {
125        Argument {
126            name,
127            required: false,
128            requires: None,
129            forbids: vec![],
130            takes_value: false,
131            allow_multiple: false,
132            default_value: None,
133            help: None,
134            user_value: None,
135        }
136    }
137
138    /// Set if the argument *must* be provided by user.
139    pub fn required(mut self, required: bool) -> Self {
140        self.required = required;
141        self
142    }
143
144    /// Add `other_arg` as a required parameter when `self` is specified.
145    pub fn requires(mut self, other_arg: &'a str) -> Self {
146        self.requires = Some(other_arg);
147        self
148    }
149
150    /// Add `other_arg` as a forbidden parameter when `self` is specified.
151    pub fn forbids(mut self, args: Vec<&'a str>) -> Self {
152        self.forbids = args;
153        self
154    }
155
156    /// If `takes_value` is true, then the user *must* provide a value for the
157    /// argument, otherwise that argument is a flag.
158    pub fn takes_value(mut self, takes_value: bool) -> Self {
159        self.takes_value = takes_value;
160        self
161    }
162
163    /// If `allow_multiple` is true, then the user can provide multiple values for the
164    /// argument (e.g --arg val1 --arg val2). It sets the `takes_value` option to true,
165    /// so the user must provides at least one value.
166    pub fn allow_multiple(mut self, allow_multiple: bool) -> Self {
167        if allow_multiple {
168            self.takes_value = true;
169        }
170        self.allow_multiple = allow_multiple;
171        self
172    }
173
174    /// Keep a default value which will be used if the user didn't provide a value for
175    /// the argument.
176    pub fn default_value(mut self, default_value: &'a str) -> Self {
177        self.default_value = Some(Value::Single(String::from(default_value)));
178        self
179    }
180
181    /// Set the information that will be displayed for the argument when user passes
182    /// `--help` flag.
183    pub fn help(mut self, help: &'a str) -> Self {
184        self.help = Some(help);
185        self
186    }
187
188    fn format_help(&self, arg_width: usize) -> String {
189        let mut help_builder = vec![];
190
191        let arg = self.format_name();
192        help_builder.push(format!("{:<arg_width$}", arg, arg_width = arg_width));
193
194        // Add three whitespaces between the argument and its help message for readability.
195        help_builder.push("   ".to_string());
196
197        match (self.help, &self.default_value) {
198            (Some(help), Some(default_value)) => {
199                help_builder.push(format!("{} [default: {}]", help, default_value))
200            }
201            (Some(help), None) => help_builder.push(help.to_string()),
202            (None, Some(default_value)) => {
203                help_builder.push(format!("[default: {}]", default_value))
204            }
205            (None, None) => (),
206        };
207
208        help_builder.concat()
209    }
210
211    fn format_name(&self) -> String {
212        if self.takes_value {
213            format!("  --{name} <{name}>", name = self.name)
214        } else {
215            format!("  --{}", self.name)
216        }
217    }
218}
219
220/// Represents the type of argument, and the values it takes.
221#[derive(Clone, Debug, PartialEq, Eq)]
222pub enum Value {
223    Flag,
224    Single(String),
225    Multiple(Vec<String>),
226}
227
228impl Value {
229    fn as_single_value(&self) -> Option<&String> {
230        match self {
231            Value::Single(s) => Some(s),
232            _ => None,
233        }
234    }
235
236    fn as_flag(&self) -> bool {
237        matches!(self, Value::Flag)
238    }
239
240    fn as_multiple(&self) -> Option<&[String]> {
241        match self {
242            Value::Multiple(v) => Some(v),
243            _ => None,
244        }
245    }
246}
247
248impl fmt::Display for Value {
249    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        match self {
251            Value::Flag => write!(f, "true"),
252            Value::Single(s) => write!(f, "\"{}\"", s),
253            Value::Multiple(v) => write!(f, "{:?}", v),
254        }
255    }
256}
257
258/// Stores the arguments of the parser.
259#[derive(Debug, Clone, Default)]
260pub struct Arguments<'a> {
261    // A BTreeMap in which the key is an argument and the value is its associated `Argument`.
262    args: BTreeMap<&'a str, Argument<'a>>,
263    // The arguments specified after `--` (i.e. end of command options).
264    extra_args: Vec<String>,
265}
266
267impl<'a> Arguments<'a> {
268    /// Add an argument with its associated `Argument` in `args`.
269    fn insert_arg(&mut self, argument: Argument<'a>) {
270        self.args.insert(argument.name, argument);
271    }
272
273    /// Get the value for the argument specified by `arg_name`.
274    fn value_of(&self, arg_name: &'static str) -> Option<&Value> {
275        self.args.get(arg_name).and_then(|argument| {
276            argument
277                .user_value
278                .as_ref()
279                .or(argument.default_value.as_ref())
280        })
281    }
282
283    /// Return the value of an argument if the argument exists and has the type
284    /// String. Otherwise return None.
285    pub fn single_value(&self, arg_name: &'static str) -> Option<&String> {
286        self.value_of(arg_name)
287            .and_then(|arg_value| arg_value.as_single_value())
288    }
289
290    /// Return whether an `arg_name` argument of type flag exists.
291    pub fn flag_present(&self, arg_name: &'static str) -> bool {
292        match self.value_of(arg_name) {
293            Some(v) => v.as_flag(),
294            None => false,
295        }
296    }
297
298    /// Return the value of an argument if the argument exists and has the type
299    /// vector. Otherwise return None.
300    pub fn multiple_values(&self, arg_name: &'static str) -> Option<&[String]> {
301        self.value_of(arg_name)
302            .and_then(|arg_value| arg_value.as_multiple())
303    }
304
305    /// Get the extra arguments (all arguments after `--`).
306    pub fn extra_args(&self) -> Vec<String> {
307        self.extra_args.clone()
308    }
309
310    // Split `args` in two slices: one with the actual arguments of the process and the other with
311    // the extra arguments, meaning all parameters specified after `--`.
312    fn split_args(args: &[String]) -> (&[String], &[String]) {
313        if let Some(index) = args.iter().position(|arg| arg == ARG_SEPARATOR) {
314            return (&args[..index], &args[index + 1..]);
315        }
316
317        (args, &[])
318    }
319
320    /// Collect the command line arguments and the values provided for them.
321    pub fn parse_from_cmdline(&mut self) -> Result<()> {
322        let args: Vec<String> = env::args().collect();
323
324        self.parse(&args)
325    }
326
327    /// Clear split between the actual arguments of the process, the extra arguments if any
328    /// and the `--help` and `--version` arguments if present.
329    pub fn parse(&mut self, args: &[String]) -> Result<()> {
330        // Skipping the first element of `args` as it is the name of the binary.
331        let (args, extra_args) = Arguments::split_args(&args[1..]);
332        self.extra_args = extra_args.to_vec();
333
334        // If `--help` or `-h`is provided as a parameter, we artificially skip the parsing of other
335        // command line arguments by adding just the help argument to the parsed list and
336        // returning.
337        if args.contains(&HELP_ARG.to_string()) || args.contains(&SHORT_HELP_ARG.to_string()) {
338            let mut help_arg = Argument::new("help").help("Show the help message.");
339            help_arg.user_value = Some(Value::Flag);
340            self.insert_arg(help_arg);
341            return Ok(());
342        }
343
344        // If `--version` is provided as a parameter, we artificially skip the parsing of other
345        // command line arguments by adding just the version argument to the parsed list and
346        // returning.
347        if args.contains(&VERSION_ARG.to_string()) {
348            let mut version_arg = Argument::new("version");
349            version_arg.user_value = Some(Value::Flag);
350            self.insert_arg(version_arg);
351            return Ok(());
352        }
353
354        // Otherwise, we continue the parsing of the other arguments.
355        self.populate_args(args)
356    }
357
358    // Check if `required`, `requires` and `forbids` field rules are indeed followed by every
359    // argument.
360    fn validate_requirements(&self, args: &[String]) -> Result<()> {
361        for argument in self.args.values() {
362            // The arguments that are marked `required` must be provided by user.
363            if argument.required && argument.user_value.is_none() {
364                return Err(UtilsArgParserError::MissingArgument(
365                    argument.name.to_string(),
366                ));
367            }
368            if argument.user_value.is_some() {
369                // For the arguments that require a specific argument to be also present in the list
370                // of arguments provided by user, search for that argument.
371                if let Some(arg_name) = argument.requires
372                    && !args.contains(&(format!("--{}", arg_name)))
373                {
374                    return Err(UtilsArgParserError::MissingArgument(arg_name.to_string()));
375                }
376                // Check the user-provided list for potential forbidden arguments.
377                for arg_name in argument.forbids.iter() {
378                    if args.contains(&(format!("--{}", arg_name))) {
379                        return Err(UtilsArgParserError::ForbiddenArgument(
380                            argument.name.to_string(),
381                            arg_name.to_string(),
382                        ));
383                    }
384                }
385            }
386        }
387        Ok(())
388    }
389
390    // Does a general validation of `arg` command line argument.
391    fn validate_arg(&self, arg: &str) -> Result<()> {
392        if !arg.starts_with(ARG_PREFIX) {
393            return Err(UtilsArgParserError::UnexpectedArgument(arg.to_string()));
394        }
395        let arg_name = &arg[ARG_PREFIX.len()..];
396
397        // Check if the argument is an expected one and, if yes, check that it was not
398        // provided more than once (unless allow_multiple is set).
399        let argument = self
400            .args
401            .get(arg_name)
402            .ok_or_else(|| UtilsArgParserError::UnexpectedArgument(arg_name.to_string()))?;
403
404        if !argument.allow_multiple && argument.user_value.is_some() {
405            return Err(UtilsArgParserError::DuplicateArgument(arg_name.to_string()));
406        }
407        Ok(())
408    }
409
410    /// Validate the arguments provided by user and their values. Insert those
411    /// values in the `Argument` instances of the corresponding arguments.
412    fn populate_args(&mut self, args: &[String]) -> Result<()> {
413        let mut iter = args.iter();
414
415        while let Some(arg) = iter.next() {
416            self.validate_arg(arg)?;
417
418            // If the `arg` argument is indeed an expected one, set the value provided by user
419            // if it's a valid one.
420            let argument = self.args.get_mut(&arg[ARG_PREFIX.len()..]).ok_or_else(|| {
421                UtilsArgParserError::UnexpectedArgument(arg[ARG_PREFIX.len()..].to_string())
422            })?;
423
424            let arg_val = if argument.takes_value {
425                let val = iter
426                    .next()
427                    .filter(|v| !v.starts_with(ARG_PREFIX))
428                    .ok_or_else(|| UtilsArgParserError::MissingValue(argument.name.to_string()))?
429                    .clone();
430
431                if argument.allow_multiple {
432                    match argument.user_value.take() {
433                        Some(Value::Multiple(mut v)) => {
434                            v.push(val);
435                            Value::Multiple(v)
436                        }
437                        None => Value::Multiple(vec![val]),
438                        _ => {
439                            return Err(UtilsArgParserError::UnexpectedArgument(
440                                argument.name.to_string(),
441                            ));
442                        }
443                    }
444                } else {
445                    Value::Single(val)
446                }
447            } else {
448                Value::Flag
449            };
450
451            argument.user_value = Some(arg_val);
452        }
453
454        // Check the constraints for the `required`, `requires` and `forbids` fields of all
455        // arguments.
456        self.validate_requirements(args)?;
457
458        Ok(())
459    }
460}
461
462#[cfg(test)]
463mod tests {
464    use super::*;
465    use crate::arg_parser::Value;
466
467    fn build_arg_parser() -> ArgParser<'static> {
468        ArgParser::new()
469            .arg(
470                Argument::new("exec-file")
471                    .required(true)
472                    .takes_value(true)
473                    .help("'exec-file' info."),
474            )
475            .arg(
476                Argument::new("no-api")
477                    .requires("config-file")
478                    .takes_value(false)
479                    .help("'no-api' info."),
480            )
481            .arg(
482                Argument::new("api-sock")
483                    .takes_value(true)
484                    .default_value("socket")
485                    .help("'api-sock' info."),
486            )
487            .arg(
488                Argument::new("id")
489                    .takes_value(true)
490                    .default_value("instance")
491                    .help("'id' info."),
492            )
493            .arg(
494                Argument::new("seccomp-filter")
495                    .takes_value(true)
496                    .help("'seccomp-filter' info.")
497                    .forbids(vec!["no-seccomp"]),
498            )
499            .arg(
500                Argument::new("no-seccomp")
501                    .help("'-no-seccomp' info.")
502                    .forbids(vec!["seccomp-filter"]),
503            )
504            .arg(
505                Argument::new("config-file")
506                    .takes_value(true)
507                    .help("'config-file' info."),
508            )
509            .arg(
510                Argument::new("describe-snapshot")
511                    .takes_value(true)
512                    .help("'describe-snapshot' info."),
513            )
514    }
515
516    #[test]
517    fn test_arg_help() {
518        // Checks help format for an argument.
519        let width = 32;
520        let short_width = 16;
521
522        let mut argument = Argument::new("exec-file").takes_value(false);
523
524        assert_eq!(
525            argument.format_help(width),
526            "  --exec-file                      "
527        );
528        assert_eq!(argument.format_help(short_width), "  --exec-file      ");
529
530        argument = Argument::new("exec-file").takes_value(true);
531
532        assert_eq!(
533            argument.format_help(width),
534            "  --exec-file <exec-file>          "
535        );
536        assert_eq!(
537            argument.format_help(short_width),
538            "  --exec-file <exec-file>   "
539        );
540
541        argument = Argument::new("exec-file")
542            .takes_value(true)
543            .help("'exec-file' info.");
544
545        assert_eq!(
546            argument.format_help(width),
547            "  --exec-file <exec-file>          'exec-file' info."
548        );
549        assert_eq!(
550            argument.format_help(short_width),
551            "  --exec-file <exec-file>   'exec-file' info."
552        );
553
554        argument = Argument::new("exec-file")
555            .takes_value(true)
556            .default_value("./exec-file");
557
558        assert_eq!(
559            argument.format_help(width),
560            "  --exec-file <exec-file>          [default: \"./exec-file\"]"
561        );
562        assert_eq!(
563            argument.format_help(short_width),
564            "  --exec-file <exec-file>   [default: \"./exec-file\"]"
565        );
566
567        argument = Argument::new("exec-file")
568            .takes_value(true)
569            .default_value("./exec-file")
570            .help("'exec-file' info.");
571
572        assert_eq!(
573            argument.format_help(width),
574            "  --exec-file <exec-file>          'exec-file' info. [default: \"./exec-file\"]"
575        );
576        assert_eq!(
577            argument.format_help(short_width),
578            "  --exec-file <exec-file>   'exec-file' info. [default: \"./exec-file\"]"
579        );
580    }
581
582    #[test]
583    fn test_arg_parser_help() {
584        // Checks help information when user passes `--help` flag.
585        let mut arg_parser = ArgParser::new()
586            .arg(
587                Argument::new("exec-file")
588                    .required(true)
589                    .takes_value(true)
590                    .help("'exec-file' info."),
591            )
592            .arg(
593                Argument::new("api-sock")
594                    .takes_value(true)
595                    .help("'api-sock' info."),
596            );
597
598        assert_eq!(
599            arg_parser.formatted_help(),
600            "required arguments:\n  --exec-file <exec-file>   'exec-file' info.\n\noptional \
601             arguments:\n  --api-sock <api-sock>   'api-sock' info."
602        );
603
604        arg_parser = ArgParser::new()
605            .arg(Argument::new("id").takes_value(true).help("'id' info."))
606            .arg(
607                Argument::new("seccomp-filter")
608                    .takes_value(true)
609                    .help("'seccomp-filter' info."),
610            )
611            .arg(
612                Argument::new("config-file")
613                    .takes_value(true)
614                    .help("'config-file' info."),
615            );
616
617        assert_eq!(
618            arg_parser.formatted_help(),
619            "optional arguments:\n  --config-file <config-file>         'config-file' info.\n  \
620             --id <id>                           'id' info.\n  --seccomp-filter <seccomp-filter>   \
621             'seccomp-filter' info."
622        );
623    }
624
625    #[test]
626    fn test_value() {
627        // Test `as_string()` and `as_flag()` functions behaviour.
628        let mut value = Value::Flag;
629        assert!(Value::as_single_value(&value).is_none());
630        value = Value::Single("arg".to_string());
631        assert_eq!(Value::as_single_value(&value).unwrap(), "arg");
632
633        value = Value::Single("arg".to_string());
634        assert!(!Value::as_flag(&value));
635        value = Value::Flag;
636        assert!(Value::as_flag(&value));
637    }
638
639    #[test]
640    fn test_parse() {
641        let arg_parser = build_arg_parser();
642
643        // Test different scenarios for the command line arguments provided by user.
644        let mut arguments = arg_parser.arguments().clone();
645
646        let args = vec!["binary-name", "--exec-file", "foo", "--help"]
647            .into_iter()
648            .map(String::from)
649            .collect::<Vec<String>>();
650
651        arguments.parse(&args).unwrap();
652        assert!(arguments.args.contains_key("help"));
653
654        arguments = arg_parser.arguments().clone();
655
656        let args = vec!["binary-name", "--exec-file", "foo", "-h"]
657            .into_iter()
658            .map(String::from)
659            .collect::<Vec<String>>();
660
661        arguments.parse(&args).unwrap();
662        assert!(arguments.args.contains_key("help"));
663
664        arguments = arg_parser.arguments().clone();
665
666        let args = vec!["binary-name", "--exec-file", "foo", "--version"]
667            .into_iter()
668            .map(String::from)
669            .collect::<Vec<String>>();
670
671        arguments.parse(&args).unwrap();
672        assert!(arguments.args.contains_key("version"));
673
674        arguments = arg_parser.arguments().clone();
675
676        let args = vec!["binary-name", "--exec-file", "foo", "--describe-snapshot"]
677            .into_iter()
678            .map(String::from)
679            .collect::<Vec<String>>();
680
681        assert_eq!(
682            arguments.parse(&args),
683            Err(UtilsArgParserError::MissingValue(
684                "describe-snapshot".to_string()
685            ))
686        );
687
688        arguments = arg_parser.arguments().clone();
689
690        let args = vec![
691            "binary-name",
692            "--exec-file",
693            "foo",
694            "--describe-snapshot",
695            "--",
696        ]
697        .into_iter()
698        .map(String::from)
699        .collect::<Vec<String>>();
700
701        assert_eq!(
702            arguments.parse(&args),
703            Err(UtilsArgParserError::MissingValue(
704                "describe-snapshot".to_string()
705            ))
706        );
707
708        arguments = arg_parser.arguments().clone();
709
710        let args = vec![
711            "binary-name",
712            "--exec-file",
713            "foo",
714            "--api-sock",
715            "--id",
716            "bar",
717        ]
718        .into_iter()
719        .map(String::from)
720        .collect::<Vec<String>>();
721
722        assert_eq!(
723            arguments.parse(&args),
724            Err(UtilsArgParserError::MissingValue("api-sock".to_string()))
725        );
726
727        arguments = arg_parser.arguments().clone();
728
729        let args = vec![
730            "binary-name",
731            "--exec-file",
732            "foo",
733            "--api-sock",
734            "bar",
735            "--api-sock",
736            "foobar",
737        ]
738        .into_iter()
739        .map(String::from)
740        .collect::<Vec<String>>();
741
742        assert_eq!(
743            arguments.parse(&args),
744            Err(UtilsArgParserError::DuplicateArgument(
745                "api-sock".to_string()
746            ))
747        );
748
749        arguments = arg_parser.arguments().clone();
750
751        let args = vec!["binary-name", "--api-sock", "foo"]
752            .into_iter()
753            .map(String::from)
754            .collect::<Vec<String>>();
755
756        assert_eq!(
757            arguments.parse(&args),
758            Err(UtilsArgParserError::MissingArgument(
759                "exec-file".to_string()
760            ))
761        );
762
763        arguments = arg_parser.arguments().clone();
764
765        let args = vec![
766            "binary-name",
767            "--exec-file",
768            "foo",
769            "--api-sock",
770            "bar",
771            "--invalid-arg",
772        ]
773        .into_iter()
774        .map(String::from)
775        .collect::<Vec<String>>();
776
777        assert_eq!(
778            arguments.parse(&args),
779            Err(UtilsArgParserError::UnexpectedArgument(
780                "invalid-arg".to_string()
781            ))
782        );
783
784        arguments = arg_parser.arguments().clone();
785
786        let args = vec![
787            "binary-name",
788            "--exec-file",
789            "foo",
790            "--api-sock",
791            "bar",
792            "--id",
793            "foobar",
794            "--no-api",
795        ]
796        .into_iter()
797        .map(String::from)
798        .collect::<Vec<String>>();
799
800        assert_eq!(
801            arguments.parse(&args),
802            Err(UtilsArgParserError::MissingArgument(
803                "config-file".to_string()
804            ))
805        );
806
807        arguments = arg_parser.arguments().clone();
808
809        let args = vec![
810            "binary-name",
811            "--exec-file",
812            "foo",
813            "--api-sock",
814            "bar",
815            "--id",
816        ]
817        .into_iter()
818        .map(String::from)
819        .collect::<Vec<String>>();
820
821        assert_eq!(
822            arguments.parse(&args),
823            Err(UtilsArgParserError::MissingValue("id".to_string()))
824        );
825
826        arguments = arg_parser.arguments().clone();
827
828        let args = vec![
829            "binary-name",
830            "--exec-file",
831            "foo",
832            "--config-file",
833            "bar",
834            "--no-api",
835            "foobar",
836        ]
837        .into_iter()
838        .map(String::from)
839        .collect::<Vec<String>>();
840
841        assert_eq!(
842            arguments.parse(&args),
843            Err(UtilsArgParserError::UnexpectedArgument(
844                "foobar".to_string()
845            ))
846        );
847
848        arguments = arg_parser.arguments().clone();
849
850        let args = vec![
851            "binary-name",
852            "--exec-file",
853            "foo",
854            "--api-sock",
855            "bar",
856            "--id",
857            "foobar",
858            "--seccomp-filter",
859            "0",
860            "--no-seccomp",
861        ]
862        .into_iter()
863        .map(String::from)
864        .collect::<Vec<String>>();
865
866        assert_eq!(
867            arguments.parse(&args),
868            Err(UtilsArgParserError::ForbiddenArgument(
869                "no-seccomp".to_string(),
870                "seccomp-filter".to_string(),
871            ))
872        );
873
874        arguments = arg_parser.arguments().clone();
875
876        let args = vec![
877            "binary-name",
878            "--exec-file",
879            "foo",
880            "--api-sock",
881            "bar",
882            "--id",
883            "foobar",
884            "--no-seccomp",
885            "--seccomp-filter",
886            "0",
887        ]
888        .into_iter()
889        .map(String::from)
890        .collect::<Vec<String>>();
891
892        assert_eq!(
893            arguments.parse(&args),
894            Err(UtilsArgParserError::ForbiddenArgument(
895                "no-seccomp".to_string(),
896                "seccomp-filter".to_string(),
897            ))
898        );
899
900        arguments = arg_parser.arguments().clone();
901
902        let args = vec![
903            "binary-name",
904            "--exec-file",
905            "foo",
906            "--api-sock",
907            "bar",
908            "foobar",
909        ]
910        .into_iter()
911        .map(String::from)
912        .collect::<Vec<String>>();
913
914        assert_eq!(
915            arguments.parse(&args),
916            Err(UtilsArgParserError::UnexpectedArgument(
917                "foobar".to_string()
918            ))
919        );
920
921        arguments = arg_parser.arguments().clone();
922
923        let args = vec!["binary-name", "foo"]
924            .into_iter()
925            .map(String::from)
926            .collect::<Vec<String>>();
927
928        assert_eq!(
929            arguments.parse(&args),
930            Err(UtilsArgParserError::UnexpectedArgument("foo".to_string()))
931        );
932
933        arguments = arg_parser.arguments().clone();
934
935        let args = vec![
936            "binary-name",
937            "--exec-file",
938            "foo",
939            "--api-sock",
940            "bar",
941            "--id",
942            "foobar",
943            "--seccomp-filter",
944            "0",
945            "--",
946            "--extra-flag",
947        ]
948        .into_iter()
949        .map(String::from)
950        .collect::<Vec<String>>();
951
952        arguments.parse(&args).unwrap();
953        assert!(arguments.extra_args.contains(&"--extra-flag".to_string()));
954    }
955
956    #[test]
957    fn test_split() {
958        let mut args = vec!["--exec-file", "foo", "--", "--extra-arg-1", "--extra-arg-2"]
959            .into_iter()
960            .map(String::from)
961            .collect::<Vec<String>>();
962        let (left, right) = Arguments::split_args(&args);
963        assert_eq!(left.to_vec(), vec!["--exec-file", "foo"]);
964        assert_eq!(right.to_vec(), vec!["--extra-arg-1", "--extra-arg-2"]);
965
966        args = vec!["--exec-file", "foo", "--"]
967            .into_iter()
968            .map(String::from)
969            .collect::<Vec<String>>();
970        let (left, right) = Arguments::split_args(&args);
971        assert_eq!(left.to_vec(), vec!["--exec-file", "foo"]);
972        assert!(right.is_empty());
973
974        args = vec!["--exec-file", "foo"]
975            .into_iter()
976            .map(String::from)
977            .collect::<Vec<String>>();
978        let (left, right) = Arguments::split_args(&args);
979        assert_eq!(left.to_vec(), vec!["--exec-file", "foo"]);
980        assert!(right.is_empty());
981    }
982
983    #[test]
984    fn test_error_display() {
985        assert_eq!(
986            format!(
987                "{}",
988                UtilsArgParserError::ForbiddenArgument("foo".to_string(), "bar".to_string())
989            ),
990            "Argument 'bar' cannot be used together with argument 'foo'."
991        );
992        assert_eq!(
993            format!(
994                "{}",
995                UtilsArgParserError::MissingArgument("foo".to_string())
996            ),
997            "Argument 'foo' required, but not found."
998        );
999        assert_eq!(
1000            format!("{}", UtilsArgParserError::MissingValue("foo".to_string())),
1001            "The argument 'foo' requires a value, but none was supplied."
1002        );
1003        assert_eq!(
1004            format!(
1005                "{}",
1006                UtilsArgParserError::UnexpectedArgument("foo".to_string())
1007            ),
1008            "Found argument 'foo' which wasn't expected, or isn't valid in this context."
1009        );
1010        assert_eq!(
1011            format!(
1012                "{}",
1013                UtilsArgParserError::DuplicateArgument("foo".to_string())
1014            ),
1015            "The argument 'foo' was provided more than once."
1016        );
1017    }
1018
1019    #[test]
1020    fn test_value_display() {
1021        assert_eq!(format!("{}", Value::Flag), "true");
1022        assert_eq!(format!("{}", Value::Single("foo".to_string())), "\"foo\"");
1023    }
1024
1025    #[test]
1026    fn test_allow_multiple() {
1027        let arg_parser = ArgParser::new()
1028            .arg(
1029                Argument::new("no-multiple")
1030                    .takes_value(true)
1031                    .help("argument that takes just one value."),
1032            )
1033            .arg(
1034                Argument::new("multiple")
1035                    .allow_multiple(true)
1036                    .help("argument that allows duplication."),
1037            );
1038
1039        let mut arguments = arg_parser.arguments().clone();
1040
1041        // Check single value arguments fails when multiple values are provided.
1042        let args = vec!["binary-name", "--no-multiple", "1", "--no-multiple", "2"]
1043            .into_iter()
1044            .map(String::from)
1045            .collect::<Vec<String>>();
1046
1047        assert_eq!(
1048            arguments.parse(&args),
1049            Err(UtilsArgParserError::DuplicateArgument(
1050                "no-multiple".to_string()
1051            ))
1052        );
1053
1054        arguments = arg_parser.arguments().clone();
1055
1056        // Check single value arguments works as expected when just one value
1057        // is provided for both arguments.
1058        let args = vec!["binary-name", "--no-multiple", "1", "--multiple", "2"]
1059            .into_iter()
1060            .map(String::from)
1061            .collect::<Vec<String>>();
1062
1063        arguments.parse(&args).unwrap();
1064
1065        arguments = arg_parser.arguments().clone();
1066
1067        // Check multiple arg allow multiple values
1068        let args = vec!["binary-name", "--multiple", "1", "--multiple", "2"]
1069            .into_iter()
1070            .map(String::from)
1071            .collect::<Vec<String>>();
1072
1073        arguments.parse(&args).unwrap();
1074
1075        // Check dulicates require a value
1076        let args = vec!["binary-name", "--multiple", "--multiple", "2"]
1077            .into_iter()
1078            .map(String::from)
1079            .collect::<Vec<String>>();
1080
1081        assert_eq!(
1082            arguments.parse(&args),
1083            Err(UtilsArgParserError::MissingValue("multiple".to_string()))
1084        );
1085    }
1086}