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§
- Argument
Group Info - Invocation-validity contract for one Clap argument group.
- Argument
Info - Canonical invocation information for one reflected positional argument or option.
- Argument
Syntax - Token-placement syntax required for one argument.
- Argument
Value - Value contract for one positional argument or option occurrence.
- CliContract
- Validated command contract used for discovery and typed command lookup.
- Command
Context - Invocation-relevant semantics owned by one ancestor command level.
- Command
Info - Canonical invocation contract for one discoverable command or command group.
- Command
Syntax - Command-level tokenization syntax required to construct argv correctly.
- Contract
Builder - Builds and validates successful-output contracts for builder-style Clap applications.
- Schema
Command Summary - Compact schema-discovery reference to one direct child command.
- Schema
Document - Resolved schema-discovery document for one selected command.
- Schema
Request - One schema-discovery request.
- Subcommand
Routing - Routing semantics between one command’s arguments and its child subcommands.
Enums§
- Error
- Contract construction and discovery error.
- Schema
Subcommand - One child entry in a
SchemaDocument.
Traits§
- CliSchema
- Trait implemented by a machine-contract-aware root Clap parser.
- Command
Schema - 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::CliSchemaimplementation. - Command
Schema - Derives command-tree registration for a Clap
Subcommandenum orArgswrapper.