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.rsorxtask -
via the builder functions to generate persistent files e.g. when building API bindings
-
using the
cargo typifycommand
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.
Typify translates JSON Schema types in a few different ways depending on some basic properties of the schema:
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).
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.
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>>).
The OneOf construct maps to a Rust enum. Typify maps this to the various
serde enum types.
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).
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).
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())?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())?)If no human will ever see the code (and this is almost never the case).
typespace.to_stream().to_string()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 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.
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).
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.