Skip to main content

Crate clap_schema

Crate clap_schema 

Source
Expand description

Checked invocation and successful-output contracts for clap applications.

Clap remains authoritative for parsing. clap_schema reflects a canonical agent-facing invocation contract from the built command tree and binds each contract-visible invocable command to the JSON shape produced by its real handler.

Every contract-visible executable command is identified by the Rust payload type already present on its Clap variant. A canonical #[schema_handler(...)] contract associates that type with the selected handler’s declared Result<T, E>, which remains the sole source of its successful output contract. For non-unit T, the crate requires T: schemars::JsonSchema + 'static and emits Schemars’ serialization-view JSON Schema. Result<(), E> has no output contract.

§Derive API

use clap::{Args, Parser, Subcommand};
use clap_schema::{CliSchema, CommandSchema, schema_handler};
use schemars::JsonSchema;

#[derive(Debug, Parser, CliSchema)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Debug, Subcommand, CommandSchema)]
enum Commands {
    Create(CreateArgs),
}

#[derive(Debug, Args)]
struct CreateArgs {
    #[arg(long)]
    name: String,
}

#[derive(Debug, JsonSchema)]
struct Item {
    id: u64,
    name: String,
}

#[schema_handler(CreateArgs)]
async fn create(args: CreateArgs) -> Result<Item, std::io::Error> {
    Ok(Item { id: 1, name: args.name })
}

let contract = Cli::schema()?;
let create = contract.command_for::<CreateArgs>().expect("create command is registered");
let output = create.output.as_ref().expect("create output");
assert_eq!(output.get("type").and_then(serde_json::Value::as_str), Some("object"));
assert!(create.options.iter().any(|argument| argument.name == "--name"));
let root = contract.schema(&clap_schema::SchemaRequest::default())?;
assert_eq!(root.subcommands.len(), 1);

CreateArgs is the Clap payload type that identifies the executable command. CommandSchema gets that identity from the variant, while the schema handler supplies its successful-output contract; removing the handler or attaching a second canonical handler therefore fails to compile. Derive-based executable commands use one named tuple payload; an empty Args type represents a command with no arguments.

§Nested command shapes

Normal #[command(subcommand)] and #[command(flatten)] enum nesting is followed automatically. When an Args payload itself contains a subcommand field, derive CommandSchema on that payload:

use clap::{Args, Parser, Subcommand};
use clap_schema::{CliSchema, CommandSchema, schema_handler};

#[derive(Parser, CliSchema)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand, CommandSchema)]
enum Commands {
    Stash(StashArgs),
}

#[derive(Args, CommandSchema)]
struct StashArgs {
    #[command(subcommand)]
    command: Option<StashCommands>,
}

#[derive(Subcommand, CommandSchema)]
enum StashCommands {
    List(ListArgs),
}

#[derive(Args)]
struct ListArgs {}

#[schema_handler(StashArgs)]
fn stash_default(_args: StashArgs) -> Result<(), std::convert::Infallible> {
    Ok(())
}

#[schema_handler(ListArgs)]
fn list(_args: ListArgs) -> Result<(), std::convert::Infallible> {
    Ok(())
}

let contract = Cli::schema()?;
let stash = contract.command_for::<StashArgs>().expect("stash command is registered");
let list = contract.command_for::<ListArgs>().expect("list command is registered");
assert!(stash.invocable);
assert_eq!(list.path.len(), 2);

The child enum type is therefore read from the same field Clap parses instead of being repeated in schema metadata. A required subcommand field makes the parent a group. An Option<Subcommands> field makes the parent directly invocable and therefore requires its own #[schema_handler(...)] contract.

§Schema handlers

Free handlers use #[schema_handler(Type)], where Type is the command payload. Synchronous, const fn, and asynchronous functions are supported, and their arguments are otherwise unrestricted. When execution already lives on the command type, annotate its inherent impl with the handler method name instead:

use clap_schema::schema_handler;
use schemars::JsonSchema;

struct GetArgs;

#[derive(JsonSchema)]
struct Item {
    id: u64,
}

#[schema_handler(run)]
impl GetArgs {
    async fn run(self, _context: &str) -> Result<Item, std::io::Error> {
        Ok(Item { id: 1 })
    }
}

In the impl form, the impl’s Self type is the command identity and the named inherent method supplies the output contract. Generic handlers and opaque impl Trait return types are rejected because they do not identify one concrete output contract.

§Builder-style Clap

Builder applications use the same handler-derived command contracts. There is no API for declaring an output type manually:

use clap::Command;
use clap_schema::{ContractBuilder, schema_handler};
use schemars::JsonSchema;

#[derive(JsonSchema)]
struct Created {
    id: u64,
}

struct CreateCommand;

#[schema_handler(CreateCommand)]
fn create(_command: CreateCommand) -> Result<Created, std::io::Error> {
    Ok(Created { id: 1 })
}

