cargo-typify

repository·main·Indexed 21 days ago

https://github.com/oxidecomputer/typify

A command-line tool and library for generating type-safe Rust code from JSON Schema files. It provides a Cargo subcommand to automate the creation of structs, enums, and validation logic, supporting builder-style interfaces, custom derives, attributes, and external crate dependencies via the x-rust-type extension. It also includes the import_types! procedural macro for direct type generation within Rust code.

Tokens
6.3K
Snippets
21
Records
30
Agent score
72%

What's inside cargo-typify

  1. Augment code generation with type overrides and macros

    main

    As of version 0.0.11, consumers can influence the generation process using several augmented generation techniques:

    • Adding derive macros: Apply specific derive macros to all generated types.
    • Renaming types: Modify specific types by name to change their identifier.
    • Adding macros to specific types: Apply specific derive macros to specific types by name.
    • Specifying replacement types: Provide a replacement type by name or by matching a specific schema pattern.
  2. Convert JSON Schema to Rust code with cargo-typify

    main

    Use the cargo typify command to convert a JSON Schema file into Rust source code. By default, the tool generates a .rs file by replacing the input file's extension with .rs.

    $ cargo typify my_types.json
  3. Install cargo-typify

    main

    Install the cargo-typify CLI tool using cargo install. This tool requires rustfmt to be installed on your system. If you do not have rustfmt, you can install it via rustup.

    For ArchLinux users, an AUR package is also available.

    # Install rustfmt if not already present
    rustup component add rustfmt
    
    # Install cargo-typify
    cargo install cargo-typify
  4. Use the `TypeSpace` interface for programmatic type construction

    main
    For complex use cases like custom generators or advanced build.rs scripts, Typify exports a TypeSpace interface. This allows for the programmatic construction of types rather than relying on the macro syntax. This is useful when the input schema definitions are dynamic or part of a larger code generation pipeline.
  5. Handle unknown crates with UnknownPolicy

    main

    When a JSON Schema uses the x-rust-type extension to refer to a type in an external crate that hasn't been explicitly configured via with_crate, the UnknownPolicy determines the behavior:

    • Generate (default): The library attempts to generate a new type based on the schema structure.
    • Allow: The library uses the specified type path from the schema. This may cause compilation errors if the crate is not a dependency in the target project.
    • Deny: The library generates a compiler warning indicating that the crate must be specified to proceed.
  6. Handle external crates in `import_types!` via the `crates` option

    main

    If your JSON schema uses the x-rust-type extension to reference existing Rust types, you must specify how to resolve those types using the crates option.

    Each entry in the crates map follows the format crate_name = "spec", where spec can be:

    • A version string: "1.0" (e.g., serde = "1.0")
    • A name and version: "original_name@version" (e.g., my_crate = "my_crate@2.0")

    If a schema references a crate not listed in this map, the behavior is determined by the unknown_crates policy: Generate, Allow, or Deny.

    import_types!(
        schema = "schema.json",
        crates = {
            serde = "1.0",
            // Maps the extension 'x-rust-type': 'some_crate' to 'actual_crate@1.2.3'
            some_crate = "actual_crate@1.2.3",
        },
        unknown_crates = "Deny",
    );
  7. Compare `import_types!` macro vs. `build.rs` approach

    main

    Typify offers two primary ways to generate types:

    1. import_types! Macro:

      • Pros: Much simpler to implement and use.
      • Cons: Generated code is harder to inspect directly (requires cargo expand).
    2. build.rs Script:

      • Pros: Generated type definitions are written to files, making them significantly easier to inspect and providing standard generated documentation.
      • Cons: Requires more manual work to process the JSON Schema and write the output files.
  8. Use the `import_types!` macro to generate Rust types from JSON Schema

    main

    The import_types! macro is the simplest way to convert JSON Schema documents into Rust types. The generated types are pub and implement Debug, Clone, Serialize, and Deserialize.

    Basic Usage

    Pass the path to the JSON schema file directly to the macro.

    Advanced Configuration

    You can customize the generation using several properties:

    • schema: The path to the JSON schema file.
    • derives: A list of additional derive macros to apply to all generated types.
    • struct_builder: When set to true, generates a builder-style interface for structs.
    • patch: Allows renaming types or adding specific derives to individual types.
    • replace: Replaces a generated type with an existing type from another crate.
    • convert: Overrides how specific JSON Schema constructs (e.g., a string with a specific format) are mapped to Rust types.
    // Basic usage
    import_types!("../example.json");
    
    // Expanded form with derives
    import_types!(
        schema = "../example.json",
        derives = [schemars::JsonSchema],
    );
    
    // Using the struct builder interface
    import_types!(
        schema = "../example.json",
        struct_builder = true,
    );
    
    // Example of using a generated builder
    let veggie: Veggie = Veggie::builder()
        .veggie_name("radish")
        .veggie_like(true)
        .try_into()
        .unwrap();
    
    // Patching types (rename and extra derives)
    import_types!(
        schema = "../example.json",
        patch = {
            Veggie = {
                rename = "Vegetable",
                derives = [ schemars::JsonSchema ],
            }
        }
    );
    
    // Replacing types with existing ones
    import_types!(
        schema = "../example.json",
        replace = {
            Ipv6Cidr = my_fancy_networking_crate::Ipv6Cidr,
        }
    );
    
    // Overriding conversions based on schema constructs
    import_types!(
        schema = "../example.json",
        convert = {
            {
                type = "string",
                format = "uuid",
            } = my_fancy_uuid_crate::MyUuid,
        }
    );
  9. Use the cargo-typify CLI to convert JSON Schema to Rust

    main

    The cargo-typify command is a CLI tool that converts JSON Schema files into Rust code. It supports generating builder-style interfaces for structs, applying custom derives and attributes, and handling external crate dependencies specified via the x-rust-type extension in the schema.

    Basic Usage

    To convert a schema, provide the input file path. If no output is specified, it defaults to the input filename with a .rs extension.

    # Generates input.rs from input.json
    cargo-typify input.json
    
    # Writes output to stdout
    cargo-typify input.json --output -
    
    # Writes output to a specific file
    cargo-typify input.json --output output.rs
    cargo-typify input.json --output output.rs
  10. Example: Converting a JSON Schema to Rust

    main

    This example demonstrates converting a JSON Schema containing a oneOf definition (an IdOrName enum) and a constrained string (a Name struct) into Rust code. The generated code includes serde implementations, FromStr conversions, and validation logic based on the schema's pattern and maxLength constraints.

    {
      "$schema": "http://json-schema.org/draft-07/schema#",
      "definitions": {
        "IdOrName": {
          "oneOf": [
            { "title": "Id", "allOf": [{ "type": "string", "format": "uuid" }] },
            { "title": "Name", "allOf": [{ "$ref": "#/definitions/Name" }] }
          ]
        },
        "Name": {
          "title": "A name unique within the parent collection",
          "type": "string",
          "pattern": "^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z][a-z0-9-]*[a-zA-Z0-9]$",
          "maxLength": 63
        }
      }
    }

    Generated Rust usage:

    $ cargo typify id-or-name.json && cat id-or-name.rs