enum2schema 0.1.0

enum2schema is a rust derive macro that emits a JSON Schema describing a rust type, including doc-comment descriptions, externally tagged enum variants, and field types.
Documentation
# enum2schema

[<img alt="github" src="https://img.shields.io/badge/github-matthewjberger/enum2schema-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/matthewjberger/enum2schema)
[<img alt="crates.io" src="https://img.shields.io/crates/v/enum2schema.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/enum2schema)
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-enum2schema-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/enum2schema)

`enum2schema` derives a JSON Schema from a Rust type, so the schema a consumer reads (for example an MCP tool `inputSchema`) lives next to the type instead of in a separate hand written `serde_json::json!` literal.

It carries `///` doc comments into `description`, models enums with serde's default externally tagged representation, and maps Rust types to their JSON Schema equivalents. Leaf types that should not derive `Schema`, such as math types or fixed color arrays, are described with field attributes.

## Why enum2schema

enum2schema is small and focused: a derive that emits an inline JSON Schema matching serde's output, for when a schema exists so a person or an agent can read it (for example an MCP tool `inputSchema`).

- **Tiny dependency.** Just `serde_json` and a derive, with no schema runtime to pull into a game engine or a wasm worker that only needs to describe a handful of types.
- **Inline, not `$ref`.** Every schema is self-contained, which is easier for an LLM (or a human) to read than a web of `$ref`s into a `definitions` table. Recursive types drop the recursive edge with `#[schema(skip)]`.
- **Built for non-deriving leaf types.** Math vectors, quaternions, matrices, and fixed color arrays are described in place with `#[schema(type = "array", items = "number", len = 3)]` or `#[schema(with = ...)]`, with no newtype wrappers or upstream impls for types like `nalgebra`'s.
- **Matched to serde.** It models serde's default externally tagged enums (plus a `string_enum` mode) and respects `rename`, `rename_all`, and `default`, so the schema reflects the real wire format.

It targets serde's externally tagged enums and inline schemas, which is what most tool and message contracts use.

## Usage

Add this to your `Cargo.toml`:

```toml
enum2schema = "0.1"
```

Example:

```rust
use enum2schema::Schema;

/// A command for the worker.
#[derive(Schema)]
enum Command {
    /// Stop everything.
    Stop,
    /// Move to a point.
    Move { x: f32, y: f32 },
}

let schema = Command::schema();
```

`schema()` returns a `serde_json::Value` holding a JSON Schema fragment. The consumer decides how to wrap it: a host wraps an args struct's schema as an MCP tool `inputSchema`; a worker uses a type's schema directly.

## Types

Scalars map to `number`, `integer`, `boolean`, and `string`. `Option<T>` is left out of `required` and made nullable. `Vec<T>`, slices, and sets become `array`; fixed length arrays `[T; N]` and tuples become an `array` carrying `minItems`/`maxItems`. `HashMap`/`BTreeMap` with string keys become an `object` with `additionalProperties`. `Box`, `Rc`, and `Arc` are transparent. `serde_json::Value` becomes the any schema. Nested types that derive `Schema` recurse. Generic types are supported: each type parameter is bounded by `Schema`, so `Envelope<T>` derives a schema once `T: Schema`.

Enums use serde's externally tagged form: a unit variant is a string const inside a `oneOf`, and a data variant is a single key object. For a closed set of unit variants, `#[schema(string_enum)]` emits a single `string` type with an `enum` list instead.

## MCP tools

The `mcp` module builds an MCP tool descriptor straight from the argument type, so the `inputSchema` lives next to the args struct instead of a hand written literal:

```rust
use enum2schema::Schema;

/// Move an entity.
#[derive(Schema)]
struct MoveArgs {
    entity: u32,
    to: [f32; 3],
}

let descriptor = enum2schema::mcp::tool::<MoveArgs>("move", "Move an entity");
// { "name": "move", "description": "Move an entity", "inputSchema": <MoveArgs schema> }
```

`tool_io::<Args, Reply>(name, description)` additionally emits an `outputSchema` from the result type, for MCP structured tool results. The actual request and response *values* are still built by `serde` (`serde_json::to_value`); enum2schema only supplies the schemas.

## Attributes

Field attributes:

- `#[schema(with = path::to::fn)]` uses `path::to::fn()` (returning `serde_json::Value`) as the field schema.
- `#[schema(type = "array", items = "number", len = 3)]` describes a fixed shape for a type that does not implement `Schema`.
- `#[schema(description = "...")]` sets or overrides the description.
- `#[schema(skip)]` omits the field, or, on an enum variant, omits that variant from the schema (serde still serializes it). Use it to drop a recursive variant so an otherwise cyclic type can derive `Schema`.

Container attribute:

- `#[schema(string_enum)]` on a unit only enum emits a `string` type with an `enum` list.

The serde attributes `#[serde(rename = "...")]`, `#[serde(rename_all = "...")]`, and `#[serde(default)]` (treated as not required) are respected.

A single `use enum2schema::Schema;` brings in both the trait and the derive; the crate also re-exports `serde_json`.