9 releases

0.0.11 Mar 18, 2023
0.0.10 Sep 10, 2022
0.0.9 Jun 20, 2022
0.0.8 May 22, 2022
0.0.5 Nov 17, 2021

#124 in Procedural macros

Download history 580/week @ 2022-12-08 1204/week @ 2022-12-15 575/week @ 2022-12-22 167/week @ 2022-12-29 385/week @ 2023-01-05 935/week @ 2023-01-12 3557/week @ 2023-01-19 4773/week @ 2023-01-26 3128/week @ 2023-02-02 4124/week @ 2023-02-09 3057/week @ 2023-02-16 3855/week @ 2023-02-23 3115/week @ 2023-03-02 5399/week @ 2023-03-09 4228/week @ 2023-03-16 4763/week @ 2023-03-23

18,058 downloads per month
Used in 13 crates (2 directly)

Apache-2.0

375KB
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
~88K SLoC