Typify - Compile JSON Schema documents into Rust types.

Related tags

Encoding JSON typify
Overview

Typify

Compile JSON Schema documents into Rust types. This can be used ...

  • via the macro import_types!("types.json") to generate Rust types directly in your program

  • via the builder functions to generate Rust types in build.rs

  • or via the builder functions to generate persistent files e.g. when building API bindings.

WIP

This is a work in progress. Here are some clear TODOs:

  • The API needs some consideration; the pieces are there, but it could stand being refined.

  • Strings with patterns and max/min lengths aren't carefully considering ATM

Just to not be overwhelmed, but what's not done: there's a lot that's neat!

  • Versions of schemars has a behavior (bug?) whereby it spits out enums as anyOf rather than oneOf; we detect when all subschemas are mutually incompatible so that we can treat these as enums.

  • All serde enum tagging types are supported.

  • Complex tuples are properly handled.

  • The test harness is pretty robust, validating that the generated TokenStream roughly matches the TokenStream of the original item (enum/struct/type).

Comments
  • Modify enum default generation

    Modify enum default generation

    Fix for #136, refactoring value_for_external_enum and separating out the enum variant and value extraction into a function usable in other contexts.

    The context that requires this is impl Default for <Enum> , where we can make use of this function to extract the variant name for simple, and only simple, enums variants.

    opened by joaommartins 7
  • defaults

    defaults

    I notice that the generated code for schemas with defaults provides serde attributes such as default::default_i64<...>() Is it intended that the user of code-generation produce those default functions? It's easy enough to do. I was just wondering if you had a library in mind.

    opened by rrichardson 6
  • Add Default derive

    Add Default derive

    This adds Default to the base derive list, excluding enums where it requires a nightly feature. This allows the generated types to be used by other frameworks that expect models to have Default.

    opened by jayvdb 4
  • cannot convert type string with format i64

    cannot convert type string with format i64

    When we have a property with some field like so:

            "properties": {
              "iterations": {
                "format": "int64",
                "type": "string"
              },
    

    Typify crashes with a panic

    Caused by:
      process didn't exit successfully: `/Users/<redacted>/projects/<redacted>/crates/target/debug/build/<redacted>-631c4662e317d757/build-script-build` (exit status: 101)
      --- stderr
      thread 'main' panicked at 'not yet implemented: Some(
          "int64",
      )', /Users/<redacted>/.cargo/git/checkouts/typify-288d5a84bbbe6a46/b712fe6/typify-impl/src/convert.rs:418:26
      note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
    
    opened by tzilist 4
  • PartialEq for nested Newtypes

    PartialEq for nested Newtypes

    When a schema describes nested newtype structs where the inner value has some kind of constraint on it we generate code like:

    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct Outer(Inner);
    // ...
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct Inner(String);
    
    impl std::convert::TryFrom<Inner> for Outer {
        type Error = &'static str;
        fn try_from(value: Inner) -> Result<Self, Self::Error> {
            if ![
                super::Inner("a".to_string()),
                super::Inner("b".to_string()),
                super::Inner("c".to_string()),
            ]
            .contains(&value)
            {
                Err("invalid value")
            } else {
                Ok(Self(value))
            }
        }
    }
    

    This will fail to compile as contains requires T: PartialEq<T>. (ignoring the super:: which is a separate issue)

    The attached changes add a PartialEq derive to the Inner type specifically when we are generating this nested case. The other option here would be to inspect the Inner type at output time and add the derive there.

    Also unsure if there are additional cases I didn't think of where a failing TryFrom will be generated.

    opened by augustuswm 3
  • Avoid duplicate derives

    Avoid duplicate derives

    If code was using with_derive("Foo"), and Foo is added to the base derive list, the result is a duplicate derive.

    This prevents that from being a failure, and also sorts the combined derives.

    opened by jayvdb 3
  • Presence of maxLength in enum property causes schema build failures

    Presence of maxLength in enum property causes schema build failures

    It seems that the presence of the maxLength field in a string enum property causes the addition of extra super:: keywords in the codegen file. Below is the failing schema, the compile error, and a schema with the maxLength field removed that successfully builds.

    failing schema:

    {
      "$id": "https://example.com/arrays.schema.json",
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "title": "example title",
      "type": "object",
      "properties": {
        "enum_property": {
          "description": "name",
          "type": "string",
          "maxLength" : 32,
          "enum": ["first", "second"]
        }
      }
    }
    

    compile error:

    error[E0433]: failed to resolve: there are too many leading `super` keywords
       |
    26 |             super::ExampleTitleEnumPropertyInner("first".to_string()),
       |             ^^^^^ there are too many leading `super` keywords
    
    error[E0433]: failed to resolve: there are too many leading `super` keywords
       |
    27 |             super::ExampleTitleEnumPropertyInner("second".to_string()),
       |             ^^^^^ there are too many leading `super` keywords
    
    

    Changes required to write schema

    {
      "$id": "https://example.com/arrays.schema.json",
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "title": "example title",
      "type": "object",
      "properties": {
        "enum_property": {
          "description": "name",
          "type": "string",
    -     "maxLength" : 32,
          "enum": ["first", "second"]
        }
      }
    }
    
    opened by lpraneis 3
  • Support telling serde where it is, i.e. when re-exported as a transitive dependency.

    Support telling serde where it is, i.e. when re-exported as a transitive dependency.

    Supported by the serde() annotation in derive macros, which otherwise will assume that serde is a direct crate dependency. When a macro emits a type that derives Serialize/Deserialize, the user of that macro would normally have to directly depend on serde themselves for the resulting expansion of those derive macros. This can be cumbersome when consuming serde transitively via some other crate (i.e. progenitor).

    https://github.com/serde-rs/serde/blob/5a8dcac2ed1407fab3f7fd23f2d56af42dcd448f/serde_derive/src/internals/attr.rs#L556-L561

    opened by lifning 3
  • Add support for rust_decimal

    Add support for rust_decimal

    Hey everyone, I encountered an openapi spec with a decimal format (primarily for currency type amounts) and figured this would be a reasonable addition to the library. This change adds support for rust_decimal to typify, inspired by the chrono support. Let me know if you have any objections/feedback to this and I'd be happy to address.

    By the way, great work on progenitor and this library. I'm really liking the direction these are heading and hoping I can make use of these in the future.

    This is the openapi spec in question: https://raw.githubusercontent.com/alpacahq/alpaca-docs/master/oas/broker/openapi.yaml

    opened by ryanahall 3
  • Allow configuration of package dependencies and schema conversions

    Allow configuration of package dependencies and schema conversions

    Currently, typify can generate code that depends on uuid and chrono. It might be useful to enable support for other packages or it might be useful to deny use, say, of uuid and represent a { "type": "string", "format": "uuid" } as a String rather than as an uuid::Uuid. One could even imagine allowing substitutes for the std types for arrays, maps, and sets.

    In addition, it could be useful to allow consumers to specify new associations between formats and Rust types or to override existing ones. For example, a user might want to use rust_decimal::Decimal" for the "decimal" format orchrono::naive::NaiveDatefor the "date" format (rather thanchrono::Date`).

    We could modify the macro, builder, and command-line interfaces to allow this (perhaps all through a shared Settings type). e.g.

    import_types!(
        schema = "../example.json",
        allow_packages = [uuid, chrono],
        additional_string_formats = {
            "decimal" = "rust_decimal::Decimal",
            "date" = "chrono::naive::naiveDate",
        },
    );
    
    opened by ahl 2
  • Bump uuid from 0.8.2 to 1.0.0

    Bump uuid from 0.8.2 to 1.0.0

    Bumps uuid from 0.8.2 to 1.0.0.

    Release notes

    Sourced from uuid's releases.

    1.0.0

    This release includes a huge amount of work from a lot of contributors. These notes are duplicated from 1.0.0-alpha.1 since they're relevant for anybody moving from 0.8.2 to 1.0.0.

    Changes since the last release

    https://github.com/uuid-rs/uuid/compare/0.8.2...main

    Contributions since the last release

    ... (truncated)

    Commits
    • 9e0dc29 Merge pull request #596 from KodrAus/cargo/1.0.0
    • 286134f prepare for 1.0.0 release
    • 43905eb Merge pull request #595 from spruceid/remove-and
    • 444ef96 Remove unnecessary AND
    • 5f649d7 Merge pull request #592 from uuid-rs/KodrAus-patch-1
    • 92ad25f update contact details in the CoC
    • 767f519 Merge pull request #591 from uuid-rs/fix/macro-diagnostics
    • 8b043af use the parsed literal as the span source for errors
    • f0d5956 Merge pull request #587 from uuid-rs/feat/fast-sha1
    • 165b7eb move to sha1_smol as the new name for the current library
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies 
    opened by dependabot[bot] 2
  • handling for arbitrary containment and derive cycles

    handling for arbitrary containment and derive cycles

    JSON Schema can define types that have cycles. This is simple to handle in languages like JavaScript or Java, but more complex in Rust since those cycles must be explicitly broken with a Box<T>. Note that use of a Vec or HashMap also breaks the containment cycle but has implications for derive computation which we will discuss later. Note too that where one "breaks" a cycle may have multiple solutions, some that require more breaks than others. Note also that it may not be feasible to reconstruct the types e.g. if the JSON Schema were derived from Rust types because the information about Box indirections is explicitly discarded (and probably reasonably so, but one could imagine including hints; more on that later as well).

    Currently we break trivial A -> A cycles such as:

    struct A {
        a: Option<Box<A>>, // this needs to be boxed
    }
    

    We can do this without a bunch of graph traversal and it solved a proximate problem.

    The more general case requires us to decompose the type graph into strongly connected subgraphs that form a DAG (e.g. with algorithms proposed by Tarjan, Dijkstra or Kosaraju). In this case, the edges are defined by structure or newtype containment either directly or via an Option type. Within each strongly connected subgraph we then would determine where to "break" the cycles by inserting Boxes. The general case of this requires exponential time to compute. While the number of nodes (types) in a cycle is likely to be small, we still may elect for a heuristic, the simplest of which would be to cut all edges. There's very little harm in cutting more than is absolutely required--the serialization isn't affected for example--the only consequence is to the legibility and ergonomics of the generated types.

    For JSON Schema generated from rust types, it could be helpful to annotate boxed types with an extension. This could act as a heuristic when slicing a strongly connected component i.e. we use these extensions to see if they properly break the containment cycle and do something else if they don't.


    The derive macros we apply to types have a similar problem. Consider, for example, the following type:

    struct A {
        value: u32,
    }
    

    For this struct we could #[derive(Eq, PartialEq)], but if we change the u32 to an f32 we could not! A Vec<T> is Eq only if T: Eq and a HashSet<T> isn't Ord regardless of the traits implemented by T.

    From the list of desirable traits to implement such as Hash, Ord, and Eq, the ones we can apply to a type depend on the types to which it refers. And those references may form a cycle. As above, we must compute the strongly connected components. Above the edges were containment; here the edges are all references (i.e. a Vec is an edge here but not above`). Within each strongly connected component we must take the intersection of all supportable traits.

    opened by ahl 3
  • consider type naming

    consider type naming

    JSON schema gives us two direct sources of types names: the keys in references dictionary and the title field in schema metadata. Note also that while the former are necessarily unique, the latter may not be. In addition, we can get hints about names. Consider for example a JSON object that looks like this:

    {
        "fooObj": {
            "a": 7,
            "b": 8
        }
    }
    

    The outer object may have a name, but the value of fooObj may not (or it may have a title that is not unique). We could include a "distinguishing hint" in these cases which might be the enclosing type and property name, or may come from the external caller e.g. our OpenAPI processor could use the operationId.

    After processing all types, we could choose to always include the distinguishing hint in the name, only include it if it was needed to resolve conflicting types, or raise an error if it as needed to disambiguate conflicting types.

    This would require a change in the API which currently is of the form schemars::schema::Schema -> proc_macro2::TokenStream i.e. we will no longer be sure of the type name until all schemas have been examined.

    opened by ahl 0
Owner
Oxide Computer Company
Servers as they should be.
Oxide Computer Company
JSON Schema validation library

A JSON Schema validator implementation. It compiles schema into a validation tree to have validation as fast as possible.

Dmitry Dygalo 308 Dec 30, 2022
Get JSON values quickly - JSON parser for Rust

get json values quickly GJSON is a Rust crate that provides a fast and simple way to get values from a json document. It has features such as one line

Josh Baker 160 Dec 29, 2022
A rust script to convert a better bibtex json file from Zotero into nice organised notes in Obsidian

Zotero to Obsidian script This is a script that takes a better bibtex JSON file exported by Zotero and generates an organised collection of reference

Sashin Exists 3 Oct 9, 2022
CLI tool to convert HOCON into valid JSON or YAML written in Rust.

{hocon:vert} CLI Tool to convert HOCON into valid JSON or YAML. Under normal circumstances this is mostly not needed because hocon configs are parsed

Mathias Oertel 23 Jan 6, 2023
Decode Metaplex mint account metadata into a JSON file.

Simple Metaplex Decoder (WIP) Install From Source Install Rust. curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh Clone the source: git c

Samuel Vanderwaal 8 Aug 25, 2022
Serialize JSON into a canonical format.

json-canon Serialize JSON into a canonical format. Safe for generating a consistent cryptographic hash or signature across platforms. Follows RFC8785:

Mikey 14 May 30, 2023
Serialize/Deserialize tch-rs types with serde

tch-serde: Serialize/Deserialize tch-rs types with serde This crate provides {ser,de}ialization methods for tch-rs common types. docs.rs | crates.io U

null 4 Apr 3, 2022
JSON parser which picks up values directly without performing tokenization in Rust

Pikkr JSON parser which picks up values directly without performing tokenization in Rust Abstract Pikkr is a JSON parser which picks up values directl

Pikkr 615 Dec 29, 2022
Strongly typed JSON library for Rust

Serde JSON   Serde is a framework for serializing and deserializing Rust data structures efficiently and generically. [dependencies] serde_json = "1.0

null 3.6k Jan 5, 2023
JSON implementation in Rust

json-rust Parse and serialize JSON with ease. Changelog - Complete Documentation - Cargo - Repository Why? JSON is a very loose format where anything

Maciej Hirsz 500 Dec 21, 2022
Rust port of gjson,get JSON value by dotpath syntax

A-JSON Read JSON values quickly - Rust JSON Parser change name to AJSON, see issue Inspiration comes from gjson in golang Installation Add it to your

Chen Jiaju 90 Dec 6, 2022
A easy and declarative way to test JSON input in Rust.

assert_json A easy and declarative way to test JSON input in Rust. assert_json is a Rust macro heavily inspired by serde json macro. Instead of creati

Charles Vandevoorde 8 Dec 5, 2022
A small rust database that uses json in memory.

Tiny Query Database (TQDB) TQDB is a small library for creating a query-able database that is encoded with json. The library is well tested (~96.30% c

Kace Cottam 2 Jan 4, 2022
A JSON Query Language CLI tool built with Rust 🦀

JQL A JSON Query Language CLI tool built with Rust ?? ?? Core philosophy ?? Stay lightweight ?? Keep its features as simple as possible ?? Avoid redun

Davy Duperron 872 Jan 1, 2023
rurl is like curl but with a json configuration file per request

rurl rurl is a curl-like cli tool made in rust, the difference is that it takes its params from a json file so you can have all different requests sav

Bruno Ribeiro da Silva 6 Sep 10, 2022
A tool for outputs semantic difference of json

jsondiff A tool for outputs semantic difference of json. "semantic" means: sort object key before comparison sort array before comparison (optional, b

niboshi 3 Sep 22, 2021
Easily create dynamic css using json notation

jss! This crate provides an easy way to write dynamic css using json notation. This gives you more convenient than you think. Considering using a dyna

Jovansonlee Cesar 7 May 14, 2022
A fast and simple command-line tool for common operations over JSON-lines files

rjp: Rapid JSON-lines processor A fast and simple command-line tool for common operations over JSON-lines files, such as: converting to and from text

Ales Tamchyna 3 Jul 8, 2022
Tools for working with Twitter JSON data

Twitter stream user info extractor This project lets you parse JSON data from the Twitter API or other sources to extract some basic user information,

Travis Brown 4 Apr 21, 2022