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
orxtask
-
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.