Config and CLI
Validate clap-parsed CLI arguments with

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

ItemDescription
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
VldClapErrorsource: ErrorSource, message: String; exit(), format_issues()
ErrorSourceValidation(VldError) or Serialization(String)
preludeThe functions and VldClapError, plus vld::prelude::*

Gotchas

  • #[derive(Validate)] generates vld_parse(), not parse(), so it does not clash with clap::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_exit requires clap::CommandFactory (any #[derive(Parser)] has it). For plain structs call validate.
  • Nested #[command(flatten)] groups and subcommand arguments are looked up in the clap Command, so their flags are named correctly too.