openapi-clap 0.1.5

Auto-generate clap CLI commands from OpenAPI specs
Documentation
# openapi-clap

Auto-generate [clap](https://crates.io/crates/clap) CLI commands from [OpenAPI](https://www.openapis.org/) 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`:

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

Build and dispatch:

```rust,no_run
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

- [Apache License, Version 2.0]LICENSE-APACHE
- [MIT License]LICENSE-MIT

at your option.