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:
- Argument parsing -- tokens are parsed into positional, named, and flag buckets
- Conflict checking --
conflicts_withrules are evaluated - Prerequisite checking --
requiresrules are evaluated - Choices checking -- values are validated against allowed sets
- Type parsing -- string values are parsed into target types
- 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.