Skip to content

Validation

clish provides compile-time and runtime validation for command-line input. This page covers all validation features: choices, conflicts, prerequisites, and type constraints.

Choices

The choices attribute restricts a parameter to a specific set of allowed values:

#[command(
    param(level, choices = ["debug", "info", "warn", "error"]),
)]
fn log(level: Named<String>) {
    println!("Log level: {level}");
}

If the user passes a value not in the list:

error: invalid choice 'trace': expected one of debug, info, warn, error
  |
1 | myapp log --level trace
  |                   ^^^^^
  |
  = hint: run 'myapp log --help' for more information

Choices with different parameter types

Parameter type Behavior
Named<T> Each value is checked against the choices list
Named<Vec<T>> Every collected value is checked
Named<Option<T>> The value is checked if provided
Pos<T> Choices are not typically used with positionals, but supported

Choices and type parsing

Choices are checked before type parsing. This means the raw string value is validated against the allowed set first:

#[command(
    param(level, choices = ["debug", "info"], default = "debug"),
)]
fn log(level: Named<String>) { ... }

The default value "debug" passes the choices check, then the value is used as a String. If you had Named<u16> with choices, the string would be checked against choices first, then parsed as u16.

Conflicts

The conflicts_with attribute declares that two parameters cannot appear together:

#[command(
    param(verbose, short = 'v', conflicts_with = ["quiet"]),
    param(quiet, short = 'q', conflicts_with = ["verbose"]),
)]
fn build(verbose: bool, quiet: bool) {
    if verbose {
        println!("Building with verbose output");
    } else if quiet {
        println!("Building quietly");
    } else {
        println!("Building");
    }
}

If the user passes both:

error: conflicting arguments --verbose and --quiet cannot be used together
  |
1 | myapp build --verbose --quiet
  |               ^^^^^^^^^^^^^^^
  |
  = hint: run 'myapp build --help' for more information

Conflict rules

  • Conflicts are checked for both named options and flags
  • The conflict is symmetric: if A conflicts with B, then B conflicts with A
  • You should declare conflicts on both sides for clarity, though declaring on one side is sufficient
  • Conflicts are only checked when the declaring parameter is present

Prerequisites

The requires attribute declares that a parameter depends on another:

#[command(
    param(output, short = 'o', requires = ["format"]),
    param(format, short = 'f'),
)]
fn build(output: Named<Option<String>>, format: Named<Option<String>>) {
    if let (Some(out), Some(fmt)) = (output, format) {
        println!("Output: {out} (format: {fmt})");
    }
}

If the user passes --output without --format:

error: missing required argument --output requires --format
  |
1 | myapp build --output out.txt
  |               ^^^^^^
  |
  = hint: run 'myapp build --help' for more information

Prerequisite rules

  • Prerequisites are only checked when the declaring parameter is present
  • If the declaring parameter is absent, no prerequisite check occurs
  • Prerequisites work for both named options and flags
  • Multiple prerequisites can be declared: requires = ["format", "output_dir"]

Compile-time type validation

The #[command] macro rejects invalid type combinations at compile time:

Rejected type Reason
Option<Vec<T>> Vec<T> already accepts zero or more values
Option<bool> Flags are already optional by presence
Multiple Pos<Vec<T>> Only one variadic positional per command
Pos<Vec<T>> not last Variadic must consume remaining positionals
Unknown type Must be Pos<T>, Named<T>, or bool

Compile-time short flag validation

Each short character can only be assigned to one parameter per command:

#[command(
    param(verbose, short = 'v'),
    param(version, short = 'v'),  // ERROR: duplicate short
)]
fn build(verbose: bool, version: bool) { ... }

This produces a compile-time error:

error: duplicate short flag '-v' used by multiple parameters

This applies across both named options and flags. A short character used by a named option cannot also be used by a flag in the same command.

Validation order

When a command is invoked, validation happens in this order:

  1. Argument parsing -- tokens are parsed into positional, named, and flag buckets
  2. Conflict checking -- conflicts_with rules are evaluated
  3. Prerequisite checking -- requires rules are evaluated
  4. Choices checking -- values are validated against allowed sets
  5. Type parsing -- string values are parsed into target types
  6. Command execution -- the user function is called

If any step fails, the remaining steps are skipped and an error is printed.

Next step

Now that you understand validation, learn about Help and Style to see how clish renders help output and how you can customize the look of your CLI.