Skip to content

ArroyoSystems/typify

 
 

Repository files navigation

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.

About

JSON Schema -> Rust type converter

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Rust 100.0%