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.
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
).
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
).
rust
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.
rust
prettyplease::unparse(&syn::parse2::<syn::File>(typespace.to_stream())?)
If no human will ever see the code (and this is almost never the case).
rust
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:
json
{
"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:
rust
struct A {
a: Box<A>,
}
.. but it does not support more complex cycles such as A -> B -> A.