11 releases

0.0.13 May 14, 2023
0.0.12 May 3, 2023
0.0.11 Mar 18, 2023
0.0.10 Sep 10, 2022
0.0.5 Nov 17, 2021

#1666 in Procedural macros

Download history 7318/week @ 2023-06-01 7664/week @ 2023-06-08 5731/week @ 2023-06-15 5356/week @ 2023-06-22 5949/week @ 2023-06-29 5868/week @ 2023-07-06 6523/week @ 2023-07-13 5615/week @ 2023-07-20 6699/week @ 2023-07-27 7648/week @ 2023-08-03 7139/week @ 2023-08-10 8421/week @ 2023-08-17 7505/week @ 2023-08-24 7193/week @ 2023-08-31 8589/week @ 2023-09-07 9172/week @ 2023-09-14

33,849 downloads per month
Used in 5 crates (via typify)

Apache-2.0

405KB
9K SLoC

Typify

Typify compiles JSON Schema documents into Rust types. It can be used in one of several ways:

  • via the macro import_types!("types.json") to generate Rust types directly in your program

  • via a builder interface to generate Rust types in build.rs or xtask

  • via the builder functions to generate persistent files e.g. when building API bindings

  • using the cargo typify command

If generation fails or is lousy: Please file an issue and include the JSON Schema and Rust output (if there is any). Use cargo typify command to generate code from the command-line.

JSON Schema → Rust types

Typify translates JSON Schema types in a few different ways depending on some basic properties of the schema:

Built-in types

Integers, floating-point numbers, strings, etc. Those all have straightforward representations in Rust. The only significant nuance is how to select the appropriate built-in type based on type attributes. For example, a JSON Schema might specify a maximum and/or minimum that indicates the appropriate integral type to use.

String schemas that include a format are represented with the appropriate Rust type. For example { "type": "string", "format": "uuid" } is represented as a uuid::Uuid (which requires the uuid crate be included as a dependency).

Arrays

JSON Schema arrays can turn into one of three Rust types Vec<T>, HashSet<T>, and tuples depending on the schema properties. An array may have a fixed length that matches a fixed list of item types; this is well represented by a Rust tuples. The distinction between Vec<T> and HashSet<T> is only if the schema's uniqueItems field is false or true respectively.

Objects

In general, objects turn in to Rust structs. If, however, the schema defines no properties, Typify emits a HashMap<String, T> if the additionalProperties schema specifies T or a HashMap<String, serde_json::Value> otherwise.

Properties that are not in the required set are typically represented as an Option<T> with the #[serde(default)] attribute applied. Non-required properties with types that already have a default value (such as a Vec<T>) simply get the #[serde(default)] attribute (so you won't see e.g. Option<Vec<T>>).

OneOf

The OneOf construct maps to a Rust enum. Typify maps this to the various serde enum types.

AnyOf / AllOf

The anyOf and allOf constructs are a little trickier to handle, but (in general) Typify models these as structs where each member is decorated with the #[serde(flatten)] attribute (with Option wrappers in the case of anyOf).

Formatting

By default Typify's generated code is not formatted. If formatted code is preferred, crates like rustfmt-wrapper and prettyplease can be used to format the generated code before writing it to a file.

The examples below show different ways to convert a TypeSpace to a string (typespace is a typify::TypeSpace).

rustfmt

Best for generation of code that might be checked in alongside hand-written code such as in the case of an xtask or stand-alone code generator (list cargo-typify).

rustfmt_wrapper::rustfmt(typespace.to_stream().to_string())?

prettyplease

Best for build.rs scripts where transitive dependencies might not have rustfmt installed so should be self-contained.

prettyplease::unparse(&syn::parse2::<syn::File>(typespace.to_stream())?)

No formatting

If no human will ever see the code (and this is almost never the case).

typespace.to_stream().to_string()

WIP

Typify is a work in progress. Changes that affect output will be indicated with a breaking change to the crate version number.

In general, if you have a JSON Schema that causes Typify to fail or if the generated type isn't what you expect, please file an issue.

There are some known areas where we'd like to improve:

Bounded numbers

Bounded numbers aren't very well handled. Consider, for example, the schema:

{
  "type": "integer",
  "minimum": 1,
  "maximum": 6
}

The resulting types won't enforce those value constraints.

Configurable dependencies

A string schema with format set to uuid will result in the uuid::Uuid type; similarly, a format of date translates to chrono::Date<chrono::offset::Utc>. For users that don't want dependencies on uuid or chrono it would be useful for Typify to optionally represent those as String (or as some other, consumer-specified type).

Cyclic types

Typify has special-case handling for self-referential types. For example:

struct A {
    a: Box<A>,
}

.. but it does not support more complex cycles such as A -> B -> A.

Dependencies

~4MB
~83K SLoC