let cli = Command::new("example").subcommand(Command::new("create"));
let contract = ContractBuilder::new(cli).command::<CreateCommand>(["create"]).build()?;
assert!(contract.command_for::<CreateCommand>().and_then(|command| command.output).is_some());

§Application-defined schema extensions

Applications may declare a schema for metadata that they add to their own machine-facing documents. clap_schema handles only the schema side: it never stores or serializes the application’s concrete metadata values.

use clap::{Args, Parser, Subcommand};
use clap_schema::{CliSchema, CommandSchema, schema_handler};
use schemars::JsonSchema;

#[derive(Debug, JsonSchema)]
struct CommandMetadata {
    destructive: bool,
}

#[derive(Debug, JsonSchema)]
#[schemars(rename_all = "camelCase")]
struct PaginationMetadata {
    cursor_argument: String,
}

#[derive(Debug, Parser, CliSchema)]
#[schema(extend = CommandMetadata)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Debug, Subcommand, CommandSchema)]
enum Commands {
    #[schema(extend = PaginationMetadata)]
    List(ListArgs),
}

#[derive(Debug, Args)]
struct ListArgs {
    #[arg(long)]
    cursor: Option<String>,
}

#[derive(Debug, JsonSchema)]
#[schemars(rename_all = "camelCase")]
struct Page {
    next_cursor: Option<String>,
}

#[schema_handler(ListArgs)]
fn list(_command: ListArgs) -> Result<Page, std::convert::Infallible> {
    Ok(Page { next_cursor: None })
}

let contract = Cli::schema()?;
assert_eq!(contract.extended_schema().unwrap()["type"], "object");
assert_eq!(
    contract.extended_schema_for_command::<ListArgs>().unwrap()["allOf"]
        .as_array()
        .map(Vec::len),
    Some(2),
);

Root extend = Type declares the application-wide vocabulary. An executable CommandSchema variant may add extend = Type as a command-specific supplement. The effective schema is the intersection of both layers, represented with JSON Schema allOf; it is not a shallow schema merge. Commands without a supplement inherit the application-wide schema unchanged. Because every allOf branch validates the same value, applications must choose extension schema types that compose correctly; clap_schema does not relax closed object schemas or otherwise rewrite application-defined constraints.

Metadata types need only schemars::JsonSchema. Applications commonly also implement serde::Serialize on those types because the application constructs the actual metadata values, but that value never crosses clap_schema. The application is responsible for making sure its emitted value satisfies the extension schema it exposes. Builder-style applications use ContractBuilder::extend and ContractBuilder::command_with_extension.

The runnable application_extension example demonstrates application-owned value construction, flattening application and command layers into one metadata value, and choosing the final machine-facing document shape.

§Scope

The wire model describes a canonical process-style invocation contract without serializing Clap’s own help representation. Global argument scope, positional order, canonical option spellings, value arity, lexical defaults and possible values, delimiters, terminators, conflicts, repeatability, exclusivity, required equals syntax, and required option-terminator syntax are reflected from Clap’s built command tree. Human-facing aliases, short alternatives, value placeholders, and rendered usage strings are intentionally omitted. Input values remain lexical rather than inferring Rust parser result types. Clap remains authoritative for parser-specific validation, and argv framing modes outside the process model are rejected. A present output schema means the command’s successful value has a machine-readable JSON Schema; absence means no typed successful-output contract is declared. See SPECIFICATION.md for the complete wire contract and reflection boundary.

Structs§

ArgumentGroupInfo
Invocation-validity contract for one Clap argument group.
ArgumentInfo
Canonical invocation information for one reflected positional argument or option.
ArgumentSyntax
Token-placement syntax required for one argument.
ArgumentValue
Value contract for one positional argument or option occurrence.
CliContract
Validated command contract used for discovery and typed command lookup.
CommandContext
Invocation-relevant semantics owned by one ancestor command level.
CommandInfo
Canonical invocation contract for one discoverable command or command group.
CommandSyntax
Command-level tokenization syntax required to construct argv correctly.
ContractBuilder
Builds and validates successful-output contracts for builder-style Clap applications.
SchemaCommandSummary
Compact schema-discovery reference to one direct child command.
SchemaDocument
Resolved schema-discovery document for one selected command.
SchemaRequest
One schema-discovery request.
SubcommandRouting
Routing semantics between one command’s arguments and its child subcommands.

Enums§

Error
Contract construction and discovery error.
SchemaSubcommand
One child entry in a SchemaDocument.

Traits§

CliSchema
Trait implemented by a machine-contract-aware root Clap parser.
CommandSchema
Trait implemented by types that contribute nested command structure to a CLI contract.

Type Aliases§

Result
Result type returned by clap_schema.

Attribute Macros§

schema_handler
Associates one executable command type with its handler-derived output contract.

Derive Macros§

CliSchema
Derives the root clap_schema::CliSchema implementation.
CommandSchema
Derives command-tree registration for a Clap Subcommand enum or Args wrapper.