Skip to main content

Module relations

Module relations 

Source
Expand description

§Argument Relations

Like Clap, arguments can declare relations with each other. Relations reference other arguments by their field name, and are checked at compile time: a relation naming a field that does not exist is rejected. This is a Clap-inspired subset: the supported keys are listed below, and relations cannot reference groups.

AttributeMeaning
#[arg(conflicts_with = "other")]The argument cannot be used together with other.
#[arg(requires = "other")]When the argument is given, other must be given too.
#[arg(required_unless_present = "other")]The argument must be given unless other is. Only allowed on optional fields (Option<T>, a default, or a flag).
#[arg(group = "id")]Makes the argument a member of the group id. See Groups.

conflicts_with, requires and required_unless_present can be repeated: a field conflicting with several arguments lists each one (conflicts_with = "a", conflicts_with = "b"), a field requiring several arguments requires all of them, and a field with several required_unless_present is required unless any of them is given (Clap’s required_unless_present_any).

Relations only look at the command line: an argument is present when the user wrote it, so a default value never satisfies requires and never triggers conflicts_with.

use getargs_derive::{Error, GetArgs};

#[derive(Debug, GetArgs)]
struct Cli<'a> {
    /// Read the configuration from this file.
    #[arg(short, long, required_unless_present = "stdin")]
    config: Option<&'a str>,
    /// Read the configuration from standard input.
    #[arg(long, conflicts_with = "config")]
    stdin: bool,
    /// Compress the output, which requires an output file.
    #[arg(long, requires = "output")]
    compress: bool,
    #[arg(short, long)]
    output: Option<&'a str>,
}

let cli = Cli::from_args(["--stdin"].into_iter()).unwrap();
assert!(cli.stdin);

let error = Cli::from_args(["--stdin", "-c", "app.toml"].into_iter()).unwrap_err();
assert_eq!(error.to_string(), "the argument '--stdin' cannot be used with '--config <CONFIG>'");

let error = Cli::from_args(["--stdin", "--compress"].into_iter()).unwrap_err();
assert_eq!(
    error,
    Error::MissingRequiredArgument { name: "--output <OUTPUT>", required_by: "--compress" }
);

let error = Cli::from_args([].into_iter()).unwrap_err();
assert_eq!(
    error.to_string(),
    "the following required arguments were not provided: <--config <CONFIG>|--stdin>"
);

§Groups

A group is a set of arguments sharing a name. Fields join a group with #[arg(group = "id")]; a field belongs to at most one group. By default, as with Clap’s ArgGroup, the members of a group are mutually exclusive and the group is optional. The group can be configured on the struct with #[command(group(...))]:

KeyMeaning
id = "id"The group name, required.
requiredAt least one member of the group must be given.
multipleSeveral members of the group may be given together.

A group used by fields without a #[command(group(...))] declaration is an optional group of mutually exclusive arguments.

use getargs_derive::{Error, GetArgs};

#[derive(Debug, GetArgs)]
#[command(group(id = "format", required))]
struct Cli {
    #[arg(long, group = "format")]
    json: bool,
    #[arg(long, group = "format")]
    yaml: bool,
}

let cli = Cli::from_args(["--yaml"].into_iter()).unwrap();
assert!(cli.yaml);

let error = Cli::from_args(["--json", "--yaml"].into_iter()).unwrap_err();
assert_eq!(error, Error::ArgumentConflict { name: "--json", other: "--yaml" });

let error = Cli::from_args([].into_iter()).unwrap_err();
assert_eq!(error, Error::MissingOneOf(&["--json", "--yaml"]));

This differs from Clap’s derive API, where groups are declared with an ArgGroup builder expression (#[command(group(ArgGroup::new("format").required(true)))]) or from a struct with #[group]; here the declaration is a plain list of keys, and groups cannot list their members with args.

§Errors

Relations are checked after all arguments are parsed, without allocation, in this order: conflicts (including exclusive groups), then required_unless_present and required groups, then requires, then missing required arguments. The first violation is returned:

VariantDisplay
ArgumentConflict { name, other }the argument '--stdin' cannot be used with '--config <CONFIG>'
MissingRequiredArgument { name, required_by }the following required argument was not provided: --output <OUTPUT> (required by '--compress')
MissingOneOf(names)the following required arguments were not provided: <--config <CONFIG>|--stdin>

See Error Handling.

§Compile-Time Diagnostics

The macro reports a compile error, pointing at the offending attribute, for:

  • a relation referencing a field that does not exist, the field itself, or the same field twice;
  • conflicts_with, requires, required_unless_present or group with an empty name or without a string literal;
  • requires between two arguments that conflict, through conflicts_with or an exclusive group;
  • required_unless_present on a required field;
  • more than one group on the same field;
  • a #[command(group(...))] without id, with an unknown key, declared twice, or with no member.