XenoAtom.CommandLine provides built-in support for validating option and argument values at parse time, as well as declaring relationships between options.
Add a validate parameter when declaring an option or argument to validate parsed values immediately:
int port = 0;
string? email = null;
string? input = null;
var app = new CommandApp("myexe")
{
{ "p|port=", "Server {PORT}", (int v) => port = v, validate: Validate.Range(1, 65535) },
{ "e|email=", "Contact {EMAIL}", v => email = v,
validate: Validate.That<string>(v => v.Contains('@'), "The value must be a valid email address.") },
{ "<input>", "Input {FILE}", v => input = v, validate: Validate.FileExists() },
(ctx, _) => ValueTask.FromResult(0)
};
If validation fails, a clear error message is shown:
myexe: Invalid value for option `--port`: The value must be between 1 and 65535.
Use `myexe --help` for usage.
XenoAtom.CommandLine includes a comprehensive set of validators in the Validate class:
| Validator | Description |
|---|---|
Validate.Range<T>(min, max) |
Value must be within the inclusive range [min, max] |
Validate.Positive<T>() |
Value must be greater than zero |
Validate.NonNegative<T>() |
Value must be greater than or equal to zero |
{ "p|port=", "Port", (int v) => port = v, validate: Validate.Range(1, 65535) }
{ "t|threads=", "Threads", (int v) => threads = v, validate: Validate.Positive<int>() }
{ "r|retries=", "Retries", (int v) => retries = v, validate: Validate.NonNegative<int>() }
| Validator | Description |
|---|---|
Validate.NonEmpty() |
Value must not be null or empty |
Validate.Matches(pattern) |
Value must match a regex pattern |
Validate.Matches(regex) |
Value must match a compiled Regex |
Validate.OneOf<T>(params T[]) |
Value must be one of the specified values |
{ "n|name=", "Name", v => name = v, validate: Validate.NonEmpty() }
{ "e|email=", "Email", v => email = v, validate: Validate.Matches(@"^[^@]+@[^@]+$", "Must be a valid email.") }
{ "l|level=", "Level", v => level = v, validate: Validate.OneOf("debug", "info", "warn", "error") }
| Validator | Description |
|---|---|
Validate.FileExists() |
Path must refer to an existing file |
Validate.DirectoryExists() |
Path must refer to an existing directory |
Validate.PathExists() |
Path must refer to an existing file or directory |
{ "<input>", "Input file", v => input = v, validate: Validate.FileExists() }
{ "o|output-dir=", "Output dir", v => dir = v, validate: Validate.DirectoryExists() }
| Validator | Description |
|---|---|
Validate.That<T>(predicate, errorMessage) |
Custom predicate with error message |
Validate.Custom<T>(validator) |
Pass-through for an OptionValidator<T> delegate |
{ "p|port=", "Port", (int v) => port = v,
validate: Validate.That<int>(v => v % 2 == 0, "Port must be an even number.") }
Use Validate.Chain(...) to combine multiple validators. The first failure wins:
{ "p|port=", "Port", (int v) => port = v,
validate: Validate.Chain(
Validate.Range(1, 65535),
Validate.That<int>(v => v != 80, "Port 80 is reserved.")
)
}
Constraints let you declare relationships between options. In collection initializer style, add constraint nodes directly in the command declaration. They are checked after all options are parsed (including environment variable fallbacks) and before the command action runs.
Prevent two or more options from being used together:
var app = new CommandApp("myexe")
{
{ "j|json", "Output JSON", _ => {} },
{ "x|xml", "Output XML", _ => {} },
{ "c|csv", "Output CSV", _ => {} },
new MutuallyExclusiveConstraint("json", "xml", "csv"),
(ctx, _) => ValueTask.FromResult(0)
};
If you are not using collection initializers, the fluent alternative is:
app.AddMutuallyExclusive("json", "xml", "csv");
If the user passes --json --xml, the error is:
myexe: Options `--json` and `--xml` cannot be used together.
Use `myexe --help` for usage.
Declare that when one option is present, another must also be specified:
var app = new CommandApp("myexe")
{
{ "u|user=", "User", _ => {} },
{ "p|password=", "Password", _ => {} },
new RequiresConstraint("password", "user"),
(ctx, _) => ValueTask.FromResult(0)
};
If you are not using collection initializers, the fluent alternative is:
app.AddRequires("password", "user");
If the user passes --password secret without --user, the error is:
myexe: Option `--password` requires `--user` to also be specified.
Use `myexe --help` for usage.
You can add multiple constraints to the same command:
var app = new CommandApp("myexe")
{
{ "j|json", "Output JSON", _ => {} },
{ "x|xml", "Output XML", _ => {} },
{ "v|verbose", "Verbose output", _ => {} },
{ "q|quiet", "Quiet output", _ => {} },
{ "u|user=", "User", _ => {} },
{ "p|password=", "Password", _ => {} },
{ "tls-cert=", "TLS cert path", _ => {} },
{ "tls-key=", "TLS key path", _ => {} },
new MutuallyExclusiveConstraint("json", "xml"),
new MutuallyExclusiveConstraint("verbose", "quiet"),
new RequiresConstraint("password", "user"),
new RequiresConstraint("tls-cert", "tls-key"),
(ctx, _) => ValueTask.FromResult(0)
};
Equivalent fluent setup:
app.AddMutuallyExclusive("json", "xml");
app.AddMutuallyExclusive("verbose", "quiet");
app.AddRequires("password", "user");
app.AddRequires("tls-cert", "tls-key");
Constraints are checked in this order:
This means environment variable fallbacks do trigger constraint checks. For example, if --json is set via an environment variable and --xml is passed on the command line, the mutually exclusive constraint still fires.