openapi-clap 0.1.4

Auto-generate clap CLI commands from OpenAPI specs
Documentation

openapi-clap

Auto-generate clap CLI commands from OpenAPI specs.

Given a dereferenced OpenAPI 3.x JSON document, openapi-clap extracts operations into an intermediate representation, builds a clap Command tree, and dispatches HTTP requests -- turning any REST API into a CLI with zero hand-written argument definitions.

Features

  • Spec-driven: paths, parameters (path / query / header), and request bodies are mapped to clap subcommands and arguments automatically.
  • Grouped by tag: operations are organized under tag-based subcommands (e.g. mycli pods list-pods).
  • Body input: supports --json '{...}' for raw JSON and --field key=value for individual fields.
  • Customizable: CliConfig lets you set the root command name, description, and default base URL.
  • Re-exports: clap and reqwest are re-exported so downstream crates can avoid version conflicts.

Quick start

Add to your Cargo.toml:

[dependencies]
openapi-clap = "0.1"
openapi-deref = "0.1"
serde_json = "1"

Build and dispatch:

use openapi_clap::{Auth, CliConfig, build_commands, extract_operations, find_operation, dispatch};
use openapi_deref::resolve;
use reqwest::blocking::Client;

fn main() {
    // Load and dereference your OpenAPI spec
    let raw: serde_json::Value =
        serde_json::from_str(include_str!("spec.json")).expect("invalid JSON");
    let resolved = resolve(&raw).expect("failed to resolve $ref");

    let ops = extract_operations(&resolved.value);
    let config = CliConfig::new("myapi", "My API CLI", "https://api.example.com");
    let cmd = build_commands(&config, &ops);

    let matches = cmd.get_matches();

    // Resolve the two-level subcommand: <group> <operation>
    let (group_name, group_matches) = matches.subcommand().expect("subcommand required");
    let (op_name, op_matches) = group_matches.subcommand().expect("operation required");

    if let Some(op) = find_operation(&ops, group_name, op_name, &config) {
        let base_url = op_matches.get_one::<String>("base-url").unwrap();
        let api_key = std::env::var("API_KEY").unwrap_or_default();
        let auth = Auth::Bearer(&api_key);
        let client = Client::new();
        match dispatch(&client, base_url, &auth, op, op_matches) {
            Ok(value) => println!("{}", serde_json::to_string_pretty(&value).unwrap()),
            Err(e) => eprintln!("error: {e}"),
        }
    }
}

How it works

  1. Parse -- extract_operations() walks the OpenAPI paths object and produces a Vec<ApiOperation>.
  2. Build -- build_commands() converts operations into a clap Command tree grouped by tag.
  3. Match -- find_operation() resolves the user's subcommand back to the original ApiOperation.
  4. Dispatch -- dispatch() constructs and sends the HTTP request, returning the response as serde_json::Value.

License

Licensed under either of

at your option.