clap checks types, defaults and required arguments; vld-clap adds value rules (formats, ranges, lengths) after Cli::parse(). The rules sit on the clap struct itself through #[derive(Validate)], so there is no second schema.
Install
Cargo.toml
[dependencies]
vld = { version = "0.5", features = ["derive"] }
vld-clap = "0.5"
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
No optional features. Requires clap 4. MSRV: 1.85 (see MSRV).
Example
use clap::Parser;
use vld::Validate;
use vld_clap::prelude::*;
#[derive(Parser, Debug, serde::Serialize, Validate)]
#[command(name = "myapp")]
struct Cli {
/// Admin email address
#[arg(long)]
#[vld(vld::string().email())]
email: String,
/// Server port
#[arg(long, default_value_t = 8080)]
#[vld(vld::number().int().min(1).max(65535))]
port: i64,
/// Output file
#[arg(long = "out")]
#[vld(vld::string().min(1))]
output_file: String,
}
fn main() {
let cli = Cli::parse(); // clap's parse
validate_or_exit(&cli); // vld rules; prints and exits with code 2 on failure
println!("{cli:?}");
}
myapp --email nope --port 0 --out report.txt prints:
error: Invalid arguments:
--email: Invalid email address
--port: Number must be at least 1
(Message wording comes from vld's message table.)
API
| Item | Description |
|---|---|
validate_cli(&args) | Validate a Parser / CommandFactory type; issue lines use clap's names (--out, -o, <INPUT>) |
validate_or_exit(&args) | validate_cli, print to stderr and exit with code 2 on error |
validate(&args) | Any VldParse + Serialize type; names guessed as --kebab-case from the field |
validate_json::<S>(&Value) | Parse a JSON value with schema S |
validate_with_schema::<S, T>(&args) | Serialize T, parse with a separate schema S |
VldClapError | source: ErrorSource, message: String; exit(), format_issues() |
ErrorSource | Validation(VldError) or Serialization(String) |
prelude | The functions and VldClapError, plus vld::prelude::* |
Gotchas
#[derive(Validate)]generatesvld_parse(), notparse(), so it does not clash withclap::Parser::parse(see derive).- The struct is serialized to JSON before validation: derive
serde::Serialize, and keep#[serde(rename)]in mind, since the issue path is the serialized name. - 0.5 change:
validate_or_exitrequiresclap::CommandFactory(any#[derive(Parser)]has it). For plain structs callvalidate. - Nested
#[command(flatten)]groups and subcommand arguments are looked up in the clapCommand, so their flags are named correctly too.