diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d37634e..7c72306 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: - nightly include: - rust: 1.60.0 - test_features: "--features impl_json_schema" + test_features: "" allow_failure: false - rust: stable test_features: "--all-features" diff --git a/CHANGELOG.md b/CHANGELOG.md index 19d073a..a99e310 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [1.0.0-alpha.3] - 2024-08-10 + +### Added + +- `#[schemars(transform = some::transform)]` for applying arbitrary modifications to generated schemas. `some::transform` must be an expression of type `schemars::transform::Transform` - note that this can be a function with the signature `fn(&mut Schema) -> ()`. +- `SchemaSettings` and `SchemaGenerator` are both now `Send` + +### Changed (_⚠️ breaking changes ⚠️_) + +- `visit` module and `Visitor` trait have been replace with `transform` and `Transform` respectively. Accordingly, these items have been renamed: + - `SchemaSettings::visitors` -> `SchemaSettings::transforms` + - `SchemaSettings::with_visitor` -> `SchemaSettings::with_transform` + - `SchemaGenerator::visitors_mut` -> `SchemaGenerator::transforms_mut` + - `GenVisitor` -> `GenTransform` + - `Visitor::visit_schema` -> `Transform::transform` + - `visit::visit_schema` -> `transform::transform_subschemas` +- `GenTransform` must also impl `Send`, but no longer needs to impl `Debug` +- Doc comments no longer have newlines collapsed when generating the `description` property (https://github.com/GREsau/schemars/pull/310) + ## [1.0.0-alpha.2] - 2024-06-05 ### Added diff --git a/Cargo.lock b/Cargo.lock index 92ed468..e8be803 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - [[package]] name = "arrayvec" version = "0.7.4" @@ -29,17 +23,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bigdecimal" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "bigdecimal" version = "0.4.2" @@ -168,12 +151,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.2" @@ -196,17 +173,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - [[package]] name = "indexmap" version = "2.0.2" @@ -214,8 +180,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", - "hashbrown 0.14.2", - "serde", + "hashbrown", ] [[package]] @@ -284,29 +249,49 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] +[[package]] +name = "ref-cast" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4846d4c50d1721b1a3bef8af76924eef20d5e723647333798c1b519b3a9473f" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "rust_decimal" version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c4216490d5a413bc6d10fa4742bd7d4955941d062c0ef873141d6b0e7b30fd" dependencies = [ - "arrayvec 0.7.4", + "arrayvec", "num-traits", ] @@ -318,20 +303,18 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "schemars" -version = "0.8.21" +version = "1.0.0-alpha.3" dependencies = [ - "arrayvec 0.5.2", - "arrayvec 0.7.4", - "bigdecimal 0.3.1", - "bigdecimal 0.4.2", + "arrayvec", + "bigdecimal", "bytes", "chrono", "dyn-clone", "either", "enumset", - "indexmap 1.9.3", - "indexmap 2.0.2", + "indexmap", "pretty_assertions", + "ref-cast", "rust_decimal", "schemars_derive", "semver", @@ -341,13 +324,12 @@ dependencies = [ "smol_str", "trybuild", "url", - "uuid 0.8.2", - "uuid 1.5.0", + "uuid", ] [[package]] name = "schemars_derive" -version = "0.8.21" +version = "1.0.0-alpha.3" dependencies = [ "pretty_assertions", "proc-macro2", @@ -361,9 +343,6 @@ name = "semver" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" -dependencies = [ - "serde", -] [[package]] name = "serde" @@ -402,6 +381,7 @@ version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ + "indexmap", "itoa", "ryu", "serde", @@ -415,18 +395,15 @@ checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "smol_str" -version = "0.1.24" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9" -dependencies = [ - "serde", -] +checksum = "e6845563ada680337a52d43bb0b29f396f2d911616f6573012645b9e3d048a49" [[package]] name = "syn" -version = "2.0.38" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -504,12 +481,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" - [[package]] name = "uuid" version = "1.5.0" diff --git a/README.md b/README.md index 9322417..18c6d4b 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # Schemars > [!NOTE] -> Schemars 1.0 is in development on [the v1 branch](https://github.com/GREsau/schemars/tree/v1), see [draft PR 290](https://github.com/GREsau/schemars/pull/290) for updates +> This branch is for the current v1 alpha version of Schemars which is still under development. +> For the current stable release of Schemars (v0.8.x), see the [v0 branch](https://github.com/GREsau/schemars/tree/v0). [![CI Build](https://img.shields.io/github/actions/workflow/status/GREsau/schemars/ci.yml?branch=master&logo=GitHub)](https://github.com/GREsau/schemars/actions) [![Crates.io](https://img.shields.io/crates/v/schemars)](https://crates.io/crates/schemars) -[![Docs](https://docs.rs/schemars/badge.svg)](https://docs.rs/schemars) -[![MSRV 1.60+](https://img.shields.io/badge/schemars-rustc_1.60+-lightgray.svg)](https://blog.rust-lang.org/2022/04/07/Rust-1.60.0.html) +[![Docs](https://img.shields.io/docsrs/schemars)](https://docs.rs/schemars) +[![MSRV 1.60+](https://img.shields.io/crates/msrv/schemars)](https://blog.rust-lang.org/2022/04/07/Rust-1.60.0.html) Generate JSON Schema documents from Rust code @@ -39,10 +40,9 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); ```json { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", - "required": ["my_bool", "my_int"], "properties": { "my_bool": { "type": "boolean" @@ -54,7 +54,7 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); "my_nullable_enum": { "anyOf": [ { - "$ref": "#/definitions/MyEnum" + "$ref": "#/$defs/MyEnum" }, { "type": "null" @@ -62,26 +62,25 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); ] } }, - "definitions": { + "required": ["my_int", "my_bool"], + "$defs": { "MyEnum": { - "anyOf": [ + "oneOf": [ { "type": "object", - "required": ["StringNewType"], "properties": { "StringNewType": { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": ["StringNewType"] }, { "type": "object", - "required": ["StructVariant"], "properties": { "StructVariant": { "type": "object", - "required": ["floats"], "properties": { "floats": { "type": "array", @@ -90,10 +89,12 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); "format": "float" } } - } + }, + "required": ["floats"] } }, - "additionalProperties": false + "additionalProperties": false, + "required": ["StructVariant"] } ] } @@ -137,24 +138,23 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); ```json { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", - "required": ["myBool", "myNumber"], "properties": { "myBool": { "type": "boolean" }, "myNullableEnum": { - "default": null, "anyOf": [ { - "$ref": "#/definitions/MyEnum" + "$ref": "#/$defs/MyEnum" }, { "type": "null" } - ] + ], + "default": null }, "myNumber": { "type": "integer", @@ -162,7 +162,8 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); } }, "additionalProperties": false, - "definitions": { + "required": ["myNumber", "myBool"], + "$defs": { "MyEnum": { "anyOf": [ { @@ -170,7 +171,6 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); }, { "type": "object", - "required": ["floats"], "properties": { "floats": { "type": "array", @@ -179,7 +179,8 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); "format": "float" } } - } + }, + "required": ["floats"] } ] } @@ -254,33 +255,28 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); ## Feature Flags - `derive` (enabled by default) - provides `#[derive(JsonSchema)]` macro -- `impl_json_schema` - implements `JsonSchema` for Schemars types themselves -- `preserve_order` - keep the order of struct fields in `Schema` and `SchemaObject` +- `preserve_order` - keep the order of struct fields in `Schema` properties - `raw_value` - implements `JsonSchema` for `serde_json::value::RawValue` (enables the serde_json `raw_value` feature) Schemars can implement `JsonSchema` on types from several popular crates, enabled via feature flags (dependency versions are shown in brackets): -- `chrono` - [chrono](https://crates.io/crates/chrono) (^0.4) -- `indexmap1` - [indexmap](https://crates.io/crates/indexmap) (^1.2) -- `indexmap2` - [indexmap](https://crates.io/crates/indexmap) (^2.0) -- `either` - [either](https://crates.io/crates/either) (^1.3) -- `uuid08` - [uuid](https://crates.io/crates/uuid) (^0.8) -- `uuid1` - [uuid](https://crates.io/crates/uuid) (^1.0) -- `smallvec` - [smallvec](https://crates.io/crates/smallvec) (^1.0) -- `arrayvec05` - [arrayvec](https://crates.io/crates/arrayvec) (^0.5) - `arrayvec07` - [arrayvec](https://crates.io/crates/arrayvec) (^0.7) -- `url` - [url](https://crates.io/crates/url) (^2.0) -- `bytes` - [bytes](https://crates.io/crates/bytes) (^1.0) -- `enumset` - [enumset](https://crates.io/crates/enumset) (^1.0) -- `rust_decimal` - [rust_decimal](https://crates.io/crates/rust_decimal) (^1.0) -- `bigdecimal03` - [bigdecimal](https://crates.io/crates/bigdecimal) (^0.3) - `bigdecimal04` - [bigdecimal](https://crates.io/crates/bigdecimal) (^0.4) -- `smol_str` - [smol_str](https://crates.io/crates/smol_str) (^0.1.17) -- `semver` - [semver](https://crates.io/crates/semver) (^1.0.9) +- `bytes1` - [bytes](https://crates.io/crates/bytes) (^1.0) +- `chrono04` - [chrono](https://crates.io/crates/chrono) (^0.4) +- `either1` - [either](https://crates.io/crates/either) (^1.3) +- `enumset1` - [enumset](https://crates.io/crates/enumset) (^1.0) +- `indexmap2` - [indexmap](https://crates.io/crates/indexmap) (^2.0) +- `rust_decimal1` - [rust_decimal](https://crates.io/crates/rust_decimal) (^1.0) +- `semver1` - [semver](https://crates.io/crates/semver) (^1.0.9) +- `smallvec1` - [smallvec](https://crates.io/crates/smallvec) (^1.0) +- `smol_str02` - [smol_str](https://crates.io/crates/smol_str) (^0.2.1) +- `url2` - [url](https://crates.io/crates/url) (^2.0) +- `uuid1` - [uuid](https://crates.io/crates/uuid) (^1.0) For example, to implement `JsonSchema` on types from `chrono`, enable it as a feature in the `schemars` dependency in your `Cargo.toml` like so: ```toml [dependencies] -schemars = { version = "0.8", features = ["chrono"] } +schemars = { version = "1.0.0-alpha.3", features = ["chrono04"] } ``` diff --git a/docs/0-migrating.md b/docs/0-migrating.md new file mode 100644 index 0000000..4ee936a --- /dev/null +++ b/docs/0-migrating.md @@ -0,0 +1,175 @@ +--- +title: Migrating from 0.8 +nav_order: 2 +has_children: true +has_toc: false +permalink: /migrating/ +layout: default +--- + +# Migrating from 0.8 to 1.0 + +
+

Schemars 1.0 is still under development, and further changes may be introduced. +

+ +## Optional dependencies + +All optional dependencies are now suffixed by their version: + +- `chrono` is now `chrono04` +- `either` is now `either1` +- `smallvec` is now `smallvec1` +- `url` is now `url2` +- `bytes` is now `bytes1` +- `rust_decimal` is now `rust_decimal1` +- `enumset` is now `enumset1` +- `smol_str` is now `smol_str02` +- `semver` is now `semver1` +- `indexmap`, `uuid08`, `arrayvec05` and `bigdecimal03` have been removed +- `indexmap2`, `arrayvec07` and `bigdecimal04` are unchanged + +## `Schema` is now a wrapper around `serde_json::Value` + +`Schema` is now defined as a wrapper around a `serde_json::Value` (which must be a `Value::Bool` or `Value::Object`), rather than a struct with a field for each JSON schema keyword (with some intermediary types). `Schema` is now available as `schemars::Schema` instead of `schemars::schema::Schema`, and all other types that were in the `schemars::schema` module have now been removed. Functions that previously returned a `RootSchema` now just return a `Schema`. + +A new macro `json_schema!(...)` is available to easily create new instances of `Schema`, which functions similarly to the [`serde_json::json!(...)` macro](https://docs.rs/serde_json/latest/serde_json/macro.json.html). + +Here's how you might create and modify a `Schema` in schemars v0.8: + +```rust +use schemars::schema::{InstanceType, ObjectValidation, Schema, SchemaObject}; +use schemars::Map; + +// Create a Schema for an object with property `foo` +let schema_object = SchemaObject { + instance_type: Some(InstanceType::Object.into()), + object: Some(Box::new(ObjectValidation { + properties: Map::from_iter([("foo".to_owned(), true.into())]), + ..Default::default() + })), + ..Default::default() +}; +let schema: Schema = schema_object.into(); + +// Make the `foo` property required +let mut schema_object = schema.into_object(); +let obj = schema_object.object(); +obj.required.insert("foo".to_owned()); +``` + +And the same thing in v1.0: + +```rust +use schemars::{json_schema, Schema}; + +// Create a Schema for an object with property `foo` +let mut schema: Schema = json_schema!({ + "type": "object", + "properties": { + "foo": true + } +}); + +// Make the `foo` property required +schema + .ensure_object() + .entry("required") + .or_insert(serde_json::Value::Array(Vec::new())) + .as_array_mut() + .expect("`required` should be an array") + .push("foo".into()); +``` + +## `visit::Visitor` replaced with `transform::Transform` + +The `visit` module and `Visitor` trait have been replace with `transform` and `Transform` respectively. Accordingly, these items have been renamed: + +- `SchemaSettings::visitors` -> `SchemaSettings::transforms` +- `SchemaSettings::with_visitor` -> `SchemaSettings::with_transform` +- `SchemaGenerator::visitors_mut` -> `SchemaGenerator::transforms_mut` +- `GenVisitor` -> `GenTransform` +- `Visitor::visit_schema` -> `Transform::transform` + - `visit_schema_object` and `visit_root_schema` methods have been removed +- `visit::visit_schema` -> `transform::transform_subschemas` + - `visit_schema_object` and `visit_root_schema` functions have been removed + +So if you had defined this `Visitor` in schemars 0.8: + +```rust +use schemars::schema::SchemaObject; +use schemars::visit::{visit_schema_object, Visitor}; + +pub struct MyVisitor; + +impl Visitor for MyVisitor { + fn visit_schema_object(&mut self, schema: &mut SchemaObject) { + // First, make our change to this schema + schema + .extensions + .insert("my_property".to_string(), serde_json::json!("hello world")); + + // Then delegate to default implementation to visit any subschemas + visit_schema_object(self, schema); + } +} + +let mut schema = schemars::schema_for!(str); +MyVisitor.visit_root_schema(&mut schema); +``` + +Then the equivalent `Transform` in schemars 1.0 would be: + +```rust +use schemars::transform::{transform_subschemas, Transform}; +use schemars::Schema; + +pub struct MyTransform; + +impl Transform for MyTransform { + fn transform(&mut self, schema: &mut Schema) { + // First, make our change to this schema + if let Some(obj) = schema.as_object_mut() { + obj.insert("my_property".to_string(), serde_json::json!("hello world")); + } + + // Then apply the transform to any subschemas + transform_subschemas(self, schema); + } +} + +let mut schema = schemars::schema_for!(str); +MyTransform.transform(&mut schema); +``` + +Also, since `Transform` is now implemented for functions that take a single `&mut Schema` argument, you could also define it as a function instead of a struct: + +```rust +fn my_transform(schema: &mut Schema) { + // First, make our change to this schema + if let Some(obj) = schema.as_object_mut() { + obj.insert("my_property".to_string(), serde_json::json!("hello world")); + } + + // Then apply the transform to any subschemas + transform_subschemas(&mut my_transform, schema); +} + +let mut schema = schemars::schema_for!(str); +my_transform(&mut schema); +// Or equivalently: +// my_transform.transform(&mut schema); +``` + +Finally, you can also use the `RecursiveTransform` newtype to convert a non-recursive `Transform` (i.e. one that does not transform subschemas) into a recursive one, like so: + +```rust +fn my_transform2(schema: &mut Schema) { + if let Some(obj) = schema.as_object_mut() { + obj.insert("my_property".to_string(), serde_json::json!("hello world")); + } +} + +let mut schema = schemars::schema_for!(str); +RecursiveTransform(my_transform2).transform(&mut schema); +``` diff --git a/docs/1-deriving.md b/docs/1-deriving.md index 6e37fc8..0bb4420 100644 --- a/docs/1-deriving.md +++ b/docs/1-deriving.md @@ -1,7 +1,6 @@ --- -layout: default title: Deriving JsonSchema -nav_order: 2 +nav_order: 3 has_children: true has_toc: false permalink: /deriving/ @@ -12,6 +11,7 @@ permalink: /deriving/ The most important trait in Schemars is `JsonSchema`, and the most important function of that trait is `json_schema(...)` which returns a JSON schema describing the type. Implementing this manually on many types would be slow and error-prone, so Schemars includes a derive macro which can implement that trait for you. Any derived implementation of `JsonSchema` should create a schema that describes the JSON representation of the type if it were to be serialized by serde_json. Usually, all you need to do to use it is to add a `#[derive(JsonSchema)]` attribute to your type: + ```rust use schemars::{JsonSchema, schema_for}; @@ -28,7 +28,8 @@ fn main() { println!("{}", serialized); } ``` - diff --git a/docs/_v0/1.1-attributes.md b/docs/_v0/1.1-attributes.md new file mode 100644 index 0000000..b668d7f --- /dev/null +++ b/docs/_v0/1.1-attributes.md @@ -0,0 +1,323 @@ +--- +title: Attributes +parent: Deriving JsonSchema +nav_order: 1 +permalink: /v0/deriving/attributes/ +--- + + + +# Attributes + +You can add attributes to your types to customize Schemars's derived `JsonSchema` implementation. + +[Serde](https://serde.rs/) allows setting `#[serde(...)]` attributes which change how types are serialized, and Schemars will generally respect these attributes to ensure that generated schemas will match how the type is serialized by serde_json. `#[serde(...)]` attributes can be overriden using `#[schemars(...)]` attributes, which behave identically (e.g. `#[schemars(rename_all = "camelCase")]`). You may find this useful if you want to change the generated schema without affecting Serde's behaviour, or if you're just not using Serde. + +[Validator](https://github.com/Keats/validator) allows setting `#[validate(...)]` attributes to restrict valid values of particular fields, many of which will be used by Schemars to generate more accurate schemas. These can also be overridden by `#[schemars(...)]` attributes. + +
+ +TABLE OF CONTENTS + + +1. [Supported Serde Attributes](#supported-serde-attributes) + - [`rename`](#rename) + - [`rename_all`](#rename_all) + - [`tag` / `content` / `untagged`](#tag) + - [`default`](#default) + - [`skip`](#skip) + - [`skip_serializing`](#skip_serializing) + - [`skip_deserializing`](#skip_deserializing) + - [`flatten`](#flatten) + - [`with`](#with) + - [`bound`](#bound) +1. [Supported Validator Attributes](#supported-validator-attributes) + - [`email` / `phone` / `url`](#email-phone-url) + - [`length`](#length) + - [`range`](#range) + - [`regex`](#regex) + - [`contains`](#contains) + - [`required` / `required_nested`](#required) +1. [Other Attributes](#other-attributes) + - [`schema_with`](#schema_with) + - [`title` / `description`](#title-description) + - [`example`](#example) + - [`deprecated`](#deprecated) + - [`crate`](#crate) + - [Doc Comments (`doc`)](#doc) + +
+ +## Supported Serde Attributes + +
+ +

+ +`#[serde(rename = "name")]` / `#[schemars(rename = "name")]` + +

+ +Set on a struct, enum, field or variant to use the given name in the generated schema instead of the Rust name. When used on a struct or enum, the given name will be used as the title for root schemas, and the key within the root's `definitions` property for subschemas. + +If set on a struct or enum with generic type parameters, then the given name may contain them enclosed in curly braces (e.g. `{T}`) and they will be replaced with the concrete type names when the schema is generated. + +Serde docs: [container](https://serde.rs/container-attrs.html#rename) / [variant](https://serde.rs/variant-attrs.html#rename) / [field](https://serde.rs/field-attrs.html#rename) + +

+ +`#[serde(rename_all = "...")]` / `#[schemars(rename_all = "...")]` + +

+ +Set on a struct, enum or variant to rename all fields according to the given case convention (see the Serde docs for details). + +Serde docs: [container](https://serde.rs/container-attrs.html#rename_all) / [variant](https://serde.rs/variant-attrs.html#rename_all) + +

+ +`#[serde(tag = "type")]` / `#[schemars(tag = "type")]`
+`#[serde(tag = "t", content = "c")]` / `#[schemars(tag = "t", content = "c")]`
+`#[serde(untagged)]` / `#[schemars(untagged)]` + +

+ +Set on an enum to generate the schema for the [internally tagged](https://serde.rs/enum-representations.html#internally-tagged), [adjacently tagged](https://serde.rs/enum-representations.html#adjacently-tagged), or [untagged](https://serde.rs/enum-representations.html#untagged) representation of this enum. + +Serde docs: [`tag`](https://serde.rs/container-attrs.html#tag) / [`tag`+`content`](https://serde.rs/container-attrs.html#tag--content) / [`untagged`](https://serde.rs/container-attrs.html#untagged) + +

+ +`#[serde(default)]` / `#[schemars(default)]` / `#[serde(default = "path")]` / `#[schemars(default = "path")]` + +

+ +Set on a struct or field to give fields a default value, which excludes them from the schema's `required` properties. The default will also be set on the field's schema's `default` property, unless it is skipped by a [`skip_serializing_if`](https://serde.rs/field-attrs.html#skip_serializing_if) attribute on the field. Any [`serialize_with`](https://serde.rs/field-attrs.html#serialize_with) or [`with`](https://serde.rs/field-attrs.html#with) attribute set on the field will be used to serialize the default value. + +Serde docs: [container](https://serde.rs/container-attrs.html#default) / [field](https://serde.rs/field-attrs.html#default) + +

+ +`#[serde(skip)]` / `#[schemars(skip)]` + +

+ +Set on a variant or field to prevent it from appearing in any generated schema. + +Serde docs: [variant](https://serde.rs/variant-attrs.html#skip) / [field](https://serde.rs/field-attrs.html#skip) + +

+ +`#[serde(skip_serializing)]` / `#[schemars(skip_serializing)]` + +

+ +Set on a field of a (non-tuple) struct to set the `writeOnly` property on that field's schema. Serde also allows this attribute on variants or tuple struct fields, but this will have no effect on generated schemas. + +Serde docs: [field](https://serde.rs/field-attrs.html#skip_deserializing) + +

+ +`#[serde(skip_deserializing)]` / `#[schemars(skip_deserializing)]` + +

+ +Set on a variant or field. When set on a field of a (non-tuple) struct, that field's schema will have the `readOnly` property set. When set on a variant or tuple struct field Schemars will treat this the same as a [`skip`](#skip) attribute. + +Serde docs: [variant](https://serde.rs/variant-attrs.html#skip_deserializing) / [field](https://serde.rs/field-attrs.html#skip_deserializing) + +

+ +`#[serde(flatten)]` / `#[schemars(flatten)]` + +

+ +Set on a field to include that field's contents as though they belonged to the field's container. + +Serde docs: [field](https://serde.rs/field-attrs.html#flatten) + +

+ +`#[serde(with = "Type")]` / `#[schemars(with = "Type")]` + +

+ +Set on a variant or field to generate its schema as the given type instead of its actual type. Serde allows the `with` attribute to refer to any module path, but Schemars requires this to be an actual type which implements `JsonSchema`. + +If the given type has any required generic type parameters, then they must all be explicitly specified in this attribute. Serde frequently allows you to omit them as it can make use of type inference, but unfortunately this is not possible with Schemars. For example, `with = "Vec::"` will work, but `with = "Vec"` and `with = "Vec::<_>"` will not. + +Serde docs: [variant](https://serde.rs/variant-attrs.html#with) / [field](https://serde.rs/field-attrs.html#with) + +

+ +`#[serde(deny_unknown_fields)]` / `#[schemars(deny_unknown_fields)]` + +

+ +Setting this on a container will set the `additionalProperties` keyword on generated schemas to `false` to show that any extra properties are explicitly disallowed. + +Serde docs: [container](https://serde.rs/container-attrs.html#deny_unknown_fields) + +

+ +`#[serde(transparent)]` / `#[schemars(transparent)]` + +

+ +Set on a newtype struct or a braced struct with one field to make the struct's generated schema exactly the same as that of the single field's. + +Serde docs: [container](https://serde.rs/container-attrs.html#transparent) + +

+ +`#[schemars(bound = "...")]` + +

+ +Where-clause for the JsonSchema impl. This replaces any trait bounds inferred by schemars. Schemars does **not** use trait bounds from `#[serde(bound)]` attributes. + +Serde docs: [container](https://serde.rs/container-attrs.html#bound) + +
+ +## Supported Validator Attributes + +
+ +

+ +`#[validate(email)]` / `#[schemars(email)]`
+`#[validate(phone)]` / `#[schemars(phone)]`
+`#[validate(url)]` / `#[schemars(url)]` + +

+ +Sets the schema's `format` to `email`/`phone`/`uri`, as appropriate. Only one of these attributes may be present on a single field. + +Validator docs: [email](https://github.com/Keats/validator#email) / [phone](https://github.com/Keats/validator#phone) / [url](https://github.com/Keats/validator#url) + +

+ +`#[validate(length(min = 1, max = 10))]` / `#[schemars(length(min = 1, max = 10))]`
+`#[validate(length(equal = 10))]` / `#[schemars(length(equal = 10))]` + +

+ +Sets the `minLength`/`maxLength` properties for string schemas, or the `minItems`/`maxItems` properties for array schemas. + +Validator docs: [length](https://github.com/Keats/validator#length) + +

+ +`#[validate(range(min = 1, max = 10))]` / `#[schemars(range(min = 1, max = 10))]` + +

+ +Sets the `minimum`/`maximum` properties for number schemas. + +Validator docs: [range](https://github.com/Keats/validator#range) + +

+ +`#[validate(regex = "path::to::regex")]` / `#[schemars(regex = "path::to::regex")]`
+`#[schemars(regex(pattern = r"^\d+$"))]` + +

+ +Sets the `pattern` property for string schemas. The `path::to::regex` will typically refer to a [`Regex`](https://docs.rs/regex/*/regex/struct.Regex.html) instance, but Schemars allows it to be any value with a `to_string()` method. + +Providing an inline regex pattern using `regex(pattern = ...)` is a Schemars extension, and not currently supported by the Validator crate. When using this form, you may want to use a `r"raw string literal"` so that `\\` characters in the regex pattern are not interpreted as escape sequences in the string. + +Validator docs: [regex](https://github.com/Keats/validator#regex) + +

+ +`#[validate(contains = "string")]` / `#[schemars(contains = "string")]` + +

+ +For string schemas, sets the `pattern` property to the given value, with any regex special characters escaped. For object schemas (e.g. when the attribute is set on a HashMap field), includes the value in the `required` property, indicating that the map must contain it as a key. + +Validator docs: [contains](https://github.com/Keats/validator#contains) + +

+ +`#[validate(required)]` / `#[schemars(required)]`
+`#[validate(required_nested)]` + +

+ +When set on an `Option` field, this will create a schemas as though the field were a `T`. + +Validator docs: [required](https://github.com/Keats/validator#required) / [required_nested](https://github.com/Keats/validator#required_nested) + +
+ +## Other Attributes + +

+ +`#[schemars(schema_with = "some::function")]` + +

+ +Set on a variant or field to generate this field's schema using the given function. This function must be callable as `fn(&mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema`. + +

+ +`#[schemars(title = "Some title", description = "Some description")]` + +

+ +Set on a container, variant or field to set the generated schema's `title` and/or `description`. If present, these will be used instead of values from any [`doc` comments/attributes](#doc). + +

+ +`#[schemars(example = "some::function")]` + +

+ +Set on a container, variant or field to include the result of the given function in the generated schema's `examples`. The function should take no parameters and can return any type that implements serde's `Serialize` trait - it does not need to return the same type as the attached struct/field. This attribute can be repeated to specify multiple examples. + +

+ +`#[deprecated]` + +

+ +Set the Rust built-in [`deprecated`](https://doc.rust-lang.org/edition-guide/rust-2018/the-compiler/an-attribute-for-deprecation.html) attribute on a struct, enum, field or variant to set the generated schema's `deprecated` keyword to `true`. + +

+ +`#[schemars(crate = "other_crate::schemars")]` + +

+ +Set the path to the schemars crate instance the generated code should depend on. This is mostly useful for other crates that depend on schemars in their macros. + +

+ +`#[schemars(inner(...))]` + +

+ +Sets properties specified by [validator attributes](#supported-validator-attributes) on items of an array schema. For example: + +```rs +struct Struct { + #[schemars(inner(url, regex(pattern = "^https://")))] + urls: Vec, +} +``` + +

+ +Doc Comments (`#[doc = "..."]`) + +

+ +If a struct, variant or field has any [doc comments](https://doc.rust-lang.org/stable/rust-by-example/meta/doc.html#doc-comments) (or [`doc` attributes](https://doc.rust-lang.org/rustdoc/the-doc-attribute.html)), then these will be used as the generated schema's `description`. If the first line is an ATX-style markdown heading (i.e. it begins with a # character), then it will be used as the schema's `title`, and the remaining lines will be the `description`. diff --git a/docs/_v0/2-implementing.md b/docs/_v0/2-implementing.md new file mode 100644 index 0000000..32f4edd --- /dev/null +++ b/docs/_v0/2-implementing.md @@ -0,0 +1,78 @@ +--- +title: Implementing JsonSchema +nav_order: 3 +permalink: /v0/implementing/ +--- + +# Implementing JsonSchema + +[Deriving `JsonSchema`]({{ site.baseurl }}{% link 1-deriving.md %}) is usually the easiest way to enable JSON schema generation for your types. But if you need more customisation, you can also implement `JsonSchema` manually. This trait has two associated functions which must be implemented, and one which can optionally be implemented: + +## schema_name + +```rust +fn schema_name() -> String; +``` + +This function returns the human-readable friendly name of the type's schema, which frequently is just the name of the type itself. The schema name is used as the title for root schemas, and the key within the root's `definitions` property for subschemas. + +NB in a future version of schemars, it's likely that this function will be changed to return a `Cow<'static, str>`. + +## schema_id + +```rust +fn schema_id() -> Cow<'static, str>; +``` + +This function returns a unique identifier of the type's schema - if two types return the same `schema_id`, then Schemars will consider them identical types. Because of this, if a type takes any generic type parameters, then its ID should depend on the type arguments. For example, the implementation of this function for `Vec where T: JsonSchema` is: + +```rust +fn schema_id() -> Cow<'static, str> { + Cow::Owned( + format!("[{}]", T::schema_id())) +} +``` + +`&mut Vec<&T>`, `LinkedList`, `Mutex>>`, and similar collection types also use that implementation, since they produce identical JSON schemas so they can be considered the same type. + +For a type with no generic type arguments, a reasonable implementation of this function would be to return the type name including module path (in case there is a type with the same name in another module/crate), e.g.: + +```rust +impl JsonSchema for NonGenericType { + fn schema_name() -> String { + // Exclude the module path to make the name in generated schemas clearer. + "NonGenericType".to_owned() + } + + fn schema_id() -> Cow<'static, str> { + // Include the module, in case a type with the same name is in another module/crate + Cow::Borrowed(concat!(module_path!(), "::NonGenericType")) + } + + fn json_schema(_gen: &mut SchemaGenerator) -> Schema { + todo!() + } +} +``` + +## json_schema + +```rust +fn json_schema(gen: &mut gen::SchemaGenerator) -> Schema; +``` + +This function creates the JSON schema itself. The `gen` argument can be used to check the schema generation settings, or to get schemas for other types. If you do need schemas for other types, you should call the `gen.subschema_for::()` method instead of `::json_schema(gen)`, as `subschema_for` can add `T`'s schema to the root schema's `definitions` so that it does not need to be duplicated when used more than once. + +`json_schema` should not return a `$ref` schema. + +## is_referenceable (optional) + +```rust +fn is_referenceable() -> bool; +``` + +If this function returns `true`, then Schemars can re-use the generate schema where possible by adding it to the root schema's `definitions` and having other schemas reference it using the `$ref` keyword. This can greatly simplify schemas that include a particular type multiple times, especially if that type's schema is fairly complex. + +Generally, this should return `false` for types with simple schemas (such as primitives). For more complex types, it should return `true`. For recursive types, this **must** return `true` to prevent infinite cycles when generating schemas. + +The default implementation of this function returns `true` to reduce the chance of someone inadvertently causing infinite cycles with recursive types. diff --git a/docs/_v0/3-generating.md b/docs/_v0/3-generating.md new file mode 100644 index 0000000..ec56349 --- /dev/null +++ b/docs/_v0/3-generating.md @@ -0,0 +1,35 @@ +--- +title: Generating Schemas +nav_order: 4 +permalink: /v0/generating/ +--- + +# Generating Schemas + +The easiest way to generate a schema for a type that implements is to use the [`schema_for!` macro](https://docs.rs/schemars/latest/schemars/macro.schema_for.html), like so: + +```rust +let my_schema = schema_for!(MyStruct); +``` + +This will create a schema that conforms to [JSON Schema Draft 7](https://json-schema.org/specification-links.html#draft-7), but this is liable to change in a future version of Schemars if support for other JSON Schema versions is added. + +If you want more control over how the schema is generated, you can use the [`gen` module](https://docs.rs/schemars/latest/schemars/gen/). There are two main types in this module: + +- [`SchemaSettings`](https://docs.rs/schemars/latest/schemars/gen/struct.SchemaSettings.html), which defines what JSON Schema features should be used when generating schemas (for example, how `Option`s should be represented). +- [`SchemaGenerator`](https://docs.rs/schemars/latest/schemars/gen/struct.SchemaGenerator.html), which manages the generation of a schema document. + +See the API documentation for more info on how to use those types for custom schema generation. + +## Schema from Example Value + +If you want a schema for a type that can't/doesn't implement `JsonSchema`, but does implement `serde::Serialize`, then you can generate a JSON schema from a value of that type using the [`schema_for_value!` macro](https://docs.rs/schemars/latest/schemars/macro.schema_for_value.html). However, this schema will generally be less precise than if the type implemented `JsonSchema` - particularly when it involves enums, since schemars will not make any assumptions about the structure of an enum based on a single variant. + +```rust +let value = MyStruct { foo = 123 }; +let my_schema = schema_for_value!(value); +``` + + diff --git a/docs/_v0/4-features.md b/docs/_v0/4-features.md new file mode 100644 index 0000000..4932fbe --- /dev/null +++ b/docs/_v0/4-features.md @@ -0,0 +1,39 @@ +--- +title: Feature Flags +nav_order: 5 +permalink: /v0/features/ +--- + +# Feature Flags and Optional Dependencies + +- `derive` (enabled by default) - provides `#[derive(JsonSchema)]` macro +- `impl_json_schema` - implements `JsonSchema` for Schemars types themselves +- `preserve_order` - keep the order of struct fields in `Schema` and `SchemaObject` +- `raw_value` - implements `JsonSchema` for `serde_json::value::RawValue` (enables the serde_json `raw_value` feature) + +Schemars can implement `JsonSchema` on types from several popular crates, enabled via feature flags (dependency versions are shown in brackets): + +- `chrono` - [chrono](https://crates.io/crates/chrono) (^0.4) +- `indexmap1` - [indexmap](https://crates.io/crates/indexmap) (^1.2) +- `indexmap2` - [indexmap](https://crates.io/crates/indexmap) (^2.0) +- `either` - [either](https://crates.io/crates/either) (^1.3) +- `uuid08` - [uuid](https://crates.io/crates/uuid) (^0.8) +- `uuid1` - [uuid](https://crates.io/crates/uuid) (^1.0) +- `smallvec` - [smallvec](https://crates.io/crates/smallvec) (^1.0) +- `arrayvec05` - [arrayvec](https://crates.io/crates/arrayvec) (^0.5) +- `arrayvec07` - [arrayvec](https://crates.io/crates/arrayvec) (^0.7) +- `url` - [url](https://crates.io/crates/url) (^2.0) +- `bytes` - [bytes](https://crates.io/crates/bytes) (^1.0) +- `enumset` - [enumset](https://crates.io/crates/enumset) (^1.0) +- `rust_decimal` - [rust_decimal](https://crates.io/crates/rust_decimal) (^1.0) +- `bigdecimal03` - [bigdecimal](https://crates.io/crates/bigdecimal) (^0.3) +- `bigdecimal04` - [bigdecimal](https://crates.io/crates/bigdecimal) (^0.4) +- `smol_str` - [smol_str](https://crates.io/crates/smol_str) (^0.1.17) +- `semver` - [semver](https://crates.io/crates/semver) (^1.0.9) + +For example, to implement `JsonSchema` on types from `chrono`, enable it as a feature in the `schemars` dependency in your `Cargo.toml` like so: + +```toml +[dependencies] +schemars = { version = "0.8", features = ["chrono"] } +``` diff --git a/docs/_v0/5-examples.md b/docs/_v0/5-examples.md new file mode 100644 index 0000000..0df7e3d --- /dev/null +++ b/docs/_v0/5-examples.md @@ -0,0 +1,8 @@ +--- +title: Examples +nav_order: 6 +has_children: true +permalink: /v0/examples/ +--- + +# Examples diff --git a/docs/_v0/examples/1-derive_jsonschema.md b/docs/_v0/examples/1-derive_jsonschema.md new file mode 100644 index 0000000..590b799 --- /dev/null +++ b/docs/_v0/examples/1-derive_jsonschema.md @@ -0,0 +1,12 @@ +--- +title: Deriving JsonSchema +parent: Examples +nav_order: 1 +summary: Deriving JsonSchema on a struct and enum. +--- + +# Deriving JsonSchema + +This is the simplest usage of Schemars. Both types are made to derive `JsonSchema`, and the `schema_for!` macro is used to generate the schema itself. + +{% include example_v0.md name="main" %} diff --git a/docs/_v0/examples/2-serde_attrs.md b/docs/_v0/examples/2-serde_attrs.md new file mode 100644 index 0000000..b665319 --- /dev/null +++ b/docs/_v0/examples/2-serde_attrs.md @@ -0,0 +1,14 @@ +--- +title: Using Serde Attributes +parent: Examples +nav_order: 2 +summary: "Deriving JsonSchema on types that use #[serde] attributes to customise serialization behaviour." +--- + +# Using Serde Attributes + +One of the main aims of this library is compatibility with [Serde](https://github.com/serde-rs/serde). Any generated schema _should_ match how [serde_json](https://github.com/serde-rs/json) would serialize/deserialize to/from JSON. To support this, Schemars will check for any `#[serde(...)]` attributes on types that derive `JsonSchema`, and adjust the generated schema accordingly. + +The list of supported `#[serde]` attributes are [documented here]({{ site.baseurl }}{% link 1.1-attributes.md %}#supported-serde-attributes). + +{% include example_v0.md name="serde_attrs" %} diff --git a/docs/_v0/examples/3-schemars_attrs.md b/docs/_v0/examples/3-schemars_attrs.md new file mode 100644 index 0000000..2a5de96 --- /dev/null +++ b/docs/_v0/examples/3-schemars_attrs.md @@ -0,0 +1,12 @@ +--- +title: Using Schemars Attributes +parent: Examples +nav_order: 3 +summary: "Deriving JsonSchema on types that use #[schemars] attributes to customise serialization behaviour." +--- + +# Using Serde Attributes + +`#[serde(...)]` attributes can be overriden (or replaced) with `#[schemars(...)]` attributes, which behave identically. You may find this useful if you want to change the generated schema without affecting Serde's behaviour, or if you're just not using Serde. + +{% include example_v0.md name="schemars_attrs" %} diff --git a/docs/_v0/examples/4-custom_settings.md b/docs/_v0/examples/4-custom_settings.md new file mode 100644 index 0000000..e439f50 --- /dev/null +++ b/docs/_v0/examples/4-custom_settings.md @@ -0,0 +1,12 @@ +--- +title: Custom Schema Settings +parent: Examples +nav_order: 4 +summary: Generating a schema using custom settings which changes how Option is handled. +--- + +# Custom Schema Settings + +The `gen` module allows you to customise how schemas are generated. For example, the default behaviour for `Option` is to include `null` in the schema's `type`s, but we can instead add a `nullable` property to its schema: + +{% include example_v0.md name="custom_settings" %} diff --git a/docs/_v0/examples/5-remote_derive.md b/docs/_v0/examples/5-remote_derive.md new file mode 100644 index 0000000..ee0adb6 --- /dev/null +++ b/docs/_v0/examples/5-remote_derive.md @@ -0,0 +1,16 @@ +--- +title: Derive for Remote Crate +parent: Examples +nav_order: 5 +summary: Deriving JsonSchema implementations for a type in somebody else's crate. +--- + +# Deriving JsonSchema for a Type in a Different Crate + +Rust's [orphan rule](https://doc.rust-lang.org/book/traits.html#rules-for-implementing-traits) requires that either the trait or the type for which you are implementing the trait must be defined in the same crate as the impl, so it is not possible to implement `JsonSchema` for a type in a different crate directly. + +To work around this, Schemars provides a way of deriving `JsonSchema` implementations for types in other people's crates. The only catch is that you have to provide a definition of the type for Schemars's derive to process. + +This is the same way that Serde allows remote deriving, which is why this page reads so similarly to [Serde's documentation](https://serde.rs/remote-derive.html)! + +{% include example_v0.md name="remote_derive" %} diff --git a/docs/_v0/examples/6-doc_comments.md b/docs/_v0/examples/6-doc_comments.md new file mode 100644 index 0000000..24de317 --- /dev/null +++ b/docs/_v0/examples/6-doc_comments.md @@ -0,0 +1,12 @@ +--- +title: Doc Comments +parent: Examples +nav_order: 6 +summary: Giving schemas a custom title and/or description using doc comments. +--- + +# Setting a Custom Title and/or Description Using Doc Comments + +If a struct, variant or field has any [doc comments](https://doc.rust-lang.org/stable/rust-by-example/meta/doc.html#doc-comments) (or [`doc` attributes](https://doc.rust-lang.org/rustdoc/the-doc-attribute.html)), then these will be used as the generated schema's `description`. If the first line is an ATX-style markdown heading (i.e. it begins with a # character), then it will be used as the schema's `title`, and the remaining lines will be the `description`. + +{% include example_v0.md name="doc_comments" %} diff --git a/docs/_v0/examples/7-custom_serialization.md b/docs/_v0/examples/7-custom_serialization.md new file mode 100644 index 0000000..499e670 --- /dev/null +++ b/docs/_v0/examples/7-custom_serialization.md @@ -0,0 +1,19 @@ +--- +title: Custom Serialization +parent: Examples +nav_order: 7 +summary: >- + If a field has a #[serde(with = "path")] attribute where "path" is not a type that implements JsonSchema, + then in order to derive JsonSchema on the type, it must also have a #[schemars(with = "Type")] attribute, + where "Type" implements JsonSchema. +--- + +# Deriving JsonSchema with Fields Using Custom Serialization + +Serde allows you to change how a field is (de)serialized by setting a [`#[serde(with = "path")]`](https://serde.rs/field-attrs.html#with) attribute, where `$path::serialize` and `$path::deserialize` must be functions with the correct signature. Schemars supports the same attribute, but `path` must be a type implementing `JsonSchema`. + +In order to derive `JsonSchema` on a type which includes a `#[serde(with = "path")]` attribute where `path` is not a type implementing `JsonSchema`, you'll need to override it with a suitable `#[schemars(with = "Type")]` or `#[schemars(schema_with = "path")]` attribute. + +{% include example_v0.md name="custom_serialization" %} + +Note that the `default` values in the schema are serialized as strings where appropriate. diff --git a/docs/_v0/examples/8-enum_repr.md b/docs/_v0/examples/8-enum_repr.md new file mode 100644 index 0000000..1cfcf81 --- /dev/null +++ b/docs/_v0/examples/8-enum_repr.md @@ -0,0 +1,13 @@ +--- +title: Serialize Enum as Number (serde_repr) +parent: Examples +nav_order: 8 +summary: >- + Generating a schema for with a C-like enum compatible with serde_repr. +--- + +# Serialize Enum as Number (serde_repr Compatibility) + +If you use the `#[repr(...)]` attribute on an enum to give it a C-like representation, then you may also want to use the [serde_repr](https://github.com/dtolnay/serde-repr) crate to serialize the enum values as numbers. In this case, you should use the corresponding `JsonSchema_repr` derive to ensure the schema for your type reflects how serde formats your type. + +{% include example_v0.md name="enum_repr" %} diff --git a/docs/_v0/examples/9-from_value.md b/docs/_v0/examples/9-from_value.md new file mode 100644 index 0000000..45198ff --- /dev/null +++ b/docs/_v0/examples/9-from_value.md @@ -0,0 +1,15 @@ +--- +title: Generate Schema from Example Value +parent: Examples +nav_order: 9 +summary: >- + Generating a schema for a serializable value. +--- + +# Generate Schema from Example Value + +If you want a schema for a type that can't/doesn't implement `JsonSchema`, but does implement [`serde::Serialize`](https://docs.serde.rs/serde/trait.Serialize.html), then you can generate a JSON schema from a value of that type. However, this schema will generally be less precise than if the type implemented `JsonSchema` - particularly when it involves enums, since schemars will not make any assumptions about the structure of an enum based on a single variant. + +{% include example_v0.md name="from_value" %} + +Note that the schema for the enum is not very useful in this case, since schemars doesn't know anything about the second variant. diff --git a/docs/_v0/index.md b/docs/_v0/index.md new file mode 100644 index 0000000..3b10e0d --- /dev/null +++ b/docs/_v0/index.md @@ -0,0 +1,20 @@ +--- +title: Overview +has_children: true +nav_order: 1 +permalink: /v0/ +--- + +# Schemars + +Schemars is a library to generate JSON Schema documents from Rust data structures. + +This is built on Rust's trait system - any type which implements the [`JsonSchema`](https://docs.rs/schemars/latest/schemars/trait.JsonSchema.html) trait can have a JSON Schema generated describing that type. Schemars implements this on many standard library types, and provides a derive macro to automatically implement it on custom types. + +One of the main aims of this library is compatibility with [Serde](https://github.com/serde-rs/serde). Any generated schema _should_ match how [serde_json](https://github.com/serde-rs/json) would serialize/deserialize to/from JSON. To support this, Schemars will check for any `#[serde(...)]` attributes on types that derive `JsonSchema`, and adjust the generated schema accordingly. + +## Basic Usage + +If you don't really care about the specifics, the easiest way to generate a JSON schema for your types is to `#[derive(JsonSchema)]` and use the `schema_for!` macro. All fields of the type must also implement `JsonSchema` - Schemars implements this for many standard library types. + +{% include example.md name="main" %} diff --git a/docs/docker-compose.yml b/docs/docker-compose.yml new file mode 100644 index 0000000..0afa5eb --- /dev/null +++ b/docs/docker-compose.yml @@ -0,0 +1,10 @@ +--- +services: + jekyll: + build: + context: . + volumes: + - ".:/docs" + working_dir: "/docs" + ports: + - 4000:4000 \ No newline at end of file diff --git a/docs/examples/1-derive_jsonschema.md b/docs/examples/1-derive_jsonschema.md index 3bf4548..efe245c 100644 --- a/docs/examples/1-derive_jsonschema.md +++ b/docs/examples/1-derive_jsonschema.md @@ -1,5 +1,4 @@ --- -layout: default title: Deriving JsonSchema parent: Examples nav_order: 1 diff --git a/docs/examples/2-serde_attrs.md b/docs/examples/2-serde_attrs.md index 0044a6e..79536e2 100644 --- a/docs/examples/2-serde_attrs.md +++ b/docs/examples/2-serde_attrs.md @@ -1,14 +1,13 @@ --- -layout: default title: Using Serde Attributes parent: Examples nav_order: 2 -summary: 'Deriving JsonSchema on types that use #[serde] attributes to customise serialization behaviour.' +summary: "Deriving JsonSchema on types that use #[serde] attributes to customise serialization behaviour." --- # Using Serde Attributes -One of the main aims of this library is compatibility with [Serde](https://github.com/serde-rs/serde). Any generated schema *should* match how [serde_json](https://github.com/serde-rs/json) would serialize/deserialize to/from JSON. To support this, Schemars will check for any `#[serde(...)]` attributes on types that derive `JsonSchema`, and adjust the generated schema accordingly. +One of the main aims of this library is compatibility with [Serde](https://github.com/serde-rs/serde). Any generated schema _should_ match how [serde_json](https://github.com/serde-rs/json) would serialize/deserialize to/from JSON. To support this, Schemars will check for any `#[serde(...)]` attributes on types that derive `JsonSchema`, and adjust the generated schema accordingly. The list of supported `#[serde]` attributes are [documented here]({{ site.baseurl }}{% link 1.1-attributes.md %}#supported-serde-attributes). diff --git a/docs/examples/3-schemars_attrs.md b/docs/examples/3-schemars_attrs.md index 5956a25..d3a2c08 100644 --- a/docs/examples/3-schemars_attrs.md +++ b/docs/examples/3-schemars_attrs.md @@ -1,9 +1,8 @@ --- -layout: default title: Using Schemars Attributes parent: Examples nav_order: 3 -summary: 'Deriving JsonSchema on types that use #[schemars] attributes to customise serialization behaviour.' +summary: "Deriving JsonSchema on types that use #[schemars] attributes to customise serialization behaviour." --- # Using Serde Attributes diff --git a/docs/examples/4-custom_settings.md b/docs/examples/4-custom_settings.md index e9482cc..7b85f65 100644 --- a/docs/examples/4-custom_settings.md +++ b/docs/examples/4-custom_settings.md @@ -1,5 +1,4 @@ --- -layout: default title: Custom Schema Settings parent: Examples nav_order: 4 diff --git a/docs/examples/5-remote_derive.md b/docs/examples/5-remote_derive.md index 93e99c1..fd7f74f 100644 --- a/docs/examples/5-remote_derive.md +++ b/docs/examples/5-remote_derive.md @@ -1,5 +1,4 @@ --- -layout: default title: Derive for Remote Crate parent: Examples nav_order: 5 diff --git a/docs/examples/6-doc_comments.md b/docs/examples/6-doc_comments.md index 9a5cdf9..66f4a49 100644 --- a/docs/examples/6-doc_comments.md +++ b/docs/examples/6-doc_comments.md @@ -1,5 +1,4 @@ --- -layout: default title: Doc Comments parent: Examples nav_order: 6 diff --git a/docs/examples/7-custom_serialization.md b/docs/examples/7-custom_serialization.md index 8caa930..b124ae7 100644 --- a/docs/examples/7-custom_serialization.md +++ b/docs/examples/7-custom_serialization.md @@ -1,5 +1,4 @@ --- -layout: default title: Custom Serialization parent: Examples nav_order: 7 @@ -13,7 +12,7 @@ summary: >- Serde allows you to change how a field is (de)serialized by setting a [`#[serde(with = "path")]`](https://serde.rs/field-attrs.html#with) attribute, where `$path::serialize` and `$path::deserialize` must be functions with the correct signature. Schemars supports the same attribute, but `path` must be a type implementing `JsonSchema`. -In order to derive `JsonSchema` on a type which includes a `#[serde(with = "path")]` attribute where `path` is not a type implementing `JsonSchema`, you'll need to override it with a suitable `#[schemars(with = "Type")]` or `#[schemars(schema_with = "path")]` attribute. +In order to derive `JsonSchema` on a type which includes a `#[serde(with = "path")]` attribute where `path` is not a type implementing `JsonSchema`, you'll need to override it with a suitable `#[schemars(with = "Type")]` or `#[schemars(schema_with = "path")]` attribute. {% include example.md name="custom_serialization" %} diff --git a/docs/examples/8-enum_repr.md b/docs/examples/8-enum_repr.md index 0312c29..9533cba 100644 --- a/docs/examples/8-enum_repr.md +++ b/docs/examples/8-enum_repr.md @@ -1,5 +1,4 @@ --- -layout: default title: Serialize Enum as Number (serde_repr) parent: Examples nav_order: 8 diff --git a/docs/examples/9-from_value.md b/docs/examples/9-from_value.md index b711be0..8922925 100644 --- a/docs/examples/9-from_value.md +++ b/docs/examples/9-from_value.md @@ -1,5 +1,4 @@ --- -layout: default title: Generate Schema from Example Value parent: Examples nav_order: 9 diff --git a/docs/index.md b/docs/index.md index 2868342..ad71e78 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,4 @@ --- -layout: default title: Overview nav_order: 1 --- @@ -10,7 +9,7 @@ Schemars is a library to generate JSON Schema documents from Rust data structure This is built on Rust's trait system - any type which implements the [`JsonSchema`](https://docs.rs/schemars/latest/schemars/trait.JsonSchema.html) trait can have a JSON Schema generated describing that type. Schemars implements this on many standard library types, and provides a derive macro to automatically implement it on custom types. -One of the main aims of this library is compatibility with [Serde](https://github.com/serde-rs/serde). Any generated schema *should* match how [serde_json](https://github.com/serde-rs/json) would serialize/deserialize to/from JSON. To support this, Schemars will check for any `#[serde(...)]` attributes on types that derive `JsonSchema`, and adjust the generated schema accordingly. +One of the main aims of this library is compatibility with [Serde](https://github.com/serde-rs/serde). Any generated schema _should_ match how [serde_json](https://github.com/serde-rs/json) would serialize/deserialize to/from JSON. To support this, Schemars will check for any `#[serde(...)]` attributes on types that derive `JsonSchema`, and adjust the generated schema accordingly. ## Basic Usage diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 264a5c5..055c010 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -3,7 +3,7 @@ name = "schemars" description = "Generate JSON Schemas from Rust code" homepage = "https://graham.cool/schemars/" repository = "https://github.com/GREsau/schemars" -version = "0.8.21" +version = "1.0.0-alpha.3" authors = ["Graham Esau "] edition = "2021" license = "MIT" @@ -14,118 +14,93 @@ build = "build.rs" rust-version = "1.60" [dependencies] -schemars_derive = { version = "=0.8.21", optional = true, path = "../schemars_derive" } -serde = { version = "1.0", features = ["derive"] } +schemars_derive = { version = "=1.0.0-alpha.3", optional = true, path = "../schemars_derive" } +serde = "1.0" serde_json = "1.0.25" dyn-clone = "1.0" +ref-cast = "1.0.22" -chrono = { version = "0.4", default-features = false, optional = true } -indexmap = { version = "1.2", features = ["serde-1"], optional = true } -indexmap2 = { version = "2.0", features = ["serde"], optional = true, package = "indexmap" } -either = { version = "1.3", default-features = false, optional = true } -uuid08 = { version = "0.8", default-features = false, optional = true, package = "uuid" } -uuid1 = { version = "1.0", default-features = false, optional = true, package = "uuid" } -smallvec = { version = "1.0", optional = true } -arrayvec05 = { version = "0.5", default-features = false, optional = true, package = "arrayvec" } +# optional dependencies arrayvec07 = { version = "0.7", default-features = false, optional = true, package = "arrayvec" } -url = { version = "2.0", default-features = false, optional = true } -bytes = { version = "1.0", optional = true } -rust_decimal = { version = "1", default-features = false, optional = true } -bigdecimal03 = { version = "0.3", default-features = false, optional = true, package = "bigdecimal" } bigdecimal04 = { version = "0.4", default-features = false, optional = true, package = "bigdecimal" } -enumset = { version = "1.0", optional = true } -smol_str = { version = "0.1.17", optional = true } -semver = { version = "1.0.9", features = ["serde"], optional = true } +bytes1 = { version = "1.0", default-features = false, optional = true, package = "bytes" } +chrono04 = { version = "0.4", default-features = false, optional = true, package = "chrono" } +either1 = { version = "1.3", default-features = false, optional = true, package = "either" } +enumset1 = { version = "1.0", default-features = false, optional = true, package = "enumset" } +indexmap2 = { version = "2.0", default-features = false, optional = true, package = "indexmap" } +rust_decimal1 = { version = "1", default-features = false, optional = true, package = "rust_decimal"} +semver1 = { version = "1.0.9", default-features = false, optional = true, package = "semver" } +smallvec1 = { version = "1.0", default-features = false, optional = true, package = "smallvec" } +smol_str02 = { version = "0.2.1", default-features = false, optional = true, package = "smol_str" } +url2 = { version = "2.0", default-features = false, optional = true, package = "url" } +uuid1 = { version = "1.0", default-features = false, optional = true, package = "uuid" } [dev-dependencies] pretty_assertions = "1.2.1" trybuild = "1.0" +serde = { version = "1.0", features = ["derive"] } [features] default = ["derive"] derive = ["schemars_derive"] - -# Use a different representation for the map type of Schemars. -# This allows data to be read into a Value and written back to a JSON string -# while preserving the order of map keys in the input. -preserve_order = ["indexmap"] - -impl_json_schema = ["derive"] -# derive_json_schema will be removed in a later version -derive_json_schema = ["impl_json_schema"] - -# `uuid` feature contains `uuid08` only for back-compat - will be changed to include uuid 1.0 instead in a later version -uuid = ["uuid08"] -# `arrayvec` feature without version suffix is included only for back-compat - will be removed in a later version -arrayvec = ["arrayvec05"] -indexmap1 = ["indexmap"] +preserve_order = ["serde_json/preserve_order"] raw_value = ["serde_json/raw_value"] -# `bigdecimal` feature without version suffix is included only for back-compat - will be removed in a later version -bigdecimal = ["bigdecimal03"] ui_test = [] -[[test]] -name = "chrono" -required-features = ["chrono"] - -[[test]] -name = "indexmap" -required-features = ["indexmap"] - -[[test]] -name = "indexmap2" -required-features = ["indexmap2"] - -[[test]] -name = "either" -required-features = ["either"] - -[[test]] -name = "uuid" -required-features = ["uuid08", "uuid1"] - -[[test]] -name = "smallvec" -required-features = ["smallvec"] - -[[test]] -name = "bytes" -required-features = ["bytes"] - -[[test]] -name = "arrayvec" -required-features = ["arrayvec05", "arrayvec07"] - -[[test]] -name = "schema_for_schema" -required-features = ["impl_json_schema"] - [[test]] name = "ui" required-features = ["ui_test"] +[[test]] +name = "chrono" +required-features = ["chrono04"] + +[[test]] +name = "indexmap" +required-features = ["indexmap2"] + +[[test]] +name = "either" +required-features = ["either1"] + +[[test]] +name = "uuid" +required-features = ["uuid1"] + +[[test]] +name = "smallvec" +required-features = ["smallvec1"] + +[[test]] +name = "bytes" +required-features = ["bytes1"] + +[[test]] +name = "arrayvec" +required-features = ["arrayvec07"] + [[test]] name = "url" -required-features = ["url"] +required-features = ["url2"] [[test]] name = "enumset" -required-features = ["enumset"] +required-features = ["enumset1"] [[test]] name = "smol_str" -required-features = ["smol_str"] +required-features = ["smol_str02"] [[test]] name = "semver" -required-features = ["semver"] +required-features = ["semver1"] [[test]] name = "decimal" -required-features = ["rust_decimal", "bigdecimal03", "bigdecimal04"] +required-features = ["rust_decimal1", "bigdecimal04"] [package.metadata.docs.rs] all-features = true diff --git a/schemars/examples/custom_serialization.rs b/schemars/examples/custom_serialization.rs index 53c78fa..c32203f 100644 --- a/schemars/examples/custom_serialization.rs +++ b/schemars/examples/custom_serialization.rs @@ -1,5 +1,4 @@ -use schemars::schema::{Schema, SchemaObject}; -use schemars::{gen::SchemaGenerator, schema_for, JsonSchema}; +use schemars::{schema_for, JsonSchema, Schema, SchemaGenerator}; use serde::{Deserialize, Serialize}; // `int_as_string` and `bool_as_string` use the schema for `String`. @@ -21,9 +20,11 @@ pub struct MyStruct { } fn make_custom_schema(gen: &mut SchemaGenerator) -> Schema { - let mut schema: SchemaObject = ::json_schema(gen).into(); - schema.format = Some("boolean".to_owned()); - schema.into() + let mut schema = String::json_schema(gen); + schema + .ensure_object() + .insert("format".into(), "boolean".into()); + schema } fn eight() -> i32 { diff --git a/schemars/examples/custom_serialization.schema.json b/schemars/examples/custom_serialization.schema.json index 42fda99..1aa0942 100644 --- a/schemars/examples/custom_serialization.schema.json +++ b/schemars/examples/custom_serialization.schema.json @@ -1,25 +1,25 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "properties": { "bool_as_string": { - "default": "false", "type": "string", - "format": "boolean" + "format": "boolean", + "default": "false" }, "bool_normal": { - "default": false, - "type": "boolean" + "type": "boolean", + "default": false }, "int_as_string": { - "default": "8", - "type": "string" + "type": "string", + "default": "8" }, "int_normal": { - "default": 8, "type": "integer", - "format": "int32" + "format": "int32", + "default": 8 } } } diff --git a/schemars/examples/custom_settings.schema.json b/schemars/examples/custom_settings.schema.json index 12ac7d5..8da4348 100644 --- a/schemars/examples/custom_settings.schema.json +++ b/schemars/examples/custom_settings.schema.json @@ -2,10 +2,6 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "MyStruct", "type": "object", - "required": [ - "my_bool", - "my_int" - ], "properties": { "my_bool": { "type": "boolean" @@ -23,32 +19,30 @@ "nullable": true } }, + "required": [ + "my_int", + "my_bool" + ], "definitions": { "MyEnum": { "oneOf": [ { "type": "object", - "required": [ - "StringNewType" - ], "properties": { "StringNewType": { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "StringNewType" + ] }, { "type": "object", - "required": [ - "StructVariant" - ], "properties": { "StructVariant": { "type": "object", - "required": [ - "floats" - ], "properties": { "floats": { "type": "array", @@ -57,10 +51,16 @@ "format": "float" } } - } + }, + "required": [ + "floats" + ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "StructVariant" + ] } ] } diff --git a/schemars/examples/doc_comments.schema.json b/schemars/examples/doc_comments.schema.json index 121cdb4..a3aa767 100644 --- a/schemars/examples/doc_comments.schema.json +++ b/schemars/examples/doc_comments.schema.json @@ -1,12 +1,8 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "My Amazing Struct", - "description": "This struct shows off generating a schema with a custom title and description.", + "description": "This struct shows off generating a schema with\n a custom title and description.", "type": "object", - "required": [ - "my_bool", - "my_int" - ], "properties": { "my_bool": { "description": "This bool has a description, but no title.", @@ -22,7 +18,7 @@ "description": "This enum might be set, or it might not.", "anyOf": [ { - "$ref": "#/definitions/MyEnum" + "$ref": "#/$defs/MyEnum" }, { "type": "null" @@ -30,35 +26,33 @@ ] } }, - "definitions": { + "required": [ + "my_int", + "my_bool" + ], + "$defs": { "MyEnum": { "title": "My Amazing Enum", "oneOf": [ { "description": "A wrapper around a `String`", "type": "object", - "required": [ - "StringNewType" - ], "properties": { "StringNewType": { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "StringNewType" + ] }, { - "description": "A struct-like enum variant which contains some floats", + "description": "A struct-like enum variant which contains\n some floats", "type": "object", - "required": [ - "StructVariant" - ], "properties": { "StructVariant": { "type": "object", - "required": [ - "floats" - ], "properties": { "floats": { "description": "The floats themselves", @@ -68,10 +62,16 @@ "format": "float" } } - } + }, + "required": [ + "floats" + ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "StructVariant" + ] } ] } diff --git a/schemars/examples/enum_repr.schema.json b/schemars/examples/enum_repr.schema.json index 04841b7..22d2233 100644 --- a/schemars/examples/enum_repr.schema.json +++ b/schemars/examples/enum_repr.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "SmallPrime", "type": "integer", "enum": [ diff --git a/schemars/examples/from_value.schema.json b/schemars/examples/from_value.schema.json index 4ba7735..64bad01 100644 --- a/schemars/examples/from_value.schema.json +++ b/schemars/examples/from_value.schema.json @@ -1,15 +1,6 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", - "examples": [ - { - "my_bool": true, - "my_int": 123, - "my_nullable_enum": { - "StringNewType": "foo" - } - } - ], "type": "object", "properties": { "my_bool": { @@ -19,5 +10,14 @@ "type": "integer" }, "my_nullable_enum": true - } + }, + "examples": [ + { + "my_bool": true, + "my_int": 123, + "my_nullable_enum": { + "StringNewType": "foo" + } + } + ] } diff --git a/schemars/examples/main.schema.json b/schemars/examples/main.schema.json index ddbd9d3..5eab909 100644 --- a/schemars/examples/main.schema.json +++ b/schemars/examples/main.schema.json @@ -1,11 +1,7 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", - "required": [ - "my_bool", - "my_int" - ], "properties": { "my_bool": { "type": "boolean" @@ -17,7 +13,7 @@ "my_nullable_enum": { "anyOf": [ { - "$ref": "#/definitions/MyEnum" + "$ref": "#/$defs/MyEnum" }, { "type": "null" @@ -25,32 +21,30 @@ ] } }, - "definitions": { + "required": [ + "my_int", + "my_bool" + ], + "$defs": { "MyEnum": { "oneOf": [ { "type": "object", - "required": [ - "StringNewType" - ], "properties": { "StringNewType": { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "StringNewType" + ] }, { "type": "object", - "required": [ - "StructVariant" - ], "properties": { "StructVariant": { "type": "object", - "required": [ - "floats" - ], "properties": { "floats": { "type": "array", @@ -59,10 +53,16 @@ "format": "float" } } - } + }, + "required": [ + "floats" + ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "StructVariant" + ] } ] } diff --git a/schemars/examples/remote_derive.schema.json b/schemars/examples/remote_derive.schema.json index e584177..df69751 100644 --- a/schemars/examples/remote_derive.schema.json +++ b/schemars/examples/remote_derive.schema.json @@ -1,12 +1,7 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Process", "type": "object", - "required": [ - "command_line", - "durations", - "wall_time" - ], "properties": { "command_line": { "type": "string" @@ -14,20 +9,21 @@ "durations": { "type": "array", "items": { - "$ref": "#/definitions/Duration" + "$ref": "#/$defs/Duration" } }, "wall_time": { - "$ref": "#/definitions/Duration" + "$ref": "#/$defs/Duration" } }, - "definitions": { + "required": [ + "command_line", + "wall_time", + "durations" + ], + "$defs": { "Duration": { "type": "object", - "required": [ - "nanos", - "secs" - ], "properties": { "nanos": { "type": "integer", @@ -37,7 +33,11 @@ "type": "integer", "format": "int64" } - } + }, + "required": [ + "secs", + "nanos" + ] } } } diff --git a/schemars/examples/schemars_attrs.rs b/schemars/examples/schemars_attrs.rs index 4ad2503..dc9e495 100644 --- a/schemars/examples/schemars_attrs.rs +++ b/schemars/examples/schemars_attrs.rs @@ -1,11 +1,11 @@ -use schemars::{schema_for, JsonSchema}; +use schemars::{schema_for, JsonSchema, Schema}; use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, JsonSchema)] -#[schemars(rename_all = "camelCase", deny_unknown_fields)] +#[schemars(rename_all = "camelCase", deny_unknown_fields, extend("x-customProperty" = "example"))] pub struct MyStruct { #[serde(rename = "thisIsOverridden")] - #[schemars(rename = "myNumber", range(min = 1, max = 10))] + #[schemars(rename = "myNumber", range(min = 1, max = 10), transform = remove_format)] pub my_int: i32, pub my_bool: bool, #[schemars(default)] @@ -24,6 +24,12 @@ pub enum MyEnum { }, } +fn remove_format(schema: &mut Schema) { + if let Some(obj) = schema.as_object_mut() { + obj.remove("format"); + } +} + fn main() { let schema = schema_for!(MyStruct); println!("{}", serde_json::to_string_pretty(&schema).unwrap()); diff --git a/schemars/examples/schemars_attrs.schema.json b/schemars/examples/schemars_attrs.schema.json index 9a6a22a..85efffd 100644 --- a/schemars/examples/schemars_attrs.schema.json +++ b/schemars/examples/schemars_attrs.schema.json @@ -1,32 +1,26 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", - "required": [ - "myBool", - "myNumber", - "myVecStr" - ], "properties": { "myBool": { "type": "boolean" }, "myNullableEnum": { - "default": null, "anyOf": [ { - "$ref": "#/definitions/MyEnum" + "$ref": "#/$defs/MyEnum" }, { "type": "null" } - ] + ], + "default": null }, "myNumber": { "type": "integer", - "format": "int32", - "maximum": 10.0, - "minimum": 1.0 + "maximum": 10, + "minimum": 1 }, "myVecStr": { "type": "array", @@ -37,7 +31,13 @@ } }, "additionalProperties": false, - "definitions": { + "required": [ + "myNumber", + "myBool", + "myVecStr" + ], + "x-customProperty": "example", + "$defs": { "MyEnum": { "anyOf": [ { @@ -46,9 +46,6 @@ }, { "type": "object", - "required": [ - "floats" - ], "properties": { "floats": { "type": "array", @@ -59,7 +56,10 @@ "maxItems": 100, "minItems": 1 } - } + }, + "required": [ + "floats" + ] } ] } diff --git a/schemars/examples/serde_attrs.schema.json b/schemars/examples/serde_attrs.schema.json index d044193..8179899 100644 --- a/schemars/examples/serde_attrs.schema.json +++ b/schemars/examples/serde_attrs.schema.json @@ -1,25 +1,21 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", - "required": [ - "myBool", - "myNumber" - ], "properties": { "myBool": { "type": "boolean" }, "myNullableEnum": { - "default": null, "anyOf": [ { - "$ref": "#/definitions/MyEnum" + "$ref": "#/$defs/MyEnum" }, { "type": "null" } - ] + ], + "default": null }, "myNumber": { "type": "integer", @@ -27,7 +23,11 @@ } }, "additionalProperties": false, - "definitions": { + "required": [ + "myNumber", + "myBool" + ], + "$defs": { "MyEnum": { "anyOf": [ { @@ -35,9 +35,6 @@ }, { "type": "object", - "required": [ - "floats" - ], "properties": { "floats": { "type": "array", @@ -46,7 +43,10 @@ "format": "float" } } - } + }, + "required": [ + "floats" + ] } ] } diff --git a/schemars/examples/validate.schema.json b/schemars/examples/validate.schema.json index 1e45a96..e9f8a1d 100644 --- a/schemars/examples/validate.schema.json +++ b/schemars/examples/validate.schema.json @@ -1,12 +1,7 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", - "required": [ - "my_bool", - "my_int", - "my_nullable_enum" - ], "properties": { "my_bool": { "type": "boolean" @@ -14,35 +9,29 @@ "my_int": { "type": "integer", "format": "int32", - "maximum": 10.0, - "minimum": 1.0 + "maximum": 10, + "minimum": 1 }, "my_nullable_enum": { "oneOf": [ { "type": "object", - "required": [ - "StringNewType" - ], "properties": { "StringNewType": { "type": "string", "format": "phone" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "StringNewType" + ] }, { "type": "object", - "required": [ - "StructVariant" - ], "properties": { "StructVariant": { "type": "object", - "required": [ - "floats" - ], "properties": { "floats": { "type": "array", @@ -53,12 +42,23 @@ "maxItems": 100, "minItems": 1 } - } + }, + "required": [ + "floats" + ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "StructVariant" + ] } ] } - } + }, + "required": [ + "my_int", + "my_bool", + "my_nullable_enum" + ] } diff --git a/schemars/src/_private.rs b/schemars/src/_private.rs index 5b03178..cdaec3a 100644 --- a/schemars/src/_private.rs +++ b/schemars/src/_private.rs @@ -1,8 +1,6 @@ -use crate::gen::SchemaGenerator; -use crate::schema::{InstanceType, ObjectValidation, Schema, SchemaObject}; -use crate::{JsonSchema, Map, Set}; +use crate::{JsonSchema, Schema, SchemaGenerator}; use serde::Serialize; -use serde_json::Value; +use serde_json::{json, map::Entry, Map, Value}; // Helper for generating schemas for flattened `Option` fields. pub fn json_schema_for_flatten( @@ -12,12 +10,8 @@ pub fn json_schema_for_flatten( let mut schema = T::_schemars_private_non_optional_json_schema(gen); if T::_schemars_private_is_option() && !required { - if let Schema::Object(SchemaObject { - object: Some(ref mut object_validation), - .. - }) = schema - { - object_validation.required.clear(); + if let Some(object) = schema.as_object_mut() { + object.remove("required"); } } @@ -55,117 +49,183 @@ impl MaybeSerializeWrapper { } } -/// Create a schema for a unit enum -pub fn new_unit_enum(variant: &str) -> Schema { - Schema::Object(SchemaObject { - instance_type: Some(InstanceType::String.into()), - enum_values: Some(vec![variant.into()]), - ..SchemaObject::default() +/// Create a schema for a unit enum variant +pub fn new_unit_enum_variant(variant: &str) -> Schema { + json_schema!({ + "type": "string", + "const": variant, }) } -/// Create a schema for an externally tagged enum -pub fn new_externally_tagged_enum(variant: &str, sub_schema: Schema) -> Schema { - Schema::Object(SchemaObject { - instance_type: Some(InstanceType::Object.into()), - object: Some(Box::new(ObjectValidation { - properties: { - let mut props = Map::new(); - props.insert(variant.to_owned(), sub_schema); - props - }, - required: { - let mut required = Set::new(); - required.insert(variant.to_owned()); - required - }, - // Externally tagged variants must prohibit additional - // properties irrespective of the disposition of - // `deny_unknown_fields`. If additional properties were allowed - // one could easily construct an object that validated against - // multiple variants since here it's the properties rather than - // the values of a property that distingish between variants. - additional_properties: Some(Box::new(false.into())), - ..Default::default() - })), - ..SchemaObject::default() +/// Create a schema for an externally tagged enum variant +pub fn new_externally_tagged_enum_variant(variant: &str, sub_schema: Schema) -> Schema { + json_schema!({ + "type": "object", + "properties": { + variant: sub_schema + }, + "required": [variant], + "additionalProperties": false, }) } -/// Create a schema for an internally tagged enum -pub fn new_internally_tagged_enum( +/// Update a schema for an internally tagged enum variant +pub fn apply_internal_enum_variant_tag( + schema: &mut Schema, tag_name: &str, variant: &str, deny_unknown_fields: bool, -) -> Schema { - let tag_schema = Schema::Object(SchemaObject { - instance_type: Some(InstanceType::String.into()), - enum_values: Some(vec![variant.into()]), - ..Default::default() - }); - Schema::Object(SchemaObject { - instance_type: Some(InstanceType::Object.into()), - object: Some(Box::new(ObjectValidation { - properties: { - let mut props = Map::new(); - props.insert(tag_name.to_owned(), tag_schema); - props - }, - required: { - let mut required = Set::new(); - required.insert(tag_name.to_owned()); - required - }, - additional_properties: deny_unknown_fields.then(|| Box::new(false.into())), - ..Default::default() - })), - ..SchemaObject::default() - }) +) { + let obj = schema.ensure_object(); + let is_unit = obj.get("type").and_then(|t| t.as_str()) == Some("null"); + + obj.insert("type".to_owned(), "object".into()); + + if let Some(properties) = obj + .entry("properties") + .or_insert(Value::Object(Map::new())) + .as_object_mut() + { + properties.insert( + tag_name.to_string(), + json!({ + "type": "string", + "const": variant + }), + ); + } + + if let Some(required) = obj + .entry("required") + .or_insert(Value::Array(Vec::new())) + .as_array_mut() + { + required.insert(0, tag_name.into()); + } + + if deny_unknown_fields && is_unit { + obj.entry("additionalProperties").or_insert(false.into()); + } } pub fn insert_object_property( - obj: &mut ObjectValidation, + schema: &mut Schema, key: &str, has_default: bool, required: bool, - schema: Schema, + sub_schema: Schema, ) { - obj.properties.insert(key.to_owned(), schema); + let obj = schema.ensure_object(); + if let Some(properties) = obj + .entry("properties") + .or_insert(Value::Object(Map::new())) + .as_object_mut() + { + properties.insert(key.to_owned(), sub_schema.into()); + } + if !has_default && (required || !T::_schemars_private_is_option()) { - obj.required.insert(key.to_owned()); + if let Some(req) = obj + .entry("required") + .or_insert(Value::Array(Vec::new())) + .as_array_mut() + { + req.push(key.into()); + } } } -pub mod metadata { - use crate::Schema; - use serde_json::Value; +pub fn insert_validation_property( + schema: &mut Schema, + required_type: &str, + key: &str, + value: impl Into, +) { + if schema.has_type(required_type) || (required_type == "number" && schema.has_type("integer")) { + schema.ensure_object().insert(key.to_owned(), value.into()); + } +} - macro_rules! add_metadata_fn { - ($method:ident, $name:ident, $ty:ty) => { - pub fn $method(schema: Schema, $name: impl Into<$ty>) -> Schema { - let value = $name.into(); - if value == <$ty>::default() { - schema - } else { - let mut schema_obj = schema.into_object(); - schema_obj.metadata().$name = value.into(); - Schema::Object(schema_obj) +pub fn append_required(schema: &mut Schema, key: &str) { + if schema.has_type("object") { + if let Value::Array(array) = schema + .ensure_object() + .entry("required") + .or_insert(Value::Array(Vec::new())) + { + let value = Value::from(key); + if !array.contains(&value) { + array.push(value); + } + } + } +} + +pub fn apply_inner_validation(schema: &mut Schema, f: fn(&mut Schema) -> ()) { + if let Some(inner_schema) = schema + .as_object_mut() + .and_then(|o| o.get_mut("items")) + .and_then(|i| <&mut Schema>::try_from(i).ok()) + { + f(inner_schema); + } +} + +pub fn flatten(schema: &mut Schema, other: Schema) { + if let Value::Object(obj2) = other.to_value() { + let obj1 = schema.ensure_object(); + + for (key, value2) in obj2 { + match obj1.entry(key) { + Entry::Vacant(vacant) => { + vacant.insert(value2); + } + Entry::Occupied(mut occupied) => { + match occupied.key().as_str() { + // This special "type" handling can probably be removed once the enum variant `with`/`schema_with` behaviour is fixed + "type" => match (occupied.get_mut(), value2) { + (Value::Array(a1), Value::Array(mut a2)) => { + a2.retain(|v2| !a1.contains(v2)); + a1.extend(a2); + } + (v1, Value::Array(mut a2)) => { + if !a2.contains(v1) { + a2.push(std::mem::take(v1)); + *occupied.get_mut() = Value::Array(a2); + } + } + (Value::Array(a1), v2) => { + if !a1.contains(&v2) { + a1.push(v2); + } + } + (v1, v2) => { + if v1 != &v2 { + *occupied.get_mut() = + Value::Array(vec![std::mem::take(v1), v2]); + } + } + }, + "required" => { + if let Value::Array(a1) = occupied.into_mut() { + if let Value::Array(a2) = value2 { + a1.extend(a2); + } + } + } + "properties" | "patternProperties" => { + if let Value::Object(o1) = occupied.into_mut() { + if let Value::Object(o2) = value2 { + o1.extend(o2); + } + } + } + _ => { + // leave the original value as it is (don't modify `schema`) + } + }; } } - }; - } - - add_metadata_fn!(add_description, description, String); - add_metadata_fn!(add_id, id, String); - add_metadata_fn!(add_title, title, String); - add_metadata_fn!(add_deprecated, deprecated, bool); - add_metadata_fn!(add_read_only, read_only, bool); - add_metadata_fn!(add_write_only, write_only, bool); - add_metadata_fn!(add_default, default, Option); - - pub fn add_examples>(schema: Schema, examples: I) -> Schema { - let mut schema_obj = schema.into_object(); - schema_obj.metadata().examples.extend(examples); - Schema::Object(schema_obj) + } } } diff --git a/schemars/src/flatten.rs b/schemars/src/flatten.rs deleted file mode 100644 index e55cb7f..0000000 --- a/schemars/src/flatten.rs +++ /dev/null @@ -1,180 +0,0 @@ -use crate::schema::*; -use crate::{Map, Set}; - -impl Schema { - /// This function is only public for use by schemars_derive. - /// - /// It should not be considered part of the public API. - #[doc(hidden)] - pub fn flatten(self, other: Self) -> Schema { - if is_null_type(&self) { - return other; - } else if is_null_type(&other) { - return self; - } - let s1: SchemaObject = self.into(); - let s2: SchemaObject = other.into(); - Schema::Object(s1.merge(s2)) - } -} - -pub(crate) trait Merge: Sized { - fn merge(self, other: Self) -> Self; -} - -macro_rules! impl_merge { - ($ty:ident { merge: $($merge_field:ident)*, or: $($or_field:ident)*, }) => { - impl Merge for $ty { - fn merge(self, other: Self) -> Self { - $ty { - $($merge_field: self.$merge_field.merge(other.$merge_field),)* - $($or_field: self.$or_field.or(other.$or_field),)* - } - } - } - }; - ($ty:ident { or: $($or_field:ident)*, }) => { - impl_merge!( $ty { merge: , or: $($or_field)*, }); - }; -} - -// For ObjectValidation::additional_properties. -impl Merge for Option> { - fn merge(self, other: Self) -> Self { - match (self.map(|x| *x), other.map(|x| *x)) { - // Perfer permissive schemas. - (Some(Schema::Bool(true)), _) => Some(Box::new(true.into())), - (_, Some(Schema::Bool(true))) => Some(Box::new(true.into())), - (None, _) => None, - (_, None) => None, - - // Merge if we have two non-trivial schemas. - (Some(Schema::Object(s1)), Some(Schema::Object(s2))) => { - Some(Box::new(Schema::Object(s1.merge(s2)))) - } - - // Perfer the more permissive schema. - (Some(s1 @ Schema::Object(_)), Some(Schema::Bool(false))) => Some(Box::new(s1)), - (Some(Schema::Bool(false)), Some(s2 @ Schema::Object(_))) => Some(Box::new(s2)), - - // Default to the null schema. - (Some(Schema::Bool(false)), Some(Schema::Bool(false))) => Some(Box::new(false.into())), - } - } -} - -impl_merge!(SchemaObject { - merge: extensions instance_type enum_values - metadata subschemas number string array object, - or: format const_value reference, -}); - -impl Merge for Metadata { - fn merge(self, other: Self) -> Self { - Metadata { - id: self.id.or(other.id), - title: self.title.or(other.title), - description: self.description.or(other.description), - default: self.default.or(other.default), - deprecated: self.deprecated || other.deprecated, - read_only: self.read_only || other.read_only, - write_only: self.write_only || other.write_only, - examples: self.examples.merge(other.examples), - } - } -} - -impl_merge!(SubschemaValidation { - or: all_of any_of one_of not if_schema then_schema else_schema, -}); - -impl_merge!(NumberValidation { - or: multiple_of maximum exclusive_maximum minimum exclusive_minimum, -}); - -impl_merge!(StringValidation { - or: max_length min_length pattern, -}); - -impl_merge!(ArrayValidation { - or: items additional_items max_items min_items unique_items contains, -}); - -impl_merge!(ObjectValidation { - merge: required properties pattern_properties additional_properties, - or: max_properties min_properties property_names, -}); - -impl Merge for Option { - fn merge(self, other: Self) -> Self { - match (self, other) { - (Some(x), Some(y)) => Some(x.merge(y)), - (None, y) => y, - (x, None) => x, - } - } -} - -impl Merge for Box { - fn merge(mut self, other: Self) -> Self { - *self = (*self).merge(*other); - self - } -} - -impl Merge for Vec { - fn merge(mut self, other: Self) -> Self { - self.extend(other); - self - } -} - -impl Merge for Map -where - K: std::hash::Hash + Eq + Ord, -{ - fn merge(mut self, other: Self) -> Self { - self.extend(other); - self - } -} - -impl Merge for Set { - fn merge(mut self, other: Self) -> Self { - self.extend(other); - self - } -} - -impl Merge for SingleOrVec { - fn merge(self, other: Self) -> Self { - if self == other { - return self; - } - let mut vec = match (self, other) { - (SingleOrVec::Vec(v1), SingleOrVec::Vec(v2)) => v1.merge(v2), - (SingleOrVec::Vec(mut v), SingleOrVec::Single(s)) - | (SingleOrVec::Single(s), SingleOrVec::Vec(mut v)) => { - v.push(*s); - v - } - (SingleOrVec::Single(s1), SingleOrVec::Single(s2)) => vec![*s1, *s2], - }; - vec.sort(); - vec.dedup(); - SingleOrVec::Vec(vec) - } -} - -fn is_null_type(schema: &Schema) -> bool { - let s = match schema { - Schema::Object(s) => s, - _ => return false, - }; - let instance_type = match &s.instance_type { - Some(SingleOrVec::Single(t)) => t, - _ => return false, - }; - - **instance_type == InstanceType::Null -} diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index a31839a..f6553eb 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -7,41 +7,45 @@ There are two main types in this module: * [`SchemaGenerator`], which manages the generation of a schema document. */ -use crate::schema::*; -use crate::{visit::*, JsonSchema, Map}; +use crate::Schema; +use crate::{transform::*, JsonSchema}; use dyn_clone::DynClone; use serde::Serialize; -use std::borrow::Cow; -use std::collections::HashMap; -use std::{any::Any, collections::HashSet, fmt::Debug}; +use serde_json::{Map, Value}; +use std::collections::{HashMap, HashSet}; +use std::{any::Any, fmt::Debug}; + +type CowStr = std::borrow::Cow<'static, str>; /// Settings to customize how Schemas are generated. /// -/// The default settings currently conform to [JSON Schema Draft 7](https://json-schema.org/specification-links.html#draft-7), but this is liable to change in a future version of Schemars if support for other JSON Schema versions is added. -/// If you require your generated schemas to conform to draft 7, consider using the [`draft07`](#method.draft07) method. +/// The default settings currently conform to [JSON Schema 2020-12](https://json-schema.org/specification-links#2020-12), but this is liable to change in a future version of Schemars if support for other JSON Schema versions is added. +/// If you rely on generated schemas conforming to draft 2020-12, consider using the [`SchemaSettings::draft2020_12()`] method. #[derive(Debug, Clone)] #[non_exhaustive] pub struct SchemaSettings { - /// If `true`, schemas for [`Option`](Option) will include a `nullable` property. + /// If `true`, schemas for [`Option`] will include a `nullable` property. /// /// This is not part of the JSON Schema spec, but is used in Swagger/OpenAPI schemas. /// /// Defaults to `false`. pub option_nullable: bool, - /// If `true`, schemas for [`Option`](Option) will have `null` added to their [`type`](../schema/struct.SchemaObject.html#structfield.instance_type). + /// If `true`, schemas for [`Option`] will have `null` added to their `type` property. /// /// Defaults to `true`. pub option_add_null_type: bool, /// A JSON pointer to the expected location of referenceable subschemas within the resulting root schema. /// - /// Defaults to `"#/definitions/"`. + /// A single leading `#` and/or single trailing `/` are ignored. + /// + /// Defaults to `"/$defs"`. pub definitions_path: String, /// The URI of the meta-schema describing the structure of the generated schemas. /// - /// Defaults to `"http://json-schema.org/draft-07/schema#"`. + /// Defaults to `"https://json-schema.org/draft/2020-12/schema"`. pub meta_schema: Option, - /// A list of visitors that get applied to all generated root schemas. - pub visitors: Vec>, + /// A list of [`Transform`]s that get applied to generated root schemas. + pub transforms: Vec>, /// Inline all subschemas instead of using references. /// /// Some references may still be generated in schemas for recursive types. @@ -51,54 +55,68 @@ pub struct SchemaSettings { } impl Default for SchemaSettings { + /// The default settings currently conform to [JSON Schema 2020-12](https://json-schema.org/specification-links#2020-12), but this is liable to change in a future version of Schemars if support for other JSON Schema versions is added. + /// If you rely on generated schemas conforming to draft 2020-12, consider using the [`SchemaSettings::draft2020_12()`] method. fn default() -> SchemaSettings { - SchemaSettings::draft07() + SchemaSettings::draft2020_12() } } impl SchemaSettings { - /// Creates `SchemaSettings` that conform to [JSON Schema Draft 7](https://json-schema.org/specification-links.html#draft-7). + /// Creates `SchemaSettings` that conform to [JSON Schema Draft 7](https://json-schema.org/specification-links#draft-7). pub fn draft07() -> SchemaSettings { SchemaSettings { option_nullable: false, option_add_null_type: true, - definitions_path: "#/definitions/".to_owned(), + definitions_path: "/definitions".to_owned(), meta_schema: Some("http://json-schema.org/draft-07/schema#".to_owned()), - visitors: vec![Box::new(RemoveRefSiblings)], + transforms: vec![Box::new(RemoveRefSiblings), Box::new(ReplacePrefixItems)], inline_subschemas: false, } } - /// Creates `SchemaSettings` that conform to [JSON Schema 2019-09](https://json-schema.org/specification-links.html#2019-09-formerly-known-as-draft-8). + /// Creates `SchemaSettings` that conform to [JSON Schema 2019-09](https://json-schema.org/specification-links#draft-2019-09-(formerly-known-as-draft-8)). pub fn draft2019_09() -> SchemaSettings { SchemaSettings { option_nullable: false, option_add_null_type: true, - definitions_path: "#/definitions/".to_owned(), + definitions_path: "/$defs".to_owned(), meta_schema: Some("https://json-schema.org/draft/2019-09/schema".to_owned()), - visitors: Vec::default(), + transforms: vec![Box::new(ReplacePrefixItems)], inline_subschemas: false, } } - /// Creates `SchemaSettings` that conform to [OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaObject). + /// Creates `SchemaSettings` that conform to [JSON Schema 2020-12](https://json-schema.org/specification-links#2020-12). + pub fn draft2020_12() -> SchemaSettings { + SchemaSettings { + option_nullable: false, + option_add_null_type: true, + definitions_path: "/$defs".to_owned(), + meta_schema: Some("https://json-schema.org/draft/2020-12/schema".to_owned()), + transforms: Vec::new(), + inline_subschemas: false, + } + } + + /// Creates `SchemaSettings` that conform to [OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schema). pub fn openapi3() -> SchemaSettings { SchemaSettings { option_nullable: true, option_add_null_type: false, - definitions_path: "#/components/schemas/".to_owned(), + definitions_path: "/components/schemas".to_owned(), meta_schema: Some( - "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema" + "https://spec.openapis.org/oas/3.0/schema/2021-09-28#/definitions/Schema" .to_owned(), ), - visitors: vec![ + transforms: vec![ Box::new(RemoveRefSiblings), Box::new(ReplaceBoolSchemas { skip_additional_properties: true, }), - Box::new(SetSingleExample { - retain_examples: false, - }), + Box::new(SetSingleExample), + Box::new(ReplaceConstValue), + Box::new(ReplacePrefixItems), ], inline_subschemas: false, } @@ -121,9 +139,9 @@ impl SchemaSettings { self } - /// Appends the given visitor to the list of [visitors](SchemaSettings::visitors) for these `SchemaSettings`. - pub fn with_visitor(mut self, visitor: impl Visitor + Debug + Clone + 'static) -> Self { - self.visitors.push(Box::new(visitor)); + /// Appends the given transform to the list of [transforms](SchemaSettings::transforms) for these `SchemaSettings`. + pub fn with_transform(mut self, transform: impl Transform + Clone + 'static + Send) -> Self { + self.transforms.push(Box::new(transform)); self } @@ -137,7 +155,7 @@ impl SchemaSettings { /// /// # Example /// ``` -/// use schemars::{JsonSchema, gen::SchemaGenerator}; +/// use schemars::{JsonSchema, SchemaGenerator}; /// /// #[derive(JsonSchema)] /// struct MyStruct { @@ -150,10 +168,10 @@ impl SchemaSettings { #[derive(Debug, Default)] pub struct SchemaGenerator { settings: SchemaSettings, - definitions: Map, - pending_schema_ids: HashSet>, - schema_id_to_name: HashMap, String>, - used_schema_names: HashSet, + definitions: Map, + pending_schema_ids: HashSet, + schema_id_to_name: HashMap, + used_schema_names: HashSet, } impl Clone for SchemaGenerator { @@ -187,7 +205,7 @@ impl SchemaGenerator { /// /// # Example /// ``` - /// use schemars::gen::SchemaGenerator; + /// use schemars::SchemaGenerator; /// /// let gen = SchemaGenerator::default(); /// let settings = gen.settings(); @@ -198,29 +216,16 @@ impl SchemaGenerator { &self.settings } - #[deprecated = "This method no longer has any effect."] - pub fn make_extensible(&self, _schema: &mut SchemaObject) {} - - #[deprecated = "Use `Schema::Bool(true)` instead"] - pub fn schema_for_any(&self) -> Schema { - Schema::Bool(true) - } - - #[deprecated = "Use `Schema::Bool(false)` instead"] - pub fn schema_for_none(&self) -> Schema { - Schema::Bool(false) - } - /// Generates a JSON Schema for the type `T`, and returns either the schema itself or a `$ref` schema referencing `T`'s schema. /// - /// If `T` is [referenceable](JsonSchema::is_referenceable), this will add `T`'s schema to this generator's definitions, and + /// If `T` is not [inlined](JsonSchema::always_inline_schema), this will add `T`'s schema to this generator's definitions, and /// return a `$ref` schema referencing that schema. Otherwise, this method behaves identically to [`JsonSchema::json_schema`]. /// - /// If `T`'s schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will + /// If `T`'s schema depends on any [non-inlined](JsonSchema::always_inline_schema) schemas, then this method will /// add them to the `SchemaGenerator`'s schema definitions. pub fn subschema_for(&mut self) -> Schema { let id = T::schema_id(); - let return_ref = T::is_referenceable() + let return_ref = !T::always_inline_schema() && (!self.settings.inline_subschemas || self.pending_schema_ids.contains(&id)); if return_ref { @@ -228,11 +233,11 @@ impl SchemaGenerator { Some(n) => n, None => { let base_name = T::schema_name(); - let mut name = String::new(); + let mut name = CowStr::Borrowed(""); - if self.used_schema_names.contains(&base_name) { + if self.used_schema_names.contains(base_name.as_ref()) { for i in 2.. { - name = format!("{}{}", base_name, i); + name = format!("{}{}", base_name, i).into(); if !self.used_schema_names.contains(&name) { break; } @@ -247,8 +252,8 @@ impl SchemaGenerator { } }; - let reference = format!("{}{}", self.settings.definitions_path, name); - if !self.definitions.contains_key(&name) { + let reference = format!("#{}/{}", self.definitions_path_stripped(), name); + if !self.definitions.contains_key(name.as_ref()) { self.insert_new_subschema_for::(name, id); } Schema::new_ref(reference) @@ -257,207 +262,165 @@ impl SchemaGenerator { } } - fn insert_new_subschema_for( - &mut self, - name: String, - id: Cow<'static, str>, - ) { - let dummy = Schema::Bool(false); + fn insert_new_subschema_for(&mut self, name: CowStr, id: CowStr) { + let dummy = false.into(); // insert into definitions BEFORE calling json_schema to avoid infinite recursion - self.definitions.insert(name.clone(), dummy); + self.definitions.insert(name.clone().into(), dummy); let schema = self.json_schema_internal::(id); - self.definitions.insert(name, schema); + self.definitions.insert(name.into(), schema.to_value()); } - /// Borrows the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated. + /// Borrows the collection of all [non-inlined](JsonSchema::always_inline_schema) schemas that have been generated. /// /// The keys of the returned `Map` are the [schema names](JsonSchema::schema_name), and the values are the schemas /// themselves. - pub fn definitions(&self) -> &Map { + pub fn definitions(&self) -> &Map { &self.definitions } - /// Mutably borrows the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated. + /// Mutably borrows the collection of all [non-inlined](JsonSchema::always_inline_schema) schemas that have been generated. /// /// The keys of the returned `Map` are the [schema names](JsonSchema::schema_name), and the values are the schemas /// themselves. - pub fn definitions_mut(&mut self) -> &mut Map { + pub fn definitions_mut(&mut self) -> &mut Map { &mut self.definitions } - /// Returns the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated, - /// leaving an empty map in its place. + /// Returns the collection of all [non-inlined](JsonSchema::always_inline_schema) schemas that have been generated, + /// leaving an empty `Map` in its place. /// /// The keys of the returned `Map` are the [schema names](JsonSchema::schema_name), and the values are the schemas /// themselves. - pub fn take_definitions(&mut self) -> Map { + pub fn take_definitions(&mut self) -> Map { std::mem::take(&mut self.definitions) } - /// Returns an iterator over the [visitors](SchemaSettings::visitors) being used by this `SchemaGenerator`. - pub fn visitors_mut(&mut self) -> impl Iterator { - self.settings.visitors.iter_mut().map(|v| v.as_mut()) + /// Returns an iterator over the [transforms](SchemaSettings::transforms) being used by this `SchemaGenerator`. + pub fn transforms_mut(&mut self) -> impl Iterator { + self.settings.transforms.iter_mut().map(|v| v.as_mut()) } - /// Generates a root JSON Schema for the type `T`. + /// Generates a JSON Schema for the type `T`. /// - /// If `T`'s schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will - /// add them to the `SchemaGenerator`'s schema definitions and include them in the returned `SchemaObject`'s - /// [`definitions`](../schema/struct.Metadata.html#structfield.definitions) - pub fn root_schema_for(&mut self) -> RootSchema { - let mut schema = self.json_schema_internal::(T::schema_id()).into_object(); - schema.metadata().title.get_or_insert_with(T::schema_name); - let mut root = RootSchema { - meta_schema: self.settings.meta_schema.clone(), - definitions: self.definitions.clone(), - schema, - }; + /// If `T`'s schema depends on any [non-inlined](JsonSchema::always_inline_schema) schemas, then this method will + /// include them in the returned `Schema` at the [definitions path](SchemaSettings::definitions_path) (by default `"$defs"`). + pub fn root_schema_for(&mut self) -> Schema { + let mut schema = self.json_schema_internal::(T::schema_id()); - for visitor in &mut self.settings.visitors { - visitor.visit_root_schema(&mut root) + let object = schema.ensure_object(); + + object + .entry("title") + .or_insert_with(|| T::schema_name().into()); + + if let Some(meta_schema) = self.settings.meta_schema.as_deref() { + object.insert("$schema".into(), meta_schema.into()); } - root + self.add_definitions(object, self.definitions.clone()); + self.apply_transforms(&mut schema); + + schema } - /// Consumes `self` and generates a root JSON Schema for the type `T`. + /// Consumes `self` and generates a JSON Schema for the type `T`. /// - /// If `T`'s schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will - /// include them in the returned `SchemaObject`'s [`definitions`](../schema/struct.Metadata.html#structfield.definitions) - pub fn into_root_schema_for(mut self) -> RootSchema { - let mut schema = self.json_schema_internal::(T::schema_id()).into_object(); - schema.metadata().title.get_or_insert_with(T::schema_name); - let mut root = RootSchema { - meta_schema: self.settings.meta_schema, - definitions: self.definitions, - schema, - }; + /// If `T`'s schema depends on any [non-inlined](JsonSchema::always_inline_schema) schemas, then this method will + /// include them in the returned `Schema` at the [definitions path](SchemaSettings::definitions_path) (by default `"$defs"`). + pub fn into_root_schema_for(mut self) -> Schema { + let mut schema = self.json_schema_internal::(T::schema_id()); - for visitor in &mut self.settings.visitors { - visitor.visit_root_schema(&mut root) + let object = schema.ensure_object(); + + object + .entry("title") + .or_insert_with(|| T::schema_name().into()); + + if let Some(meta_schema) = std::mem::take(&mut self.settings.meta_schema) { + object.insert("$schema".into(), meta_schema.into()); } - root + let definitions = self.take_definitions(); + self.add_definitions(object, definitions); + self.apply_transforms(&mut schema); + + schema } - /// Generates a root JSON Schema for the given example value. + /// Generates a JSON Schema for the given example value. /// /// If the value implements [`JsonSchema`](crate::JsonSchema), then prefer using the [`root_schema_for()`](Self::root_schema_for()) /// function which will generally produce a more precise schema, particularly when the value contains any enums. + /// + /// If the `Serialize` implementation of the value decides to fail, this will return an [`Err`]. pub fn root_schema_for_value( &mut self, value: &T, - ) -> Result { - let mut schema = value - .serialize(crate::ser::Serializer { - gen: self, - include_title: true, - })? - .into_object(); + ) -> Result { + let mut schema = value.serialize(crate::ser::Serializer { + gen: self, + include_title: true, + })?; + + let object = schema.ensure_object(); if let Ok(example) = serde_json::to_value(value) { - schema.metadata().examples.push(example); + object.insert("examples".into(), vec![example].into()); } - let mut root = RootSchema { - meta_schema: self.settings.meta_schema.clone(), - definitions: self.definitions.clone(), - schema, - }; - - for visitor in &mut self.settings.visitors { - visitor.visit_root_schema(&mut root) + if let Some(meta_schema) = self.settings.meta_schema.as_deref() { + object.insert("$schema".into(), meta_schema.into()); } - Ok(root) + self.add_definitions(object, self.definitions.clone()); + self.apply_transforms(&mut schema); + + Ok(schema) } - /// Consumes `self` and generates a root JSON Schema for the given example value. + /// Consumes `self` and generates a JSON Schema for the given example value. /// /// If the value implements [`JsonSchema`](crate::JsonSchema), then prefer using the [`into_root_schema_for()!`](Self::into_root_schema_for()) /// function which will generally produce a more precise schema, particularly when the value contains any enums. + /// + /// If the `Serialize` implementation of the value decides to fail, this will return an [`Err`]. pub fn into_root_schema_for_value( mut self, value: &T, - ) -> Result { - let mut schema = value - .serialize(crate::ser::Serializer { - gen: &mut self, - include_title: true, - })? - .into_object(); + ) -> Result { + let mut schema = value.serialize(crate::ser::Serializer { + gen: &mut self, + include_title: true, + })?; + + let object = schema.ensure_object(); if let Ok(example) = serde_json::to_value(value) { - schema.metadata().examples.push(example); + object.insert("examples".into(), vec![example].into()); } - let mut root = RootSchema { - meta_schema: self.settings.meta_schema, - definitions: self.definitions, - schema, - }; - - for visitor in &mut self.settings.visitors { - visitor.visit_root_schema(&mut root) + if let Some(meta_schema) = std::mem::take(&mut self.settings.meta_schema) { + object.insert("$schema".into(), meta_schema.into()); } - Ok(root) + let definitions = self.take_definitions(); + self.add_definitions(object, definitions); + self.apply_transforms(&mut schema); + + Ok(schema) } - /// Attemps to find the schema that the given `schema` is referencing. - /// - /// If the given `schema` has a [`$ref`](../schema/struct.SchemaObject.html#structfield.reference) property which refers - /// to another schema in `self`'s schema definitions, the referenced schema will be returned. Otherwise, returns `None`. - /// - /// # Example - /// ``` - /// use schemars::{JsonSchema, gen::SchemaGenerator}; - /// - /// #[derive(JsonSchema)] - /// struct MyStruct { - /// foo: i32, - /// } - /// - /// let mut gen = SchemaGenerator::default(); - /// let ref_schema = gen.subschema_for::(); - /// - /// assert!(ref_schema.is_ref()); - /// - /// let dereferenced = gen.dereference(&ref_schema); - /// - /// assert!(dereferenced.is_some()); - /// assert!(!dereferenced.unwrap().is_ref()); - /// assert_eq!(dereferenced, gen.definitions().get("MyStruct")); - /// ``` - pub fn dereference<'a>(&'a self, schema: &Schema) -> Option<&'a Schema> { - match schema { - Schema::Object(SchemaObject { - reference: Some(ref schema_ref), - .. - }) => { - let definitions_path = &self.settings().definitions_path; - if schema_ref.starts_with(definitions_path) { - let name = &schema_ref[definitions_path.len()..]; - self.definitions.get(name) - } else { - None - } - } - _ => None, - } - } - - fn json_schema_internal(&mut self, id: Cow<'static, str>) -> Schema { + fn json_schema_internal(&mut self, id: CowStr) -> Schema { struct PendingSchemaState<'a> { gen: &'a mut SchemaGenerator, - id: Cow<'static, str>, + id: CowStr, did_add: bool, } impl<'a> PendingSchemaState<'a> { - fn new(gen: &'a mut SchemaGenerator, id: Cow<'static, str>) -> Self { + fn new(gen: &'a mut SchemaGenerator, id: CowStr) -> Self { let did_add = gen.pending_schema_ids.insert(id.clone()); Self { gen, id, did_add } } @@ -474,41 +437,157 @@ impl SchemaGenerator { let pss = PendingSchemaState::new(self, id); T::json_schema(pss.gen) } + + fn add_definitions( + &mut self, + schema_object: &mut Map, + mut definitions: Map, + ) { + if definitions.is_empty() { + return; + } + + let pointer = self.definitions_path_stripped(); + let target = match json_pointer_mut(schema_object, pointer, true) { + Some(d) => d, + None => return, + }; + + target.append(&mut definitions); + } + + fn apply_transforms(&mut self, schema: &mut Schema) { + for transform in self.transforms_mut() { + transform.transform(schema); + } + } + + /// Returns `self.settings.definitions_path` as a plain JSON pointer to the definitions object, + /// i.e. without a leading '#' or trailing '/' + fn definitions_path_stripped(&self) -> &str { + let path = &self.settings.definitions_path; + let path = path.strip_prefix('#').unwrap_or(path); + path.strip_suffix('/').unwrap_or(path) + } } -/// A [Visitor](Visitor) which implements additional traits required to be included in a [SchemaSettings]. +fn json_pointer_mut<'a>( + mut object: &'a mut Map, + pointer: &str, + create_if_missing: bool, +) -> Option<&'a mut Map> { + let pointer = pointer.strip_prefix('/')?; + if pointer.is_empty() { + return Some(object); + } + + for mut segment in pointer.split('/') { + let replaced: String; + if segment.contains('~') { + replaced = segment.replace("~1", "/").replace("~0", "~"); + segment = &replaced; + } + + use serde_json::map::Entry; + let next_value = match object.entry(segment) { + Entry::Occupied(o) => o.into_mut(), + Entry::Vacant(v) if create_if_missing => v.insert(Value::Object(Map::default())), + Entry::Vacant(_) => return None, + }; + + object = next_value.as_object_mut()?; + } + + Some(object) +} + +/// A [Transform] which implements additional traits required to be included in a [SchemaSettings]. /// /// You will rarely need to use this trait directly as it is automatically implemented for any type which implements all of: -/// - [`Visitor`] -/// - [`std::fmt::Debug`] +/// - [`Transform`] /// - [`std::any::Any`] (implemented for all `'static` types) /// - [`std::clone::Clone`] +/// - [`std::marker::Send`] /// /// # Example /// ``` -/// use schemars::visit::Visitor; -/// use schemars::gen::GenVisitor; +/// use schemars::transform::Transform; +/// use schemars::gen::GenTransform; /// /// #[derive(Debug, Clone)] -/// struct MyVisitor; +/// struct MyTransform; /// -/// impl Visitor for MyVisitor { } +/// impl Transform for MyTransform { +/// fn transform(&mut self, schema: &mut schemars::Schema) { +/// todo!() +/// } +/// } /// -/// let v: &dyn GenVisitor = &MyVisitor; -/// assert!(v.as_any().is::()); +/// let v: &dyn GenTransform = &MyTransform; +/// assert!(v.as_any().is::()); /// ``` -pub trait GenVisitor: Visitor + Debug + DynClone + Any { - /// Upcasts this visitor into an `Any`, which can be used to inspect and manipulate it as its concrete type. +pub trait GenTransform: Transform + DynClone + Any + Send { + /// Upcasts this transform into an [`Any`], which can be used to inspect and manipulate it as its concrete type. + /// + /// # Example + /// To remove a specific transform from an instance of `SchemaSettings`: + /// ``` + /// use schemars::gen::SchemaSettings; + /// use schemars::transform::ReplaceBoolSchemas; + /// + /// let mut settings = SchemaSettings::openapi3(); + /// let original_len = settings.transforms.len(); + /// + /// settings + /// .transforms + /// .retain(|t| !t.as_any().is::()); + /// + /// assert_eq!(settings.transforms.len(), original_len - 1); + /// ``` fn as_any(&self) -> &dyn Any; + + /// Mutably upcasts this transform into an [`Any`], which can be used to inspect and manipulate it as its concrete type. + /// + /// # Example + /// To modify a specific transform in an instance of `SchemaSettings`: + /// ``` + /// use schemars::gen::SchemaSettings; + /// use schemars::transform::ReplaceBoolSchemas; + /// + /// let mut settings = SchemaSettings::openapi3(); + /// for t in &mut settings.transforms { + /// if let Some(replace_bool_schemas) = t.as_any_mut().downcast_mut::() { + /// replace_bool_schemas.skip_additional_properties = false; + /// } + /// } + /// ``` + fn as_any_mut(&mut self) -> &mut dyn Any; } -dyn_clone::clone_trait_object!(GenVisitor); +dyn_clone::clone_trait_object!(GenTransform); -impl GenVisitor for T +impl GenTransform for T where - T: Visitor + Debug + Clone + Any, + T: Transform + Clone + Any + Send, { fn as_any(&self) -> &dyn Any { self } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} + +impl Debug for Box { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self._debug_type_name(f) + } +} + +fn _assert_send() { + fn _assert() {} + + _assert::(); + _assert::(); } diff --git a/schemars/src/json_schema_impls/array.rs b/schemars/src/json_schema_impls/array.rs index 036d042..7dccbfb 100644 --- a/schemars/src/json_schema_impls/array.rs +++ b/schemars/src/json_schema_impls/array.rs @@ -1,30 +1,24 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; +use crate::{json_schema, JsonSchema, Schema}; use std::borrow::Cow; // Does not require T: JsonSchema. impl JsonSchema for [T; 0] { - no_ref_schema!(); + always_inline!(); - fn schema_name() -> String { - "EmptyArray".to_owned() + fn schema_name() -> Cow<'static, str> { + "EmptyArray".into() } fn schema_id() -> Cow<'static, str> { - Cow::Borrowed("[]") + "[]".into() } fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::Array.into()), - array: Some(Box::new(ArrayValidation { - max_items: Some(0), - ..Default::default() - })), - ..Default::default() - } - .into() + json_schema!({ + "type": "array", + "maxItems": 0, + }) } } @@ -32,29 +26,23 @@ macro_rules! array_impls { ($($len:tt)+) => { $( impl JsonSchema for [T; $len] { - no_ref_schema!(); + always_inline!(); - fn schema_name() -> String { - format!("Array_size_{}_of_{}", $len, T::schema_name()) + fn schema_name() -> Cow<'static, str> { + format!("Array_size_{}_of_{}", $len, T::schema_name()).into() } fn schema_id() -> Cow<'static, str> { - Cow::Owned( - format!("[{}; {}]", $len, T::schema_id())) + format!("[{}; {}]", $len, T::schema_id()).into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::Array.into()), - array: Some(Box::new(ArrayValidation { - items: Some(gen.subschema_for::().into()), - max_items: Some($len), - min_items: Some($len), - ..Default::default() - })), - ..Default::default() - } - .into() + json_schema!({ + "type": "array", + "items": serde_json::Value::from(gen.subschema_for::()), + "minItems": $len, + "maxItems": $len, + }) } } )+ @@ -67,40 +55,3 @@ array_impls! { 21 22 23 24 25 26 27 28 29 30 31 32 } - -#[cfg(test)] -mod tests { - use super::*; - use crate::tests::{schema_for, schema_object_for}; - use pretty_assertions::assert_eq; - - #[test] - fn schema_for_array() { - let schema = schema_object_for::<[i32; 8]>(); - assert_eq!( - schema.instance_type, - Some(SingleOrVec::from(InstanceType::Array)) - ); - let array_validation = schema.array.unwrap(); - assert_eq!( - array_validation.items, - Some(SingleOrVec::from(schema_for::())) - ); - assert_eq!(array_validation.max_items, Some(8)); - assert_eq!(array_validation.min_items, Some(8)); - } - - // SomeStruct does not implement JsonSchema - struct SomeStruct; - - #[test] - fn schema_for_empty_array() { - let schema = schema_object_for::<[SomeStruct; 0]>(); - assert_eq!( - schema.instance_type, - Some(SingleOrVec::from(InstanceType::Array)) - ); - let array_validation = schema.array.unwrap(); - assert_eq!(array_validation.max_items, Some(0)); - } -} diff --git a/schemars/src/json_schema_impls/arrayvec05.rs b/schemars/src/json_schema_impls/arrayvec05.rs deleted file mode 100644 index 281bde7..0000000 --- a/schemars/src/json_schema_impls/arrayvec05.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use arrayvec05::{Array, ArrayString, ArrayVec}; -use std::convert::TryInto; - -// Do not set maxLength on the schema as that describes length in characters, but we only -// know max length in bytes. -forward_impl!(( JsonSchema for ArrayString where A: Array + Copy) => String); - -impl JsonSchema for ArrayVec -where - A::Item: JsonSchema, -{ - no_ref_schema!(); - - fn schema_name() -> String { - format!( - "Array_up_to_size_{}_of_{}", - A::CAPACITY, - A::Item::schema_name() - ) - } - - fn json_schema(gen: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::Array.into()), - array: Some(Box::new(ArrayValidation { - items: Some(gen.subschema_for::().into()), - max_items: A::CAPACITY.try_into().ok(), - ..Default::default() - })), - ..Default::default() - } - .into() - } -} diff --git a/schemars/src/json_schema_impls/arrayvec07.rs b/schemars/src/json_schema_impls/arrayvec07.rs index e2d92c5..cd5f154 100644 --- a/schemars/src/json_schema_impls/arrayvec07.rs +++ b/schemars/src/json_schema_impls/arrayvec07.rs @@ -1,8 +1,6 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; +use crate::{json_schema, JsonSchema, Schema}; use arrayvec07::{ArrayString, ArrayVec}; -use std::convert::TryInto; // Do not set maxLength on the schema as that describes length in characters, but we only // know max length in bytes. @@ -12,22 +10,17 @@ impl JsonSchema for ArrayVec where T: JsonSchema, { - no_ref_schema!(); + always_inline!(); - fn schema_name() -> String { - format!("Array_up_to_size_{}_of_{}", CAP, T::schema_name()) + fn schema_name() -> std::borrow::Cow<'static, str> { + format!("Array_up_to_size_{}_of_{}", CAP, T::schema_name()).into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::Array.into()), - array: Some(Box::new(ArrayValidation { - items: Some(gen.subschema_for::().into()), - max_items: CAP.try_into().ok(), - ..Default::default() - })), - ..Default::default() - } - .into() + json_schema!({ + "type": "array", + "items": gen.subschema_for::(), + "maxItems": CAP + }) } } diff --git a/schemars/src/json_schema_impls/atomic.rs b/schemars/src/json_schema_impls/atomic.rs index 5d8f6d9..53d55c6 100644 --- a/schemars/src/json_schema_impls/atomic.rs +++ b/schemars/src/json_schema_impls/atomic.rs @@ -1,6 +1,3 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; use std::sync::atomic::*; forward_impl!(AtomicBool => bool); @@ -22,12 +19,12 @@ forward_impl!(AtomicUsize => usize); #[cfg(test)] mod tests { use super::*; - use crate::tests::schema_object_for; + use crate::schema_for; use pretty_assertions::assert_eq; #[test] fn schema_for_atomics() { - let atomic_schema = schema_object_for::<( + let atomic_schema = schema_for!(( AtomicBool, AtomicI8, AtomicI16, @@ -39,9 +36,8 @@ mod tests { AtomicU32, AtomicU64, AtomicUsize, - )>(); - let basic_schema = - schema_object_for::<(bool, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize)>(); + )); + let basic_schema = schema_for!((bool, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize)); assert_eq!(atomic_schema, basic_schema); } } diff --git a/schemars/src/json_schema_impls/bytes.rs b/schemars/src/json_schema_impls/bytes.rs deleted file mode 100644 index f1a0f29..0000000 --- a/schemars/src/json_schema_impls/bytes.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use bytes::{Bytes, BytesMut}; - -forward_impl!((JsonSchema for Bytes) => Vec); -forward_impl!((JsonSchema for BytesMut) => Vec); diff --git a/schemars/src/json_schema_impls/chrono.rs b/schemars/src/json_schema_impls/chrono.rs deleted file mode 100644 index 3a1731b..0000000 --- a/schemars/src/json_schema_impls/chrono.rs +++ /dev/null @@ -1,68 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use chrono::prelude::*; -use serde_json::json; -use std::borrow::Cow; - -impl JsonSchema for Weekday { - no_ref_schema!(); - - fn schema_name() -> String { - "Weekday".to_owned() - } - - fn schema_id() -> Cow<'static, str> { - Cow::Borrowed("chrono::Weekday") - } - - fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::String.into()), - enum_values: Some(vec![ - json!("Mon"), - json!("Tue"), - json!("Wed"), - json!("Thu"), - json!("Fri"), - json!("Sat"), - json!("Sun"), - ]), - ..Default::default() - } - .into() - } -} - -macro_rules! formatted_string_impl { - ($ty:ident, $format:literal) => { - formatted_string_impl!($ty, $format, JsonSchema for $ty); - }; - ($ty:ident, $format:literal, $($desc:tt)+) => { - impl $($desc)+ { - no_ref_schema!(); - - fn schema_name() -> String { - stringify!($ty).to_owned() - } - - fn schema_id() -> Cow<'static, str> { - Cow::Borrowed(stringify!(chrono::$ty)) - } - - fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::String.into()), - format: Some($format.to_owned()), - ..Default::default() - } - .into() - } - } - }; -} - -formatted_string_impl!(NaiveDate, "date"); -formatted_string_impl!(NaiveDateTime, "partial-date-time"); -formatted_string_impl!(NaiveTime, "partial-date-time"); -formatted_string_impl!(DateTime, "date-time", JsonSchema for DateTime); diff --git a/schemars/src/json_schema_impls/chrono04.rs b/schemars/src/json_schema_impls/chrono04.rs new file mode 100644 index 0000000..2635e56 --- /dev/null +++ b/schemars/src/json_schema_impls/chrono04.rs @@ -0,0 +1,62 @@ +use crate::gen::SchemaGenerator; +use crate::{json_schema, JsonSchema, Schema}; +use chrono04::prelude::*; +use std::borrow::Cow; + +impl JsonSchema for Weekday { + always_inline!(); + + fn schema_name() -> Cow<'static, str> { + "Weekday".into() + } + + fn schema_id() -> Cow<'static, str> { + "chrono::Weekday".into() + } + + fn json_schema(_: &mut SchemaGenerator) -> Schema { + json_schema!({ + "type": "string", + "enum": [ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + ] + }) + } +} + +macro_rules! formatted_string_impl { + ($ty:ident, $format:literal) => { + formatted_string_impl!($ty, $format, JsonSchema for $ty); + }; + ($ty:ident, $format:literal, $($desc:tt)+) => { + impl $($desc)+ { + always_inline!(); + + fn schema_name() -> Cow<'static, str> { + stringify!($ty).into() + } + + fn schema_id() -> Cow<'static, str> { + stringify!(chrono::$ty).into() + } + + fn json_schema(_: &mut SchemaGenerator) -> Schema { + json_schema!({ + "type": "string", + "format": $format + }) + } + } + }; +} + +formatted_string_impl!(NaiveDate, "date"); +formatted_string_impl!(NaiveDateTime, "partial-date-time"); +formatted_string_impl!(NaiveTime, "partial-date-time"); +formatted_string_impl!(DateTime, "date-time", JsonSchema for DateTime); diff --git a/schemars/src/json_schema_impls/core.rs b/schemars/src/json_schema_impls/core.rs index 955ead6..d49f392 100644 --- a/schemars/src/json_schema_impls/core.rs +++ b/schemars/src/json_schema_impls/core.rs @@ -1,52 +1,61 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use serde_json::json; +use crate::{json_schema, JsonSchema, Schema}; +use serde_json::Value; use std::borrow::Cow; use std::ops::{Bound, Range, RangeInclusive}; impl JsonSchema for Option { - no_ref_schema!(); + always_inline!(); - fn schema_name() -> String { - format!("Nullable_{}", T::schema_name()) + fn schema_name() -> Cow<'static, str> { + format!("Nullable_{}", T::schema_name()).into() } fn schema_id() -> Cow<'static, str> { - Cow::Owned(format!("Option<{}>", T::schema_id())) + format!("Option<{}>", T::schema_id()).into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { let mut schema = gen.subschema_for::(); + if gen.settings().option_add_null_type { - schema = match schema { - Schema::Bool(true) => Schema::Bool(true), - Schema::Bool(false) => <()>::json_schema(gen), - Schema::Object(SchemaObject { - instance_type: Some(ref mut instance_type), - .. - }) => { - add_null_type(instance_type); - schema + schema = match schema.try_to_object() { + Ok(mut obj) => { + let instance_type = obj.get_mut("type"); + match instance_type { + Some(Value::Array(array)) => { + let null = Value::from("null"); + if !array.contains(&null) { + array.push(null); + } + obj.into() + } + Some(Value::String(string)) => { + if string != "null" { + *instance_type.unwrap() = + Value::Array(vec![std::mem::take(string).into(), "null".into()]) + } + obj.into() + } + _ => json_schema!({ + "anyOf": [ + obj, + <()>::json_schema(gen) + ] + }), + } } - schema => SchemaObject { - // TODO technically the schema already accepts null, so this may be unnecessary - subschemas: Some(Box::new(SubschemaValidation { - any_of: Some(vec![schema, <()>::json_schema(gen)]), - ..Default::default() - })), - ..Default::default() - } - .into(), + Err(true) => true.into(), + Err(false) => <()>::json_schema(gen), } } + if gen.settings().option_nullable { - let mut schema_obj = schema.into_object(); - schema_obj - .extensions - .insert("nullable".to_owned(), json!(true)); - schema = Schema::Object(schema_obj); + schema + .ensure_object() + .insert("nullable".into(), true.into()); }; + schema } @@ -59,116 +68,91 @@ impl JsonSchema for Option { } } -fn add_null_type(instance_type: &mut SingleOrVec) { - match instance_type { - SingleOrVec::Single(ty) if **ty != InstanceType::Null => { - *instance_type = vec![**ty, InstanceType::Null].into() - } - SingleOrVec::Vec(ty) if !ty.contains(&InstanceType::Null) => ty.push(InstanceType::Null), - _ => {} - }; -} - impl JsonSchema for Result { - fn schema_name() -> String { - format!("Result_of_{}_or_{}", T::schema_name(), E::schema_name()) + fn schema_name() -> Cow<'static, str> { + format!("Result_of_{}_or_{}", T::schema_name(), E::schema_name()).into() } fn schema_id() -> Cow<'static, str> { - Cow::Owned(format!("Result<{}, {}>", T::schema_id(), E::schema_id())) + format!("Result<{}, {}>", T::schema_id(), E::schema_id()).into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { - let mut ok_schema = SchemaObject { - instance_type: Some(InstanceType::Object.into()), - ..Default::default() - }; - let obj = ok_schema.object(); - obj.required.insert("Ok".to_owned()); - obj.properties - .insert("Ok".to_owned(), gen.subschema_for::()); - - let mut err_schema = SchemaObject { - instance_type: Some(InstanceType::Object.into()), - ..Default::default() - }; - let obj = err_schema.object(); - obj.required.insert("Err".to_owned()); - obj.properties - .insert("Err".to_owned(), gen.subschema_for::()); - - let mut schema = SchemaObject::default(); - schema.subschemas().one_of = Some(vec![ok_schema.into(), err_schema.into()]); - schema.into() + json_schema!({ + "oneOf": [ + { + "type": "object", + "properties": { + "Ok": gen.subschema_for::() + }, + "required": ["Ok"] + }, + { + "type": "object", + "properties": { + "Err": gen.subschema_for::() + }, + "required": ["Err"] + } + ] + }) } } impl JsonSchema for Bound { - fn schema_name() -> String { - format!("Bound_of_{}", T::schema_name()) + fn schema_name() -> Cow<'static, str> { + format!("Bound_of_{}", T::schema_name()).into() } fn schema_id() -> Cow<'static, str> { - Cow::Owned(format!("Bound<{}>", T::schema_id())) + format!("Bound<{}>", T::schema_id()).into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { - let mut included_schema = SchemaObject { - instance_type: Some(InstanceType::Object.into()), - ..Default::default() - }; - let obj = included_schema.object(); - obj.required.insert("Included".to_owned()); - obj.properties - .insert("Included".to_owned(), gen.subschema_for::()); - - let mut excluded_schema = SchemaObject { - instance_type: Some(InstanceType::Object.into()), - ..Default::default() - }; - let obj = excluded_schema.object(); - obj.required.insert("Excluded".to_owned()); - obj.properties - .insert("Excluded".to_owned(), gen.subschema_for::()); - - let unbounded_schema = SchemaObject { - instance_type: Some(InstanceType::String.into()), - const_value: Some(json!("Unbounded")), - ..Default::default() - }; - - let mut schema = SchemaObject::default(); - schema.subschemas().one_of = Some(vec![ - included_schema.into(), - excluded_schema.into(), - unbounded_schema.into(), - ]); - schema.into() + json_schema!({ + "oneOf": [ + { + "type": "object", + "properties": { + "Included": gen.subschema_for::() + }, + "required": ["Included"] + }, + { + "type": "object", + "properties": { + "Excluded": gen.subschema_for::() + }, + "required": ["Excluded"] + }, + { + "type": "string", + "const": "Unbounded" + } + ] + }) } } impl JsonSchema for Range { - fn schema_name() -> String { - format!("Range_of_{}", T::schema_name()) + fn schema_name() -> Cow<'static, str> { + format!("Range_of_{}", T::schema_name()).into() } fn schema_id() -> Cow<'static, str> { - Cow::Owned(format!("Range<{}>", T::schema_id())) + format!("Range<{}>", T::schema_id()).into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { - let mut schema = SchemaObject { - instance_type: Some(InstanceType::Object.into()), - ..Default::default() - }; - let obj = schema.object(); - obj.required.insert("start".to_owned()); - obj.required.insert("end".to_owned()); - obj.properties - .insert("start".to_owned(), gen.subschema_for::()); - obj.properties - .insert("end".to_owned(), gen.subschema_for::()); - schema.into() + let subschema = gen.subschema_for::(); + json_schema!({ + "type": "object", + "properties": { + "start": subschema, + "end": subschema + }, + "required": ["start", "end"] + }) } } @@ -177,54 +161,3 @@ forward_impl!(( JsonSchema for RangeInclusive) => Range); forward_impl!(( JsonSchema for std::marker::PhantomData) => ()); forward_impl!((<'a> JsonSchema for std::fmt::Arguments<'a>) => String); - -#[cfg(test)] -mod tests { - use super::*; - use crate::tests::{schema_for, schema_object_for}; - use pretty_assertions::assert_eq; - - #[test] - fn schema_for_option() { - let schema = schema_object_for::>(); - assert_eq!( - schema.instance_type, - Some(vec![InstanceType::Integer, InstanceType::Null].into()) - ); - assert_eq!(schema.extensions.get("nullable"), None); - assert_eq!(schema.subschemas.is_none(), true); - } - - #[test] - fn schema_for_option_with_ref() { - use crate as schemars; - #[derive(JsonSchema)] - struct Foo; - - let schema = schema_object_for::>(); - assert_eq!(schema.instance_type, None); - assert_eq!(schema.extensions.get("nullable"), None); - assert_eq!(schema.subschemas.is_some(), true); - let any_of = schema.subschemas.unwrap().any_of.unwrap(); - assert_eq!(any_of.len(), 2); - assert_eq!(any_of[0], Schema::new_ref("#/definitions/Foo".to_string())); - assert_eq!(any_of[1], schema_for::<()>()); - } - - #[test] - fn schema_for_result() { - let schema = schema_object_for::>(); - let one_of = schema.subschemas.unwrap().one_of.unwrap(); - assert_eq!(one_of.len(), 2); - - let ok_schema: SchemaObject = one_of[0].clone().into(); - let obj = ok_schema.object.unwrap(); - assert!(obj.required.contains("Ok")); - assert_eq!(obj.properties["Ok"], schema_for::()); - - let err_schema: SchemaObject = one_of[1].clone().into(); - let obj = err_schema.object.unwrap(); - assert!(obj.required.contains("Err")); - assert_eq!(obj.properties["Err"], schema_for::()); - } -} diff --git a/schemars/src/json_schema_impls/decimal.rs b/schemars/src/json_schema_impls/decimal.rs index 643ca44..3266ccc 100644 --- a/schemars/src/json_schema_impls/decimal.rs +++ b/schemars/src/json_schema_impls/decimal.rs @@ -1,39 +1,27 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; +use crate::{json_schema, JsonSchema, Schema}; use std::borrow::Cow; macro_rules! decimal_impl { ($type:ty) => { impl JsonSchema for $type { - no_ref_schema!(); + always_inline!(); - fn schema_name() -> String { - "Decimal".to_owned() - } - - fn schema_id() -> Cow<'static, str> { - Cow::Borrowed("Decimal") + fn schema_name() -> Cow<'static, str> { + "Decimal".into() } fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::String.into()), - string: Some(Box::new(StringValidation { - pattern: Some(r"^-?[0-9]+(\.[0-9]+)?$".to_owned()), - ..Default::default() - })), - ..Default::default() - } - .into() + json_schema!({ + "type": "string", + "pattern": r"^-?[0-9]+(\.[0-9]+)?$", + }) } } }; } -#[cfg(feature = "rust_decimal")] -decimal_impl!(rust_decimal::Decimal); -#[cfg(feature = "bigdecimal03")] -decimal_impl!(bigdecimal03::BigDecimal); +#[cfg(feature = "rust_decimal1")] +decimal_impl!(rust_decimal1::Decimal); #[cfg(feature = "bigdecimal04")] decimal_impl!(bigdecimal04::BigDecimal); diff --git a/schemars/src/json_schema_impls/either.rs b/schemars/src/json_schema_impls/either.rs deleted file mode 100644 index 957fdd1..0000000 --- a/schemars/src/json_schema_impls/either.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use either::Either; -use std::borrow::Cow; - -impl JsonSchema for Either { - no_ref_schema!(); - - fn schema_name() -> String { - format!("Either_{}_or_{}", L::schema_name(), R::schema_name()) - } - - fn schema_id() -> Cow<'static, str> { - Cow::Owned(format!( - "either::Either<{}, {}>", - L::schema_id(), - R::schema_id() - )) - } - - fn json_schema(gen: &mut SchemaGenerator) -> Schema { - let mut schema = SchemaObject::default(); - schema.subschemas().any_of = Some(vec![gen.subschema_for::(), gen.subschema_for::()]); - schema.into() - } -} diff --git a/schemars/src/json_schema_impls/either1.rs b/schemars/src/json_schema_impls/either1.rs new file mode 100644 index 0000000..fcc3479 --- /dev/null +++ b/schemars/src/json_schema_impls/either1.rs @@ -0,0 +1,22 @@ +use crate::gen::SchemaGenerator; +use crate::{json_schema, JsonSchema, Schema}; +use either1::Either; +use std::borrow::Cow; + +impl JsonSchema for Either { + always_inline!(); + + fn schema_name() -> Cow<'static, str> { + format!("Either_{}_or_{}", L::schema_name(), R::schema_name()).into() + } + + fn schema_id() -> Cow<'static, str> { + format!("either::Either<{}, {}>", L::schema_id(), R::schema_id()).into() + } + + fn json_schema(gen: &mut SchemaGenerator) -> Schema { + json_schema!({ + "anyOf": [gen.subschema_for::(), gen.subschema_for::()], + }) + } +} diff --git a/schemars/src/json_schema_impls/enumset.rs b/schemars/src/json_schema_impls/enumset.rs deleted file mode 100644 index 22a3ebc..0000000 --- a/schemars/src/json_schema_impls/enumset.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use enumset::{EnumSet, EnumSetType}; - -forward_impl!(( JsonSchema for EnumSet where T: EnumSetType + JsonSchema) => std::collections::BTreeSet); diff --git a/schemars/src/json_schema_impls/ffi.rs b/schemars/src/json_schema_impls/ffi.rs index 55b5012..026d5ae 100644 --- a/schemars/src/json_schema_impls/ffi.rs +++ b/schemars/src/json_schema_impls/ffi.rs @@ -1,40 +1,36 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; +use crate::{json_schema, JsonSchema, Schema}; use std::borrow::Cow; use std::ffi::{CStr, CString, OsStr, OsString}; impl JsonSchema for OsString { - fn schema_name() -> String { - "OsString".to_owned() + fn schema_name() -> Cow<'static, str> { + "OsString".into() } fn schema_id() -> Cow<'static, str> { - Cow::Borrowed("std::ffi::OsString") + "std::ffi::OsString".into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { - let mut unix_schema = SchemaObject { - instance_type: Some(InstanceType::Object.into()), - ..Default::default() - }; - let obj = unix_schema.object(); - obj.required.insert("Unix".to_owned()); - obj.properties - .insert("Unix".to_owned(), >::json_schema(gen)); - - let mut win_schema = SchemaObject { - instance_type: Some(InstanceType::Object.into()), - ..Default::default() - }; - let obj = win_schema.object(); - obj.required.insert("Windows".to_owned()); - obj.properties - .insert("Windows".to_owned(), >::json_schema(gen)); - - let mut schema = SchemaObject::default(); - schema.subschemas().one_of = Some(vec![unix_schema.into(), win_schema.into()]); - schema.into() + json_schema!({ + "oneOf": [ + { + "type": "object", + "properties": { + "Unix": >::json_schema(gen) + }, + "required": ["Unix"] + }, + { + "type": "object", + "properties": { + "Windows": >::json_schema(gen) + }, + "required": ["Windows"] + }, + ] + }) } } diff --git a/schemars/src/json_schema_impls/indexmap.rs b/schemars/src/json_schema_impls/indexmap.rs deleted file mode 100644 index 3bd3e54..0000000 --- a/schemars/src/json_schema_impls/indexmap.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use indexmap::{IndexMap, IndexSet}; -use std::collections::{HashMap, HashSet}; - -forward_impl!(( JsonSchema for IndexMap) => HashMap); -forward_impl!(( JsonSchema for IndexSet) => HashSet); diff --git a/schemars/src/json_schema_impls/indexmap2.rs b/schemars/src/json_schema_impls/indexmap2.rs index 44eeedb..9473dea 100644 --- a/schemars/src/json_schema_impls/indexmap2.rs +++ b/schemars/src/json_schema_impls/indexmap2.rs @@ -1,5 +1,3 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; use crate::JsonSchema; use indexmap2::{IndexMap, IndexSet}; use std::collections::{HashMap, HashSet}; diff --git a/schemars/src/json_schema_impls/maps.rs b/schemars/src/json_schema_impls/maps.rs index 7c27808..19ae5cf 100644 --- a/schemars/src/json_schema_impls/maps.rs +++ b/schemars/src/json_schema_impls/maps.rs @@ -1,6 +1,5 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; +use crate::{json_schema, JsonSchema, Schema}; use std::borrow::Cow; macro_rules! map_impl { @@ -9,27 +8,21 @@ macro_rules! map_impl { where V: JsonSchema, { - no_ref_schema!(); + always_inline!(); - fn schema_name() -> String { - format!("Map_of_{}", V::schema_name()) + fn schema_name() -> Cow<'static, str> { + format!("Map_of_{}", V::schema_name()).into() } fn schema_id() -> Cow<'static, str> { - Cow::Owned(format!("Map<{}>", V::schema_id())) + format!("Map<{}>", V::schema_id()).into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { - let subschema = gen.subschema_for::(); - SchemaObject { - instance_type: Some(InstanceType::Object.into()), - object: Some(Box::new(ObjectValidation { - additional_properties: Some(Box::new(subschema)), - ..Default::default() - })), - ..Default::default() - } - .into() + json_schema!({ + "type": "object", + "additionalProperties": gen.subschema_for::(), + }) } } }; diff --git a/schemars/src/json_schema_impls/mod.rs b/schemars/src/json_schema_impls/mod.rs index 0c548eb..ca9ff28 100644 --- a/schemars/src/json_schema_impls/mod.rs +++ b/schemars/src/json_schema_impls/mod.rs @@ -1,7 +1,7 @@ -macro_rules! no_ref_schema { +macro_rules! always_inline { () => { - fn is_referenceable() -> bool { - false + fn always_inline_schema() -> bool { + true } }; } @@ -9,11 +9,11 @@ macro_rules! no_ref_schema { macro_rules! forward_impl { (($($impl:tt)+) => $target:ty) => { impl $($impl)+ { - fn is_referenceable() -> bool { - <$target>::is_referenceable() + fn always_inline_schema() -> bool { + <$target>::always_inline_schema() } - fn schema_name() -> String { + fn schema_name() -> std::borrow::Cow<'static, str> { <$target>::schema_name() } @@ -21,11 +21,11 @@ macro_rules! forward_impl { <$target>::schema_id() } - fn json_schema(gen: &mut SchemaGenerator) -> Schema { + fn json_schema(gen: &mut $crate::gen::SchemaGenerator) -> $crate::Schema { <$target>::json_schema(gen) } - fn _schemars_private_non_optional_json_schema(gen: &mut SchemaGenerator) -> Schema { + fn _schemars_private_non_optional_json_schema(gen: &mut $crate::gen::SchemaGenerator) -> $crate::Schema { <$target>::_schemars_private_non_optional_json_schema(gen) } @@ -35,55 +35,61 @@ macro_rules! forward_impl { } }; ($ty:ty => $target:ty) => { - forward_impl!((JsonSchema for $ty) => $target); + forward_impl!(($crate::JsonSchema for $ty) => $target); }; } mod array; -#[cfg(feature = "arrayvec05")] -mod arrayvec05; -#[cfg(feature = "arrayvec07")] -mod arrayvec07; -#[cfg(std_atomic)] -mod atomic; -#[cfg(feature = "bytes")] -mod bytes; -#[cfg(feature = "chrono")] -mod chrono; mod core; -#[cfg(any( - feature = "rust_decimal", - feature = "bigdecimal03", - feature = "bigdecimal04" -))] -mod decimal; -#[cfg(feature = "either")] -mod either; -#[cfg(feature = "enumset")] -mod enumset; mod ffi; -#[cfg(feature = "indexmap")] -mod indexmap; -#[cfg(feature = "indexmap2")] -mod indexmap2; mod maps; mod nonzero_signed; mod nonzero_unsigned; mod primitives; -#[cfg(feature = "semver")] -mod semver; mod sequences; mod serdejson; -#[cfg(feature = "smallvec")] -mod smallvec; -#[cfg(feature = "smol_str")] -mod smol_str; mod time; mod tuple; -#[cfg(feature = "url")] -mod url; -#[cfg(feature = "uuid08")] -mod uuid08; +mod wrapper; + +#[cfg(std_atomic)] +mod atomic; + +#[cfg(feature = "arrayvec07")] +mod arrayvec07; + +#[cfg(feature = "bytes1")] +mod bytes1 { + forward_impl!(bytes1::Bytes => Vec); + forward_impl!(bytes1::BytesMut => Vec); +} + +#[cfg(feature = "chrono04")] +mod chrono04; + +#[cfg(any(feature = "rust_decimal1", feature = "bigdecimal04"))] +mod decimal; + +#[cfg(feature = "either1")] +mod either1; + +#[cfg(feature = "enumset1")] +forward_impl!(( crate::JsonSchema for enumset1::EnumSet) => std::collections::BTreeSet); + +#[cfg(feature = "indexmap2")] +mod indexmap2; + +#[cfg(feature = "semver1")] +mod semver1; + +#[cfg(feature = "smallvec1")] +forward_impl!(( crate::JsonSchema for smallvec1::SmallVec where A::Item: crate::JsonSchema) => Vec); + +#[cfg(feature = "smol_str02")] +forward_impl!(smol_str02::SmolStr => String); + +#[cfg(feature = "url2")] +mod url2; + #[cfg(feature = "uuid1")] mod uuid1; -mod wrapper; diff --git a/schemars/src/json_schema_impls/nonzero_signed.rs b/schemars/src/json_schema_impls/nonzero_signed.rs index c2fba2b..c9d0919 100644 --- a/schemars/src/json_schema_impls/nonzero_signed.rs +++ b/schemars/src/json_schema_impls/nonzero_signed.rs @@ -1,31 +1,28 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; +use crate::{JsonSchema, Schema}; use std::borrow::Cow; use std::num::*; macro_rules! nonzero_unsigned_impl { ($type:ty => $primitive:ty) => { impl JsonSchema for $type { - no_ref_schema!(); + always_inline!(); - fn schema_name() -> String { - stringify!($type).to_owned() + fn schema_name() -> Cow<'static, str> { + stringify!($type).into() } fn schema_id() -> Cow<'static, str> { - Cow::Borrowed(stringify!(std::num::$type)) + stringify!(std::num::$type).into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { - let zero_schema: Schema = SchemaObject { - const_value: Some(0.into()), - ..Default::default() - } - .into(); - let mut schema: SchemaObject = <$primitive>::json_schema(gen).into(); - schema.subschemas().not = Some(Box::from(zero_schema)); - schema.into() + let mut schema = <$primitive>::json_schema(gen); + let object = schema.ensure_object(); + object.insert("not".to_owned(), serde_json::json!({ + "const": 0 + })); + schema } } }; diff --git a/schemars/src/json_schema_impls/nonzero_unsigned.rs b/schemars/src/json_schema_impls/nonzero_unsigned.rs index 1963d56..afabc05 100644 --- a/schemars/src/json_schema_impls/nonzero_unsigned.rs +++ b/schemars/src/json_schema_impls/nonzero_unsigned.rs @@ -1,26 +1,27 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; use crate::JsonSchema; +use crate::Schema; use std::borrow::Cow; use std::num::*; macro_rules! nonzero_unsigned_impl { ($type:ty => $primitive:ty) => { impl JsonSchema for $type { - no_ref_schema!(); + always_inline!(); - fn schema_name() -> String { - stringify!($type).to_owned() + fn schema_name() -> Cow<'static, str> { + stringify!($type).into() } fn schema_id() -> Cow<'static, str> { - Cow::Borrowed(stringify!(std::num::$type)) + stringify!(std::num::$type).into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { - let mut schema: SchemaObject = <$primitive>::json_schema(gen).into(); - schema.number().minimum = Some(1.0); - schema.into() + let mut schema = <$primitive>::json_schema(gen); + let object = schema.ensure_object(); + object.insert("minimum".to_owned(), 1.into()); + schema } } }; @@ -32,18 +33,3 @@ nonzero_unsigned_impl!(NonZeroU32 => u32); nonzero_unsigned_impl!(NonZeroU64 => u64); nonzero_unsigned_impl!(NonZeroU128 => u128); nonzero_unsigned_impl!(NonZeroUsize => usize); - -#[cfg(test)] -mod tests { - use super::*; - use crate::tests::schema_object_for; - use pretty_assertions::assert_eq; - - #[test] - fn schema_for_nonzero_u32() { - let schema = schema_object_for::(); - assert_eq!(schema.number.unwrap().minimum, Some(1.0)); - assert_eq!(schema.instance_type, Some(InstanceType::Integer.into())); - assert_eq!(schema.format, Some("uint32".to_owned())); - } -} diff --git a/schemars/src/json_schema_impls/primitives.rs b/schemars/src/json_schema_impls/primitives.rs index d182984..a3bcce6 100644 --- a/schemars/src/json_schema_impls/primitives.rs +++ b/schemars/src/json_schema_impls/primitives.rs @@ -1,119 +1,110 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; +use crate::{json_schema, JsonSchema, Schema}; use std::borrow::Cow; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use std::path::{Path, PathBuf}; macro_rules! simple_impl { - ($type:ty => $instance_type:ident) => { - simple_impl!($type => $instance_type, stringify!($instance_type), None); - }; - ($type:ty => $instance_type:ident, $format:literal) => { - simple_impl!($type => $instance_type, $format, Some($format.to_owned())); - }; - ($type:ty => $instance_type:ident, $name:expr, $format:expr) => { + ($type:ty => $instance_type:literal) => { impl JsonSchema for $type { - no_ref_schema!(); + always_inline!(); - fn schema_name() -> String { - $name.to_owned() - } - - fn schema_id() -> Cow<'static, str> { - Cow::Borrowed($name) + fn schema_name() -> Cow<'static, str> { + $instance_type.into() } fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::$instance_type.into()), - format: $format, - ..Default::default() - } - .into() + json_schema!({ + "type": $instance_type + }) + } + } + }; + ($type:ty => $instance_type:literal, $format:literal) => { + impl JsonSchema for $type { + always_inline!(); + + fn schema_name() -> Cow<'static, str> { + $format.into() + } + + fn json_schema(_: &mut SchemaGenerator) -> Schema { + json_schema!({ + "type": $instance_type, + "format": $format + }) } } }; } -simple_impl!(str => String); -simple_impl!(String => String); -simple_impl!(bool => Boolean); -simple_impl!(f32 => Number, "float"); -simple_impl!(f64 => Number, "double"); -simple_impl!(i8 => Integer, "int8"); -simple_impl!(i16 => Integer, "int16"); -simple_impl!(i32 => Integer, "int32"); -simple_impl!(i64 => Integer, "int64"); -simple_impl!(i128 => Integer, "int128"); -simple_impl!(isize => Integer, "int"); -simple_impl!(() => Null); +simple_impl!(str => "string"); +simple_impl!(String => "string"); +simple_impl!(bool => "boolean"); +simple_impl!(f32 => "number", "float"); +simple_impl!(f64 => "number", "double"); +simple_impl!(i8 => "integer", "int8"); +simple_impl!(i16 => "integer", "int16"); +simple_impl!(i32 => "integer", "int32"); +simple_impl!(i64 => "integer", "int64"); +simple_impl!(i128 => "integer", "int128"); +simple_impl!(isize => "integer", "int"); +simple_impl!(() => "null"); -simple_impl!(Path => String); -simple_impl!(PathBuf => String); +simple_impl!(Path => "string"); +simple_impl!(PathBuf => "string"); -simple_impl!(Ipv4Addr => String, "ipv4"); -simple_impl!(Ipv6Addr => String, "ipv6"); -simple_impl!(IpAddr => String, "ip"); +simple_impl!(Ipv4Addr => "string", "ipv4"); +simple_impl!(Ipv6Addr => "string", "ipv6"); +simple_impl!(IpAddr => "string", "ip"); -simple_impl!(SocketAddr => String); -simple_impl!(SocketAddrV4 => String); -simple_impl!(SocketAddrV6 => String); +simple_impl!(SocketAddr => "string"); +simple_impl!(SocketAddrV4 => "string"); +simple_impl!(SocketAddrV6 => "string"); macro_rules! unsigned_impl { - ($type:ty => $instance_type:ident, $format:expr) => { + ($type:ty => $instance_type:literal, $format:literal) => { impl JsonSchema for $type { - no_ref_schema!(); + always_inline!(); - fn schema_name() -> String { - $format.to_owned() - } - - fn schema_id() -> Cow<'static, str> { - Cow::Borrowed($format) + fn schema_name() -> Cow<'static, str> { + $format.into() } fn json_schema(_: &mut SchemaGenerator) -> Schema { - let mut schema = SchemaObject { - instance_type: Some(InstanceType::$instance_type.into()), - format: Some($format.to_owned()), - ..Default::default() - }; - schema.number().minimum = Some(0.0); - schema.into() + json_schema!({ + "type": $instance_type, + "format": $format, + "minimum": 0 + }) } } }; } -unsigned_impl!(u8 => Integer, "uint8"); -unsigned_impl!(u16 => Integer, "uint16"); -unsigned_impl!(u32 => Integer, "uint32"); -unsigned_impl!(u64 => Integer, "uint64"); -unsigned_impl!(u128 => Integer, "uint128"); -unsigned_impl!(usize => Integer, "uint"); +unsigned_impl!(u8 => "integer", "uint8"); +unsigned_impl!(u16 => "integer", "uint16"); +unsigned_impl!(u32 => "integer", "uint32"); +unsigned_impl!(u64 => "integer", "uint64"); +unsigned_impl!(u128 => "integer", "uint128"); +unsigned_impl!(usize => "integer", "uint"); impl JsonSchema for char { - no_ref_schema!(); + always_inline!(); - fn schema_name() -> String { - "Character".to_owned() + fn schema_name() -> Cow<'static, str> { + "Character".into() } fn schema_id() -> Cow<'static, str> { - Cow::Borrowed("char") + "char".into() } fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::String.into()), - string: Some(Box::new(StringValidation { - min_length: Some(1), - max_length: Some(1), - ..Default::default() - })), - ..Default::default() - } - .into() + json_schema!({ + "type": "string", + "minLength": 1, + "maxLength": 1, + }) } } diff --git a/schemars/src/json_schema_impls/semver.rs b/schemars/src/json_schema_impls/semver.rs deleted file mode 100644 index dd8ed8e..0000000 --- a/schemars/src/json_schema_impls/semver.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use semver::Version; -use std::borrow::Cow; - -impl JsonSchema for Version { - no_ref_schema!(); - - fn schema_name() -> String { - "Version".to_owned() - } - - fn schema_id() -> Cow<'static, str> { - Cow::Borrowed("semver::Version") - } - - fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::String.into()), - string: Some(Box::new(StringValidation { - // https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string - pattern: Some(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$".to_owned()), - ..Default::default() - })), - ..Default::default() - } - .into() - } -} diff --git a/schemars/src/json_schema_impls/semver1.rs b/schemars/src/json_schema_impls/semver1.rs new file mode 100644 index 0000000..eb121fb --- /dev/null +++ b/schemars/src/json_schema_impls/semver1.rs @@ -0,0 +1,24 @@ +use crate::gen::SchemaGenerator; +use crate::{json_schema, JsonSchema, Schema}; +use semver1::Version; +use std::borrow::Cow; + +impl JsonSchema for Version { + always_inline!(); + + fn schema_name() -> Cow<'static, str> { + "Version".into() + } + + fn schema_id() -> Cow<'static, str> { + "semver::Version".into() + } + + fn json_schema(_: &mut SchemaGenerator) -> Schema { + json_schema!({ + "type": "string", + // https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string + "pattern": r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$" + }) + } +} diff --git a/schemars/src/json_schema_impls/sequences.rs b/schemars/src/json_schema_impls/sequences.rs index 780307f..c131339 100644 --- a/schemars/src/json_schema_impls/sequences.rs +++ b/schemars/src/json_schema_impls/sequences.rs @@ -1,6 +1,5 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; +use crate::{json_schema, JsonSchema, Schema}; use std::borrow::Cow; macro_rules! seq_impl { @@ -9,27 +8,21 @@ macro_rules! seq_impl { where T: JsonSchema, { - no_ref_schema!(); + always_inline!(); - fn schema_name() -> String { - format!("Array_of_{}", T::schema_name()) + fn schema_name() -> Cow<'static, str> { + format!("Array_of_{}", T::schema_name()).into() } fn schema_id() -> Cow<'static, str> { - Cow::Owned( - format!("[{}]", T::schema_id())) + format!("[{}]", T::schema_id()).into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::Array.into()), - array: Some(Box::new(ArrayValidation { - items: Some(gen.subschema_for::().into()), - ..Default::default() - })), - ..Default::default() - } - .into() + json_schema!({ + "type": "array", + "items": gen.subschema_for::(), + }) } } }; @@ -41,28 +34,22 @@ macro_rules! set_impl { where T: JsonSchema, { - no_ref_schema!(); + always_inline!(); - fn schema_name() -> String { - format!("Set_of_{}", T::schema_name()) + fn schema_name() -> Cow<'static, str> { + format!("Set_of_{}", T::schema_name()).into() } fn schema_id() -> Cow<'static, str> { - Cow::Owned( - format!("Set<{}>", T::schema_id())) + format!("Set<{}>", T::schema_id()).into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::Array.into()), - array: Some(Box::new(ArrayValidation { - unique_items: Some(true), - items: Some(gen.subschema_for::().into()), - ..Default::default() - })), - ..Default::default() - } - .into() + json_schema!({ + "type": "array", + "uniqueItems": true, + "items": gen.subschema_for::(), + }) } } }; diff --git a/schemars/src/json_schema_impls/serdejson.rs b/schemars/src/json_schema_impls/serdejson.rs index 41eafd5..cd25326 100644 --- a/schemars/src/json_schema_impls/serdejson.rs +++ b/schemars/src/json_schema_impls/serdejson.rs @@ -1,45 +1,34 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; +use crate::{json_schema, JsonSchema, Schema}; use serde_json::{Map, Number, Value}; use std::borrow::Cow; use std::collections::BTreeMap; impl JsonSchema for Value { - no_ref_schema!(); + always_inline!(); - fn schema_name() -> String { - "AnyValue".to_owned() - } - - fn schema_id() -> Cow<'static, str> { - Cow::Borrowed("AnyValue") + fn schema_name() -> Cow<'static, str> { + "AnyValue".into() } fn json_schema(_: &mut SchemaGenerator) -> Schema { - Schema::Bool(true) + true.into() } } forward_impl!(Map => BTreeMap); impl JsonSchema for Number { - no_ref_schema!(); + always_inline!(); - fn schema_name() -> String { - "Number".to_owned() - } - - fn schema_id() -> Cow<'static, str> { - Cow::Borrowed("Number") + fn schema_name() -> Cow<'static, str> { + "Number".into() } fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::Number.into()), - ..Default::default() - } - .into() + json_schema!({ + "type": "number" + }) } } diff --git a/schemars/src/json_schema_impls/smallvec.rs b/schemars/src/json_schema_impls/smallvec.rs deleted file mode 100644 index f7a75e3..0000000 --- a/schemars/src/json_schema_impls/smallvec.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use smallvec::{Array, SmallVec}; - -forward_impl!(( JsonSchema for SmallVec where A::Item: JsonSchema) => Vec); diff --git a/schemars/src/json_schema_impls/smol_str.rs b/schemars/src/json_schema_impls/smol_str.rs deleted file mode 100644 index cbca4a1..0000000 --- a/schemars/src/json_schema_impls/smol_str.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use smol_str::SmolStr; - -forward_impl!(SmolStr => String); diff --git a/schemars/src/json_schema_impls/time.rs b/schemars/src/json_schema_impls/time.rs index 767a1d2..0f1eb74 100644 --- a/schemars/src/json_schema_impls/time.rs +++ b/schemars/src/json_schema_impls/time.rs @@ -1,55 +1,46 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; +use crate::{json_schema, JsonSchema, Schema}; use std::borrow::Cow; use std::time::{Duration, SystemTime}; impl JsonSchema for Duration { - fn schema_name() -> String { - "Duration".to_owned() + fn schema_name() -> Cow<'static, str> { + "Duration".into() } fn schema_id() -> Cow<'static, str> { - Cow::Borrowed("std::time::Duration") + "std::time::Duration".into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { - let mut schema = SchemaObject { - instance_type: Some(InstanceType::Object.into()), - ..Default::default() - }; - let obj = schema.object(); - obj.required.insert("secs".to_owned()); - obj.required.insert("nanos".to_owned()); - obj.properties - .insert("secs".to_owned(), ::json_schema(gen)); - obj.properties - .insert("nanos".to_owned(), ::json_schema(gen)); - schema.into() + json_schema!({ + "type": "object", + "required": ["secs", "nanos"], + "properties": { + "secs": u64::json_schema(gen), + "nanos": u32::json_schema(gen), + } + }) } } impl JsonSchema for SystemTime { - fn schema_name() -> String { - "SystemTime".to_owned() + fn schema_name() -> Cow<'static, str> { + "SystemTime".into() } fn schema_id() -> Cow<'static, str> { - Cow::Borrowed("std::time::SystemTime") + "std::time::SystemTime".into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { - let mut schema = SchemaObject { - instance_type: Some(InstanceType::Object.into()), - ..Default::default() - }; - let obj = schema.object(); - obj.required.insert("secs_since_epoch".to_owned()); - obj.required.insert("nanos_since_epoch".to_owned()); - obj.properties - .insert("secs_since_epoch".to_owned(), ::json_schema(gen)); - obj.properties - .insert("nanos_since_epoch".to_owned(), ::json_schema(gen)); - schema.into() + json_schema!({ + "type": "object", + "required": ["secs_since_epoch", "nanos_since_epoch"], + "properties": { + "secs_since_epoch": u64::json_schema(gen), + "nanos_since_epoch": u32::json_schema(gen), + } + }) } } diff --git a/schemars/src/json_schema_impls/tuple.rs b/schemars/src/json_schema_impls/tuple.rs index f67f06a..c05a5f8 100644 --- a/schemars/src/json_schema_impls/tuple.rs +++ b/schemars/src/json_schema_impls/tuple.rs @@ -1,18 +1,17 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; +use crate::{json_schema, JsonSchema, Schema}; use std::borrow::Cow; macro_rules! tuple_impls { ($($len:expr => ($($name:ident)+))+) => { $( impl<$($name: JsonSchema),+> JsonSchema for ($($name,)+) { - no_ref_schema!(); + always_inline!(); - fn schema_name() -> String { + fn schema_name() -> Cow<'static, str> { let mut name = "Tuple_of_".to_owned(); name.push_str(&[$($name::schema_name()),+].join("_and_")); - name + name.into() } fn schema_id() -> Cow<'static, str> { @@ -20,24 +19,18 @@ macro_rules! tuple_impls { id.push_str(&[$($name::schema_id()),+].join(",")); id.push(')'); - Cow::Owned(id) + id.into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { - let items = vec![ - $(gen.subschema_for::<$name>()),+ - ]; - SchemaObject { - instance_type: Some(InstanceType::Array.into()), - array: Some(Box::new(ArrayValidation { - items: Some(items.into()), - max_items: Some($len), - min_items: Some($len), - ..Default::default() - })), - ..Default::default() - } - .into() + json_schema!({ + "type": "array", + "prefixItems": [ + $(gen.subschema_for::<$name>()),+ + ], + "minItems": $len, + "maxItems": $len, + }) } } )+ @@ -62,29 +55,3 @@ tuple_impls! { 15 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14) 16 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15) } - -#[cfg(test)] -mod tests { - use super::*; - use crate::tests::{schema_for, schema_object_for}; - use pretty_assertions::assert_eq; - - #[test] - fn schema_for_map_any_value() { - let schema = schema_object_for::<(i32, bool)>(); - assert_eq!( - schema.instance_type, - Some(SingleOrVec::from(InstanceType::Array)) - ); - let array_validation = schema.array.unwrap(); - assert_eq!( - array_validation.items, - Some(SingleOrVec::Vec(vec![ - schema_for::(), - schema_for::() - ])) - ); - assert_eq!(array_validation.max_items, Some(2)); - assert_eq!(array_validation.min_items, Some(2)); - } -} diff --git a/schemars/src/json_schema_impls/url.rs b/schemars/src/json_schema_impls/url.rs deleted file mode 100644 index be18612..0000000 --- a/schemars/src/json_schema_impls/url.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use std::borrow::Cow; -use url::Url; - -impl JsonSchema for Url { - no_ref_schema!(); - - fn schema_name() -> String { - "Url".to_owned() - } - - fn schema_id() -> Cow<'static, str> { - Cow::Borrowed("url::Url") - } - - fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::String.into()), - format: Some("uri".to_owned()), - ..Default::default() - } - .into() - } -} diff --git a/schemars/src/json_schema_impls/url2.rs b/schemars/src/json_schema_impls/url2.rs new file mode 100644 index 0000000..5a467d9 --- /dev/null +++ b/schemars/src/json_schema_impls/url2.rs @@ -0,0 +1,23 @@ +use crate::gen::SchemaGenerator; +use crate::{json_schema, JsonSchema, Schema}; +use std::borrow::Cow; +use url2::Url; + +impl JsonSchema for Url { + always_inline!(); + + fn schema_name() -> Cow<'static, str> { + "Url".into() + } + + fn schema_id() -> Cow<'static, str> { + "url::Url".into() + } + + fn json_schema(_: &mut SchemaGenerator) -> Schema { + json_schema!({ + "type": "string", + "format": "uri", + }) + } +} diff --git a/schemars/src/json_schema_impls/uuid08.rs b/schemars/src/json_schema_impls/uuid08.rs deleted file mode 100644 index b3b18f8..0000000 --- a/schemars/src/json_schema_impls/uuid08.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use std::borrow::Cow; -use uuid08::Uuid; - -impl JsonSchema for Uuid { - no_ref_schema!(); - - fn schema_name() -> String { - "Uuid".to_string() - } - - fn schema_id() -> Cow<'static, str> { - Cow::Borrowed("uuid::Uuid") - } - - fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::String.into()), - format: Some("uuid".to_string()), - ..Default::default() - } - .into() - } -} diff --git a/schemars/src/json_schema_impls/uuid1.rs b/schemars/src/json_schema_impls/uuid1.rs index 2e0c6e9..a56c10c 100644 --- a/schemars/src/json_schema_impls/uuid1.rs +++ b/schemars/src/json_schema_impls/uuid1.rs @@ -1,26 +1,23 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; +use crate::{json_schema, JsonSchema, Schema}; use std::borrow::Cow; use uuid1::Uuid; impl JsonSchema for Uuid { - no_ref_schema!(); + always_inline!(); - fn schema_name() -> String { - "Uuid".to_string() + fn schema_name() -> Cow<'static, str> { + "Uuid".into() } fn schema_id() -> Cow<'static, str> { - Cow::Borrowed("uuid::Uuid") + "uuid::Uuid".into() } fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::String.into()), - format: Some("uuid".to_string()), - ..Default::default() - } - .into() + json_schema!({ + "type": "string", + "format": "uuid", + }) } } diff --git a/schemars/src/json_schema_impls/wrapper.rs b/schemars/src/json_schema_impls/wrapper.rs index 243a06b..e7e233c 100644 --- a/schemars/src/json_schema_impls/wrapper.rs +++ b/schemars/src/json_schema_impls/wrapper.rs @@ -1,5 +1,3 @@ -use crate::gen::SchemaGenerator; -use crate::schema::Schema; use crate::JsonSchema; macro_rules! wrapper_impl { diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs index f2332e5..afe49d1 100644 --- a/schemars/src/lib.rs +++ b/schemars/src/lib.rs @@ -1,32 +1,8 @@ -#![forbid(unsafe_code)] +#![deny(unsafe_code)] #![doc = include_str!("../README.md")] -/// The map type used by schemars types. -/// -/// Currently a `BTreeMap` or `IndexMap` can be used, but this may change to a different implementation -/// with a similar interface in a future version of schemars. -/// The `IndexMap` will be used when the `preserve_order` feature flag is set. -#[cfg(not(feature = "preserve_order"))] -pub type Map = std::collections::BTreeMap; -#[cfg(feature = "preserve_order")] -pub type Map = indexmap::IndexMap; -/// The set type used by schemars types. -/// -/// Currently a `BTreeSet`, but this may change to a different implementation -/// with a similar interface in a future version of schemars. -pub type Set = std::collections::BTreeSet; - -/// A view into a single entry in a map, which may either be vacant or occupied. -// -/// This is constructed from the `entry` method on `BTreeMap` or `IndexMap`, -/// depending on whether the `preserve_order` feature flag is set. -#[cfg(not(feature = "preserve_order"))] -pub type MapEntry<'a, K, V> = std::collections::btree_map::Entry<'a, K, V>; -#[cfg(feature = "preserve_order")] -pub type MapEntry<'a, K, V> = indexmap::map::Entry<'a, K, V>; - -mod flatten; mod json_schema_impls; +mod schema; mod ser; #[macro_use] mod macros; @@ -35,9 +11,10 @@ mod macros; /// outside of `schemars`, and should not be considered part of the public API. #[doc(hidden)] pub mod _private; +/// Types for generating JSON schemas. pub mod gen; -pub mod schema; -pub mod visit; +/// Types for defining modifications to JSON schemas. +pub mod transform; #[cfg(feature = "schemars_derive")] extern crate schemars_derive; @@ -50,7 +27,8 @@ pub use schemars_derive::*; #[doc(hidden)] pub use serde_json as _serde_json; -use schema::Schema; +pub use gen::SchemaGenerator; +pub use schema::Schema; /// A type which can be described as a JSON Schema document. /// @@ -75,24 +53,26 @@ use schema::Schema; /// you will need to determine an appropriate name and ID for the type. /// For non-generic types, the type name/path are suitable for this: /// ``` -/// use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema}; +/// use schemars::{SchemaGenerator, Schema, JsonSchema, json_schema}; /// use std::borrow::Cow; /// /// struct NonGenericType; /// /// impl JsonSchema for NonGenericType { -/// fn schema_name() -> String { +/// fn schema_name() -> Cow<'static, str> { /// // Exclude the module path to make the name in generated schemas clearer. -/// "NonGenericType".to_owned() +/// "NonGenericType".into() /// } /// /// fn schema_id() -> Cow<'static, str> { /// // Include the module, in case a type with the same name is in another module/crate -/// Cow::Borrowed(concat!(module_path!(), "::NonGenericType")) +/// concat!(module_path!(), "::NonGenericType").into() /// } /// /// fn json_schema(_gen: &mut SchemaGenerator) -> Schema { -/// todo!() +/// json_schema!({ +/// "foo": "bar" +/// }) /// } /// } /// @@ -101,26 +81,28 @@ use schema::Schema; /// /// But generic type parameters which may affect the generated schema should typically be included in the name/ID: /// ``` -/// use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema}; +/// use schemars::{SchemaGenerator, Schema, JsonSchema, json_schema}; /// use std::{borrow::Cow, marker::PhantomData}; /// /// struct GenericType(PhantomData); /// /// impl JsonSchema for GenericType { -/// fn schema_name() -> String { -/// format!("GenericType_{}", T::schema_name()) +/// fn schema_name() -> Cow<'static, str> { +/// format!("GenericType_{}", T::schema_name()).into() /// } /// /// fn schema_id() -> Cow<'static, str> { -/// Cow::Owned(format!( +/// format!( /// "{}::GenericType<{}>", /// module_path!(), /// T::schema_id() -/// )) +/// ).into() /// } /// /// fn json_schema(_gen: &mut SchemaGenerator) -> Schema { -/// todo!() +/// json_schema!({ +/// "foo": "bar" +/// }) /// } /// } /// @@ -129,20 +111,21 @@ use schema::Schema; /// pub trait JsonSchema { - /// Whether JSON Schemas generated for this type should be re-used where possible using the `$ref` keyword. + /// Whether JSON Schemas generated for this type should be included directly in parent schemas, rather than being + /// re-used where possible using the `$ref` keyword. /// - /// For trivial types (such as primitives), this should return `false`. For more complex types, it should return `true`. - /// For recursive types, this **must** return `true` to prevent infinite cycles when generating schemas. + /// For trivial types (such as primitives), this should return `true`. For more complex types, it should return `false`. + /// For recursive types, this **must** return `false` to prevent infinite cycles when generating schemas. /// - /// By default, this returns `true`. - fn is_referenceable() -> bool { - true + /// By default, this returns `false`. + fn always_inline_schema() -> bool { + false } /// The name of the generated JSON Schema. /// /// This is used as the title for root schemas, and the key within the root's `definitions` property for subschemas. - fn schema_name() -> String; + fn schema_name() -> Cow<'static, str>; /// Returns a string that uniquely identifies the schema produced by this type. /// @@ -150,22 +133,22 @@ pub trait JsonSchema { /// If two types produce different schemas, then they **must** have different `schema_id()`s, /// but two types that produce identical schemas should *ideally* have the same `schema_id()`. /// - /// The default implementation returns the same value as `schema_name()`. + /// The default implementation returns the same value as [`schema_name()`](JsonSchema::schema_name). fn schema_id() -> Cow<'static, str> { - Cow::Owned(Self::schema_name()) + Self::schema_name() } /// Generates a JSON Schema for this type. /// - /// If the returned schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will - /// add them to the [`SchemaGenerator`](gen::SchemaGenerator)'s schema definitions. + /// If the returned schema depends on any [non-inlined](JsonSchema::always_inline_schema) schemas, then this method will + /// add them to the [`SchemaGenerator`](SchemaGenerator)'s schema definitions. /// /// This should not return a `$ref` schema. - fn json_schema(gen: &mut gen::SchemaGenerator) -> Schema; + fn json_schema(gen: &mut SchemaGenerator) -> Schema; // TODO document and bring into public API? #[doc(hidden)] - fn _schemars_private_non_optional_json_schema(gen: &mut gen::SchemaGenerator) -> Schema { + fn _schemars_private_non_optional_json_schema(gen: &mut SchemaGenerator) -> Schema { Self::json_schema(gen) } @@ -175,24 +158,3 @@ pub trait JsonSchema { false } } - -#[cfg(test)] -pub mod tests { - use super::*; - - pub fn schema_object_for() -> schema::SchemaObject { - schema_object(schema_for::()) - } - - pub fn schema_for() -> schema::Schema { - let mut gen = gen::SchemaGenerator::default(); - T::json_schema(&mut gen) - } - - pub fn schema_object(schema: schema::Schema) -> schema::SchemaObject { - match schema { - schema::Schema::Object(o) => o, - s => panic!("Schema was not an object: {:?}", s), - } - } -} diff --git a/schemars/src/macros.rs b/schemars/src/macros.rs index 18a6810..e327284 100644 --- a/schemars/src/macros.rs +++ b/schemars/src/macros.rs @@ -17,11 +17,12 @@ #[macro_export] macro_rules! schema_for { ($type:ty) => { - $crate::gen::SchemaGenerator::default().into_root_schema_for::<$type>() + $crate::SchemaGenerator::default().into_root_schema_for::<$type>() }; } -/// Generates a [`RootSchema`](crate::schema::RootSchema) for the given type using default settings. +/// Generates a [`Schema`](crate::Schema) for the given type using default settings. +/// The default settings currently conform to [JSON Schema 2020-12](https://json-schema.org/specification-links#2020-12), but this is liable to change in a future version of Schemars if support for other JSON Schema versions is added. /// /// The type must implement [`JsonSchema`](crate::JsonSchema). /// @@ -40,22 +41,23 @@ macro_rules! schema_for { #[macro_export] macro_rules! schema_for { ($type:ty) => { - $crate::gen::SchemaGenerator::default().into_root_schema_for::<$type>() + $crate::SchemaGenerator::default().into_root_schema_for::<$type>() }; ($_:expr) => { compile_error!("This argument to `schema_for!` is not a type - did you mean to use `schema_for_value!` instead?") }; } -/// Generates a [`RootSchema`](crate::schema::RootSchema) for the given example value using default settings. +/// Generates a [`Schema`](crate::Schema) for the given example value using default settings. +/// The default settings currently conform to [JSON Schema 2020-12](https://json-schema.org/specification-links#2020-12), but this is liable to change in a future version of Schemars if support for other JSON Schema versions is added. /// /// The value must implement [`Serialize`](serde::Serialize). If the value also implements [`JsonSchema`](crate::JsonSchema), -/// then prefer using the [`schema_for!`](schema_for) macro which will generally produce a more precise schema, +/// then prefer using the [`schema_for!(Type)`](schema_for) macro which will generally produce a more precise schema, /// particularly when the value contains any enums. /// /// If the `Serialize` implementation of the value decides to fail, this macro will panic. -/// For a non-panicking alternative, create a [`SchemaGenerator`](crate::gen::SchemaGenerator) and use -/// its [`into_root_schema_for_value`](crate::gen::SchemaGenerator::into_root_schema_for_value) method. +/// For a non-panicking alternative, create a [`SchemaGenerator`](crate::SchemaGenerator) and use +/// its [`into_root_schema_for_value`](crate::SchemaGenerator::into_root_schema_for_value) method. /// /// # Example /// ``` @@ -71,8 +73,37 @@ macro_rules! schema_for { #[macro_export] macro_rules! schema_for_value { ($value:expr) => { - $crate::gen::SchemaGenerator::default() + $crate::SchemaGenerator::default() .into_root_schema_for_value(&$value) .unwrap() }; } + +/// Construct a [`Schema`](crate::Schema) from a JSON literal. This can either be a JSON object, or a boolean (`true` or `false`). +/// +/// You can interpolate variables or expressions into a JSON object using the same rules as the [`serde_json::json`] macro. +/// +/// # Example +/// ``` +/// use schemars::{Schema, json_schema}; +/// +/// let desc = "A helpful description."; +/// let my_schema: Schema = json_schema!({ +/// "description": desc, +/// "type": ["object", "null"] +/// }); +/// ``` +#[macro_export] +macro_rules! json_schema { + ( + {$($json_object:tt)*} + ) => { + $crate::Schema::try_from($crate::_serde_json::json!({$($json_object)*})).unwrap() + }; + (true) => { + $crate::Schema::from(true) + }; + (false) => { + $crate::Schema::from(false) + }; +} diff --git a/schemars/src/schema.rs b/schemars/src/schema.rs index 4bef66e..dd9e866 100644 --- a/schemars/src/schema.rs +++ b/schemars/src/schema.rs @@ -2,550 +2,311 @@ JSON Schema types. */ -#[cfg(feature = "impl_json_schema")] -use crate as schemars; -#[cfg(feature = "impl_json_schema")] -use crate::JsonSchema; -use crate::{Map, Set}; +use ref_cast::{ref_cast_custom, RefCastCustom}; use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::ops::Deref; +use serde_json::{Map, Value}; /// A JSON Schema. -#[allow(clippy::large_enum_variant)] -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] -#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] -#[serde(untagged)] -pub enum Schema { - /// A trivial boolean JSON Schema. - /// - /// The schema `true` matches everything (always passes validation), whereas the schema `false` - /// matches nothing (always fails validation). - Bool(bool), - /// A JSON Schema object. - Object(SchemaObject), +/// +/// This wraps a JSON [`Value`] that must be either an [object](Value::Object) or a [bool](Value::Bool). +/// +/// A custom JSON schema can be created using the [`json_schema!`](crate::json_schema) macro: +/// ``` +/// use schemars::{Schema, json_schema}; +/// +/// let my_schema: Schema = json_schema!({ +/// "type": ["object", "null"] +/// }); +/// ``` +/// +/// Because a `Schema` is a thin wrapper around a `Value`, you can also use [`TryFrom::try_from`]/[`TryInto::try_into`] to create a `Schema` from an existing `Value`. +/// This operation is fallible, because only [objects](Value::Object) and [bools](Value::Bool) can be converted in this way. +/// ``` +/// use schemars::{Schema, json_schema}; +/// use serde_json::json; +/// +/// let json_object = json!({"type": ["object", "null"]}); +/// let object_schema: Schema = json_object.try_into().unwrap(); +/// +/// let json_bool = json!(true); +/// let bool_schema: Schema = json_bool.try_into().unwrap(); +/// +/// let json_string = json!("This is neither an object nor a bool!"); +/// assert!(Schema::try_from(json_string).is_err()); +/// +/// // You can also convert a `&Value`/`&mut Value` to a `&Schema`/`&mut Schema` the same way: +/// +/// let json_object = json!({"type": ["object", "null"]}); +/// let object_schema_ref: &Schema = (&json_object).try_into().unwrap(); +/// +/// let mut json_object = json!({"type": ["object", "null"]}); +/// let object_schema_mut: &mut Schema = (&mut json_object).try_into().unwrap(); +/// +/// ``` +/// +/// Similarly, you can use [`From`]/[`Into`] to (infallibly) create a `Schema` from an existing [`Map`] or [`bool`]. +/// ``` +/// use schemars::{Schema, json_schema}; +/// use serde_json::{Map, json}; +/// +/// let mut map = Map::new(); +/// map.insert("type".to_owned(), json!(["object", "null"])); +/// let object_schema: Schema = map.into(); +/// +/// let bool_schema: Schema = true.into(); +/// ``` +#[derive(Debug, Clone, PartialEq, RefCastCustom)] +#[repr(transparent)] +pub struct Schema(Value); + +impl<'de> Deserialize<'de> for Schema { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = Value::deserialize(deserializer)?; + Schema::validate(&value)?; + Ok(Schema(value)) + } +} + +impl Serialize for Schema { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + ser::OrderedKeywordWrapper(&self.0).serialize(serializer) + } } impl Schema { - /// Creates a new `$ref` schema. + /// Creates a new schema object with a single string property `"$ref"`. /// /// The given reference string should be a URI reference. This will usually be a JSON Pointer /// in [URI Fragment representation](https://tools.ietf.org/html/rfc6901#section-6). pub fn new_ref(reference: String) -> Self { - SchemaObject::new_ref(reference).into() + let mut map = Map::new(); + map.insert("$ref".to_owned(), Value::String(reference)); + Self(Value::Object(map)) } - /// Returns `true` if `self` is a `$ref` schema. + /// Borrows the `Schema`'s underlying JSON value. + pub fn as_value(&self) -> &Value { + &self.0 + } + + /// If the `Schema`'s underlying JSON value is a bool, returns the bool value. + pub fn as_bool(&self) -> Option { + self.0.as_bool() + } + + /// If the `Schema`'s underlying JSON value is an object, borrows the object as a `Map` of properties. + pub fn as_object(&self) -> Option<&Map> { + self.0.as_object() + } + + /// If the `Schema`'s underlying JSON value is an object, mutably borrows the object as a `Map` of properties. + pub fn as_object_mut(&mut self) -> Option<&mut Map> { + self.0.as_object_mut() + } + + pub(crate) fn try_to_object(self) -> Result, bool> { + match self.0 { + Value::Object(m) => Ok(m), + Value::Bool(b) => Err(b), + _ => unreachable!(), + } + } + + /// Returns the `Schema`'s underlying JSON value. + pub fn to_value(self) -> Value { + self.0 + } + + /// Converts the `Schema` (if it wraps a bool value) into an equivalent object schema. Then mutably borrows the object as a `Map` of properties. /// - /// If `self` is a [`SchemaObject`] with `Some` [`reference`](struct.SchemaObject.html#structfield.reference) set, this returns `true`. - /// Otherwise, returns `false`. - pub fn is_ref(&self) -> bool { - match self { - Schema::Object(o) => o.is_ref(), + /// `true` is transformed into an empty schema `{}`, which successfully validates against all possible values. + /// `false` is transformed into the schema `{"not": {}}`, which does not successfully validate against any value. + pub fn ensure_object(&mut self) -> &mut Map { + if let Some(b) = self.as_bool() { + let mut map = Map::new(); + if !b { + map.insert("not".into(), Value::Object(Map::new())); + } + self.0 = Value::Object(map); + } + + self.as_object_mut() + .expect("Schema value should be of type Object.") + } + + pub(crate) fn has_type(&self, ty: &str) -> bool { + match self.0.get("type") { + Some(Value::Array(values)) => values.iter().any(|v| v.as_str() == Some(ty)), + Some(Value::String(s)) => s == ty, _ => false, } } - /// Converts the given schema (if it is a boolean schema) into an equivalent schema object. - /// - /// If the given schema is already a schema object, this has no effect. - /// - /// # Example - /// ``` - /// use schemars::schema::{Schema, SchemaObject}; - /// - /// let bool_schema = Schema::Bool(true); - /// - /// assert_eq!(bool_schema.into_object(), SchemaObject::default()); - /// ``` - pub fn into_object(self) -> SchemaObject { - match self { - Schema::Object(o) => o, - Schema::Bool(true) => SchemaObject::default(), - Schema::Bool(false) => SchemaObject { - subschemas: Some(Box::new(SubschemaValidation { - not: Some(Schema::Object(Default::default()).into()), - ..Default::default() - })), - ..Default::default() - }, - } + fn validate(value: &Value) -> Result<(), E> { + use serde::de::Unexpected; + let unexpected = match value { + Value::Bool(_) | Value::Object(_) => return Ok(()), + Value::Null => Unexpected::Unit, + Value::Number(n) => { + if let Some(u) = n.as_u64() { + Unexpected::Unsigned(u) + } else if let Some(i) = n.as_i64() { + Unexpected::Signed(i) + } else if let Some(f) = n.as_f64() { + Unexpected::Float(f) + } else { + unreachable!() + } + } + Value::String(s) => Unexpected::Str(s), + Value::Array(_) => Unexpected::Seq, + }; + + Err(E::invalid_type(unexpected, &"object or boolean")) + } + + #[allow(unsafe_code)] + #[ref_cast_custom] + fn ref_cast(value: &Value) -> &Self; + + #[allow(unsafe_code)] + #[ref_cast_custom] + fn ref_cast_mut(value: &mut Value) -> &mut Self; +} + +impl From for Value { + fn from(v: Schema) -> Value { + v.0 } } -impl From for Schema { - fn from(o: SchemaObject) -> Self { - Schema::Object(o) +impl std::convert::TryFrom for Schema { + type Error = serde_json::Error; + + fn try_from(value: Value) -> serde_json::Result { + Schema::validate(&value)?; + Ok(Schema(value)) + } +} + +impl<'a> std::convert::TryFrom<&'a Value> for &'a Schema { + type Error = serde_json::Error; + + fn try_from(value: &Value) -> serde_json::Result<&Schema> { + Schema::validate(value)?; + Ok(Schema::ref_cast(value)) + } +} + +impl<'a> std::convert::TryFrom<&'a mut Value> for &'a mut Schema { + type Error = serde_json::Error; + + fn try_from(value: &mut Value) -> serde_json::Result<&mut Schema> { + Schema::validate(value)?; + Ok(Schema::ref_cast_mut(value)) + } +} + +impl Default for Schema { + fn default() -> Self { + Self(Value::Object(Map::new())) + } +} + +impl From> for Schema { + fn from(o: Map) -> Self { + Schema(Value::Object(o)) } } impl From for Schema { fn from(b: bool) -> Self { - Schema::Bool(b) + Schema(Value::Bool(b)) } } -/// The root object of a JSON Schema document. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] -#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", default)] -pub struct RootSchema { - /// The `$schema` keyword. - /// - /// See [JSON Schema 8.1.1. The "$schema" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.1.1). - #[serde(rename = "$schema", skip_serializing_if = "Option::is_none")] - pub meta_schema: Option, - /// The root schema itself. - #[serde(flatten)] - pub schema: SchemaObject, - /// The `definitions` keyword. - /// - /// In JSON Schema draft 2019-09 this was replaced by $defs, but in Schemars this is still - /// serialized as `definitions` for backward-compatibility. - /// - /// See [JSON Schema 8.2.5. Schema Re-Use With "$defs"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.5), - /// and [JSON Schema (draft 07) 9. Schema Re-Use With "definitions"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-01#section-9). - #[serde(alias = "$defs", skip_serializing_if = "Map::is_empty")] - pub definitions: Map, -} +impl crate::JsonSchema for Schema { + fn schema_name() -> std::borrow::Cow<'static, str> { + "Schema".into() + } -/// A JSON Schema object. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] -#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", default)] -pub struct SchemaObject { - /// Properties which annotate the [`SchemaObject`] which typically have no effect when an object is being validated against the schema. - #[serde(flatten, deserialize_with = "skip_if_default")] - pub metadata: Option>, - /// The `type` keyword. - /// - /// See [JSON Schema Validation 6.1.1. "type"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.1) - /// and [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1). - #[serde(rename = "type", skip_serializing_if = "Option::is_none")] - pub instance_type: Option>, - /// The `format` keyword. - /// - /// See [JSON Schema Validation 7. A Vocabulary for Semantic Content With "format"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-7). - #[serde(skip_serializing_if = "Option::is_none")] - pub format: Option, - /// The `enum` keyword. - /// - /// See [JSON Schema Validation 6.1.2. "enum"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.2) - #[serde(rename = "enum", skip_serializing_if = "Option::is_none")] - pub enum_values: Option>, - /// The `const` keyword. - /// - /// See [JSON Schema Validation 6.1.3. "const"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.3) - #[serde( - rename = "const", - skip_serializing_if = "Option::is_none", - deserialize_with = "allow_null" - )] - pub const_value: Option, - /// Properties of the [`SchemaObject`] which define validation assertions in terms of other schemas. - #[serde(flatten, deserialize_with = "skip_if_default")] - pub subschemas: Option>, - /// Properties of the [`SchemaObject`] which define validation assertions for numbers. - #[serde(flatten, deserialize_with = "skip_if_default")] - pub number: Option>, - /// Properties of the [`SchemaObject`] which define validation assertions for strings. - #[serde(flatten, deserialize_with = "skip_if_default")] - pub string: Option>, - /// Properties of the [`SchemaObject`] which define validation assertions for arrays. - #[serde(flatten, deserialize_with = "skip_if_default")] - pub array: Option>, - /// Properties of the [`SchemaObject`] which define validation assertions for objects. - #[serde(flatten, deserialize_with = "skip_if_default")] - pub object: Option>, - /// The `$ref` keyword. - /// - /// See [JSON Schema 8.2.4.1. Direct References with "$ref"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.4.1). - #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")] - pub reference: Option, - /// Arbitrary extra properties which are not part of the JSON Schema specification, or which `schemars` does not support. - #[serde(flatten)] - pub extensions: Map, -} + fn schema_id() -> std::borrow::Cow<'static, str> { + "schemars::Schema".into() + } -// Deserializing "null" to `Option` directly results in `None`, -// this function instead makes it deserialize to `Some(Value::Null)`. -fn allow_null<'de, D>(de: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - Value::deserialize(de).map(Option::Some) -} - -fn skip_if_default<'de, D, T>(deserializer: D) -> Result>, D::Error> -where - D: serde::Deserializer<'de>, - T: Deserialize<'de> + Default + PartialEq, -{ - let value = T::deserialize(deserializer)?; - if value == T::default() { - Ok(None) - } else { - Ok(Some(Box::new(value))) + fn json_schema(_: &mut crate::gen::SchemaGenerator) -> Schema { + crate::json_schema!({ + "type": ["object", "boolean"] + }) } } -macro_rules! get_or_insert_default_fn { - ($name:ident, $ret:ty) => { - get_or_insert_default_fn!( - concat!( - "Returns a mutable reference to this schema's [`", - stringify!($ret), - "`](#structfield.", - stringify!($name), - "), creating it if it was `None`." - ), - $name, - $ret - ); - }; - ($doc:expr, $name:ident, $ret:ty) => { - #[doc = $doc] - pub fn $name(&mut self) -> &mut $ret { - self.$name.get_or_insert_with(Default::default) - } - }; -} +mod ser { + use serde::ser::{Serialize, SerializeMap, SerializeSeq}; + use serde_json::Value; -impl SchemaObject { - /// Creates a new `$ref` schema. - /// - /// The given reference string should be a URI reference. This will usually be a JSON Pointer - /// in [URI Fragment representation](https://tools.ietf.org/html/rfc6901#section-6). - pub fn new_ref(reference: String) -> Self { - SchemaObject { - reference: Some(reference), - ..Default::default() - } - } + // The order of properties in a JSON Schema object is insignificant, but we explicitly order + // some of them here to make them easier for a human to read. All other properties are ordered + // either lexicographically (by default) or by insertion order (if `preserve_order` is enabled) + const ORDERED_KEYWORDS_START: [&str; 7] = [ + "$id", + "$schema", + "title", + "description", + "type", + "format", + "properties", + ]; + const ORDERED_KEYWORDS_END: [&str; 2] = ["$defs", "definitions"]; - /// Returns `true` if `self` is a `$ref` schema. - /// - /// If `self` has `Some` [`reference`](struct.SchemaObject.html#structfield.reference) set, this returns `true`. - /// Otherwise, returns `false`. - pub fn is_ref(&self) -> bool { - self.reference.is_some() - } + pub(super) struct OrderedKeywordWrapper<'a>(pub &'a Value); - /// Returns `true` if `self` accepts values of the given type, according to the [`instance_type`](struct.SchemaObject.html#structfield.instance_type) field. - /// - /// This is a basic check that always returns `true` if no `instance_type` is specified on the schema, - /// and does not check any subschemas. Because of this, both `{}` and `{"not": {}}` accept any type according - /// to this method. - pub fn has_type(&self, ty: InstanceType) -> bool { - self.instance_type - .as_ref() - .map_or(true, |x| x.contains(&ty)) - } + impl Serialize for OrderedKeywordWrapper<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self.0 { + Value::Array(array) => { + let mut seq = serializer.serialize_seq(Some(array.len()))?; + for value in array { + seq.serialize_element(&OrderedKeywordWrapper(value))?; + } + seq.end() + } + Value::Object(object) => { + let mut map = serializer.serialize_map(Some(object.len()))?; - get_or_insert_default_fn!(metadata, Metadata); - get_or_insert_default_fn!(subschemas, SubschemaValidation); - get_or_insert_default_fn!(number, NumberValidation); - get_or_insert_default_fn!(string, StringValidation); - get_or_insert_default_fn!(array, ArrayValidation); - get_or_insert_default_fn!(object, ObjectValidation); -} + for key in ORDERED_KEYWORDS_START { + if let Some(value) = object.get(key) { + map.serialize_entry(key, &OrderedKeywordWrapper(value))?; + } + } -impl From for SchemaObject { - fn from(schema: Schema) -> Self { - schema.into_object() - } -} + for (key, value) in object { + if !ORDERED_KEYWORDS_START.contains(&key.as_str()) + && !ORDERED_KEYWORDS_END.contains(&key.as_str()) + { + map.serialize_entry(key, &OrderedKeywordWrapper(value))?; + } + } -/// Properties which annotate a [`SchemaObject`] which typically have no effect when an object is being validated against the schema. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] -#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", default)] -pub struct Metadata { - /// The `$id` keyword. - /// - /// See [JSON Schema 8.2.2. The "$id" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.2). - #[serde(rename = "$id", skip_serializing_if = "Option::is_none")] - pub id: Option, - /// The `title` keyword. - /// - /// See [JSON Schema Validation 9.1. "title" and "description"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1). - #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, - /// The `description` keyword. - /// - /// See [JSON Schema Validation 9.1. "title" and "description"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1). - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// The `default` keyword. - /// - /// See [JSON Schema Validation 9.2. "default"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.2). - #[serde( - skip_serializing_if = "Option::is_none", - deserialize_with = "allow_null" - )] - pub default: Option, - /// The `deprecated` keyword. - /// - /// See [JSON Schema Validation 9.3. "deprecated"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.3). - #[serde(skip_serializing_if = "is_false")] - pub deprecated: bool, - /// The `readOnly` keyword. - /// - /// See [JSON Schema Validation 9.4. "readOnly" and "writeOnly"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4). - #[serde(skip_serializing_if = "is_false")] - pub read_only: bool, - /// The `writeOnly` keyword. - /// - /// See [JSON Schema Validation 9.4. "readOnly" and "writeOnly"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4). - #[serde(skip_serializing_if = "is_false")] - pub write_only: bool, - /// The `examples` keyword. - /// - /// See [JSON Schema Validation 9.5. "examples"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.5). - #[serde(skip_serializing_if = "Vec::is_empty")] - pub examples: Vec, -} + for key in ORDERED_KEYWORDS_END { + if let Some(value) = object.get(key) { + map.serialize_entry(key, &OrderedKeywordWrapper(value))?; + } + } -#[allow(clippy::trivially_copy_pass_by_ref)] -fn is_false(b: &bool) -> bool { - !b -} - -/// Properties of a [`SchemaObject`] which define validation assertions in terms of other schemas. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] -#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", default)] -pub struct SubschemaValidation { - /// The `allOf` keyword. - /// - /// See [JSON Schema 9.2.1.1. "allOf"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.1). - #[serde(skip_serializing_if = "Option::is_none")] - pub all_of: Option>, - /// The `anyOf` keyword. - /// - /// See [JSON Schema 9.2.1.2. "anyOf"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.2). - #[serde(skip_serializing_if = "Option::is_none")] - pub any_of: Option>, - /// The `oneOf` keyword. - /// - /// See [JSON Schema 9.2.1.3. "oneOf"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.3). - #[serde(skip_serializing_if = "Option::is_none")] - pub one_of: Option>, - /// The `not` keyword. - /// - /// See [JSON Schema 9.2.1.4. "not"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.4). - #[serde(skip_serializing_if = "Option::is_none")] - pub not: Option>, - /// The `if` keyword. - /// - /// See [JSON Schema 9.2.2.1. "if"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.1). - #[serde(rename = "if", skip_serializing_if = "Option::is_none")] - pub if_schema: Option>, - /// The `then` keyword. - /// - /// See [JSON Schema 9.2.2.2. "then"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.2). - #[serde(rename = "then", skip_serializing_if = "Option::is_none")] - pub then_schema: Option>, - /// The `else` keyword. - /// - /// See [JSON Schema 9.2.2.3. "else"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.3). - #[serde(rename = "else", skip_serializing_if = "Option::is_none")] - pub else_schema: Option>, -} - -/// Properties of a [`SchemaObject`] which define validation assertions for numbers. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] -#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", default)] -pub struct NumberValidation { - /// The `multipleOf` keyword. - /// - /// See [JSON Schema Validation 6.2.1. "multipleOf"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.1). - #[serde(skip_serializing_if = "Option::is_none")] - pub multiple_of: Option, - /// The `maximum` keyword. - /// - /// See [JSON Schema Validation 6.2.2. "maximum"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.2). - #[serde(skip_serializing_if = "Option::is_none")] - pub maximum: Option, - /// The `exclusiveMaximum` keyword. - /// - /// See [JSON Schema Validation 6.2.3. "exclusiveMaximum"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.3). - #[serde(skip_serializing_if = "Option::is_none")] - pub exclusive_maximum: Option, - /// The `minimum` keyword. - /// - /// See [JSON Schema Validation 6.2.4. "minimum"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.4). - #[serde(skip_serializing_if = "Option::is_none")] - pub minimum: Option, - /// The `exclusiveMinimum` keyword. - /// - /// See [JSON Schema Validation 6.2.5. "exclusiveMinimum"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.5). - #[serde(skip_serializing_if = "Option::is_none")] - pub exclusive_minimum: Option, -} - -/// Properties of a [`SchemaObject`] which define validation assertions for strings. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] -#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", default)] -pub struct StringValidation { - /// The `maxLength` keyword. - /// - /// See [JSON Schema Validation 6.3.1. "maxLength"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.1). - #[serde(skip_serializing_if = "Option::is_none")] - pub max_length: Option, - /// The `minLength` keyword. - /// - /// See [JSON Schema Validation 6.3.2. "minLength"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.2). - #[serde(skip_serializing_if = "Option::is_none")] - pub min_length: Option, - /// The `pattern` keyword. - /// - /// See [JSON Schema Validation 6.3.3. "pattern"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.3). - #[serde(skip_serializing_if = "Option::is_none")] - pub pattern: Option, -} - -/// Properties of a [`SchemaObject`] which define validation assertions for arrays. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] -#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", default)] -pub struct ArrayValidation { - /// The `items` keyword. - /// - /// See [JSON Schema 9.3.1.1. "items"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.1). - #[serde(skip_serializing_if = "Option::is_none")] - pub items: Option>, - /// The `additionalItems` keyword. - /// - /// See [JSON Schema 9.3.1.2. "additionalItems"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.2). - #[serde(skip_serializing_if = "Option::is_none")] - pub additional_items: Option>, - /// The `maxItems` keyword. - /// - /// See [JSON Schema Validation 6.4.1. "maxItems"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.1). - #[serde(skip_serializing_if = "Option::is_none")] - pub max_items: Option, - /// The `minItems` keyword. - /// - /// See [JSON Schema Validation 6.4.2. "minItems"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.2). - #[serde(skip_serializing_if = "Option::is_none")] - pub min_items: Option, - /// The `uniqueItems` keyword. - /// - /// See [JSON Schema Validation 6.4.3. "uniqueItems"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.3). - #[serde(skip_serializing_if = "Option::is_none")] - pub unique_items: Option, - /// The `contains` keyword. - /// - /// See [JSON Schema 9.3.1.4. "contains"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.4). - #[serde(skip_serializing_if = "Option::is_none")] - pub contains: Option>, -} - -/// Properties of a [`SchemaObject`] which define validation assertions for objects. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] -#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", default)] -pub struct ObjectValidation { - /// The `maxProperties` keyword. - /// - /// See [JSON Schema Validation 6.5.1. "maxProperties"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.1). - #[serde(skip_serializing_if = "Option::is_none")] - pub max_properties: Option, - /// The `minProperties` keyword. - /// - /// See [JSON Schema Validation 6.5.2. "minProperties"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.2). - #[serde(skip_serializing_if = "Option::is_none")] - pub min_properties: Option, - /// The `required` keyword. - /// - /// See [JSON Schema Validation 6.5.3. "required"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.3). - #[serde(skip_serializing_if = "Set::is_empty")] - pub required: Set, - /// The `properties` keyword. - /// - /// See [JSON Schema 9.3.2.1. "properties"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.1). - #[serde(skip_serializing_if = "Map::is_empty")] - pub properties: Map, - /// The `patternProperties` keyword. - /// - /// See [JSON Schema 9.3.2.2. "patternProperties"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.2). - #[serde(skip_serializing_if = "Map::is_empty")] - pub pattern_properties: Map, - /// The `additionalProperties` keyword. - /// - /// See [JSON Schema 9.3.2.3. "additionalProperties"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.3). - #[serde(skip_serializing_if = "Option::is_none")] - pub additional_properties: Option>, - /// The `propertyNames` keyword. - /// - /// See [JSON Schema 9.3.2.5. "propertyNames"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.5). - #[serde(skip_serializing_if = "Option::is_none")] - pub property_names: Option>, -} - -/// The possible types of values in JSON Schema documents. -/// -/// See [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1). -#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase")] -pub enum InstanceType { - Null, - Boolean, - Object, - Array, - Number, - String, - Integer, -} - -/// A type which can be serialized as a single item, or multiple items. -/// -/// In some contexts, a `Single` may be semantically distinct from a `Vec` containing only item. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] -#[serde(untagged)] -pub enum SingleOrVec { - Single(Box), - Vec(Vec), -} - -impl From for SingleOrVec { - fn from(single: T) -> Self { - SingleOrVec::Single(Box::new(single)) - } -} - -impl From> for SingleOrVec { - fn from(vec: Vec) -> Self { - SingleOrVec::Vec(vec) - } -} - -impl SingleOrVec { - /// Returns `true` if `self` is either a `Single` equal to `x`, or a `Vec` containing `x`. - /// - /// # Examples - /// - /// ``` - /// use schemars::schema::SingleOrVec; - /// - /// let s = SingleOrVec::from(10); - /// assert!(s.contains(&10)); - /// assert!(!s.contains(&20)); - /// - /// let v = SingleOrVec::from(vec![10, 20]); - /// assert!(v.contains(&10)); - /// assert!(v.contains(&20)); - /// assert!(!v.contains(&30)); - /// ``` - pub fn contains(&self, x: &T) -> bool { - match self { - SingleOrVec::Single(s) => s.deref() == x, - SingleOrVec::Vec(v) => v.contains(x), + map.end() + } + _ => self.0.serialize(serializer), + } } } } diff --git a/schemars/src/ser.rs b/schemars/src/ser.rs index 3f69bef..f42c187 100644 --- a/schemars/src/ser.rs +++ b/schemars/src/ser.rs @@ -1,8 +1,6 @@ -use crate::schema::*; -use crate::JsonSchema; -use crate::{gen::SchemaGenerator, Map}; -use serde_json::{Error, Value}; -use std::{convert::TryInto, fmt::Display}; +use crate::{json_schema, JsonSchema, Schema, SchemaGenerator}; +use serde_json::{Error, Map, Value}; +use std::fmt::Display; pub(crate) struct Serializer<'a> { pub(crate) gen: &'a mut SchemaGenerator, @@ -22,7 +20,7 @@ pub(crate) struct SerializeTuple<'a> { pub(crate) struct SerializeMap<'a> { gen: &'a mut SchemaGenerator, - properties: Map, + properties: Map, current_key: Option, title: &'static str, } @@ -36,13 +34,11 @@ macro_rules! forward_to_subschema_for { } macro_rules! return_instance_type { - ($fn:ident, $ty:ty, $instance_type:ident) => { + ($fn:ident, $ty:ty, $instance_type:expr) => { fn $fn(self, _value: $ty) -> Result { - Ok(SchemaObject { - instance_type: Some(InstanceType::$instance_type.into()), - ..Default::default() - } - .into()) + Ok(json_schema!({ + "type": $instance_type + })) } }; } @@ -59,18 +55,18 @@ impl<'a> serde::Serializer for Serializer<'a> { type SerializeStruct = SerializeMap<'a>; type SerializeStructVariant = Self; - return_instance_type!(serialize_i8, i8, Integer); - return_instance_type!(serialize_i16, i16, Integer); - return_instance_type!(serialize_i32, i32, Integer); - return_instance_type!(serialize_i64, i64, Integer); - return_instance_type!(serialize_i128, i128, Integer); - return_instance_type!(serialize_u8, u8, Integer); - return_instance_type!(serialize_u16, u16, Integer); - return_instance_type!(serialize_u32, u32, Integer); - return_instance_type!(serialize_u64, u64, Integer); - return_instance_type!(serialize_u128, u128, Integer); - return_instance_type!(serialize_f32, f32, Number); - return_instance_type!(serialize_f64, f64, Number); + return_instance_type!(serialize_i8, i8, "integer"); + return_instance_type!(serialize_i16, i16, "integer"); + return_instance_type!(serialize_i32, i32, "integer"); + return_instance_type!(serialize_i64, i64, "integer"); + return_instance_type!(serialize_i128, i128, "integer"); + return_instance_type!(serialize_u8, u8, "integer"); + return_instance_type!(serialize_u16, u16, "integer"); + return_instance_type!(serialize_u32, u32, "integer"); + return_instance_type!(serialize_u64, u64, "integer"); + return_instance_type!(serialize_u128, u128, "integer"); + return_instance_type!(serialize_f32, f32, "number"); + return_instance_type!(serialize_f64, f64, "number"); forward_to_subschema_for!(serialize_bool, bool); forward_to_subschema_for!(serialize_char, char); @@ -93,7 +89,7 @@ impl<'a> serde::Serializer for Serializer<'a> { let value_schema = iter .into_iter() .try_fold(None, |acc, (_, v)| { - if acc == Some(Schema::Bool(true)) { + if acc == Some(true.into()) { return Ok(acc); } @@ -103,21 +99,16 @@ impl<'a> serde::Serializer for Serializer<'a> { })?; Ok(match &acc { None => Some(schema), - Some(items) if items != &schema => Some(Schema::Bool(true)), + Some(items) if items != &schema => Some(true.into()), _ => acc, }) })? - .unwrap_or(Schema::Bool(true)); + .unwrap_or(true.into()); - Ok(SchemaObject { - instance_type: Some(InstanceType::Object.into()), - object: Some(Box::new(ObjectValidation { - additional_properties: Some(Box::new(value_schema)), - ..ObjectValidation::default() - })), - ..SchemaObject::default() - } - .into()) + Ok(json_schema!({ + "type": "object", + "additionalProperties": value_schema, + })) } fn serialize_none(self) -> Result { @@ -132,52 +123,47 @@ impl<'a> serde::Serializer for Serializer<'a> { where T: serde::Serialize, { - // FIXME nasty duplication of `impl JsonSchema for Option` - fn add_null_type(instance_type: &mut SingleOrVec) { - match instance_type { - SingleOrVec::Single(ty) if **ty != InstanceType::Null => { - *instance_type = vec![**ty, InstanceType::Null].into() - } - SingleOrVec::Vec(ty) if !ty.contains(&InstanceType::Null) => { - ty.push(InstanceType::Null) - } - _ => {} - }; - } - let mut schema = value.serialize(Serializer { gen: self.gen, include_title: false, })?; if self.gen.settings().option_add_null_type { - schema = match schema { - Schema::Bool(true) => Schema::Bool(true), - Schema::Bool(false) => <()>::json_schema(self.gen), - Schema::Object(SchemaObject { - instance_type: Some(ref mut instance_type), - .. - }) => { - add_null_type(instance_type); - schema + schema = match schema.try_to_object() { + Ok(mut obj) => { + let value = obj.get_mut("type"); + match value { + Some(Value::Array(array)) => { + let null = Value::from("null"); + if !array.contains(&null) { + array.push(null); + } + obj.into() + } + Some(Value::String(string)) => { + if string != "null" { + *value.unwrap() = + Value::Array(vec![std::mem::take(string).into(), "null".into()]) + } + obj.into() + } + _ => json_schema!({ + "anyOf": [ + obj, + <()>::json_schema(self.gen) + ] + }), + } } - schema => SchemaObject { - subschemas: Some(Box::new(SubschemaValidation { - any_of: Some(vec![schema, <()>::json_schema(self.gen)]), - ..Default::default() - })), - ..Default::default() - } - .into(), + Err(true) => true.into(), + Err(false) => <()>::json_schema(self.gen), } } if self.gen.settings().option_nullable { - let mut schema_obj = schema.into_object(); - schema_obj - .extensions - .insert("nullable".to_owned(), serde_json::json!(true)); - schema = Schema::Object(schema_obj); + schema + .ensure_object() + .insert("nullable".into(), true.into()); }; Ok(schema) @@ -193,7 +179,7 @@ impl<'a> serde::Serializer for Serializer<'a> { _variant_index: u32, _variant: &'static str, ) -> Result { - Ok(Schema::Bool(true)) + Ok(true.into()) } fn serialize_newtype_struct( @@ -205,15 +191,13 @@ impl<'a> serde::Serializer for Serializer<'a> { T: serde::Serialize, { let include_title = self.include_title; - let mut result = value.serialize(self); + let mut schema = value.serialize(self)?; - if include_title { - if let Ok(Schema::Object(ref mut object)) = result { - object.metadata().title = Some(name.to_string()); - } + if include_title && !name.is_empty() { + schema.ensure_object().insert("title".into(), name.into()); } - result + Ok(schema) } fn serialize_newtype_variant( @@ -226,7 +210,7 @@ impl<'a> serde::Serializer for Serializer<'a> { where T: serde::Serialize, { - Ok(Schema::Bool(true)) + Ok(true.into()) } fn serialize_seq(self, _len: Option) -> Result { @@ -313,7 +297,7 @@ impl serde::ser::SerializeTupleVariant for Serializer<'_> { } fn end(self) -> Result { - Ok(Schema::Bool(true)) + Ok(true.into()) } } @@ -333,7 +317,7 @@ impl serde::ser::SerializeStructVariant for Serializer<'_> { } fn end(self) -> Result { - Ok(Schema::Bool(true)) + Ok(true.into()) } } @@ -345,7 +329,7 @@ impl serde::ser::SerializeSeq for SerializeSeq<'_> { where T: serde::Serialize, { - if self.items != Some(Schema::Bool(true)) { + if self.items != Some(true.into()) { let schema = value.serialize(Serializer { gen: self.gen, include_title: false, @@ -354,7 +338,7 @@ impl serde::ser::SerializeSeq for SerializeSeq<'_> { None => self.items = Some(schema), Some(items) => { if items != &schema { - self.items = Some(Schema::Bool(true)) + self.items = Some(true.into()) } } } @@ -364,16 +348,12 @@ impl serde::ser::SerializeSeq for SerializeSeq<'_> { } fn end(self) -> Result { - let items = self.items.unwrap_or(Schema::Bool(true)); - Ok(SchemaObject { - instance_type: Some(InstanceType::Array.into()), - array: Some(Box::new(ArrayValidation { - items: Some(items.into()), - ..ArrayValidation::default() - })), - ..SchemaObject::default() - } - .into()) + let items = self.items.unwrap_or(true.into()); + + Ok(json_schema!({ + "type": "array", + "items": items + })) } } @@ -394,23 +374,21 @@ impl serde::ser::SerializeTuple for SerializeTuple<'_> { } fn end(self) -> Result { - let len = self.items.len().try_into().ok(); - let mut schema = SchemaObject { - instance_type: Some(InstanceType::Array.into()), - array: Some(Box::new(ArrayValidation { - items: Some(SingleOrVec::Vec(self.items)), - max_items: len, - min_items: len, - ..ArrayValidation::default() - })), - ..SchemaObject::default() - }; + let len = self.items.len(); + let mut schema = json_schema!({ + "type": "array", + "prefixItems": self.items, + "maxItems": len, + "minItems": len, + }); if !self.title.is_empty() { - schema.metadata().title = Some(self.title.to_owned()); + schema + .ensure_object() + .insert("title".into(), self.title.into()); } - Ok(schema.into()) + Ok(schema) } } @@ -459,26 +437,24 @@ impl serde::ser::SerializeMap for SerializeMap<'_> { gen: self.gen, include_title: false, })?; - self.properties.insert(key, schema); + self.properties.insert(key, schema.into()); Ok(()) } fn end(self) -> Result { - let mut schema = SchemaObject { - instance_type: Some(InstanceType::Object.into()), - object: Some(Box::new(ObjectValidation { - properties: self.properties, - ..ObjectValidation::default() - })), - ..SchemaObject::default() - }; + let mut schema = json_schema!({ + "type": "object", + "properties": self.properties, + }); if !self.title.is_empty() { - schema.metadata().title = Some(self.title.to_owned()); + schema + .ensure_object() + .insert("title".into(), self.title.into()); } - Ok(schema.into()) + Ok(schema) } } @@ -498,7 +474,7 @@ impl serde::ser::SerializeStruct for SerializeMap<'_> { gen: self.gen, include_title: false, })?; - self.properties.insert(key.to_string(), prop_schema); + self.properties.insert(key.to_string(), prop_schema.into()); Ok(()) } diff --git a/schemars/src/transform.rs b/schemars/src/transform.rs new file mode 100644 index 0000000..7482b31 --- /dev/null +++ b/schemars/src/transform.rs @@ -0,0 +1,371 @@ +/*! +Contains the [`Transform`] trait, used to modify a constructed schema and optionally its subschemas. +This trait is automatically implemented for functions of the form `fn(&mut Schema) -> ()`. + +# Recursive Transforms + +To make a transform recursive (i.e. apply it to subschemas), you have two options: +1. call the [`transform_subschemas`] function within the transform function +2. wrap the `Transform` in a [`RecursiveTransform`] + +# Examples + +To add a custom property to all object schemas: + +``` +# use schemars::{Schema, json_schema}; +use schemars::transform::{Transform, transform_subschemas}; + +pub struct MyTransform; + +impl Transform for MyTransform { + fn transform(&mut self, schema: &mut Schema) { + // First, make our change to this schema + if let Some(obj) = schema.as_object_mut() { + obj.insert("my_property".to_string(), serde_json::json!("hello world")); + } + + // Then apply the transform to any subschemas + transform_subschemas(self, schema); + } +} + +let mut schema = json_schema!({ + "type": "array", + "items": {} +}); + +MyTransform.transform(&mut schema); + +assert_eq!( + schema, + json_schema!({ + "type": "array", + "items": { + "my_property": "hello world" + }, + "my_property": "hello world" + }) +); +``` + +The same example with a `fn` transform`: +``` +# use schemars::{Schema, json_schema}; +use schemars::transform::transform_subschemas; + +fn add_property(schema: &mut Schema) { + if let Some(obj) = schema.as_object_mut() { + obj.insert("my_property".to_string(), serde_json::json!("hello world")); + } + + transform_subschemas(&mut add_property, schema) +} + +let mut schema = json_schema!({ + "type": "array", + "items": {} +}); + +add_property(&mut schema); + +assert_eq!( + schema, + json_schema!({ + "type": "array", + "items": { + "my_property": "hello world" + }, + "my_property": "hello world" + }) +); +``` + +And the same example using a closure wrapped in a `RecursiveTransform`: +``` +# use schemars::{Schema, json_schema}; +use schemars::transform::{Transform, RecursiveTransform}; + +let mut transform = RecursiveTransform(|schema: &mut Schema| { + if let Some(obj) = schema.as_object_mut() { + obj.insert("my_property".to_string(), serde_json::json!("hello world")); + } +}); + +let mut schema = json_schema!({ + "type": "array", + "items": {} +}); + +transform.transform(&mut schema); + +assert_eq!( + schema, + json_schema!({ + "type": "array", + "items": { + "my_property": "hello world" + }, + "my_property": "hello world" + }) +); +``` + +*/ +use serde_json::{json, Value}; + +use crate::Schema; + +/// Trait used to modify a constructed schema and optionally its subschemas. +/// +/// See the [module documentation](self) for more details on implementing this trait. +pub trait Transform { + /// Applies the transform to the given [`Schema`]. + /// + /// When overriding this method, you may want to call the [`transform_subschemas`] function to also transform any subschemas. + fn transform(&mut self, schema: &mut Schema); + + // Not public API + // Hack to enable implementing Debug on Box even though closures don't implement Debug + #[doc(hidden)] + fn _debug_type_name(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(std::any::type_name::()) + } +} + +impl Transform for F +where + F: FnMut(&mut Schema), +{ + fn transform(&mut self, schema: &mut Schema) { + self(schema) + } +} + +/// Applies the given [`Transform`] to all direct subschemas of the [`Schema`]. +pub fn transform_subschemas(t: &mut T, schema: &mut Schema) { + if let Some(obj) = schema.as_object_mut() { + for (key, value) in obj { + // This is intentionally written to work with multiple JSON Schema versions, so that + // users can add their own transforms on the end of e.g. `SchemaSettings::draft07()` and + // they will still apply to all subschemas "as expected". + // This is why this match statement contains both `additionalProperties` (which was + // dropped in draft 2020-12) and `prefixItems` (which was added in draft 2020-12). + match key.as_str() { + "not" + | "if" + | "then" + | "else" + | "contains" + | "additionalProperties" + | "propertyNames" + | "additionalItems" => { + if let Ok(subschema) = value.try_into() { + t.transform(subschema) + } + } + "allOf" | "anyOf" | "oneOf" | "prefixItems" => { + if let Some(array) = value.as_array_mut() { + for value in array { + if let Ok(subschema) = value.try_into() { + t.transform(subschema) + } + } + } + } + // Support `items` array even though this is not allowed in draft 2020-12 (see above comment) + "items" => { + if let Some(array) = value.as_array_mut() { + for value in array { + if let Ok(subschema) = value.try_into() { + t.transform(subschema) + } + } + } else if let Ok(subschema) = value.try_into() { + t.transform(subschema) + } + } + "properties" | "patternProperties" | "$defs" | "definitions" => { + if let Some(obj) = value.as_object_mut() { + for value in obj.values_mut() { + if let Ok(subschema) = value.try_into() { + t.transform(subschema) + } + } + } + } + _ => {} + } + } + } +} + +/// A helper struct that can wrap a non-recursive [`Transform`] (i.e. one that does not apply to subschemas) into a recursive one. +/// +/// Its implementation of `Transform` will first apply the inner transform to the "parent" schema, and then its subschemas (and their subschemas, and so on). +/// +/// # Example +/// ``` +/// # use schemars::{Schema, json_schema}; +/// use schemars::transform::{Transform, RecursiveTransform}; +/// +/// let mut transform = RecursiveTransform(|schema: &mut Schema| { +/// if let Some(obj) = schema.as_object_mut() { +/// obj.insert("my_property".to_string(), serde_json::json!("hello world")); +/// } +/// }); +/// +/// let mut schema = json_schema!({ +/// "type": "array", +/// "items": {} +/// }); +/// +/// transform.transform(&mut schema); +/// +/// assert_eq!( +/// schema, +/// json_schema!({ +/// "type": "array", +/// "items": { +/// "my_property": "hello world" +/// }, +/// "my_property": "hello world" +/// }) +/// ); +/// ``` +#[derive(Debug, Clone)] +pub struct RecursiveTransform(pub T); + +impl Transform for RecursiveTransform +where + T: Transform, +{ + fn transform(&mut self, schema: &mut Schema) { + self.0.transform(schema); + transform_subschemas(self, schema); + } +} + +/// Replaces boolean JSON Schemas with equivalent object schemas. +/// This also applies to subschemas. +/// +/// This is useful for dialects of JSON Schema (e.g. OpenAPI 3.0) that do not support booleans as schemas. +#[derive(Debug, Clone)] +pub struct ReplaceBoolSchemas { + /// When set to `true`, a schema's `additionalProperties` property will not be changed from a boolean. + pub skip_additional_properties: bool, +} + +impl Transform for ReplaceBoolSchemas { + fn transform(&mut self, schema: &mut Schema) { + if let Some(obj) = schema.as_object_mut() { + if self.skip_additional_properties { + if let Some((ap_key, ap_value)) = obj.remove_entry("additionalProperties") { + transform_subschemas(self, schema); + + if let Some(obj) = schema.as_object_mut() { + obj.insert(ap_key, ap_value); + } + + return; + } + } + + transform_subschemas(self, schema); + } else { + schema.ensure_object(); + } + } +} + +/// Restructures JSON Schema objects so that the `$ref` property will never appear alongside any other properties. +/// This also applies to subschemas. +/// +/// This is useful for versions of JSON Schema (e.g. Draft 7) that do not support other properties alongside `$ref`. +#[derive(Debug, Clone)] +pub struct RemoveRefSiblings; + +impl Transform for RemoveRefSiblings { + fn transform(&mut self, schema: &mut Schema) { + transform_subschemas(self, schema); + + if let Some(obj) = schema.as_object_mut() { + if obj.len() > 1 { + if let Some(ref_value) = obj.remove("$ref") { + if let Value::Array(all_of) = + obj.entry("allOf").or_insert(Value::Array(Vec::new())) + { + all_of.push(json!({ + "$ref": ref_value + })); + } + } + } + } + } +} + +/// Removes the `examples` schema property and (if present) set its first value as the `example` property. +/// This also applies to subschemas. +/// +/// This is useful for dialects of JSON Schema (e.g. OpenAPI 3.0) that do not support the `examples` property. +#[derive(Debug, Clone)] +pub struct SetSingleExample; + +impl Transform for SetSingleExample { + fn transform(&mut self, schema: &mut Schema) { + transform_subschemas(self, schema); + + if let Some(obj) = schema.as_object_mut() { + if let Some(Value::Array(examples)) = obj.remove("examples") { + if let Some(first_example) = examples.into_iter().next() { + obj.insert("example".into(), first_example); + } + } + } + } +} + +/// Replaces the `const` schema property with a single-valued `enum` property. +/// This also applies to subschemas. +/// +/// This is useful for dialects of JSON Schema (e.g. OpenAPI 3.0) that do not support the `const` property. +#[derive(Debug, Clone)] +pub struct ReplaceConstValue; + +impl Transform for ReplaceConstValue { + fn transform(&mut self, schema: &mut Schema) { + transform_subschemas(self, schema); + + if let Some(obj) = schema.as_object_mut() { + if let Some(value) = obj.remove("const") { + obj.insert("enum".into(), Value::Array(vec![value])); + } + } + } +} + +/// Rename the `prefixItems` schema property to `items`. +/// This also applies to subschemas. +/// +/// If the schema contains both `prefixItems` and `items`, then this additionally renames `items` to `additionalItems`. +/// +/// This is useful for versions of JSON Schema (e.g. Draft 7) that do not support the `prefixItems` property. +#[derive(Debug, Clone)] +pub struct ReplacePrefixItems; + +impl Transform for ReplacePrefixItems { + fn transform(&mut self, schema: &mut Schema) { + transform_subschemas(self, schema); + + if let Some(obj) = schema.as_object_mut() { + if let Some(prefix_items) = obj.remove("prefixItems") { + let previous_items = obj.insert("items".to_owned(), prefix_items); + + if let Some(previous_items) = previous_items { + obj.insert("additionalItems".to_owned(), previous_items); + } + } + } + } +} diff --git a/schemars/src/visit.rs b/schemars/src/visit.rs deleted file mode 100644 index 182b572..0000000 --- a/schemars/src/visit.rs +++ /dev/null @@ -1,212 +0,0 @@ -/*! -Contains the [`Visitor`] trait, used to recursively modify a constructed schema and its subschemas. - -Sometimes you may want to apply a change to a schema, as well as all schemas contained within it. -The easiest way to achieve this is by defining a type that implements [`Visitor`]. -All methods of `Visitor` have a default implementation that makes no change but recursively visits all subschemas. -When overriding one of these methods, you will *usually* want to still call this default implementation. - -# Example -To add a custom property to all schemas: -``` -use schemars::schema::SchemaObject; -use schemars::visit::{Visitor, visit_schema_object}; - -pub struct MyVisitor; - -impl Visitor for MyVisitor { - fn visit_schema_object(&mut self, schema: &mut SchemaObject) { - // First, make our change to this schema - schema.extensions.insert("my_property".to_string(), serde_json::json!("hello world")); - - // Then delegate to default implementation to visit any subschemas - visit_schema_object(self, schema); - } -} -``` -*/ -use crate::schema::{RootSchema, Schema, SchemaObject, SingleOrVec}; - -/// Trait used to recursively modify a constructed schema and its subschemas. -pub trait Visitor { - /// Override this method to modify a [`RootSchema`] and (optionally) its subschemas. - /// - /// When overriding this method, you will usually want to call the [`visit_root_schema`] function to visit subschemas. - fn visit_root_schema(&mut self, root: &mut RootSchema) { - visit_root_schema(self, root) - } - - /// Override this method to modify a [`Schema`] and (optionally) its subschemas. - /// - /// When overriding this method, you will usually want to call the [`visit_schema`] function to visit subschemas. - fn visit_schema(&mut self, schema: &mut Schema) { - visit_schema(self, schema) - } - - /// Override this method to modify a [`SchemaObject`] and (optionally) its subschemas. - /// - /// When overriding this method, you will usually want to call the [`visit_schema_object`] function to visit subschemas. - fn visit_schema_object(&mut self, schema: &mut SchemaObject) { - visit_schema_object(self, schema) - } -} - -/// Visits all subschemas of the [`RootSchema`]. -pub fn visit_root_schema(v: &mut V, root: &mut RootSchema) { - v.visit_schema_object(&mut root.schema); - visit_map_values(v, &mut root.definitions); -} - -/// Visits all subschemas of the [`Schema`]. -pub fn visit_schema(v: &mut V, schema: &mut Schema) { - if let Schema::Object(schema) = schema { - v.visit_schema_object(schema) - } -} - -/// Visits all subschemas of the [`SchemaObject`]. -pub fn visit_schema_object(v: &mut V, schema: &mut SchemaObject) { - if let Some(sub) = &mut schema.subschemas { - visit_vec(v, &mut sub.all_of); - visit_vec(v, &mut sub.any_of); - visit_vec(v, &mut sub.one_of); - visit_box(v, &mut sub.not); - visit_box(v, &mut sub.if_schema); - visit_box(v, &mut sub.then_schema); - visit_box(v, &mut sub.else_schema); - } - - if let Some(arr) = &mut schema.array { - visit_single_or_vec(v, &mut arr.items); - visit_box(v, &mut arr.additional_items); - visit_box(v, &mut arr.contains); - } - - if let Some(obj) = &mut schema.object { - visit_map_values(v, &mut obj.properties); - visit_map_values(v, &mut obj.pattern_properties); - visit_box(v, &mut obj.additional_properties); - visit_box(v, &mut obj.property_names); - } -} - -fn visit_box(v: &mut V, target: &mut Option>) { - if let Some(s) = target { - v.visit_schema(s) - } -} - -fn visit_vec(v: &mut V, target: &mut Option>) { - if let Some(vec) = target { - for s in vec { - v.visit_schema(s) - } - } -} - -fn visit_map_values(v: &mut V, target: &mut crate::Map) { - for s in target.values_mut() { - v.visit_schema(s) - } -} - -fn visit_single_or_vec(v: &mut V, target: &mut Option>) { - match target { - None => {} - Some(SingleOrVec::Single(s)) => v.visit_schema(s), - Some(SingleOrVec::Vec(vec)) => { - for s in vec { - v.visit_schema(s) - } - } - } -} - -/// This visitor will replace all boolean JSON Schemas with equivalent object schemas. -/// -/// This is useful for dialects of JSON Schema (e.g. OpenAPI 3.0) that do not support booleans as schemas. -#[derive(Debug, Clone)] -pub struct ReplaceBoolSchemas { - /// When set to `true`, a schema's `additionalProperties` property will not be changed from a boolean. - pub skip_additional_properties: bool, -} - -impl Visitor for ReplaceBoolSchemas { - fn visit_schema(&mut self, schema: &mut Schema) { - visit_schema(self, schema); - - if let Schema::Bool(b) = *schema { - *schema = Schema::Bool(b).into_object().into() - } - } - - fn visit_schema_object(&mut self, schema: &mut SchemaObject) { - if self.skip_additional_properties { - if let Some(obj) = &mut schema.object { - if let Some(ap) = &obj.additional_properties { - if let Schema::Bool(_) = ap.as_ref() { - let additional_properties = obj.additional_properties.take(); - visit_schema_object(self, schema); - schema.object().additional_properties = additional_properties; - - return; - } - } - } - } - - visit_schema_object(self, schema); - } -} - -/// This visitor will restructure JSON Schema objects so that the `$ref` property will never appear alongside any other properties. -/// -/// This is useful for dialects of JSON Schema (e.g. Draft 7) that do not support other properties alongside `$ref`. -#[derive(Debug, Clone)] -pub struct RemoveRefSiblings; - -impl Visitor for RemoveRefSiblings { - fn visit_schema_object(&mut self, schema: &mut SchemaObject) { - visit_schema_object(self, schema); - - if let Some(reference) = schema.reference.take() { - if schema == &SchemaObject::default() { - schema.reference = Some(reference); - } else { - let ref_schema = Schema::new_ref(reference); - let all_of = &mut schema.subschemas().all_of; - match all_of { - Some(vec) => vec.push(ref_schema), - None => *all_of = Some(vec![ref_schema]), - } - } - } - } -} - -/// This visitor will remove the `examples` schema property and (if present) set its first value as the `example` property. -/// -/// This is useful for dialects of JSON Schema (e.g. OpenAPI 3.0) that do not support the `examples` property. -#[derive(Debug, Clone)] -pub struct SetSingleExample { - /// When set to `true`, the `examples` property will not be removed, but its first value will still be copied to `example`. - pub retain_examples: bool, -} - -impl Visitor for SetSingleExample { - fn visit_schema_object(&mut self, schema: &mut SchemaObject) { - visit_schema_object(self, schema); - - let first_example = schema.metadata.as_mut().and_then(|m| { - if self.retain_examples { - m.examples.first().cloned() - } else { - m.examples.drain(..).next() - } - }); - - if let Some(example) = first_example { - schema.extensions.insert("example".to_owned(), example); - } - } -} diff --git a/schemars/tests/arrayvec.rs b/schemars/tests/arrayvec.rs index b00b395..e32d1a7 100644 --- a/schemars/tests/arrayvec.rs +++ b/schemars/tests/arrayvec.rs @@ -1,16 +1,6 @@ mod util; use util::*; -#[test] -fn arrayvec05() -> TestResult { - test_default_generated_schema::>("arrayvec") -} - -#[test] -fn arrayvec05_string() -> TestResult { - test_default_generated_schema::>("arrayvec_string") -} - #[test] fn arrayvec07() -> TestResult { test_default_generated_schema::>("arrayvec") diff --git a/schemars/tests/bytes.rs b/schemars/tests/bytes.rs index 688ab21..5797411 100644 --- a/schemars/tests/bytes.rs +++ b/schemars/tests/bytes.rs @@ -1,5 +1,5 @@ mod util; -use bytes::{Bytes, BytesMut}; +use bytes1::{Bytes, BytesMut}; use util::*; #[test] diff --git a/schemars/tests/chrono.rs b/schemars/tests/chrono.rs index 7f518d7..da6f8d4 100644 --- a/schemars/tests/chrono.rs +++ b/schemars/tests/chrono.rs @@ -1,5 +1,5 @@ mod util; -use chrono::prelude::*; +use chrono04::prelude::*; use schemars::JsonSchema; use util::*; diff --git a/schemars/tests/decimal.rs b/schemars/tests/decimal.rs index d245583..246e813 100644 --- a/schemars/tests/decimal.rs +++ b/schemars/tests/decimal.rs @@ -3,12 +3,7 @@ use util::*; #[test] fn rust_decimal() -> TestResult { - test_default_generated_schema::("rust_decimal") -} - -#[test] -fn bigdecimal03() -> TestResult { - test_default_generated_schema::("bigdecimal03") + test_default_generated_schema::("rust_decimal") } #[test] diff --git a/schemars/tests/dereference.rs b/schemars/tests/dereference.rs deleted file mode 100644 index b1abab0..0000000 --- a/schemars/tests/dereference.rs +++ /dev/null @@ -1,23 +0,0 @@ -use schemars::{gen::SchemaGenerator, JsonSchema}; -use std::ptr; - -#[allow(dead_code)] -#[derive(JsonSchema)] -struct Struct { - foo: i32, - bar: bool, -} - -#[test] -fn dereference_struct() { - let mut gen = SchemaGenerator::default(); - let struct_ref_schema = gen.subschema_for::(); - let struct_schema = gen.definitions().get(&::schema_name()).unwrap(); - - assert!(struct_ref_schema.is_ref()); - assert!(!struct_schema.is_ref()); - - let dereferenced = gen.dereference(&struct_ref_schema); - assert!(dereferenced.is_some()); - assert!(ptr::eq(dereferenced.unwrap(), struct_schema)); -} diff --git a/schemars/tests/docs.rs b/schemars/tests/docs.rs index 788140d..7d1d6d8 100644 --- a/schemars/tests/docs.rs +++ b/schemars/tests/docs.rs @@ -5,12 +5,10 @@ use util::*; #[allow(dead_code)] #[derive(JsonSchema)] /** - * - * # This is the struct's title - * - * This is the struct's description. - * - */ +# This is the struct's title + +This is the struct's description. +*/ struct MyStruct { /// # An integer my_int: i32, diff --git a/schemars/tests/either.rs b/schemars/tests/either.rs index 16a2e59..5dcd079 100644 --- a/schemars/tests/either.rs +++ b/schemars/tests/either.rs @@ -1,5 +1,5 @@ mod util; -use either::Either; +use either1::Either; use util::*; #[test] diff --git a/schemars/tests/enum.rs b/schemars/tests/enum.rs index 1ed6046..4c57f34 100644 --- a/schemars/tests/enum.rs +++ b/schemars/tests/enum.rs @@ -1,5 +1,7 @@ mod util; -use schemars::{JsonSchema, Map}; +use std::collections::BTreeMap; + +use schemars::JsonSchema; use util::*; // Ensure that schemars_derive uses the full path to std::string::String @@ -20,7 +22,7 @@ struct Struct { #[schemars(rename_all = "camelCase")] enum External { UnitOne, - StringMap(Map<&'static str, &'static str>), + StringMap(BTreeMap<&'static str, &'static str>), UnitStructNewType(UnitStruct), StructNewType(Struct), Struct { @@ -43,7 +45,7 @@ fn enum_external_tag() -> TestResult { #[schemars(tag = "typeProperty")] enum Internal { UnitOne, - StringMap(Map<&'static str, &'static str>), + StringMap(BTreeMap<&'static str, &'static str>), UnitStructNewType(UnitStruct), StructNewType(Struct), Struct { @@ -65,7 +67,7 @@ fn enum_internal_tag() -> TestResult { #[schemars(untagged)] enum Untagged { UnitOne, - StringMap(Map<&'static str, &'static str>), + StringMap(BTreeMap<&'static str, &'static str>), UnitStructNewType(UnitStruct), StructNewType(Struct), Struct { @@ -87,7 +89,7 @@ fn enum_untagged() -> TestResult { #[schemars(tag = "t", content = "c")] enum Adjacent { UnitOne, - StringMap(Map<&'static str, &'static str>), + StringMap(BTreeMap<&'static str, &'static str>), UnitStructNewType(UnitStruct), StructNewType(Struct), Struct { diff --git a/schemars/tests/enum_deny_unknown_fields.rs b/schemars/tests/enum_deny_unknown_fields.rs index 6201784..ef56d05 100644 --- a/schemars/tests/enum_deny_unknown_fields.rs +++ b/schemars/tests/enum_deny_unknown_fields.rs @@ -1,5 +1,7 @@ mod util; -use schemars::{JsonSchema, Map}; +use std::collections::BTreeMap; + +use schemars::JsonSchema; use util::*; // Ensure that schemars_derive uses the full path to std::string::String @@ -22,7 +24,7 @@ struct Struct { #[schemars(rename_all = "camelCase", deny_unknown_fields)] enum External { UnitOne, - StringMap(Map<&'static str, &'static str>), + StringMap(BTreeMap<&'static str, &'static str>), UnitStructNewType(UnitStruct), StructNewType(Struct), Struct { @@ -46,7 +48,7 @@ fn enum_external_tag() -> TestResult { #[schemars(tag = "typeProperty", deny_unknown_fields)] enum Internal { UnitOne, - StringMap(Map<&'static str, &'static str>), + StringMap(BTreeMap<&'static str, &'static str>), UnitStructNewType(UnitStruct), StructNewType(Struct), Struct { @@ -69,7 +71,7 @@ fn enum_internal_tag() -> TestResult { #[schemars(untagged, deny_unknown_fields)] enum Untagged { UnitOne, - StringMap(Map<&'static str, &'static str>), + StringMap(BTreeMap<&'static str, &'static str>), UnitStructNewType(UnitStruct), StructNewType(Struct), Struct { @@ -92,7 +94,7 @@ fn enum_untagged() -> TestResult { #[schemars(tag = "t", content = "c", deny_unknown_fields)] enum Adjacent { UnitOne, - StringMap(Map<&'static str, &'static str>), + StringMap(BTreeMap<&'static str, &'static str>), UnitStructNewType(UnitStruct), StructNewType(Struct), Struct { diff --git a/schemars/tests/enumset.rs b/schemars/tests/enumset.rs index 27f2030..c04ba0e 100644 --- a/schemars/tests/enumset.rs +++ b/schemars/tests/enumset.rs @@ -1,8 +1,11 @@ mod util; -use enumset::{EnumSet, EnumSetType}; +use enumset1::{EnumSet, EnumSetType}; use schemars::JsonSchema; use util::*; +// needed to derive EnumSetType when using a crate alias +extern crate enumset1 as enumset; + #[derive(EnumSetType, JsonSchema)] enum Foo { Bar, diff --git a/schemars/tests/expected/arrayvec.json b/schemars/tests/expected/arrayvec.json index 3de09a0..998d087 100644 --- a/schemars/tests/expected/arrayvec.json +++ b/schemars/tests/expected/arrayvec.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Array_up_to_size_16_of_int32", "type": "array", "items": { diff --git a/schemars/tests/expected/arrayvec_string.json b/schemars/tests/expected/arrayvec_string.json index 42f099a..0604332 100644 --- a/schemars/tests/expected/arrayvec_string.json +++ b/schemars/tests/expected/arrayvec_string.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "String", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "string", "type": "string" } \ No newline at end of file diff --git a/schemars/tests/expected/bigdecimal03.json b/schemars/tests/expected/bigdecimal03.json deleted file mode 100644 index 855db6f..0000000 --- a/schemars/tests/expected/bigdecimal03.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Decimal", - "type": "string", - "pattern": "^-?[0-9]+(\\.[0-9]+)?$" -} \ No newline at end of file diff --git a/schemars/tests/expected/bigdecimal04.json b/schemars/tests/expected/bigdecimal04.json index 855db6f..e94ca27 100644 --- a/schemars/tests/expected/bigdecimal04.json +++ b/schemars/tests/expected/bigdecimal04.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Decimal", "type": "string", "pattern": "^-?[0-9]+(\\.[0-9]+)?$" diff --git a/schemars/tests/expected/bound.json b/schemars/tests/expected/bound.json index 3c022dc..a5645c6 100644 --- a/schemars/tests/expected/bound.json +++ b/schemars/tests/expected/bound.json @@ -1,11 +1,7 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyContainer", "type": "object", - "required": [ - "associated", - "generic" - ], "properties": { "associated": { "type": "string" @@ -13,5 +9,9 @@ "generic": { "type": "null" } - } + }, + "required": [ + "associated", + "generic" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/bytes.json b/schemars/tests/expected/bytes.json index 5e1f9a5..80486c1 100644 --- a/schemars/tests/expected/bytes.json +++ b/schemars/tests/expected/bytes.json @@ -1,14 +1,14 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Tuple_of_Array_of_uint8_and_Array_of_uint8", "type": "array", - "items": [ + "prefixItems": [ { "type": "array", "items": { "type": "integer", "format": "uint8", - "minimum": 0.0 + "minimum": 0 } }, { @@ -16,10 +16,10 @@ "items": { "type": "integer", "format": "uint8", - "minimum": 0.0 + "minimum": 0 } } ], - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 } \ No newline at end of file diff --git a/schemars/tests/expected/chrono-types.json b/schemars/tests/expected/chrono-types.json index e3e788f..de26130 100644 --- a/schemars/tests/expected/chrono-types.json +++ b/schemars/tests/expected/chrono-types.json @@ -1,14 +1,7 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "ChronoTypes", "type": "object", - "required": [ - "date_time", - "naive_date", - "naive_date_time", - "naive_time", - "weekday" - ], "properties": { "weekday": { "type": "string", @@ -38,5 +31,12 @@ "type": "string", "format": "partial-date-time" } - } + }, + "required": [ + "weekday", + "date_time", + "naive_date", + "naive_date_time", + "naive_time" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/crate_alias.json b/schemars/tests/expected/crate_alias.json index 66bf749..c2f1430 100644 --- a/schemars/tests/expected/crate_alias.json +++ b/schemars/tests/expected/crate_alias.json @@ -1,11 +1,7 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Struct", "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { "foo": { "description": "This is a document", @@ -15,5 +11,9 @@ "bar": { "type": "boolean" } - } + }, + "required": [ + "foo", + "bar" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/default.json b/schemars/tests/expected/default.json index 4155d53..72e580d 100644 --- a/schemars/tests/expected/default.json +++ b/schemars/tests/expected/default.json @@ -1,51 +1,47 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "properties": { "my_int": { - "default": 0, "type": "integer", - "format": "int32" + "format": "int32", + "default": 0 }, "my_bool": { - "default": false, - "type": "boolean" + "type": "boolean", + "default": false }, "my_optional_string": { - "default": null, "type": [ "string", "null" - ] + ], + "default": null }, "my_struct2": { - "default": "i:0 b:false", - "allOf": [ - { - "$ref": "#/definitions/MyStruct2" - } - ] + "$ref": "#/$defs/MyStruct2", + "default": "i:0 b:false" }, "my_struct2_default_skipped": { - "$ref": "#/definitions/MyStruct2" + "$ref": "#/$defs/MyStruct2" }, "not_serialize": { - "$ref": "#/definitions/NotSerialize" + "$ref": "#/$defs/NotSerialize" } }, - "definitions": { + "$defs": { "MyStruct2": { "type": "object", "properties": { "my_int": { - "default": 6, "type": "integer", - "format": "int32" + "format": "int32", + "default": 6 }, "my_bool": { - "default": true, - "type": "boolean" + "type": "boolean", + "default": true } } }, diff --git a/schemars/tests/expected/deprecated-enum.json b/schemars/tests/expected/deprecated-enum.json index 825ae50..a61c322 100644 --- a/schemars/tests/expected/deprecated-enum.json +++ b/schemars/tests/expected/deprecated-enum.json @@ -1,7 +1,6 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "DeprecatedEnum", - "deprecated": true, "oneOf": [ { "type": "string", @@ -10,38 +9,37 @@ ] }, { - "deprecated": true, "type": "string", - "enum": [ - "DeprecatedUnitVariant" - ] + "const": "DeprecatedUnitVariant", + "deprecated": true }, { - "deprecated": true, "type": "object", - "required": [ - "DeprecatedStructVariant" - ], "properties": { "DeprecatedStructVariant": { "type": "object", - "required": [ - "deprecated_field", - "foo" - ], "properties": { - "deprecated_field": { - "deprecated": true, - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" + }, + "deprecated_field": { + "type": "boolean", + "deprecated": true } - } + }, + "required": [ + "foo", + "deprecated_field" + ] } }, - "additionalProperties": false + "required": [ + "DeprecatedStructVariant" + ], + "additionalProperties": false, + "deprecated": true } - ] + ], + "deprecated": true } \ No newline at end of file diff --git a/schemars/tests/expected/deprecated-struct.json b/schemars/tests/expected/deprecated-struct.json index 8f7ba71..b7396ab 100644 --- a/schemars/tests/expected/deprecated-struct.json +++ b/schemars/tests/expected/deprecated-struct.json @@ -1,20 +1,20 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "DeprecatedStruct", - "deprecated": true, "type": "object", - "required": [ - "deprecated_field", - "foo" - ], "properties": { "foo": { "type": "integer", "format": "int32" }, "deprecated_field": { - "deprecated": true, - "type": "boolean" + "type": "boolean", + "deprecated": true } - } + }, + "required": [ + "foo", + "deprecated_field" + ], + "deprecated": true } \ No newline at end of file diff --git a/schemars/tests/expected/doc_comments_enum.json b/schemars/tests/expected/doc_comments_enum.json index 53aaa86..0da3bed 100644 --- a/schemars/tests/expected/doc_comments_enum.json +++ b/schemars/tests/expected/doc_comments_enum.json @@ -1,7 +1,7 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "This is the enum's title", - "description": "This is the enum's description.", + "description": "This is \n the enum's description.", "oneOf": [ { "type": "string", @@ -13,24 +13,19 @@ { "description": "This comment is included in the generated schema :)", "type": "string", - "enum": [ - "DocumentedUnit" - ] + "const": "DocumentedUnit" }, { "title": "Complex variant", "description": "This is a struct-like variant.", "type": "object", - "required": [ - "Complex" - ], "properties": { "Complex": { "type": "object", "properties": { "my_nullable_string": { "title": "A nullable string", - "description": "This field is a nullable string.\n\nThis is the second line!\n\nAnd this is the third!", + "description": "This field is a nullable string.\n\n This\nis\n the second\n line!\n\n\n\n\n And this is the third!", "type": [ "string", "null" @@ -39,6 +34,9 @@ } } }, + "required": [ + "Complex" + ], "additionalProperties": false } ] diff --git a/schemars/tests/expected/doc_comments_override.json b/schemars/tests/expected/doc_comments_override.json index 83dce79..b184d2a 100644 --- a/schemars/tests/expected/doc_comments_override.json +++ b/schemars/tests/expected/doc_comments_override.json @@ -1,12 +1,8 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "OverrideDocs struct", "description": "New description", "type": "object", - "required": [ - "my_int", - "my_undocumented_bool" - ], "properties": { "my_int": { "title": "My integer", @@ -17,5 +13,9 @@ "my_undocumented_bool": { "type": "boolean" } - } + }, + "required": [ + "my_int", + "my_undocumented_bool" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/doc_comments_struct.json b/schemars/tests/expected/doc_comments_struct.json index 9f1b4bf..655121a 100644 --- a/schemars/tests/expected/doc_comments_struct.json +++ b/schemars/tests/expected/doc_comments_struct.json @@ -1,13 +1,8 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "This is the struct's title", "description": "This is the struct's description.", "type": "object", - "required": [ - "my_int", - "my_undocumented_bool", - "my_unit" - ], "properties": { "my_int": { "title": "An integer", @@ -19,14 +14,15 @@ }, "my_unit": { "description": "A unit struct instance", - "allOf": [ - { - "$ref": "#/definitions/MyUnitStruct" - } - ] + "$ref": "#/$defs/MyUnitStruct" } }, - "definitions": { + "required": [ + "my_int", + "my_undocumented_bool", + "my_unit" + ], + "$defs": { "MyUnitStruct": { "title": "A Unit", "type": "null" diff --git a/schemars/tests/expected/doc_comments_struct_ref_siblings.json b/schemars/tests/expected/doc_comments_struct_ref_siblings.json index 35bb648..6b6caeb 100644 --- a/schemars/tests/expected/doc_comments_struct_ref_siblings.json +++ b/schemars/tests/expected/doc_comments_struct_ref_siblings.json @@ -3,11 +3,6 @@ "title": "This is the struct's title", "description": "This is the struct's description.", "type": "object", - "required": [ - "my_int", - "my_undocumented_bool", - "my_unit" - ], "properties": { "my_int": { "title": "An integer", @@ -19,10 +14,15 @@ }, "my_unit": { "description": "A unit struct instance", - "$ref": "#/definitions/MyUnitStruct" + "$ref": "#/$defs/MyUnitStruct" } }, - "definitions": { + "required": [ + "my_int", + "my_undocumented_bool", + "my_unit" + ], + "$defs": { "MyUnitStruct": { "title": "A Unit", "type": "null" diff --git a/schemars/tests/expected/duration_and_systemtime.json b/schemars/tests/expected/duration_and_systemtime.json index d01f7d5..7e301e5 100644 --- a/schemars/tests/expected/duration_and_systemtime.json +++ b/schemars/tests/expected/duration_and_systemtime.json @@ -1,57 +1,57 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", + "properties": { + "duration": { + "$ref": "#/$defs/Duration" + }, + "time": { + "$ref": "#/$defs/SystemTime" + } + }, "required": [ "duration", "time" ], - "properties": { - "duration": { - "$ref": "#/definitions/Duration" - }, - "time": { - "$ref": "#/definitions/SystemTime" - } - }, - "definitions": { + "$defs": { "Duration": { "type": "object", - "required": [ - "nanos", - "secs" - ], "properties": { "secs": { "type": "integer", "format": "uint64", - "minimum": 0.0 + "minimum": 0 }, "nanos": { "type": "integer", "format": "uint32", - "minimum": 0.0 + "minimum": 0 } - } + }, + "required": [ + "secs", + "nanos" + ] }, "SystemTime": { "type": "object", - "required": [ - "nanos_since_epoch", - "secs_since_epoch" - ], "properties": { "secs_since_epoch": { "type": "integer", "format": "uint64", - "minimum": 0.0 + "minimum": 0 }, "nanos_since_epoch": { "type": "integer", "format": "uint32", - "minimum": 0.0 + "minimum": 0 } - } + }, + "required": [ + "secs_since_epoch", + "nanos_since_epoch" + ] } } } \ No newline at end of file diff --git a/schemars/tests/expected/either.json b/schemars/tests/expected/either.json index b028057..18d8f51 100644 --- a/schemars/tests/expected/either.json +++ b/schemars/tests/expected/either.json @@ -1,6 +1,6 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Either_int32_or_Either_Boolean_or_Null", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Either_int32_or_Either_boolean_or_null", "anyOf": [ { "type": "integer", diff --git a/schemars/tests/expected/enum-adjacent-tagged-duf.json b/schemars/tests/expected/enum-adjacent-tagged-duf.json index c5b54c8..dfb3bb8 100644 --- a/schemars/tests/expected/enum-adjacent-tagged-duf.json +++ b/schemars/tests/expected/enum-adjacent-tagged-duf.json @@ -1,12 +1,9 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Adjacent", "oneOf": [ { "type": "object", - "required": [ - "t" - ], "properties": { "t": { "type": "string", @@ -15,111 +12,116 @@ ] } }, + "required": [ + "t" + ], "additionalProperties": false }, { "type": "object", - "required": [ - "c", - "t" - ], "properties": { - "c": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, "t": { "type": "string", "enum": [ "StringMap" ] + }, + "c": { + "type": "object", + "additionalProperties": { + "type": "string" + } } }, + "required": [ + "t", + "c" + ], "additionalProperties": false }, { "type": "object", - "required": [ - "c", - "t" - ], "properties": { - "c": { - "$ref": "#/definitions/UnitStruct" - }, "t": { "type": "string", "enum": [ "UnitStructNewType" ] + }, + "c": { + "$ref": "#/$defs/UnitStruct" } }, + "required": [ + "t", + "c" + ], "additionalProperties": false }, { "type": "object", - "required": [ - "c", - "t" - ], "properties": { - "c": { - "$ref": "#/definitions/Struct" - }, "t": { "type": "string", "enum": [ "StructNewType" ] + }, + "c": { + "$ref": "#/$defs/Struct" } }, + "required": [ + "t", + "c" + ], "additionalProperties": false }, { "type": "object", - "required": [ - "c", - "t" - ], "properties": { - "c": { - "type": "object", - "required": [ - "bar", - "foo" - ], - "properties": { - "bar": { - "type": "boolean" - }, - "foo": { - "type": "integer", - "format": "int32" - } - }, - "additionalProperties": false - }, "t": { "type": "string", "enum": [ "Struct" ] + }, + "c": { + "type": "object", + "properties": { + "foo": { + "type": "integer", + "format": "int32" + }, + "bar": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "foo", + "bar" + ] } }, + "required": [ + "t", + "c" + ], "additionalProperties": false }, { "type": "object", - "required": [ - "c", - "t" - ], "properties": { + "t": { + "type": "string", + "enum": [ + "Tuple" + ] + }, "c": { "type": "array", - "items": [ + "prefixItems": [ { "type": "integer", "format": "int32" @@ -128,23 +130,18 @@ "type": "boolean" } ], - "maxItems": 2, - "minItems": 2 - }, - "t": { - "type": "string", - "enum": [ - "Tuple" - ] + "minItems": 2, + "maxItems": 2 } }, + "required": [ + "t", + "c" + ], "additionalProperties": false }, { "type": "object", - "required": [ - "t" - ], "properties": { "t": { "type": "string", @@ -153,48 +150,51 @@ ] } }, + "required": [ + "t" + ], "additionalProperties": false }, { "type": "object", - "required": [ - "c", - "t" - ], "properties": { - "c": { - "type": "integer", - "format": "int32" - }, "t": { "type": "string", "enum": [ "WithInt" ] - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Struct": { - "type": "object", - "required": [ - "bar", - "foo" - ], - "properties": { - "bar": { - "type": "boolean" }, - "foo": { + "c": { "type": "integer", "format": "int32" } - } - }, + }, + "required": [ + "t", + "c" + ], + "additionalProperties": false + } + ], + "$defs": { "UnitStruct": { "type": "null" + }, + "Struct": { + "type": "object", + "properties": { + "foo": { + "type": "integer", + "format": "int32" + }, + "bar": { + "type": "boolean" + } + }, + "required": [ + "foo", + "bar" + ] } } } \ No newline at end of file diff --git a/schemars/tests/expected/enum-adjacent-tagged.json b/schemars/tests/expected/enum-adjacent-tagged.json index efe19dc..c631ae5 100644 --- a/schemars/tests/expected/enum-adjacent-tagged.json +++ b/schemars/tests/expected/enum-adjacent-tagged.json @@ -1,12 +1,9 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Adjacent", "oneOf": [ { "type": "object", - "required": [ - "t" - ], "properties": { "t": { "type": "string", @@ -14,106 +11,111 @@ "UnitOne" ] } - } + }, + "required": [ + "t" + ] }, { "type": "object", - "required": [ - "c", - "t" - ], "properties": { - "c": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, "t": { "type": "string", "enum": [ "StringMap" ] + }, + "c": { + "type": "object", + "additionalProperties": { + "type": "string" + } } - } + }, + "required": [ + "t", + "c" + ] }, { "type": "object", - "required": [ - "c", - "t" - ], "properties": { - "c": { - "$ref": "#/definitions/UnitStruct" - }, "t": { "type": "string", "enum": [ "UnitStructNewType" ] + }, + "c": { + "$ref": "#/$defs/UnitStruct" } - } + }, + "required": [ + "t", + "c" + ] }, { "type": "object", - "required": [ - "c", - "t" - ], "properties": { - "c": { - "$ref": "#/definitions/Struct" - }, "t": { "type": "string", "enum": [ "StructNewType" ] + }, + "c": { + "$ref": "#/$defs/Struct" } - } + }, + "required": [ + "t", + "c" + ] }, { "type": "object", - "required": [ - "c", - "t" - ], "properties": { - "c": { - "type": "object", - "required": [ - "bar", - "foo" - ], - "properties": { - "bar": { - "type": "boolean" - }, - "foo": { - "type": "integer", - "format": "int32" - } - } - }, "t": { "type": "string", "enum": [ "Struct" ] + }, + "c": { + "type": "object", + "properties": { + "foo": { + "type": "integer", + "format": "int32" + }, + "bar": { + "type": "boolean" + } + }, + "required": [ + "foo", + "bar" + ] } - } + }, + "required": [ + "t", + "c" + ] }, { "type": "object", - "required": [ - "c", - "t" - ], "properties": { + "t": { + "type": "string", + "enum": [ + "Tuple" + ] + }, "c": { "type": "array", - "items": [ + "prefixItems": [ { "type": "integer", "format": "int32" @@ -122,22 +124,17 @@ "type": "boolean" } ], - "maxItems": 2, - "minItems": 2 - }, - "t": { - "type": "string", - "enum": [ - "Tuple" - ] + "minItems": 2, + "maxItems": 2 } - } + }, + "required": [ + "t", + "c" + ] }, { "type": "object", - "required": [ - "t" - ], "properties": { "t": { "type": "string", @@ -145,47 +142,50 @@ "UnitTwo" ] } - } + }, + "required": [ + "t" + ] }, { "type": "object", - "required": [ - "c", - "t" - ], "properties": { - "c": { - "type": "integer", - "format": "int32" - }, "t": { "type": "string", "enum": [ "WithInt" ] - } - } - } - ], - "definitions": { - "Struct": { - "type": "object", - "required": [ - "bar", - "foo" - ], - "properties": { - "bar": { - "type": "boolean" }, - "foo": { + "c": { "type": "integer", "format": "int32" } - } - }, + }, + "required": [ + "t", + "c" + ] + } + ], + "$defs": { "UnitStruct": { "type": "null" + }, + "Struct": { + "type": "object", + "properties": { + "foo": { + "type": "integer", + "format": "int32" + }, + "bar": { + "type": "boolean" + } + }, + "required": [ + "foo", + "bar" + ] } } } \ No newline at end of file diff --git a/schemars/tests/expected/enum-external-duf.json b/schemars/tests/expected/enum-external-duf.json index b6b7b99..76be5b3 100644 --- a/schemars/tests/expected/enum-external-duf.json +++ b/schemars/tests/expected/enum-external-duf.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "External", "oneOf": [ { @@ -11,9 +11,6 @@ }, { "type": "object", - "required": [ - "stringMap" - ], "properties": { "stringMap": { "type": "object", @@ -22,67 +19,67 @@ } } }, + "required": [ + "stringMap" + ], "additionalProperties": false }, { "type": "object", + "properties": { + "unitStructNewType": { + "$ref": "#/$defs/UnitStruct" + } + }, "required": [ "unitStructNewType" ], - "properties": { - "unitStructNewType": { - "$ref": "#/definitions/UnitStruct" - } - }, "additionalProperties": false }, { "type": "object", + "properties": { + "structNewType": { + "$ref": "#/$defs/Struct" + } + }, "required": [ "structNewType" ], - "properties": { - "structNewType": { - "$ref": "#/definitions/Struct" - } - }, "additionalProperties": false }, { "type": "object", - "required": [ - "struct" - ], "properties": { "struct": { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" + }, + "bar": { + "type": "boolean" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "foo", + "bar" + ] } }, + "required": [ + "struct" + ], "additionalProperties": false }, { "type": "object", - "required": [ - "tuple" - ], "properties": { "tuple": { "type": "array", - "items": [ + "prefixItems": [ { "type": "integer", "format": "int32" @@ -91,45 +88,48 @@ "type": "boolean" } ], - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 } }, + "required": [ + "tuple" + ], "additionalProperties": false }, { "type": "object", - "required": [ - "withInt" - ], "properties": { "withInt": { "type": "integer", "format": "int32" } }, + "required": [ + "withInt" + ], "additionalProperties": false } ], - "definitions": { + "$defs": { + "UnitStruct": { + "type": "null" + }, "Struct": { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" + }, + "bar": { + "type": "boolean" } - } - }, - "UnitStruct": { - "type": "null" + }, + "required": [ + "foo", + "bar" + ] } } } \ No newline at end of file diff --git a/schemars/tests/expected/enum-external.json b/schemars/tests/expected/enum-external.json index cc721df..3c660fb 100644 --- a/schemars/tests/expected/enum-external.json +++ b/schemars/tests/expected/enum-external.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "External", "oneOf": [ { @@ -11,9 +11,6 @@ }, { "type": "object", - "required": [ - "stringMap" - ], "properties": { "stringMap": { "type": "object", @@ -22,66 +19,66 @@ } } }, + "required": [ + "stringMap" + ], "additionalProperties": false }, { "type": "object", + "properties": { + "unitStructNewType": { + "$ref": "#/$defs/UnitStruct" + } + }, "required": [ "unitStructNewType" ], - "properties": { - "unitStructNewType": { - "$ref": "#/definitions/UnitStruct" - } - }, "additionalProperties": false }, { "type": "object", + "properties": { + "structNewType": { + "$ref": "#/$defs/Struct" + } + }, "required": [ "structNewType" ], - "properties": { - "structNewType": { - "$ref": "#/definitions/Struct" - } - }, "additionalProperties": false }, { "type": "object", - "required": [ - "struct" - ], "properties": { "struct": { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" + }, + "bar": { + "type": "boolean" } - } + }, + "required": [ + "foo", + "bar" + ] } }, + "required": [ + "struct" + ], "additionalProperties": false }, { "type": "object", - "required": [ - "tuple" - ], "properties": { "tuple": { "type": "array", - "items": [ + "prefixItems": [ { "type": "integer", "format": "int32" @@ -90,45 +87,48 @@ "type": "boolean" } ], - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 } }, + "required": [ + "tuple" + ], "additionalProperties": false }, { "type": "object", - "required": [ - "withInt" - ], "properties": { "withInt": { "type": "integer", "format": "int32" } }, + "required": [ + "withInt" + ], "additionalProperties": false } ], - "definitions": { + "$defs": { + "UnitStruct": { + "type": "null" + }, "Struct": { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" + }, + "bar": { + "type": "boolean" } - } - }, - "UnitStruct": { - "type": "null" + }, + "required": [ + "foo", + "bar" + ] } } } \ No newline at end of file diff --git a/schemars/tests/expected/enum-internal-duf.json b/schemars/tests/expected/enum-internal-duf.json index fc36644..73e4743 100644 --- a/schemars/tests/expected/enum-internal-duf.json +++ b/schemars/tests/expected/enum-internal-duf.json @@ -1,134 +1,116 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Internal", "oneOf": [ { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", - "enum": [ - "UnitOne" - ] + "const": "UnitOne" } }, + "required": [ + "typeProperty" + ], "additionalProperties": false }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", - "enum": [ - "StringMap" - ] + "const": "StringMap" } }, "additionalProperties": { "type": "string" - } + }, + "required": [ + "typeProperty" + ] }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", - "enum": [ - "UnitStructNewType" - ] + "const": "UnitStructNewType" } }, + "required": [ + "typeProperty" + ], "additionalProperties": false }, { "type": "object", - "required": [ - "bar", - "foo", - "typeProperty" - ], "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" }, - "typeProperty": { - "type": "string", - "enum": [ - "StructNewType" - ] - } - } - }, - { - "type": "object", - "required": [ - "bar", - "foo", - "typeProperty" - ], - "properties": { "bar": { "type": "boolean" }, + "typeProperty": { + "type": "string", + "const": "StructNewType" + } + }, + "required": [ + "typeProperty", + "foo", + "bar" + ] + }, + { + "type": "object", + "properties": { "foo": { "type": "integer", "format": "int32" }, + "bar": { + "type": "boolean" + }, "typeProperty": { "type": "string", - "enum": [ - "Struct" - ] + "const": "Struct" } }, + "additionalProperties": false, + "required": [ + "typeProperty", + "foo", + "bar" + ] + }, + { + "type": "object", + "properties": { + "typeProperty": { + "type": "string", + "const": "UnitTwo" + } + }, + "required": [ + "typeProperty" + ], "additionalProperties": false }, { "type": "object", - "required": [ - "typeProperty" - ], - "properties": { - "typeProperty": { - "type": "string", - "enum": [ - "UnitTwo" - ] - } - }, - "additionalProperties": false - }, - { - "type": [ - "object", - "integer" - ], "format": "int32", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", - "enum": [ - "WithInt" - ] + "const": "WithInt" } }, - "additionalProperties": false + "required": [ + "typeProperty" + ] } ] } \ No newline at end of file diff --git a/schemars/tests/expected/enum-internal.json b/schemars/tests/expected/enum-internal.json index 37739b0..2fd9770 100644 --- a/schemars/tests/expected/enum-internal.json +++ b/schemars/tests/expected/enum-internal.json @@ -1,126 +1,112 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Internal", "oneOf": [ { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", - "enum": [ - "UnitOne" - ] + "const": "UnitOne" } - } + }, + "required": [ + "typeProperty" + ] }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", - "enum": [ - "StringMap" - ] + "const": "StringMap" } - } + }, + "additionalProperties": { + "type": "string" + }, + "required": [ + "typeProperty" + ] }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", - "enum": [ - "UnitStructNewType" - ] + "const": "UnitStructNewType" } - } + }, + "required": [ + "typeProperty" + ] }, { "type": "object", - "required": [ - "bar", - "foo", - "typeProperty" - ], "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" }, - "typeProperty": { - "type": "string", - "enum": [ - "StructNewType" - ] - } - } - }, - { - "type": "object", - "required": [ - "bar", - "foo", - "typeProperty" - ], - "properties": { "bar": { "type": "boolean" }, + "typeProperty": { + "type": "string", + "const": "StructNewType" + } + }, + "required": [ + "typeProperty", + "foo", + "bar" + ] + }, + { + "type": "object", + "properties": { "foo": { "type": "integer", "format": "int32" }, + "bar": { + "type": "boolean" + }, "typeProperty": { "type": "string", - "enum": [ - "Struct" - ] + "const": "Struct" } - } + }, + "required": [ + "typeProperty", + "foo", + "bar" + ] }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", - "enum": [ - "UnitTwo" - ] + "const": "UnitTwo" } - } + }, + "required": [ + "typeProperty" + ] }, { - "type": [ - "object", - "integer" - ], + "type": "object", "format": "int32", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", - "enum": [ - "WithInt" - ] + "const": "WithInt" } - } + }, + "required": [ + "typeProperty" + ] } ] } \ No newline at end of file diff --git a/schemars/tests/expected/enum-repr-with-attrs.json b/schemars/tests/expected/enum-repr-with-attrs.json index 7070de8..89d941b 100644 --- a/schemars/tests/expected/enum-repr-with-attrs.json +++ b/schemars/tests/expected/enum-repr-with-attrs.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Renamed", "description": "Description from comment", "type": "integer", diff --git a/schemars/tests/expected/enum-repr.json b/schemars/tests/expected/enum-repr.json index 92d6f3a..b3e6bb9 100644 --- a/schemars/tests/expected/enum-repr.json +++ b/schemars/tests/expected/enum-repr.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Enum", "type": "integer", "enum": [ diff --git a/schemars/tests/expected/enum-simple-internal-duf.json b/schemars/tests/expected/enum-simple-internal-duf.json index 833f7b7..aaf1eef 100644 --- a/schemars/tests/expected/enum-simple-internal-duf.json +++ b/schemars/tests/expected/enum-simple-internal-duf.json @@ -1,50 +1,44 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "SimpleInternal", "oneOf": [ { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", - "enum": [ - "A" - ] + "const": "A" } }, + "required": [ + "typeProperty" + ], "additionalProperties": false }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", - "enum": [ - "B" - ] + "const": "B" } }, + "required": [ + "typeProperty" + ], "additionalProperties": false }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", - "enum": [ - "C" - ] + "const": "C" } }, + "required": [ + "typeProperty" + ], "additionalProperties": false } ] diff --git a/schemars/tests/expected/enum-simple-internal.json b/schemars/tests/expected/enum-simple-internal.json index 50cd62c..e955d2a 100644 --- a/schemars/tests/expected/enum-simple-internal.json +++ b/schemars/tests/expected/enum-simple-internal.json @@ -1,48 +1,42 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "SimpleInternal", "oneOf": [ { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", - "enum": [ - "A" - ] + "const": "A" } - } + }, + "required": [ + "typeProperty" + ] }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", - "enum": [ - "B" - ] + "const": "B" } - } + }, + "required": [ + "typeProperty" + ] }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", - "enum": [ - "C" - ] + "const": "C" } - } + }, + "required": [ + "typeProperty" + ] } ] } \ No newline at end of file diff --git a/schemars/tests/expected/enum-unit-doc.json b/schemars/tests/expected/enum-unit-doc.json index 11a5c2a..1000435 100644 --- a/schemars/tests/expected/enum-unit-doc.json +++ b/schemars/tests/expected/enum-unit-doc.json @@ -1,28 +1,22 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "SoundOfMusic", "oneOf": [ { "title": "A deer", "description": "A female deer", "type": "string", - "enum": [ - "Do" - ] + "const": "Do" }, { "description": "A drop of golden sun", "type": "string", - "enum": [ - "Re" - ] + "const": "Re" }, { "description": "A name I call myself", "type": "string", - "enum": [ - "Mi" - ] + "const": "Mi" } ] } \ No newline at end of file diff --git a/schemars/tests/expected/enum-untagged-duf.json b/schemars/tests/expected/enum-untagged-duf.json index 24397bf..58bdbe1 100644 --- a/schemars/tests/expected/enum-untagged-duf.json +++ b/schemars/tests/expected/enum-untagged-duf.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Untagged", "anyOf": [ { @@ -12,17 +12,13 @@ } }, { - "$ref": "#/definitions/UnitStruct" + "$ref": "#/$defs/UnitStruct" }, { - "$ref": "#/definitions/Struct" + "$ref": "#/$defs/Struct" }, { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { "foo": { "type": "integer", @@ -32,11 +28,15 @@ "type": "boolean" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "foo", + "bar" + ] }, { "type": "array", - "items": [ + "prefixItems": [ { "type": "integer", "format": "int32" @@ -45,24 +45,20 @@ "type": "boolean" } ], - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 }, { "type": "integer", "format": "int32" } ], - "definitions": { + "$defs": { "UnitStruct": { "type": "null" }, "Struct": { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { "foo": { "type": "integer", @@ -71,7 +67,11 @@ "bar": { "type": "boolean" } - } + }, + "required": [ + "foo", + "bar" + ] } } } \ No newline at end of file diff --git a/schemars/tests/expected/enum-untagged.json b/schemars/tests/expected/enum-untagged.json index 7eccbe2..643cd20 100644 --- a/schemars/tests/expected/enum-untagged.json +++ b/schemars/tests/expected/enum-untagged.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Untagged", "anyOf": [ { @@ -12,17 +12,13 @@ } }, { - "$ref": "#/definitions/UnitStruct" + "$ref": "#/$defs/UnitStruct" }, { - "$ref": "#/definitions/Struct" + "$ref": "#/$defs/Struct" }, { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { "foo": { "type": "integer", @@ -31,11 +27,15 @@ "bar": { "type": "boolean" } - } + }, + "required": [ + "foo", + "bar" + ] }, { "type": "array", - "items": [ + "prefixItems": [ { "type": "integer", "format": "int32" @@ -44,24 +44,20 @@ "type": "boolean" } ], - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 }, { "type": "integer", "format": "int32" } ], - "definitions": { + "$defs": { "UnitStruct": { "type": "null" }, "Struct": { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { "foo": { "type": "integer", @@ -70,7 +66,11 @@ "bar": { "type": "boolean" } - } + }, + "required": [ + "foo", + "bar" + ] } } } \ No newline at end of file diff --git a/schemars/tests/expected/enumset.json b/schemars/tests/expected/enumset.json index 4950c93..72a39a1 100644 --- a/schemars/tests/expected/enumset.json +++ b/schemars/tests/expected/enumset.json @@ -1,12 +1,12 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Set_of_Foo", "type": "array", - "items": { - "$ref": "#/definitions/Foo" - }, "uniqueItems": true, - "definitions": { + "items": { + "$ref": "#/$defs/Foo" + }, + "$defs": { "Foo": { "type": "string", "enum": [ @@ -15,4 +15,4 @@ ] } } -} +} \ No newline at end of file diff --git a/schemars/tests/expected/examples.json b/schemars/tests/expected/examples.json index c02a139..b891aa0 100644 --- a/schemars/tests/expected/examples.json +++ b/schemars/tests/expected/examples.json @@ -1,39 +1,39 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Struct", - "examples": [ - { - "bar": false, - "baz": null, - "foo": 0 - }, - null - ], "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { "foo": { + "type": "integer", + "format": "int32", "examples": [ 8, null - ], - "type": "integer", - "format": "int32" + ] }, "bar": { "type": "boolean" }, "baz": { - "examples": [ - null - ], "type": [ "string", "null" + ], + "examples": [ + null ] } - } + }, + "required": [ + "foo", + "bar" + ], + "examples": [ + { + "foo": 0, + "bar": false, + "baz": null + }, + null + ] } \ No newline at end of file diff --git a/schemars/tests/expected/extend_enum_adjacent.json b/schemars/tests/expected/extend_enum_adjacent.json new file mode 100644 index 0000000..6241e07 --- /dev/null +++ b/schemars/tests/expected/extend_enum_adjacent.json @@ -0,0 +1,101 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Adjacent", + "oneOf": [ + { + "type": "object", + "properties": { + "t": { + "type": "string", + "enum": [ + "Unit" + ] + } + }, + "required": [ + "t" + ], + "foo": "bar" + }, + { + "type": "object", + "properties": { + "t": { + "type": "string", + "enum": [ + "NewType" + ] + }, + "c": true + }, + "required": [ + "t", + "c" + ], + "foo": "bar" + }, + { + "type": "object", + "properties": { + "t": { + "type": "string", + "enum": [ + "Tuple" + ] + }, + "c": { + "type": "array", + "prefixItems": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "boolean" + } + ], + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "t", + "c" + ], + "foo": "bar" + }, + { + "type": "object", + "properties": { + "t": { + "type": "string", + "enum": [ + "Struct" + ] + }, + "c": { + "type": "object", + "properties": { + "i": { + "type": "integer", + "format": "int32" + }, + "b": { + "type": "boolean" + } + }, + "required": [ + "i", + "b" + ] + } + }, + "required": [ + "t", + "c" + ], + "foo": "bar" + } + ], + "foo": "bar" +} \ No newline at end of file diff --git a/schemars/tests/expected/extend_enum_external.json b/schemars/tests/expected/extend_enum_external.json new file mode 100644 index 0000000..c15d47f --- /dev/null +++ b/schemars/tests/expected/extend_enum_external.json @@ -0,0 +1,73 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "External", + "oneOf": [ + { + "type": "string", + "const": "Unit", + "foo": "bar" + }, + { + "type": "object", + "properties": { + "NewType": true + }, + "required": [ + "NewType" + ], + "additionalProperties": false, + "foo": "bar" + }, + { + "type": "object", + "properties": { + "Tuple": { + "type": "array", + "prefixItems": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "boolean" + } + ], + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "Tuple" + ], + "additionalProperties": false, + "foo": "bar" + }, + { + "type": "object", + "properties": { + "Struct": { + "type": "object", + "properties": { + "i": { + "type": "integer", + "format": "int32" + }, + "b": { + "type": "boolean" + } + }, + "required": [ + "i", + "b" + ] + } + }, + "required": [ + "Struct" + ], + "additionalProperties": false, + "foo": "bar" + } + ], + "foo": "bar" +} \ No newline at end of file diff --git a/schemars/tests/expected/extend_enum_internal.json b/schemars/tests/expected/extend_enum_internal.json new file mode 100644 index 0000000..0dee817 --- /dev/null +++ b/schemars/tests/expected/extend_enum_internal.json @@ -0,0 +1,55 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Internal", + "oneOf": [ + { + "type": "object", + "properties": { + "typeProperty": { + "type": "string", + "const": "Unit" + } + }, + "required": [ + "typeProperty" + ], + "foo": "bar" + }, + { + "type": "object", + "properties": { + "typeProperty": { + "type": "string", + "const": "NewType" + } + }, + "required": [ + "typeProperty" + ], + "foo": "bar" + }, + { + "type": "object", + "properties": { + "i": { + "type": "integer", + "format": "int32" + }, + "b": { + "type": "boolean" + }, + "typeProperty": { + "type": "string", + "const": "Struct" + } + }, + "required": [ + "typeProperty", + "i", + "b" + ], + "foo": "bar" + } + ], + "foo": "bar" +} \ No newline at end of file diff --git a/schemars/tests/expected/extend_enum_untagged.json b/schemars/tests/expected/extend_enum_untagged.json new file mode 100644 index 0000000..4f733fe --- /dev/null +++ b/schemars/tests/expected/extend_enum_untagged.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Untagged", + "anyOf": [ + { + "type": "null", + "foo": "bar" + }, + { + "foo": "bar" + }, + { + "type": "array", + "prefixItems": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "boolean" + } + ], + "minItems": 2, + "maxItems": 2, + "foo": "bar" + }, + { + "type": "object", + "properties": { + "i": { + "type": "integer", + "format": "int32" + }, + "b": { + "type": "boolean" + } + }, + "required": [ + "i", + "b" + ], + "foo": "bar" + } + ], + "foo": "bar" +} \ No newline at end of file diff --git a/schemars/tests/expected/extend_struct.json b/schemars/tests/expected/extend_struct.json new file mode 100644 index 0000000..fc7dd50 --- /dev/null +++ b/schemars/tests/expected/extend_struct.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Struct", + "type": "object", + "properties": { + "value": { + "foo": "bar" + }, + "int": { + "type": "overridden", + "format": "int32" + } + }, + "required": [ + "value", + "int" + ], + "msg": "hello world", + "obj": { + "array": [ + null, + null + ] + }, + "3": 3.0, + "pi": 3.14 +} \ No newline at end of file diff --git a/schemars/tests/expected/flatten.json b/schemars/tests/expected/flatten.json index fedf571..7dfd54e 100644 --- a/schemars/tests/expected/flatten.json +++ b/schemars/tests/expected/flatten.json @@ -1,13 +1,7 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Flat", "type": "object", - "required": [ - "b", - "f", - "s", - "v" - ], "properties": { "f": { "type": "number", @@ -20,8 +14,8 @@ "type": "string" }, "os": { - "default": "", - "type": "string" + "type": "string", + "default": "" }, "v": { "type": "array", @@ -30,5 +24,11 @@ "format": "int32" } } - } + }, + "required": [ + "f", + "b", + "s", + "v" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/from_json_value.json b/schemars/tests/expected/from_json_value.json index 0d94b66..b217fad 100644 --- a/schemars/tests/expected/from_json_value.json +++ b/schemars/tests/expected/from_json_value.json @@ -1,29 +1,22 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "examples": [ - { - "bool": true, - "minusOne": -1, - "null": null, - "object": { - "array": [ - "foo", - "bar" - ] - }, - "one": 1, - "zero": 0, - "zeroPointZero": 0.0 - } - ], + "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { - "bool": { - "type": "boolean" + "zero": { + "type": "integer" + }, + "one": { + "type": "integer" }, "minusOne": { "type": "integer" }, + "zeroPointZero": { + "type": "number" + }, + "bool": { + "type": "boolean" + }, "null": true, "object": { "type": "object", @@ -35,15 +28,22 @@ } } } - }, - "one": { - "type": "integer" - }, - "zero": { - "type": "integer" - }, - "zeroPointZero": { - "type": "number" } - } + }, + "examples": [ + { + "zero": 0, + "one": 1, + "minusOne": -1, + "zeroPointZero": 0.0, + "bool": true, + "null": null, + "object": { + "array": [ + "foo", + "bar" + ] + } + } + ] } \ No newline at end of file diff --git a/schemars/tests/expected/from_value_2019_09.json b/schemars/tests/expected/from_value_2019_09.json index 9cea709..939410d 100644 --- a/schemars/tests/expected/from_value_2019_09.json +++ b/schemars/tests/expected/from_value_2019_09.json @@ -1,28 +1,6 @@ { "$schema": "https://json-schema.org/draft/2019-09/schema", "title": "MyStruct", - "examples": [ - { - "myBool": true, - "myInnerStruct": { - "my_empty_map": {}, - "my_empty_vec": [], - "my_map": { - "": 0.0 - }, - "my_tuple": [ - "💩", - 42 - ], - "my_vec": [ - "hello", - "world" - ] - }, - "myInt": 123, - "myNullableEnum": null - } - ], "type": "object", "properties": { "myInt": { @@ -57,20 +35,42 @@ }, "my_tuple": { "type": "array", + "minItems": 2, + "maxItems": 2, "items": [ { "type": "string", - "maxLength": 1, - "minLength": 1 + "minLength": 1, + "maxLength": 1 }, { "type": "integer" } - ], - "maxItems": 2, - "minItems": 2 + ] } } } - } + }, + "examples": [ + { + "myInt": 123, + "myBool": true, + "myNullableEnum": null, + "myInnerStruct": { + "my_map": { + "": 0.0 + }, + "my_vec": [ + "hello", + "world" + ], + "my_empty_map": {}, + "my_empty_vec": [], + "my_tuple": [ + "💩", + 42 + ] + } + } + ] } \ No newline at end of file diff --git a/schemars/tests/expected/from_value_draft07.json b/schemars/tests/expected/from_value_draft07.json index dec3996..721e871 100644 --- a/schemars/tests/expected/from_value_draft07.json +++ b/schemars/tests/expected/from_value_draft07.json @@ -1,28 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "MyStruct", - "examples": [ - { - "myBool": true, - "myInnerStruct": { - "my_empty_map": {}, - "my_empty_vec": [], - "my_map": { - "": 0.0 - }, - "my_tuple": [ - "💩", - 42 - ], - "my_vec": [ - "hello", - "world" - ] - }, - "myInt": 123, - "myNullableEnum": null - } - ], "type": "object", "properties": { "myInt": { @@ -57,20 +35,42 @@ }, "my_tuple": { "type": "array", + "minItems": 2, + "maxItems": 2, "items": [ { "type": "string", - "maxLength": 1, - "minLength": 1 + "minLength": 1, + "maxLength": 1 }, { "type": "integer" } - ], - "maxItems": 2, - "minItems": 2 + ] } } } - } + }, + "examples": [ + { + "myInt": 123, + "myBool": true, + "myNullableEnum": null, + "myInnerStruct": { + "my_map": { + "": 0.0 + }, + "my_vec": [ + "hello", + "world" + ], + "my_empty_map": {}, + "my_empty_vec": [], + "my_tuple": [ + "💩", + 42 + ] + } + } + ] } \ No newline at end of file diff --git a/schemars/tests/expected/from_value_openapi3.json b/schemars/tests/expected/from_value_openapi3.json index 132a59a..88f08a7 100644 --- a/schemars/tests/expected/from_value_openapi3.json +++ b/schemars/tests/expected/from_value_openapi3.json @@ -1,5 +1,5 @@ { - "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", + "$schema": "https://spec.openapis.org/oas/3.0/schema/2021-09-28#/definitions/Schema", "title": "MyStruct", "type": "object", "properties": { @@ -37,40 +37,40 @@ }, "my_tuple": { "type": "array", + "minItems": 2, + "maxItems": 2, "items": [ { "type": "string", - "maxLength": 1, - "minLength": 1 + "minLength": 1, + "maxLength": 1 }, { "type": "integer" } - ], - "maxItems": 2, - "minItems": 2 + ] } } } }, "example": { + "myInt": 123, "myBool": true, + "myNullableEnum": null, "myInnerStruct": { - "my_empty_map": {}, - "my_empty_vec": [], "my_map": { "": 0.0 }, - "my_tuple": [ - "💩", - 42 - ], "my_vec": [ "hello", "world" + ], + "my_empty_map": {}, + "my_empty_vec": [], + "my_tuple": [ + "💩", + 42 ] - }, - "myInt": 123, - "myNullableEnum": null + } } } \ No newline at end of file diff --git a/schemars/tests/expected/indexmap.json b/schemars/tests/expected/indexmap.json index 98065d9..8ba90a8 100644 --- a/schemars/tests/expected/indexmap.json +++ b/schemars/tests/expected/indexmap.json @@ -1,11 +1,7 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "IndexMapTypes", "type": "object", - "required": [ - "map", - "set" - ], "properties": { "map": { "type": "object", @@ -15,11 +11,15 @@ }, "set": { "type": "array", + "uniqueItems": true, "items": { "type": "integer", "format": "int" - }, - "uniqueItems": true + } } - } + }, + "required": [ + "map", + "set" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/inline-subschemas-recursive.json b/schemars/tests/expected/inline-subschemas-recursive.json index e051e5d..21f7f31 100644 --- a/schemars/tests/expected/inline-subschemas-recursive.json +++ b/schemars/tests/expected/inline-subschemas-recursive.json @@ -1,12 +1,12 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "RecursiveOuter", "type": "object", "properties": { "direct": { "anyOf": [ { - "$ref": "#/definitions/RecursiveOuter" + "$ref": "#/$defs/RecursiveOuter" }, { "type": "null" @@ -18,24 +18,24 @@ "object", "null" ], - "required": [ - "recursive" - ], "properties": { "recursive": { - "$ref": "#/definitions/RecursiveOuter" + "$ref": "#/$defs/RecursiveOuter" } - } + }, + "required": [ + "recursive" + ] } }, - "definitions": { + "$defs": { "RecursiveOuter": { "type": "object", "properties": { "direct": { "anyOf": [ { - "$ref": "#/definitions/RecursiveOuter" + "$ref": "#/$defs/RecursiveOuter" }, { "type": "null" @@ -47,14 +47,14 @@ "object", "null" ], - "required": [ - "recursive" - ], "properties": { "recursive": { - "$ref": "#/definitions/RecursiveOuter" + "$ref": "#/$defs/RecursiveOuter" } - } + }, + "required": [ + "recursive" + ] } } } diff --git a/schemars/tests/expected/inline-subschemas.json b/schemars/tests/expected/inline-subschemas.json index b731553..cbee457 100644 --- a/schemars/tests/expected/inline-subschemas.json +++ b/schemars/tests/expected/inline-subschemas.json @@ -1,23 +1,23 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyJob", "type": "object", - "required": [ - "spec" - ], "properties": { "spec": { "type": "object", - "required": [ - "replicas" - ], "properties": { "replicas": { "type": "integer", "format": "uint32", - "minimum": 0.0 + "minimum": 0 } - } + }, + "required": [ + "replicas" + ] } - } + }, + "required": [ + "spec" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/macro_built_enum.json b/schemars/tests/expected/macro_built_enum.json index d030787..b27df44 100644 --- a/schemars/tests/expected/macro_built_enum.json +++ b/schemars/tests/expected/macro_built_enum.json @@ -1,32 +1,32 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "OuterEnum", "oneOf": [ { "type": "object", + "properties": { + "InnerStruct": { + "$ref": "#/$defs/InnerStruct" + } + }, "required": [ "InnerStruct" ], - "properties": { - "InnerStruct": { - "$ref": "#/definitions/InnerStruct" - } - }, "additionalProperties": false } ], - "definitions": { + "$defs": { "InnerStruct": { "type": "object", - "required": [ - "x" - ], "properties": { "x": { "type": "integer", "format": "int32" } - } + }, + "required": [ + "x" + ] } } } \ No newline at end of file diff --git a/schemars/tests/expected/macro_built_struct.json b/schemars/tests/expected/macro_built_struct.json index 811eae3..1d0fd08 100644 --- a/schemars/tests/expected/macro_built_struct.json +++ b/schemars/tests/expected/macro_built_struct.json @@ -1,20 +1,20 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "A", "type": "object", - "required": [ - "v", - "x" - ], "properties": { "x": { "type": "integer", "format": "uint8", - "minimum": 0.0 + "minimum": 0 }, "v": { "type": "integer", "format": "int32" } - } + }, + "required": [ + "x", + "v" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/no-variants.json b/schemars/tests/expected/no-variants.json index efe2853..14de7f4 100644 --- a/schemars/tests/expected/no-variants.json +++ b/schemars/tests/expected/no-variants.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "NoVariants", "type": "string", "enum": [] diff --git a/schemars/tests/expected/nonzero_ints.json b/schemars/tests/expected/nonzero_ints.json index cbfcfd9..432e3b0 100644 --- a/schemars/tests/expected/nonzero_ints.json +++ b/schemars/tests/expected/nonzero_ints.json @@ -1,23 +1,17 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", - "required": [ - "nonzero_signed", - "nonzero_unsigned", - "signed", - "unsigned" - ], "properties": { "unsigned": { "type": "integer", "format": "uint32", - "minimum": 0.0 + "minimum": 0 }, "nonzero_unsigned": { "type": "integer", "format": "uint32", - "minimum": 1.0 + "minimum": 1 }, "signed": { "type": "integer", @@ -30,5 +24,11 @@ "const": 0 } } - } + }, + "required": [ + "unsigned", + "nonzero_unsigned", + "signed", + "nonzero_signed" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/os_strings.json b/schemars/tests/expected/os_strings.json index d333675..72e0be1 100644 --- a/schemars/tests/expected/os_strings.json +++ b/schemars/tests/expected/os_strings.json @@ -1,53 +1,53 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "OsStrings", "type": "object", - "required": [ - "borrowed", - "owned" - ], "properties": { "owned": { - "$ref": "#/definitions/OsString" + "$ref": "#/$defs/OsString" }, "borrowed": { - "$ref": "#/definitions/OsString" + "$ref": "#/$defs/OsString" } }, - "definitions": { + "required": [ + "owned", + "borrowed" + ], + "$defs": { "OsString": { "oneOf": [ { "type": "object", - "required": [ - "Unix" - ], "properties": { "Unix": { "type": "array", "items": { "type": "integer", "format": "uint8", - "minimum": 0.0 + "minimum": 0 } } - } + }, + "required": [ + "Unix" + ] }, { "type": "object", - "required": [ - "Windows" - ], "properties": { "Windows": { "type": "array", "items": { "type": "integer", "format": "uint16", - "minimum": 0.0 + "minimum": 0 } } - } + }, + "required": [ + "Windows" + ] } ] } diff --git a/schemars/tests/expected/property-name-struct.json b/schemars/tests/expected/property-name-struct.json index aa708fd..82ea9ed 100644 --- a/schemars/tests/expected/property-name-struct.json +++ b/schemars/tests/expected/property-name-struct.json @@ -1,12 +1,7 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", - "required": [ - "camelCase", - "new_name_1", - "new_name_2" - ], "properties": { "camelCase": { "type": "integer", @@ -20,5 +15,10 @@ "type": "integer", "format": "int32" } - } + }, + "required": [ + "camelCase", + "new_name_1", + "new_name_2" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/range.json b/schemars/tests/expected/range.json index a3b14af..64db4bd 100644 --- a/schemars/tests/expected/range.json +++ b/schemars/tests/expected/range.json @@ -1,49 +1,45 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", - "required": [ - "bound", - "inclusive", - "range" - ], "properties": { "range": { - "$ref": "#/definitions/Range_of_uint" + "$ref": "#/$defs/Range_of_uint" }, "inclusive": { - "$ref": "#/definitions/Range_of_double" + "$ref": "#/$defs/Range_of_double" }, "bound": { - "$ref": "#/definitions/Bound_of_String" + "$ref": "#/$defs/Bound_of_string" } }, - "definitions": { + "required": [ + "range", + "inclusive", + "bound" + ], + "$defs": { "Range_of_uint": { "type": "object", - "required": [ - "end", - "start" - ], "properties": { "start": { "type": "integer", "format": "uint", - "minimum": 0.0 + "minimum": 0 }, "end": { "type": "integer", "format": "uint", - "minimum": 0.0 + "minimum": 0 } - } + }, + "required": [ + "start", + "end" + ] }, "Range_of_double": { "type": "object", - "required": [ - "end", - "start" - ], "properties": { "start": { "type": "number", @@ -53,31 +49,35 @@ "type": "number", "format": "double" } - } + }, + "required": [ + "start", + "end" + ] }, - "Bound_of_String": { + "Bound_of_string": { "oneOf": [ { "type": "object", - "required": [ - "Included" - ], "properties": { "Included": { "type": "string" } - } + }, + "required": [ + "Included" + ] }, { "type": "object", - "required": [ - "Excluded" - ], "properties": { "Excluded": { "type": "string" } - } + }, + "required": [ + "Excluded" + ] }, { "type": "string", diff --git a/schemars/tests/expected/remote_derive.json b/schemars/tests/expected/remote_derive.json index 6f09eb2..760630d 100644 --- a/schemars/tests/expected/remote_derive.json +++ b/schemars/tests/expected/remote_derive.json @@ -1,45 +1,33 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Process", "type": "object", - "required": [ - "command_line", - "wall_time" - ], "properties": { "command_line": { "type": "string" }, "wall_time": { - "$ref": "#/definitions/Duration" + "$ref": "#/$defs/Duration" }, "user_cpu_time": { + "$ref": "#/$defs/Duration", "default": { - "nanos": 0, - "secs": 0 - }, - "allOf": [ - { - "$ref": "#/definitions/Duration" - } - ] + "secs": 0, + "nanos": 0 + } }, "system_cpu_time": { - "default": "0.000000000s", - "allOf": [ - { - "$ref": "#/definitions/Duration" - } - ] + "$ref": "#/$defs/Duration", + "default": "0.000000000s" } }, - "definitions": { + "required": [ + "command_line", + "wall_time" + ], + "$defs": { "Duration": { "type": "object", - "required": [ - "nanos", - "secs" - ], "properties": { "secs": { "type": "integer", @@ -49,7 +37,11 @@ "type": "integer", "format": "int32" } - } + }, + "required": [ + "secs", + "nanos" + ] } } } \ No newline at end of file diff --git a/schemars/tests/expected/remote_derive_generic.json b/schemars/tests/expected/remote_derive_generic.json index 2fea80a..bef4d6a 100644 --- a/schemars/tests/expected/remote_derive_generic.json +++ b/schemars/tests/expected/remote_derive_generic.json @@ -1,48 +1,48 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct_for_int32", "type": "object", - "required": [ - "byte_or_bool2", - "fake_map", - "s", - "unit_or_t2" - ], "properties": { "byte_or_bool2": { - "$ref": "#/definitions/Or_for_uint8_and_Boolean" + "$ref": "#/$defs/Or_for_uint8_and_boolean" }, "unit_or_t2": { - "$ref": "#/definitions/Or_for_Null_and_int32" + "$ref": "#/$defs/Or_for_null_and_int32" }, "s": { - "$ref": "#/definitions/Str" + "$ref": "#/$defs/Str" }, "fake_map": { "type": "object", "additionalProperties": { "type": "array", + "uniqueItems": true, "items": { "type": "string" - }, - "uniqueItems": true + } } } }, - "definitions": { - "Or_for_uint8_and_Boolean": { + "required": [ + "byte_or_bool2", + "unit_or_t2", + "s", + "fake_map" + ], + "$defs": { + "Or_for_uint8_and_boolean": { "anyOf": [ { "type": "integer", "format": "uint8", - "minimum": 0.0 + "minimum": 0 }, { "type": "boolean" } ] }, - "Or_for_Null_and_int32": { + "Or_for_null_and_int32": { "anyOf": [ { "type": "null" diff --git a/schemars/tests/expected/result.json b/schemars/tests/expected/result.json index d8d6ec1..b468835 100644 --- a/schemars/tests/expected/result.json +++ b/schemars/tests/expected/result.json @@ -1,38 +1,35 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Container", "type": "object", + "properties": { + "result1": { + "$ref": "#/$defs/Result_of_MyStruct_or_Array_of_string" + }, + "result2": { + "$ref": "#/$defs/Result_of_boolean_or_null" + } + }, "required": [ "result1", "result2" ], - "properties": { - "result1": { - "$ref": "#/definitions/Result_of_MyStruct_or_Array_of_String" - }, - "result2": { - "$ref": "#/definitions/Result_of_Boolean_or_Null" - } - }, - "definitions": { - "Result_of_MyStruct_or_Array_of_String": { + "$defs": { + "Result_of_MyStruct_or_Array_of_string": { "oneOf": [ { "type": "object", - "required": [ - "Ok" - ], "properties": { "Ok": { - "$ref": "#/definitions/MyStruct" + "$ref": "#/$defs/MyStruct" } - } + }, + "required": [ + "Ok" + ] }, { "type": "object", - "required": [ - "Err" - ], "properties": { "Err": { "type": "array", @@ -40,45 +37,48 @@ "type": "string" } } - } + }, + "required": [ + "Err" + ] } ] }, "MyStruct": { "type": "object", - "required": [ - "foo" - ], "properties": { "foo": { "type": "integer", "format": "int32" } - } + }, + "required": [ + "foo" + ] }, - "Result_of_Boolean_or_Null": { + "Result_of_boolean_or_null": { "oneOf": [ { "type": "object", - "required": [ - "Ok" - ], "properties": { "Ok": { "type": "boolean" } - } + }, + "required": [ + "Ok" + ] }, { "type": "object", - "required": [ - "Err" - ], "properties": { "Err": { "type": "null" } - } + }, + "required": [ + "Err" + ] } ] } diff --git a/schemars/tests/expected/rust_decimal.json b/schemars/tests/expected/rust_decimal.json index 855db6f..e94ca27 100644 --- a/schemars/tests/expected/rust_decimal.json +++ b/schemars/tests/expected/rust_decimal.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Decimal", "type": "string", "pattern": "^-?[0-9]+(\\.[0-9]+)?$" diff --git a/schemars/tests/expected/same_name.json b/schemars/tests/expected/same_name.json index 9e4e6b3..fb65854 100644 --- a/schemars/tests/expected/same_name.json +++ b/schemars/tests/expected/same_name.json @@ -1,41 +1,41 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Config2", "type": "object", + "properties": { + "a_cfg": { + "$ref": "#/$defs/Config" + }, + "b_cfg": { + "$ref": "#/$defs/Config2" + } + }, "required": [ "a_cfg", "b_cfg" ], - "properties": { - "a_cfg": { - "$ref": "#/definitions/Config" - }, - "b_cfg": { - "$ref": "#/definitions/Config2" - } - }, - "definitions": { + "$defs": { "Config": { "type": "object", - "required": [ - "test" - ], "properties": { "test": { "type": "string" } - } + }, + "required": [ + "test" + ] }, "Config2": { "type": "object", - "required": [ - "test2" - ], "properties": { "test2": { "type": "string" } - } + }, + "required": [ + "test2" + ] } } } \ No newline at end of file diff --git a/schemars/tests/expected/schema-2019_09.json b/schemars/tests/expected/schema-2019_09.json deleted file mode 100644 index cf11d35..0000000 --- a/schemars/tests/expected/schema-2019_09.json +++ /dev/null @@ -1,758 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2019-09/schema", - "title": "RootSchema", - "description": "The root object of a JSON Schema document.", - "type": "object", - "properties": { - "$schema": { - "description": "The `$schema` keyword.\n\nSee [JSON Schema 8.1.1. The \"$schema\" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.1.1).", - "type": [ - "string", - "null" - ] - }, - "definitions": { - "description": "The `definitions` keyword.\n\nIn JSON Schema draft 2019-09 this was replaced by $defs, but in Schemars this is still serialized as `definitions` for backward-compatibility.\n\nSee [JSON Schema 8.2.5. Schema Re-Use With \"$defs\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.5), and [JSON Schema (draft 07) 9. Schema Re-Use With \"definitions\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-01#section-9).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "type": { - "description": "The `type` keyword.\n\nSee [JSON Schema Validation 6.1.1. \"type\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.1) and [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1).", - "anyOf": [ - { - "$ref": "#/definitions/SingleOrVec_for_InstanceType" - }, - { - "type": "null" - } - ] - }, - "format": { - "description": "The `format` keyword.\n\nSee [JSON Schema Validation 7. A Vocabulary for Semantic Content With \"format\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-7).", - "type": [ - "string", - "null" - ] - }, - "enum": { - "description": "The `enum` keyword.\n\nSee [JSON Schema Validation 6.1.2. \"enum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.2)", - "type": [ - "array", - "null" - ], - "items": true - }, - "const": { - "description": "The `const` keyword.\n\nSee [JSON Schema Validation 6.1.3. \"const\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.3)" - }, - "$ref": { - "description": "The `$ref` keyword.\n\nSee [JSON Schema 8.2.4.1. Direct References with \"$ref\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.4.1).", - "type": [ - "string", - "null" - ] - }, - "$id": { - "description": "The `$id` keyword.\n\nSee [JSON Schema 8.2.2. The \"$id\" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.2).", - "type": [ - "string", - "null" - ] - }, - "title": { - "description": "The `title` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": [ - "string", - "null" - ] - }, - "description": { - "description": "The `description` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": [ - "string", - "null" - ] - }, - "default": { - "description": "The `default` keyword.\n\nSee [JSON Schema Validation 9.2. \"default\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.2)." - }, - "deprecated": { - "description": "The `deprecated` keyword.\n\nSee [JSON Schema Validation 9.3. \"deprecated\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.3).", - "type": "boolean" - }, - "readOnly": { - "description": "The `readOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "writeOnly": { - "description": "The `writeOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "examples": { - "description": "The `examples` keyword.\n\nSee [JSON Schema Validation 9.5. \"examples\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.5).", - "type": "array", - "items": true - }, - "allOf": { - "description": "The `allOf` keyword.\n\nSee [JSON Schema 9.2.1.1. \"allOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.1).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "anyOf": { - "description": "The `anyOf` keyword.\n\nSee [JSON Schema 9.2.1.2. \"anyOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.2).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "oneOf": { - "description": "The `oneOf` keyword.\n\nSee [JSON Schema 9.2.1.3. \"oneOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.3).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "not": { - "description": "The `not` keyword.\n\nSee [JSON Schema 9.2.1.4. \"not\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.4).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "if": { - "description": "The `if` keyword.\n\nSee [JSON Schema 9.2.2.1. \"if\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.1).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "then": { - "description": "The `then` keyword.\n\nSee [JSON Schema 9.2.2.2. \"then\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.2).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "else": { - "description": "The `else` keyword.\n\nSee [JSON Schema 9.2.2.3. \"else\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.3).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "multipleOf": { - "description": "The `multipleOf` keyword.\n\nSee [JSON Schema Validation 6.2.1. \"multipleOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.1).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "maximum": { - "description": "The `maximum` keyword.\n\nSee [JSON Schema Validation 6.2.2. \"maximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.2).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "exclusiveMaximum": { - "description": "The `exclusiveMaximum` keyword.\n\nSee [JSON Schema Validation 6.2.3. \"exclusiveMaximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.3).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "minimum": { - "description": "The `minimum` keyword.\n\nSee [JSON Schema Validation 6.2.4. \"minimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.4).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "exclusiveMinimum": { - "description": "The `exclusiveMinimum` keyword.\n\nSee [JSON Schema Validation 6.2.5. \"exclusiveMinimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.5).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "maxLength": { - "description": "The `maxLength` keyword.\n\nSee [JSON Schema Validation 6.3.1. \"maxLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minLength": { - "description": "The `minLength` keyword.\n\nSee [JSON Schema Validation 6.3.2. \"minLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "pattern": { - "description": "The `pattern` keyword.\n\nSee [JSON Schema Validation 6.3.3. \"pattern\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.3).", - "type": [ - "string", - "null" - ] - }, - "items": { - "description": "The `items` keyword.\n\nSee [JSON Schema 9.3.1.1. \"items\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.1).", - "anyOf": [ - { - "$ref": "#/definitions/SingleOrVec_for_Schema" - }, - { - "type": "null" - } - ] - }, - "additionalItems": { - "description": "The `additionalItems` keyword.\n\nSee [JSON Schema 9.3.1.2. \"additionalItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.2).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "maxItems": { - "description": "The `maxItems` keyword.\n\nSee [JSON Schema Validation 6.4.1. \"maxItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minItems": { - "description": "The `minItems` keyword.\n\nSee [JSON Schema Validation 6.4.2. \"minItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "uniqueItems": { - "description": "The `uniqueItems` keyword.\n\nSee [JSON Schema Validation 6.4.3. \"uniqueItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.3).", - "type": [ - "boolean", - "null" - ] - }, - "contains": { - "description": "The `contains` keyword.\n\nSee [JSON Schema 9.3.1.4. \"contains\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.4).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "maxProperties": { - "description": "The `maxProperties` keyword.\n\nSee [JSON Schema Validation 6.5.1. \"maxProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minProperties": { - "description": "The `minProperties` keyword.\n\nSee [JSON Schema Validation 6.5.2. \"minProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "required": { - "description": "The `required` keyword.\n\nSee [JSON Schema Validation 6.5.3. \"required\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.3).", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "properties": { - "description": "The `properties` keyword.\n\nSee [JSON Schema 9.3.2.1. \"properties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.1).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "patternProperties": { - "description": "The `patternProperties` keyword.\n\nSee [JSON Schema 9.3.2.2. \"patternProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.2).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "additionalProperties": { - "description": "The `additionalProperties` keyword.\n\nSee [JSON Schema 9.3.2.3. \"additionalProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.3).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "propertyNames": { - "description": "The `propertyNames` keyword.\n\nSee [JSON Schema 9.3.2.5. \"propertyNames\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.5).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": true, - "definitions": { - "Schema": { - "description": "A JSON Schema.", - "anyOf": [ - { - "description": "A trivial boolean JSON Schema.\n\nThe schema `true` matches everything (always passes validation), whereas the schema `false` matches nothing (always fails validation).", - "type": "boolean" - }, - { - "description": "A JSON Schema object.", - "$ref": "#/definitions/SchemaObject" - } - ] - }, - "SchemaObject": { - "description": "A JSON Schema object.", - "type": "object", - "properties": { - "type": { - "description": "The `type` keyword.\n\nSee [JSON Schema Validation 6.1.1. \"type\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.1) and [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1).", - "anyOf": [ - { - "$ref": "#/definitions/SingleOrVec_for_InstanceType" - }, - { - "type": "null" - } - ] - }, - "format": { - "description": "The `format` keyword.\n\nSee [JSON Schema Validation 7. A Vocabulary for Semantic Content With \"format\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-7).", - "type": [ - "string", - "null" - ] - }, - "enum": { - "description": "The `enum` keyword.\n\nSee [JSON Schema Validation 6.1.2. \"enum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.2)", - "type": [ - "array", - "null" - ], - "items": true - }, - "const": { - "description": "The `const` keyword.\n\nSee [JSON Schema Validation 6.1.3. \"const\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.3)" - }, - "$ref": { - "description": "The `$ref` keyword.\n\nSee [JSON Schema 8.2.4.1. Direct References with \"$ref\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.4.1).", - "type": [ - "string", - "null" - ] - }, - "$id": { - "description": "The `$id` keyword.\n\nSee [JSON Schema 8.2.2. The \"$id\" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.2).", - "type": [ - "string", - "null" - ] - }, - "title": { - "description": "The `title` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": [ - "string", - "null" - ] - }, - "description": { - "description": "The `description` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": [ - "string", - "null" - ] - }, - "default": { - "description": "The `default` keyword.\n\nSee [JSON Schema Validation 9.2. \"default\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.2)." - }, - "deprecated": { - "description": "The `deprecated` keyword.\n\nSee [JSON Schema Validation 9.3. \"deprecated\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.3).", - "type": "boolean" - }, - "readOnly": { - "description": "The `readOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "writeOnly": { - "description": "The `writeOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "examples": { - "description": "The `examples` keyword.\n\nSee [JSON Schema Validation 9.5. \"examples\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.5).", - "type": "array", - "items": true - }, - "allOf": { - "description": "The `allOf` keyword.\n\nSee [JSON Schema 9.2.1.1. \"allOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.1).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "anyOf": { - "description": "The `anyOf` keyword.\n\nSee [JSON Schema 9.2.1.2. \"anyOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.2).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "oneOf": { - "description": "The `oneOf` keyword.\n\nSee [JSON Schema 9.2.1.3. \"oneOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.3).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "not": { - "description": "The `not` keyword.\n\nSee [JSON Schema 9.2.1.4. \"not\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.4).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "if": { - "description": "The `if` keyword.\n\nSee [JSON Schema 9.2.2.1. \"if\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.1).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "then": { - "description": "The `then` keyword.\n\nSee [JSON Schema 9.2.2.2. \"then\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.2).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "else": { - "description": "The `else` keyword.\n\nSee [JSON Schema 9.2.2.3. \"else\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.3).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "multipleOf": { - "description": "The `multipleOf` keyword.\n\nSee [JSON Schema Validation 6.2.1. \"multipleOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.1).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "maximum": { - "description": "The `maximum` keyword.\n\nSee [JSON Schema Validation 6.2.2. \"maximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.2).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "exclusiveMaximum": { - "description": "The `exclusiveMaximum` keyword.\n\nSee [JSON Schema Validation 6.2.3. \"exclusiveMaximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.3).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "minimum": { - "description": "The `minimum` keyword.\n\nSee [JSON Schema Validation 6.2.4. \"minimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.4).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "exclusiveMinimum": { - "description": "The `exclusiveMinimum` keyword.\n\nSee [JSON Schema Validation 6.2.5. \"exclusiveMinimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.5).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "maxLength": { - "description": "The `maxLength` keyword.\n\nSee [JSON Schema Validation 6.3.1. \"maxLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minLength": { - "description": "The `minLength` keyword.\n\nSee [JSON Schema Validation 6.3.2. \"minLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "pattern": { - "description": "The `pattern` keyword.\n\nSee [JSON Schema Validation 6.3.3. \"pattern\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.3).", - "type": [ - "string", - "null" - ] - }, - "items": { - "description": "The `items` keyword.\n\nSee [JSON Schema 9.3.1.1. \"items\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.1).", - "anyOf": [ - { - "$ref": "#/definitions/SingleOrVec_for_Schema" - }, - { - "type": "null" - } - ] - }, - "additionalItems": { - "description": "The `additionalItems` keyword.\n\nSee [JSON Schema 9.3.1.2. \"additionalItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.2).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "maxItems": { - "description": "The `maxItems` keyword.\n\nSee [JSON Schema Validation 6.4.1. \"maxItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minItems": { - "description": "The `minItems` keyword.\n\nSee [JSON Schema Validation 6.4.2. \"minItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "uniqueItems": { - "description": "The `uniqueItems` keyword.\n\nSee [JSON Schema Validation 6.4.3. \"uniqueItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.3).", - "type": [ - "boolean", - "null" - ] - }, - "contains": { - "description": "The `contains` keyword.\n\nSee [JSON Schema 9.3.1.4. \"contains\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.4).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "maxProperties": { - "description": "The `maxProperties` keyword.\n\nSee [JSON Schema Validation 6.5.1. \"maxProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minProperties": { - "description": "The `minProperties` keyword.\n\nSee [JSON Schema Validation 6.5.2. \"minProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "required": { - "description": "The `required` keyword.\n\nSee [JSON Schema Validation 6.5.3. \"required\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.3).", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "properties": { - "description": "The `properties` keyword.\n\nSee [JSON Schema 9.3.2.1. \"properties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.1).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "patternProperties": { - "description": "The `patternProperties` keyword.\n\nSee [JSON Schema 9.3.2.2. \"patternProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.2).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "additionalProperties": { - "description": "The `additionalProperties` keyword.\n\nSee [JSON Schema 9.3.2.3. \"additionalProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.3).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "propertyNames": { - "description": "The `propertyNames` keyword.\n\nSee [JSON Schema 9.3.2.5. \"propertyNames\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.5).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": true - }, - "SingleOrVec_for_InstanceType": { - "description": "A type which can be serialized as a single item, or multiple items.\n\nIn some contexts, a `Single` may be semantically distinct from a `Vec` containing only item.", - "anyOf": [ - { - "$ref": "#/definitions/InstanceType" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/InstanceType" - } - } - ] - }, - "InstanceType": { - "description": "The possible types of values in JSON Schema documents.\n\nSee [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1).", - "type": "string", - "enum": [ - "null", - "boolean", - "object", - "array", - "number", - "string", - "integer" - ] - }, - "SingleOrVec_for_Schema": { - "description": "A type which can be serialized as a single item, or multiple items.\n\nIn some contexts, a `Single` may be semantically distinct from a `Vec` containing only item.", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/Schema" - } - } - ] - } - } -} \ No newline at end of file diff --git a/schemars/tests/expected/schema-name-const-generics.json b/schemars/tests/expected/schema-name-const-generics.json index 7505b28..3c2edcc 100644 --- a/schemars/tests/expected/schema-name-const-generics.json +++ b/schemars/tests/expected/schema-name-const-generics.json @@ -1,14 +1,14 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "const-generics-z-42", "type": "object", - "required": [ - "foo" - ], "properties": { "foo": { "type": "integer", "format": "int32" } - } + }, + "required": [ + "foo" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/schema-name-custom.json b/schemars/tests/expected/schema-name-custom.json index 866fb9d..70b4d98 100644 --- a/schemars/tests/expected/schema-name-custom.json +++ b/schemars/tests/expected/schema-name-custom.json @@ -1,14 +1,7 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "a-new-name-Array_of_String-int32-int32", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "a-new-name-Array_of_string-int32-int32", "type": "object", - "required": [ - "inner", - "t", - "u", - "v", - "w" - ], "properties": { "t": { "type": "integer", @@ -27,21 +20,28 @@ } }, "inner": { - "$ref": "#/definitions/another-new-name" + "$ref": "#/$defs/another-new-name" } }, - "definitions": { + "required": [ + "t", + "u", + "v", + "w", + "inner" + ], + "$defs": { "another-new-name": { "type": "object", - "required": [ - "foo" - ], "properties": { "foo": { "type": "integer", "format": "int32" } - } + }, + "required": [ + "foo" + ] } } } \ No newline at end of file diff --git a/schemars/tests/expected/schema-name-default.json b/schemars/tests/expected/schema-name-default.json index 31f7f26..2243ddd 100644 --- a/schemars/tests/expected/schema-name-default.json +++ b/schemars/tests/expected/schema-name-default.json @@ -1,14 +1,7 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MyStruct_for_int32_and_Null_and_Boolean_and_Array_of_String", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "MyStruct_for_int32_and_null_and_boolean_and_Array_of_string", "type": "object", - "required": [ - "inner", - "t", - "u", - "v", - "w" - ], "properties": { "t": { "type": "integer", @@ -27,21 +20,28 @@ } }, "inner": { - "$ref": "#/definitions/MySimpleStruct" + "$ref": "#/$defs/MySimpleStruct" } }, - "definitions": { + "required": [ + "t", + "u", + "v", + "w", + "inner" + ], + "$defs": { "MySimpleStruct": { "type": "object", - "required": [ - "foo" - ], "properties": { "foo": { "type": "integer", "format": "int32" } - } + }, + "required": [ + "foo" + ] } } } \ No newline at end of file diff --git a/schemars/tests/expected/schema-name-mixed-generics.json b/schemars/tests/expected/schema-name-mixed-generics.json index 32ac797..85c6792 100644 --- a/schemars/tests/expected/schema-name-mixed-generics.json +++ b/schemars/tests/expected/schema-name-mixed-generics.json @@ -1,46 +1,24 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MixedGenericStruct_for_MyStruct_for_int32_and_Null_and_Boolean_and_Array_of_String_and_42_and_z", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "MixedGenericStruct_for_MyStruct_for_int32_and_null_and_boolean_and_Array_of_string_and_42_and_z", "type": "object", - "required": [ - "foo", - "generic" - ], "properties": { + "generic": { + "$ref": "#/$defs/MyStruct_for_int32_and_null_and_boolean_and_Array_of_string" + }, "foo": { "type": "integer", "format": "int32" - }, - "generic": { - "$ref": "#/definitions/MyStruct_for_int32_and_Null_and_Boolean_and_Array_of_String" } }, - "definitions": { - "MySimpleStruct": { + "required": [ + "generic", + "foo" + ], + "$defs": { + "MyStruct_for_int32_and_null_and_boolean_and_Array_of_string": { "type": "object", - "required": [ - "foo" - ], "properties": { - "foo": { - "type": "integer", - "format": "int32" - } - } - }, - "MyStruct_for_int32_and_Null_and_Boolean_and_Array_of_String": { - "type": "object", - "required": [ - "inner", - "t", - "u", - "v", - "w" - ], - "properties": { - "inner": { - "$ref": "#/definitions/MySimpleStruct" - }, "t": { "type": "integer", "format": "int32" @@ -56,8 +34,30 @@ "items": { "type": "string" } + }, + "inner": { + "$ref": "#/$defs/MySimpleStruct" } - } + }, + "required": [ + "t", + "u", + "v", + "w", + "inner" + ] + }, + "MySimpleStruct": { + "type": "object", + "properties": { + "foo": { + "type": "integer", + "format": "int32" + } + }, + "required": [ + "foo" + ] } } } \ No newline at end of file diff --git a/schemars/tests/expected/schema-openapi3.json b/schemars/tests/expected/schema-openapi3.json deleted file mode 100644 index 0548828..0000000 --- a/schemars/tests/expected/schema-openapi3.json +++ /dev/null @@ -1,636 +0,0 @@ -{ - "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", - "title": "RootSchema", - "description": "The root object of a JSON Schema document.", - "type": "object", - "properties": { - "$schema": { - "description": "The `$schema` keyword.\n\nSee [JSON Schema 8.1.1. The \"$schema\" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.1.1).", - "type": "string", - "nullable": true - }, - "definitions": { - "description": "The `definitions` keyword.\n\nIn JSON Schema draft 2019-09 this was replaced by $defs, but in Schemars this is still serialized as `definitions` for backward-compatibility.\n\nSee [JSON Schema 8.2.5. Schema Re-Use With \"$defs\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.5), and [JSON Schema (draft 07) 9. Schema Re-Use With \"definitions\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-01#section-9).", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/Schema" - } - }, - "type": { - "description": "The `type` keyword.\n\nSee [JSON Schema Validation 6.1.1. \"type\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.1) and [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1).", - "allOf": [ - { - "$ref": "#/components/schemas/SingleOrVec_for_InstanceType" - } - ], - "nullable": true - }, - "format": { - "description": "The `format` keyword.\n\nSee [JSON Schema Validation 7. A Vocabulary for Semantic Content With \"format\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-7).", - "type": "string", - "nullable": true - }, - "enum": { - "description": "The `enum` keyword.\n\nSee [JSON Schema Validation 6.1.2. \"enum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.2)", - "type": "array", - "items": {}, - "nullable": true - }, - "const": { - "description": "The `const` keyword.\n\nSee [JSON Schema Validation 6.1.3. \"const\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.3)", - "nullable": true - }, - "$ref": { - "description": "The `$ref` keyword.\n\nSee [JSON Schema 8.2.4.1. Direct References with \"$ref\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.4.1).", - "type": "string", - "nullable": true - }, - "$id": { - "description": "The `$id` keyword.\n\nSee [JSON Schema 8.2.2. The \"$id\" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.2).", - "type": "string", - "nullable": true - }, - "title": { - "description": "The `title` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": "string", - "nullable": true - }, - "description": { - "description": "The `description` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": "string", - "nullable": true - }, - "default": { - "description": "The `default` keyword.\n\nSee [JSON Schema Validation 9.2. \"default\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.2).", - "nullable": true - }, - "deprecated": { - "description": "The `deprecated` keyword.\n\nSee [JSON Schema Validation 9.3. \"deprecated\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.3).", - "type": "boolean" - }, - "readOnly": { - "description": "The `readOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "writeOnly": { - "description": "The `writeOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "examples": { - "description": "The `examples` keyword.\n\nSee [JSON Schema Validation 9.5. \"examples\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.5).", - "type": "array", - "items": {} - }, - "allOf": { - "description": "The `allOf` keyword.\n\nSee [JSON Schema 9.2.1.1. \"allOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.1).", - "type": "array", - "items": { - "$ref": "#/components/schemas/Schema" - }, - "nullable": true - }, - "anyOf": { - "description": "The `anyOf` keyword.\n\nSee [JSON Schema 9.2.1.2. \"anyOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.2).", - "type": "array", - "items": { - "$ref": "#/components/schemas/Schema" - }, - "nullable": true - }, - "oneOf": { - "description": "The `oneOf` keyword.\n\nSee [JSON Schema 9.2.1.3. \"oneOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.3).", - "type": "array", - "items": { - "$ref": "#/components/schemas/Schema" - }, - "nullable": true - }, - "not": { - "description": "The `not` keyword.\n\nSee [JSON Schema 9.2.1.4. \"not\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.4).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "if": { - "description": "The `if` keyword.\n\nSee [JSON Schema 9.2.2.1. \"if\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.1).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "then": { - "description": "The `then` keyword.\n\nSee [JSON Schema 9.2.2.2. \"then\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.2).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "else": { - "description": "The `else` keyword.\n\nSee [JSON Schema 9.2.2.3. \"else\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.3).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "multipleOf": { - "description": "The `multipleOf` keyword.\n\nSee [JSON Schema Validation 6.2.1. \"multipleOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.1).", - "type": "number", - "format": "double", - "nullable": true - }, - "maximum": { - "description": "The `maximum` keyword.\n\nSee [JSON Schema Validation 6.2.2. \"maximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.2).", - "type": "number", - "format": "double", - "nullable": true - }, - "exclusiveMaximum": { - "description": "The `exclusiveMaximum` keyword.\n\nSee [JSON Schema Validation 6.2.3. \"exclusiveMaximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.3).", - "type": "number", - "format": "double", - "nullable": true - }, - "minimum": { - "description": "The `minimum` keyword.\n\nSee [JSON Schema Validation 6.2.4. \"minimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.4).", - "type": "number", - "format": "double", - "nullable": true - }, - "exclusiveMinimum": { - "description": "The `exclusiveMinimum` keyword.\n\nSee [JSON Schema Validation 6.2.5. \"exclusiveMinimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.5).", - "type": "number", - "format": "double", - "nullable": true - }, - "maxLength": { - "description": "The `maxLength` keyword.\n\nSee [JSON Schema Validation 6.3.1. \"maxLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.1).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "minLength": { - "description": "The `minLength` keyword.\n\nSee [JSON Schema Validation 6.3.2. \"minLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.2).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "pattern": { - "description": "The `pattern` keyword.\n\nSee [JSON Schema Validation 6.3.3. \"pattern\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.3).", - "type": "string", - "nullable": true - }, - "items": { - "description": "The `items` keyword.\n\nSee [JSON Schema 9.3.1.1. \"items\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.1).", - "allOf": [ - { - "$ref": "#/components/schemas/SingleOrVec_for_Schema" - } - ], - "nullable": true - }, - "additionalItems": { - "description": "The `additionalItems` keyword.\n\nSee [JSON Schema 9.3.1.2. \"additionalItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.2).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "maxItems": { - "description": "The `maxItems` keyword.\n\nSee [JSON Schema Validation 6.4.1. \"maxItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.1).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "minItems": { - "description": "The `minItems` keyword.\n\nSee [JSON Schema Validation 6.4.2. \"minItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.2).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "uniqueItems": { - "description": "The `uniqueItems` keyword.\n\nSee [JSON Schema Validation 6.4.3. \"uniqueItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.3).", - "type": "boolean", - "nullable": true - }, - "contains": { - "description": "The `contains` keyword.\n\nSee [JSON Schema 9.3.1.4. \"contains\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.4).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "maxProperties": { - "description": "The `maxProperties` keyword.\n\nSee [JSON Schema Validation 6.5.1. \"maxProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.1).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "minProperties": { - "description": "The `minProperties` keyword.\n\nSee [JSON Schema Validation 6.5.2. \"minProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.2).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "required": { - "description": "The `required` keyword.\n\nSee [JSON Schema Validation 6.5.3. \"required\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.3).", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "properties": { - "description": "The `properties` keyword.\n\nSee [JSON Schema 9.3.2.1. \"properties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.1).", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/Schema" - } - }, - "patternProperties": { - "description": "The `patternProperties` keyword.\n\nSee [JSON Schema 9.3.2.2. \"patternProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.2).", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/Schema" - } - }, - "additionalProperties": { - "description": "The `additionalProperties` keyword.\n\nSee [JSON Schema 9.3.2.3. \"additionalProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.3).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "propertyNames": { - "description": "The `propertyNames` keyword.\n\nSee [JSON Schema 9.3.2.5. \"propertyNames\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.5).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - } - }, - "additionalProperties": true, - "definitions": { - "Schema": { - "description": "A JSON Schema.", - "anyOf": [ - { - "description": "A trivial boolean JSON Schema.\n\nThe schema `true` matches everything (always passes validation), whereas the schema `false` matches nothing (always fails validation).", - "type": "boolean" - }, - { - "description": "A JSON Schema object.", - "allOf": [ - { - "$ref": "#/components/schemas/SchemaObject" - } - ] - } - ] - }, - "SchemaObject": { - "description": "A JSON Schema object.", - "type": "object", - "properties": { - "type": { - "description": "The `type` keyword.\n\nSee [JSON Schema Validation 6.1.1. \"type\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.1) and [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1).", - "allOf": [ - { - "$ref": "#/components/schemas/SingleOrVec_for_InstanceType" - } - ], - "nullable": true - }, - "format": { - "description": "The `format` keyword.\n\nSee [JSON Schema Validation 7. A Vocabulary for Semantic Content With \"format\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-7).", - "type": "string", - "nullable": true - }, - "enum": { - "description": "The `enum` keyword.\n\nSee [JSON Schema Validation 6.1.2. \"enum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.2)", - "type": "array", - "items": {}, - "nullable": true - }, - "const": { - "description": "The `const` keyword.\n\nSee [JSON Schema Validation 6.1.3. \"const\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.3)", - "nullable": true - }, - "$ref": { - "description": "The `$ref` keyword.\n\nSee [JSON Schema 8.2.4.1. Direct References with \"$ref\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.4.1).", - "type": "string", - "nullable": true - }, - "$id": { - "description": "The `$id` keyword.\n\nSee [JSON Schema 8.2.2. The \"$id\" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.2).", - "type": "string", - "nullable": true - }, - "title": { - "description": "The `title` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": "string", - "nullable": true - }, - "description": { - "description": "The `description` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": "string", - "nullable": true - }, - "default": { - "description": "The `default` keyword.\n\nSee [JSON Schema Validation 9.2. \"default\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.2).", - "nullable": true - }, - "deprecated": { - "description": "The `deprecated` keyword.\n\nSee [JSON Schema Validation 9.3. \"deprecated\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.3).", - "type": "boolean" - }, - "readOnly": { - "description": "The `readOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "writeOnly": { - "description": "The `writeOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "examples": { - "description": "The `examples` keyword.\n\nSee [JSON Schema Validation 9.5. \"examples\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.5).", - "type": "array", - "items": {} - }, - "allOf": { - "description": "The `allOf` keyword.\n\nSee [JSON Schema 9.2.1.1. \"allOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.1).", - "type": "array", - "items": { - "$ref": "#/components/schemas/Schema" - }, - "nullable": true - }, - "anyOf": { - "description": "The `anyOf` keyword.\n\nSee [JSON Schema 9.2.1.2. \"anyOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.2).", - "type": "array", - "items": { - "$ref": "#/components/schemas/Schema" - }, - "nullable": true - }, - "oneOf": { - "description": "The `oneOf` keyword.\n\nSee [JSON Schema 9.2.1.3. \"oneOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.3).", - "type": "array", - "items": { - "$ref": "#/components/schemas/Schema" - }, - "nullable": true - }, - "not": { - "description": "The `not` keyword.\n\nSee [JSON Schema 9.2.1.4. \"not\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.4).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "if": { - "description": "The `if` keyword.\n\nSee [JSON Schema 9.2.2.1. \"if\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.1).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "then": { - "description": "The `then` keyword.\n\nSee [JSON Schema 9.2.2.2. \"then\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.2).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "else": { - "description": "The `else` keyword.\n\nSee [JSON Schema 9.2.2.3. \"else\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.3).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "multipleOf": { - "description": "The `multipleOf` keyword.\n\nSee [JSON Schema Validation 6.2.1. \"multipleOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.1).", - "type": "number", - "format": "double", - "nullable": true - }, - "maximum": { - "description": "The `maximum` keyword.\n\nSee [JSON Schema Validation 6.2.2. \"maximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.2).", - "type": "number", - "format": "double", - "nullable": true - }, - "exclusiveMaximum": { - "description": "The `exclusiveMaximum` keyword.\n\nSee [JSON Schema Validation 6.2.3. \"exclusiveMaximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.3).", - "type": "number", - "format": "double", - "nullable": true - }, - "minimum": { - "description": "The `minimum` keyword.\n\nSee [JSON Schema Validation 6.2.4. \"minimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.4).", - "type": "number", - "format": "double", - "nullable": true - }, - "exclusiveMinimum": { - "description": "The `exclusiveMinimum` keyword.\n\nSee [JSON Schema Validation 6.2.5. \"exclusiveMinimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.5).", - "type": "number", - "format": "double", - "nullable": true - }, - "maxLength": { - "description": "The `maxLength` keyword.\n\nSee [JSON Schema Validation 6.3.1. \"maxLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.1).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "minLength": { - "description": "The `minLength` keyword.\n\nSee [JSON Schema Validation 6.3.2. \"minLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.2).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "pattern": { - "description": "The `pattern` keyword.\n\nSee [JSON Schema Validation 6.3.3. \"pattern\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.3).", - "type": "string", - "nullable": true - }, - "items": { - "description": "The `items` keyword.\n\nSee [JSON Schema 9.3.1.1. \"items\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.1).", - "allOf": [ - { - "$ref": "#/components/schemas/SingleOrVec_for_Schema" - } - ], - "nullable": true - }, - "additionalItems": { - "description": "The `additionalItems` keyword.\n\nSee [JSON Schema 9.3.1.2. \"additionalItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.2).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "maxItems": { - "description": "The `maxItems` keyword.\n\nSee [JSON Schema Validation 6.4.1. \"maxItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.1).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "minItems": { - "description": "The `minItems` keyword.\n\nSee [JSON Schema Validation 6.4.2. \"minItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.2).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "uniqueItems": { - "description": "The `uniqueItems` keyword.\n\nSee [JSON Schema Validation 6.4.3. \"uniqueItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.3).", - "type": "boolean", - "nullable": true - }, - "contains": { - "description": "The `contains` keyword.\n\nSee [JSON Schema 9.3.1.4. \"contains\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.4).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "maxProperties": { - "description": "The `maxProperties` keyword.\n\nSee [JSON Schema Validation 6.5.1. \"maxProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.1).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "minProperties": { - "description": "The `minProperties` keyword.\n\nSee [JSON Schema Validation 6.5.2. \"minProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.2).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "required": { - "description": "The `required` keyword.\n\nSee [JSON Schema Validation 6.5.3. \"required\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.3).", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "properties": { - "description": "The `properties` keyword.\n\nSee [JSON Schema 9.3.2.1. \"properties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.1).", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/Schema" - } - }, - "patternProperties": { - "description": "The `patternProperties` keyword.\n\nSee [JSON Schema 9.3.2.2. \"patternProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.2).", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/Schema" - } - }, - "additionalProperties": { - "description": "The `additionalProperties` keyword.\n\nSee [JSON Schema 9.3.2.3. \"additionalProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.3).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "propertyNames": { - "description": "The `propertyNames` keyword.\n\nSee [JSON Schema 9.3.2.5. \"propertyNames\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.5).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - } - }, - "additionalProperties": true - }, - "SingleOrVec_for_InstanceType": { - "description": "A type which can be serialized as a single item, or multiple items.\n\nIn some contexts, a `Single` may be semantically distinct from a `Vec` containing only item.", - "anyOf": [ - { - "$ref": "#/components/schemas/InstanceType" - }, - { - "type": "array", - "items": { - "$ref": "#/components/schemas/InstanceType" - } - } - ] - }, - "InstanceType": { - "description": "The possible types of values in JSON Schema documents.\n\nSee [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1).", - "type": "string", - "enum": [ - "null", - "boolean", - "object", - "array", - "number", - "string", - "integer" - ] - }, - "SingleOrVec_for_Schema": { - "description": "A type which can be serialized as a single item, or multiple items.\n\nIn some contexts, a `Single` may be semantically distinct from a `Vec` containing only item.", - "anyOf": [ - { - "$ref": "#/components/schemas/Schema" - }, - { - "type": "array", - "items": { - "$ref": "#/components/schemas/Schema" - } - } - ] - } - } -} \ No newline at end of file diff --git a/schemars/tests/expected/schema.json b/schemars/tests/expected/schema.json deleted file mode 100644 index 6cf840b..0000000 --- a/schemars/tests/expected/schema.json +++ /dev/null @@ -1,762 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "RootSchema", - "description": "The root object of a JSON Schema document.", - "type": "object", - "properties": { - "$schema": { - "description": "The `$schema` keyword.\n\nSee [JSON Schema 8.1.1. The \"$schema\" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.1.1).", - "type": [ - "string", - "null" - ] - }, - "definitions": { - "description": "The `definitions` keyword.\n\nIn JSON Schema draft 2019-09 this was replaced by $defs, but in Schemars this is still serialized as `definitions` for backward-compatibility.\n\nSee [JSON Schema 8.2.5. Schema Re-Use With \"$defs\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.5), and [JSON Schema (draft 07) 9. Schema Re-Use With \"definitions\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-01#section-9).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "type": { - "description": "The `type` keyword.\n\nSee [JSON Schema Validation 6.1.1. \"type\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.1) and [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1).", - "anyOf": [ - { - "$ref": "#/definitions/SingleOrVec_for_InstanceType" - }, - { - "type": "null" - } - ] - }, - "format": { - "description": "The `format` keyword.\n\nSee [JSON Schema Validation 7. A Vocabulary for Semantic Content With \"format\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-7).", - "type": [ - "string", - "null" - ] - }, - "enum": { - "description": "The `enum` keyword.\n\nSee [JSON Schema Validation 6.1.2. \"enum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.2)", - "type": [ - "array", - "null" - ], - "items": true - }, - "const": { - "description": "The `const` keyword.\n\nSee [JSON Schema Validation 6.1.3. \"const\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.3)" - }, - "$ref": { - "description": "The `$ref` keyword.\n\nSee [JSON Schema 8.2.4.1. Direct References with \"$ref\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.4.1).", - "type": [ - "string", - "null" - ] - }, - "$id": { - "description": "The `$id` keyword.\n\nSee [JSON Schema 8.2.2. The \"$id\" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.2).", - "type": [ - "string", - "null" - ] - }, - "title": { - "description": "The `title` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": [ - "string", - "null" - ] - }, - "description": { - "description": "The `description` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": [ - "string", - "null" - ] - }, - "default": { - "description": "The `default` keyword.\n\nSee [JSON Schema Validation 9.2. \"default\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.2)." - }, - "deprecated": { - "description": "The `deprecated` keyword.\n\nSee [JSON Schema Validation 9.3. \"deprecated\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.3).", - "type": "boolean" - }, - "readOnly": { - "description": "The `readOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "writeOnly": { - "description": "The `writeOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "examples": { - "description": "The `examples` keyword.\n\nSee [JSON Schema Validation 9.5. \"examples\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.5).", - "type": "array", - "items": true - }, - "allOf": { - "description": "The `allOf` keyword.\n\nSee [JSON Schema 9.2.1.1. \"allOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.1).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "anyOf": { - "description": "The `anyOf` keyword.\n\nSee [JSON Schema 9.2.1.2. \"anyOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.2).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "oneOf": { - "description": "The `oneOf` keyword.\n\nSee [JSON Schema 9.2.1.3. \"oneOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.3).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "not": { - "description": "The `not` keyword.\n\nSee [JSON Schema 9.2.1.4. \"not\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.4).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "if": { - "description": "The `if` keyword.\n\nSee [JSON Schema 9.2.2.1. \"if\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.1).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "then": { - "description": "The `then` keyword.\n\nSee [JSON Schema 9.2.2.2. \"then\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.2).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "else": { - "description": "The `else` keyword.\n\nSee [JSON Schema 9.2.2.3. \"else\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.3).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "multipleOf": { - "description": "The `multipleOf` keyword.\n\nSee [JSON Schema Validation 6.2.1. \"multipleOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.1).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "maximum": { - "description": "The `maximum` keyword.\n\nSee [JSON Schema Validation 6.2.2. \"maximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.2).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "exclusiveMaximum": { - "description": "The `exclusiveMaximum` keyword.\n\nSee [JSON Schema Validation 6.2.3. \"exclusiveMaximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.3).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "minimum": { - "description": "The `minimum` keyword.\n\nSee [JSON Schema Validation 6.2.4. \"minimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.4).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "exclusiveMinimum": { - "description": "The `exclusiveMinimum` keyword.\n\nSee [JSON Schema Validation 6.2.5. \"exclusiveMinimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.5).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "maxLength": { - "description": "The `maxLength` keyword.\n\nSee [JSON Schema Validation 6.3.1. \"maxLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minLength": { - "description": "The `minLength` keyword.\n\nSee [JSON Schema Validation 6.3.2. \"minLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "pattern": { - "description": "The `pattern` keyword.\n\nSee [JSON Schema Validation 6.3.3. \"pattern\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.3).", - "type": [ - "string", - "null" - ] - }, - "items": { - "description": "The `items` keyword.\n\nSee [JSON Schema 9.3.1.1. \"items\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.1).", - "anyOf": [ - { - "$ref": "#/definitions/SingleOrVec_for_Schema" - }, - { - "type": "null" - } - ] - }, - "additionalItems": { - "description": "The `additionalItems` keyword.\n\nSee [JSON Schema 9.3.1.2. \"additionalItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.2).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "maxItems": { - "description": "The `maxItems` keyword.\n\nSee [JSON Schema Validation 6.4.1. \"maxItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minItems": { - "description": "The `minItems` keyword.\n\nSee [JSON Schema Validation 6.4.2. \"minItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "uniqueItems": { - "description": "The `uniqueItems` keyword.\n\nSee [JSON Schema Validation 6.4.3. \"uniqueItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.3).", - "type": [ - "boolean", - "null" - ] - }, - "contains": { - "description": "The `contains` keyword.\n\nSee [JSON Schema 9.3.1.4. \"contains\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.4).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "maxProperties": { - "description": "The `maxProperties` keyword.\n\nSee [JSON Schema Validation 6.5.1. \"maxProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minProperties": { - "description": "The `minProperties` keyword.\n\nSee [JSON Schema Validation 6.5.2. \"minProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "required": { - "description": "The `required` keyword.\n\nSee [JSON Schema Validation 6.5.3. \"required\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.3).", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "properties": { - "description": "The `properties` keyword.\n\nSee [JSON Schema 9.3.2.1. \"properties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.1).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "patternProperties": { - "description": "The `patternProperties` keyword.\n\nSee [JSON Schema 9.3.2.2. \"patternProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.2).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "additionalProperties": { - "description": "The `additionalProperties` keyword.\n\nSee [JSON Schema 9.3.2.3. \"additionalProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.3).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "propertyNames": { - "description": "The `propertyNames` keyword.\n\nSee [JSON Schema 9.3.2.5. \"propertyNames\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.5).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": true, - "definitions": { - "Schema": { - "description": "A JSON Schema.", - "anyOf": [ - { - "description": "A trivial boolean JSON Schema.\n\nThe schema `true` matches everything (always passes validation), whereas the schema `false` matches nothing (always fails validation).", - "type": "boolean" - }, - { - "description": "A JSON Schema object.", - "allOf": [ - { - "$ref": "#/definitions/SchemaObject" - } - ] - } - ] - }, - "SchemaObject": { - "description": "A JSON Schema object.", - "type": "object", - "properties": { - "type": { - "description": "The `type` keyword.\n\nSee [JSON Schema Validation 6.1.1. \"type\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.1) and [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1).", - "anyOf": [ - { - "$ref": "#/definitions/SingleOrVec_for_InstanceType" - }, - { - "type": "null" - } - ] - }, - "format": { - "description": "The `format` keyword.\n\nSee [JSON Schema Validation 7. A Vocabulary for Semantic Content With \"format\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-7).", - "type": [ - "string", - "null" - ] - }, - "enum": { - "description": "The `enum` keyword.\n\nSee [JSON Schema Validation 6.1.2. \"enum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.2)", - "type": [ - "array", - "null" - ], - "items": true - }, - "const": { - "description": "The `const` keyword.\n\nSee [JSON Schema Validation 6.1.3. \"const\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.3)" - }, - "$ref": { - "description": "The `$ref` keyword.\n\nSee [JSON Schema 8.2.4.1. Direct References with \"$ref\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.4.1).", - "type": [ - "string", - "null" - ] - }, - "$id": { - "description": "The `$id` keyword.\n\nSee [JSON Schema 8.2.2. The \"$id\" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.2).", - "type": [ - "string", - "null" - ] - }, - "title": { - "description": "The `title` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": [ - "string", - "null" - ] - }, - "description": { - "description": "The `description` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": [ - "string", - "null" - ] - }, - "default": { - "description": "The `default` keyword.\n\nSee [JSON Schema Validation 9.2. \"default\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.2)." - }, - "deprecated": { - "description": "The `deprecated` keyword.\n\nSee [JSON Schema Validation 9.3. \"deprecated\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.3).", - "type": "boolean" - }, - "readOnly": { - "description": "The `readOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "writeOnly": { - "description": "The `writeOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "examples": { - "description": "The `examples` keyword.\n\nSee [JSON Schema Validation 9.5. \"examples\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.5).", - "type": "array", - "items": true - }, - "allOf": { - "description": "The `allOf` keyword.\n\nSee [JSON Schema 9.2.1.1. \"allOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.1).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "anyOf": { - "description": "The `anyOf` keyword.\n\nSee [JSON Schema 9.2.1.2. \"anyOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.2).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "oneOf": { - "description": "The `oneOf` keyword.\n\nSee [JSON Schema 9.2.1.3. \"oneOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.3).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "not": { - "description": "The `not` keyword.\n\nSee [JSON Schema 9.2.1.4. \"not\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.4).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "if": { - "description": "The `if` keyword.\n\nSee [JSON Schema 9.2.2.1. \"if\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.1).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "then": { - "description": "The `then` keyword.\n\nSee [JSON Schema 9.2.2.2. \"then\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.2).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "else": { - "description": "The `else` keyword.\n\nSee [JSON Schema 9.2.2.3. \"else\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.3).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "multipleOf": { - "description": "The `multipleOf` keyword.\n\nSee [JSON Schema Validation 6.2.1. \"multipleOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.1).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "maximum": { - "description": "The `maximum` keyword.\n\nSee [JSON Schema Validation 6.2.2. \"maximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.2).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "exclusiveMaximum": { - "description": "The `exclusiveMaximum` keyword.\n\nSee [JSON Schema Validation 6.2.3. \"exclusiveMaximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.3).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "minimum": { - "description": "The `minimum` keyword.\n\nSee [JSON Schema Validation 6.2.4. \"minimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.4).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "exclusiveMinimum": { - "description": "The `exclusiveMinimum` keyword.\n\nSee [JSON Schema Validation 6.2.5. \"exclusiveMinimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.5).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "maxLength": { - "description": "The `maxLength` keyword.\n\nSee [JSON Schema Validation 6.3.1. \"maxLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minLength": { - "description": "The `minLength` keyword.\n\nSee [JSON Schema Validation 6.3.2. \"minLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "pattern": { - "description": "The `pattern` keyword.\n\nSee [JSON Schema Validation 6.3.3. \"pattern\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.3).", - "type": [ - "string", - "null" - ] - }, - "items": { - "description": "The `items` keyword.\n\nSee [JSON Schema 9.3.1.1. \"items\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.1).", - "anyOf": [ - { - "$ref": "#/definitions/SingleOrVec_for_Schema" - }, - { - "type": "null" - } - ] - }, - "additionalItems": { - "description": "The `additionalItems` keyword.\n\nSee [JSON Schema 9.3.1.2. \"additionalItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.2).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "maxItems": { - "description": "The `maxItems` keyword.\n\nSee [JSON Schema Validation 6.4.1. \"maxItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minItems": { - "description": "The `minItems` keyword.\n\nSee [JSON Schema Validation 6.4.2. \"minItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "uniqueItems": { - "description": "The `uniqueItems` keyword.\n\nSee [JSON Schema Validation 6.4.3. \"uniqueItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.3).", - "type": [ - "boolean", - "null" - ] - }, - "contains": { - "description": "The `contains` keyword.\n\nSee [JSON Schema 9.3.1.4. \"contains\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.4).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "maxProperties": { - "description": "The `maxProperties` keyword.\n\nSee [JSON Schema Validation 6.5.1. \"maxProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minProperties": { - "description": "The `minProperties` keyword.\n\nSee [JSON Schema Validation 6.5.2. \"minProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "required": { - "description": "The `required` keyword.\n\nSee [JSON Schema Validation 6.5.3. \"required\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.3).", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "properties": { - "description": "The `properties` keyword.\n\nSee [JSON Schema 9.3.2.1. \"properties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.1).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "patternProperties": { - "description": "The `patternProperties` keyword.\n\nSee [JSON Schema 9.3.2.2. \"patternProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.2).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "additionalProperties": { - "description": "The `additionalProperties` keyword.\n\nSee [JSON Schema 9.3.2.3. \"additionalProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.3).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "propertyNames": { - "description": "The `propertyNames` keyword.\n\nSee [JSON Schema 9.3.2.5. \"propertyNames\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.5).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": true - }, - "SingleOrVec_for_InstanceType": { - "description": "A type which can be serialized as a single item, or multiple items.\n\nIn some contexts, a `Single` may be semantically distinct from a `Vec` containing only item.", - "anyOf": [ - { - "$ref": "#/definitions/InstanceType" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/InstanceType" - } - } - ] - }, - "InstanceType": { - "description": "The possible types of values in JSON Schema documents.\n\nSee [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1).", - "type": "string", - "enum": [ - "null", - "boolean", - "object", - "array", - "number", - "string", - "integer" - ] - }, - "SingleOrVec_for_Schema": { - "description": "A type which can be serialized as a single item, or multiple items.\n\nIn some contexts, a `Single` may be semantically distinct from a `Vec` containing only item.", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/Schema" - } - } - ] - } - } -} \ No newline at end of file diff --git a/schemars/tests/expected/schema_settings-2019_09.json b/schemars/tests/expected/schema_settings-2019_09.json index fb936a7..bc99f15 100644 --- a/schemars/tests/expected/schema_settings-2019_09.json +++ b/schemars/tests/expected/schema_settings-2019_09.json @@ -2,19 +2,14 @@ "$schema": "https://json-schema.org/draft/2019-09/schema", "title": "Outer", "type": "object", - "required": [ - "int", - "value", - "values" - ], "properties": { "int": { + "type": "integer", + "format": "int32", "examples": [ 8, null - ], - "type": "integer", - "format": "int32" + ] }, "values": { "type": "object", @@ -24,15 +19,40 @@ "inner": { "anyOf": [ { - "$ref": "#/definitions/Inner" + "$ref": "#/$defs/Inner" }, { "type": "null" } ] + }, + "tuples": { + "type": "array", + "items": { + "type": "array", + "maxItems": 2, + "minItems": 2, + "items": [ + { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + { + "type": "integer", + "format": "int64" + } + ] + } } }, - "definitions": { + "required": [ + "int", + "values", + "value", + "tuples" + ], + "$defs": { "Inner": { "oneOf": [ { @@ -45,18 +65,16 @@ { "description": "This is a documented unit variant", "type": "string", - "enum": [ - "DocumentedUnit" - ] + "const": "DocumentedUnit" }, { "type": "object", - "required": [ - "ValueNewType" - ], "properties": { "ValueNewType": true }, + "required": [ + "ValueNewType" + ], "additionalProperties": false } ] diff --git a/schemars/tests/expected/schema_settings-2020_12.json b/schemars/tests/expected/schema_settings-2020_12.json new file mode 100644 index 0000000..7964058 --- /dev/null +++ b/schemars/tests/expected/schema_settings-2020_12.json @@ -0,0 +1,83 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Outer", + "type": "object", + "properties": { + "int": { + "type": "integer", + "format": "int32", + "examples": [ + 8, + null + ] + }, + "values": { + "type": "object", + "additionalProperties": true + }, + "value": true, + "inner": { + "anyOf": [ + { + "$ref": "#/$defs/Inner" + }, + { + "type": "null" + } + ] + }, + "tuples": { + "type": "array", + "items": { + "type": "array", + "prefixItems": [ + { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + { + "type": "integer", + "format": "int64" + } + ], + "minItems": 2, + "maxItems": 2 + } + } + }, + "required": [ + "int", + "values", + "value", + "tuples" + ], + "$defs": { + "Inner": { + "oneOf": [ + { + "type": "string", + "enum": [ + "UndocumentedUnit1", + "UndocumentedUnit2" + ] + }, + { + "description": "This is a documented unit variant", + "type": "string", + "const": "DocumentedUnit" + }, + { + "type": "object", + "properties": { + "ValueNewType": true + }, + "required": [ + "ValueNewType" + ], + "additionalProperties": false + } + ] + } + } +} \ No newline at end of file diff --git a/schemars/tests/expected/schema_settings-openapi3.json b/schemars/tests/expected/schema_settings-openapi3.json index 17fe805..8365822 100644 --- a/schemars/tests/expected/schema_settings-openapi3.json +++ b/schemars/tests/expected/schema_settings-openapi3.json @@ -1,12 +1,7 @@ { - "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", + "$schema": "https://spec.openapis.org/oas/3.0/schema/2021-09-28#/definitions/Schema", "title": "Outer", "type": "object", - "required": [ - "int", - "value", - "values" - ], "properties": { "int": { "type": "integer", @@ -19,42 +14,69 @@ }, "value": {}, "inner": { + "nullable": true, "allOf": [ { "$ref": "#/components/schemas/Inner" } - ], - "nullable": true + ] + }, + "tuples": { + "type": "array", + "items": { + "type": "array", + "maxItems": 2, + "minItems": 2, + "items": [ + { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + { + "type": "integer", + "format": "int64" + } + ] + } } }, - "definitions": { - "Inner": { - "oneOf": [ - { - "type": "string", - "enum": [ - "UndocumentedUnit1", - "UndocumentedUnit2" - ] - }, - { - "description": "This is a documented unit variant", - "type": "string", - "enum": [ - "DocumentedUnit" - ] - }, - { - "type": "object", - "required": [ - "ValueNewType" - ], - "properties": { - "ValueNewType": {} + "required": [ + "int", + "values", + "value", + "tuples" + ], + "components": { + "schemas": { + "Inner": { + "oneOf": [ + { + "type": "string", + "enum": [ + "UndocumentedUnit1", + "UndocumentedUnit2" + ] }, - "additionalProperties": false - } - ] + { + "description": "This is a documented unit variant", + "type": "string", + "enum": [ + "DocumentedUnit" + ] + }, + { + "type": "object", + "properties": { + "ValueNewType": {} + }, + "required": [ + "ValueNewType" + ], + "additionalProperties": false + } + ] + } } } } \ No newline at end of file diff --git a/schemars/tests/expected/schema_settings.json b/schemars/tests/expected/schema_settings.json index c876d6f..4c435ad 100644 --- a/schemars/tests/expected/schema_settings.json +++ b/schemars/tests/expected/schema_settings.json @@ -2,19 +2,14 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Outer", "type": "object", - "required": [ - "int", - "value", - "values" - ], "properties": { "int": { + "type": "integer", + "format": "int32", "examples": [ 8, null - ], - "type": "integer", - "format": "int32" + ] }, "values": { "type": "object", @@ -30,8 +25,33 @@ "type": "null" } ] + }, + "tuples": { + "type": "array", + "items": { + "type": "array", + "maxItems": 2, + "minItems": 2, + "items": [ + { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + { + "type": "integer", + "format": "int64" + } + ] + } } }, + "required": [ + "int", + "values", + "value", + "tuples" + ], "definitions": { "Inner": { "oneOf": [ @@ -45,18 +65,16 @@ { "description": "This is a documented unit variant", "type": "string", - "enum": [ - "DocumentedUnit" - ] + "const": "DocumentedUnit" }, { "type": "object", - "required": [ - "ValueNewType" - ], "properties": { "ValueNewType": true }, + "required": [ + "ValueNewType" + ], "additionalProperties": false } ] diff --git a/schemars/tests/expected/schema_with-enum-adjacent-tagged.json b/schemars/tests/expected/schema_with-enum-adjacent-tagged.json index 3ecba4c..3e7173d 100644 --- a/schemars/tests/expected/schema_with-enum-adjacent-tagged.json +++ b/schemars/tests/expected/schema_with-enum-adjacent-tagged.json @@ -1,61 +1,63 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Adjacent", "oneOf": [ { "type": "object", - "required": [ - "c", - "t" - ], "properties": { - "c": { - "type": "object", - "required": [ - "foo" - ], - "properties": { - "foo": { - "type": "boolean" - } - } - }, "t": { "type": "string", "enum": [ "Struct" ] + }, + "c": { + "type": "object", + "properties": { + "foo": { + "type": "boolean" + } + }, + "required": [ + "foo" + ] } - } + }, + "required": [ + "t", + "c" + ] }, { "type": "object", - "required": [ - "c", - "t" - ], "properties": { - "c": { - "type": "boolean" - }, "t": { "type": "string", "enum": [ "NewType" ] + }, + "c": { + "type": "boolean" } - } + }, + "required": [ + "t", + "c" + ] }, { "type": "object", - "required": [ - "c", - "t" - ], "properties": { + "t": { + "type": "string", + "enum": [ + "Tuple" + ] + }, "c": { "type": "array", - "items": [ + "prefixItems": [ { "type": "boolean" }, @@ -64,34 +66,32 @@ "format": "int32" } ], - "maxItems": 2, - "minItems": 2 - }, - "t": { - "type": "string", - "enum": [ - "Tuple" - ] + "minItems": 2, + "maxItems": 2 } - } + }, + "required": [ + "t", + "c" + ] }, { "type": "object", - "required": [ - "c", - "t" - ], "properties": { - "c": { - "type": "boolean" - }, "t": { "type": "string", "enum": [ "Unit" ] + }, + "c": { + "type": "boolean" } - } + }, + "required": [ + "t", + "c" + ] } ] } \ No newline at end of file diff --git a/schemars/tests/expected/schema_with-enum-external.json b/schemars/tests/expected/schema_with-enum-external.json index dea0219..5f5fe83 100644 --- a/schemars/tests/expected/schema_with-enum-external.json +++ b/schemars/tests/expected/schema_with-enum-external.json @@ -1,48 +1,45 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "External", "oneOf": [ { "type": "object", - "required": [ - "struct" - ], "properties": { "struct": { "type": "object", - "required": [ - "foo" - ], "properties": { "foo": { "type": "boolean" } - } + }, + "required": [ + "foo" + ] } }, + "required": [ + "struct" + ], "additionalProperties": false }, { "type": "object", - "required": [ - "newType" - ], "properties": { "newType": { "type": "boolean" } }, + "required": [ + "newType" + ], "additionalProperties": false }, { "type": "object", - "required": [ - "tuple" - ], "properties": { "tuple": { "type": "array", - "items": [ + "prefixItems": [ { "type": "boolean" }, @@ -51,22 +48,25 @@ "format": "int32" } ], - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 } }, + "required": [ + "tuple" + ], "additionalProperties": false }, { "type": "object", - "required": [ - "unit" - ], "properties": { "unit": { "type": "boolean" } }, + "required": [ + "unit" + ], "additionalProperties": false } ] diff --git a/schemars/tests/expected/schema_with-enum-internal.json b/schemars/tests/expected/schema_with-enum-internal.json index 7ede7e6..e6d707b 100644 --- a/schemars/tests/expected/schema_with-enum-internal.json +++ b/schemars/tests/expected/schema_with-enum-internal.json @@ -1,58 +1,46 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Internal", "oneOf": [ { "type": "object", - "required": [ - "foo", - "typeProperty" - ], "properties": { "foo": { "type": "boolean" }, "typeProperty": { "type": "string", - "enum": [ - "Struct" - ] + "const": "Struct" } - } + }, + "required": [ + "typeProperty", + "foo" + ] }, { - "type": [ - "boolean", - "object" - ], - "required": [ - "typeProperty" - ], + "type": "object", "properties": { "typeProperty": { "type": "string", - "enum": [ - "NewType" - ] + "const": "NewType" } - } - }, - { - "type": [ - "boolean", - "object" - ], + }, "required": [ "typeProperty" - ], + ] + }, + { + "type": "object", "properties": { "typeProperty": { "type": "string", - "enum": [ - "Unit" - ] + "const": "Unit" } - } + }, + "required": [ + "typeProperty" + ] } ] } \ No newline at end of file diff --git a/schemars/tests/expected/schema_with-enum-untagged.json b/schemars/tests/expected/schema_with-enum-untagged.json index 55147de..9057c87 100644 --- a/schemars/tests/expected/schema_with-enum-untagged.json +++ b/schemars/tests/expected/schema_with-enum-untagged.json @@ -1,24 +1,24 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Untagged", "anyOf": [ { "type": "object", - "required": [ - "foo" - ], "properties": { "foo": { "type": "boolean" } - } + }, + "required": [ + "foo" + ] }, { "type": "boolean" }, { "type": "array", - "items": [ + "prefixItems": [ { "type": "boolean" }, @@ -27,8 +27,8 @@ "format": "int32" } ], - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 }, { "type": "boolean" diff --git a/schemars/tests/expected/schema_with-newtype.json b/schemars/tests/expected/schema_with-newtype.json index 8b709fb..eb4a2f6 100644 --- a/schemars/tests/expected/schema_with-newtype.json +++ b/schemars/tests/expected/schema_with-newtype.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Newtype", "type": "boolean" } \ No newline at end of file diff --git a/schemars/tests/expected/schema_with-struct.json b/schemars/tests/expected/schema_with-struct.json index feb5b51..0674f1e 100644 --- a/schemars/tests/expected/schema_with-struct.json +++ b/schemars/tests/expected/schema_with-struct.json @@ -1,12 +1,7 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Struct", "type": "object", - "required": [ - "bar", - "baz", - "foo" - ], "properties": { "foo": { "type": "boolean" @@ -18,5 +13,10 @@ "baz": { "type": "boolean" } - } + }, + "required": [ + "foo", + "bar", + "baz" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/schema_with-transparent-newtype.json b/schemars/tests/expected/schema_with-transparent-newtype.json index 9f6afb3..2aeff68 100644 --- a/schemars/tests/expected/schema_with-transparent-newtype.json +++ b/schemars/tests/expected/schema_with-transparent-newtype.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "schema_fn", "type": "boolean" } \ No newline at end of file diff --git a/schemars/tests/expected/schema_with-tuple.json b/schemars/tests/expected/schema_with-tuple.json index 1a8f8ca..1296e33 100644 --- a/schemars/tests/expected/schema_with-tuple.json +++ b/schemars/tests/expected/schema_with-tuple.json @@ -1,8 +1,8 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Tuple", "type": "array", - "items": [ + "prefixItems": [ { "type": "boolean" }, @@ -14,6 +14,6 @@ "type": "boolean" } ], - "maxItems": 3, - "minItems": 3 + "minItems": 3, + "maxItems": 3 } \ No newline at end of file diff --git a/schemars/tests/expected/semver.json b/schemars/tests/expected/semver.json index d87ad04..9f5b155 100644 --- a/schemars/tests/expected/semver.json +++ b/schemars/tests/expected/semver.json @@ -1,12 +1,14 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "SemverTypes", "type": "object", - "required": ["version"], "properties": { "version": { "type": "string", "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" } - } -} + }, + "required": [ + "version" + ] +} \ No newline at end of file diff --git a/schemars/tests/expected/skip_enum_variants.json b/schemars/tests/expected/skip_enum_variants.json index ba1bf23..bd3a893 100644 --- a/schemars/tests/expected/skip_enum_variants.json +++ b/schemars/tests/expected/skip_enum_variants.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyEnum", "oneOf": [ { @@ -10,15 +10,15 @@ }, { "type": "object", - "required": [ - "Included1" - ], "properties": { "Included1": { "type": "number", "format": "float" } }, + "required": [ + "Included1" + ], "additionalProperties": false } ] diff --git a/schemars/tests/expected/skip_struct_fields.json b/schemars/tests/expected/skip_struct_fields.json index d488561..2a74c32 100644 --- a/schemars/tests/expected/skip_struct_fields.json +++ b/schemars/tests/expected/skip_struct_fields.json @@ -1,24 +1,24 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", - "required": [ - "included", - "writable" - ], "properties": { "readable": { - "default": "", + "type": "string", "readOnly": true, - "type": "string" + "default": "" }, "writable": { - "writeOnly": true, "type": "number", - "format": "float" + "format": "float", + "writeOnly": true }, "included": { "type": "null" } - } + }, + "required": [ + "writable", + "included" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/skip_tuple_fields.json b/schemars/tests/expected/skip_tuple_fields.json index 9e5745b..c3a4255 100644 --- a/schemars/tests/expected/skip_tuple_fields.json +++ b/schemars/tests/expected/skip_tuple_fields.json @@ -1,8 +1,8 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "TupleStruct", "type": "array", - "items": [ + "prefixItems": [ { "type": "number", "format": "float" @@ -11,6 +11,6 @@ "type": "null" } ], - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 } \ No newline at end of file diff --git a/schemars/tests/expected/smallvec.json b/schemars/tests/expected/smallvec.json index eab45c0..be50ed7 100644 --- a/schemars/tests/expected/smallvec.json +++ b/schemars/tests/expected/smallvec.json @@ -1,6 +1,6 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_String", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Array_of_string", "type": "array", "items": { "type": "string" diff --git a/schemars/tests/expected/smol_str.json b/schemars/tests/expected/smol_str.json index 42f099a..0604332 100644 --- a/schemars/tests/expected/smol_str.json +++ b/schemars/tests/expected/smol_str.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "String", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "string", "type": "string" } \ No newline at end of file diff --git a/schemars/tests/expected/struct-newtype.json b/schemars/tests/expected/struct-newtype.json index 284e47d..e7b1cba 100644 --- a/schemars/tests/expected/struct-newtype.json +++ b/schemars/tests/expected/struct-newtype.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Newtype", "type": "integer", "format": "int32" diff --git a/schemars/tests/expected/struct-normal-additional-properties.json b/schemars/tests/expected/struct-normal-additional-properties.json index 1a8d749..2c55fb7 100644 --- a/schemars/tests/expected/struct-normal-additional-properties.json +++ b/schemars/tests/expected/struct-normal-additional-properties.json @@ -1,11 +1,7 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Struct", "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { "foo": { "type": "integer", @@ -21,5 +17,9 @@ ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "foo", + "bar" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/struct-normal.json b/schemars/tests/expected/struct-normal.json index cee25b1..c49757c 100644 --- a/schemars/tests/expected/struct-normal.json +++ b/schemars/tests/expected/struct-normal.json @@ -1,11 +1,7 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Struct", "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { "foo": { "type": "integer", @@ -20,5 +16,9 @@ "null" ] } - } + }, + "required": [ + "foo", + "bar" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/struct-tuple.json b/schemars/tests/expected/struct-tuple.json index ced169e..8960068 100644 --- a/schemars/tests/expected/struct-tuple.json +++ b/schemars/tests/expected/struct-tuple.json @@ -1,8 +1,8 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Tuple", "type": "array", - "items": [ + "prefixItems": [ { "type": "integer", "format": "int32" @@ -17,6 +17,6 @@ ] } ], - "maxItems": 3, - "minItems": 3 + "minItems": 3, + "maxItems": 3 } \ No newline at end of file diff --git a/schemars/tests/expected/struct-unit.json b/schemars/tests/expected/struct-unit.json index 3252d20..46f6fcf 100644 --- a/schemars/tests/expected/struct-unit.json +++ b/schemars/tests/expected/struct-unit.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Unit", "type": "null" } \ No newline at end of file diff --git a/schemars/tests/expected/transform_enum_external.json b/schemars/tests/expected/transform_enum_external.json new file mode 100644 index 0000000..af746e3 --- /dev/null +++ b/schemars/tests/expected/transform_enum_external.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "External", + "oneOf": [ + { + "type": "string", + "const": "Unit", + "propertyCount": 0, + "upperType": "STRING" + }, + { + "type": "object", + "properties": { + "NewType": true + }, + "required": [ + "NewType" + ], + "additionalProperties": false, + "propertyCount": 1, + "upperType": "OBJECT" + } + ], + "propertyCount": 0 +} \ No newline at end of file diff --git a/schemars/tests/expected/transform_struct.json b/schemars/tests/expected/transform_struct.json new file mode 100644 index 0000000..6723414 --- /dev/null +++ b/schemars/tests/expected/transform_struct.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Struct", + "type": "object", + "properties": { + "value": true, + "int": { + "type": "integer", + "format": "int32", + "propertyCount": 0, + "upperType": "INTEGER" + } + }, + "required": [ + "value", + "int" + ], + "upperType": "OBJECT", + "propertyCount": 2 +} \ No newline at end of file diff --git a/schemars/tests/expected/transparent-struct.json b/schemars/tests/expected/transparent-struct.json index e83f905..2692069 100644 --- a/schemars/tests/expected/transparent-struct.json +++ b/schemars/tests/expected/transparent-struct.json @@ -1,12 +1,12 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "OuterStruct", "type": "object", "properties": { "inner": { "anyOf": [ { - "$ref": "#/definitions/InnerStruct" + "$ref": "#/$defs/InnerStruct" }, { "type": "null" @@ -14,10 +14,10 @@ ] } }, - "definitions": { + "$defs": { "InnerStruct": { "type": "array", - "items": [ + "prefixItems": [ { "type": "string" }, @@ -26,8 +26,8 @@ "format": "int32" } ], - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 } } } \ No newline at end of file diff --git a/schemars/tests/expected/url.json b/schemars/tests/expected/url.json index ddb8c5a..425bf85 100644 --- a/schemars/tests/expected/url.json +++ b/schemars/tests/expected/url.json @@ -1,14 +1,14 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "UrlTypes", "type": "object", - "required": [ - "url" - ], "properties": { "url": { "type": "string", "format": "uri" } - } -} + }, + "required": [ + "url" + ] +} \ No newline at end of file diff --git a/schemars/tests/expected/uuid.json b/schemars/tests/expected/uuid.json index 8ba1a01..98ae408 100644 --- a/schemars/tests/expected/uuid.json +++ b/schemars/tests/expected/uuid.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Uuid", "type": "string", "format": "uuid" diff --git a/schemars/tests/expected/validate.json b/schemars/tests/expected/validate.json index d4a14e3..57c7f52 100644 --- a/schemars/tests/expected/validate.json +++ b/schemars/tests/expected/validate.json @@ -1,37 +1,19 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Struct", "type": "object", - "required": [ - "contains_str1", - "contains_str2", - "email_address", - "homepage", - "map_contains", - "min_max", - "min_max2", - "non_empty_str", - "non_empty_str2", - "pair", - "regex_str1", - "regex_str2", - "regex_str3", - "required_option", - "tel", - "x" - ], "properties": { "min_max": { "type": "number", "format": "float", - "maximum": 100.0, - "minimum": 0.01 + "minimum": 0.01, + "maximum": 100 }, "min_max2": { "type": "number", "format": "float", - "maximum": 1000.0, - "minimum": 1.0 + "minimum": 1, + "maximum": 1000 }, "regex_str1": { "type": "string", @@ -67,13 +49,13 @@ }, "non_empty_str": { "type": "string", - "maxLength": 100, - "minLength": 1 + "minLength": 1, + "maxLength": 100 }, "non_empty_str2": { "type": "string", - "maxLength": 1000, - "minLength": 1 + "minLength": 1, + "maxLength": 1000 }, "pair": { "type": "array", @@ -81,17 +63,17 @@ "type": "integer", "format": "int32" }, - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 }, "map_contains": { "type": "object", - "required": [ - "map_key" - ], "additionalProperties": { "type": "null" - } + }, + "required": [ + "map_key" + ] }, "required_option": { "type": "boolean" @@ -100,5 +82,23 @@ "type": "integer", "format": "int32" } - } + }, + "required": [ + "min_max", + "min_max2", + "regex_str1", + "regex_str2", + "regex_str3", + "contains_str1", + "contains_str2", + "email_address", + "tel", + "homepage", + "non_empty_str", + "non_empty_str2", + "pair", + "map_contains", + "required_option", + "x" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/validate_inner.json b/schemars/tests/expected/validate_inner.json index 443a9fa..a77e0d7 100644 --- a/schemars/tests/expected/validate_inner.json +++ b/schemars/tests/expected/validate_inner.json @@ -1,26 +1,17 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Struct", "type": "object", - "required": [ - "array_str_length", - "slice_str_contains", - "vec_i32_range", - "vec_str_length", - "vec_str_length2", - "vec_str_regex", - "vec_str_url" - ], "properties": { "array_str_length": { "type": "array", "items": { "type": "string", - "maxLength": 100, - "minLength": 5 + "minLength": 5, + "maxLength": 100 }, - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 }, "slice_str_contains": { "type": "array", @@ -29,33 +20,6 @@ "pattern": "substring\\.\\.\\." } }, - "vec_i32_range": { - "type": "array", - "items": { - "type": "integer", - "format": "int32", - "maximum": 10.0, - "minimum": -10.0 - } - }, - "vec_str_length": { - "type": "array", - "items": { - "type": "string", - "maxLength": 100, - "minLength": 1 - } - }, - "vec_str_length2": { - "type": "array", - "items": { - "type": "string", - "maxLength": 100, - "minLength": 1 - }, - "maxItems": 3, - "minItems": 1 - }, "vec_str_regex": { "type": "array", "items": { @@ -63,12 +27,48 @@ "pattern": "^[Hh]ello\\b" } }, + "vec_str_length": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 100 + } + }, + "vec_str_length2": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "minItems": 1, + "maxItems": 3 + }, "vec_str_url": { "type": "array", "items": { "type": "string", "format": "uri" } + }, + "vec_i32_range": { + "type": "array", + "items": { + "type": "integer", + "format": "int32", + "minimum": -10, + "maximum": 10 + } } - } + }, + "required": [ + "array_str_length", + "slice_str_contains", + "vec_str_regex", + "vec_str_length", + "vec_str_length2", + "vec_str_url", + "vec_i32_range" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/validate_newtype.json b/schemars/tests/expected/validate_newtype.json index 796aecd..cd835f5 100644 --- a/schemars/tests/expected/validate_newtype.json +++ b/schemars/tests/expected/validate_newtype.json @@ -1,8 +1,8 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "NewType", "type": "integer", "format": "uint8", - "maximum": 10.0, - "minimum": 0.0 + "minimum": 0, + "maximum": 10 } \ No newline at end of file diff --git a/schemars/tests/expected/validate_schemars_attrs.json b/schemars/tests/expected/validate_schemars_attrs.json index 86658ac..5e7d31d 100644 --- a/schemars/tests/expected/validate_schemars_attrs.json +++ b/schemars/tests/expected/validate_schemars_attrs.json @@ -1,37 +1,19 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Struct2", "type": "object", - "required": [ - "contains_str1", - "contains_str2", - "email_address", - "homepage", - "map_contains", - "min_max", - "min_max2", - "non_empty_str", - "non_empty_str2", - "pair", - "regex_str1", - "regex_str2", - "regex_str3", - "required_option", - "tel", - "x" - ], "properties": { "min_max": { "type": "number", "format": "float", - "maximum": 100.0, - "minimum": 0.01 + "minimum": 0.01, + "maximum": 100 }, "min_max2": { "type": "number", "format": "float", - "maximum": 1000.0, - "minimum": 1.0 + "minimum": 1, + "maximum": 1000 }, "regex_str1": { "type": "string", @@ -67,13 +49,13 @@ }, "non_empty_str": { "type": "string", - "maxLength": 100, - "minLength": 1 + "minLength": 1, + "maxLength": 100 }, "non_empty_str2": { "type": "string", - "maxLength": 1000, - "minLength": 1 + "minLength": 1, + "maxLength": 1000 }, "pair": { "type": "array", @@ -81,17 +63,17 @@ "type": "integer", "format": "int32" }, - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 }, "map_contains": { "type": "object", - "required": [ - "map_key" - ], "additionalProperties": { "type": "null" - } + }, + "required": [ + "map_key" + ] }, "required_option": { "type": "boolean" @@ -100,5 +82,23 @@ "type": "integer", "format": "int32" } - } + }, + "required": [ + "min_max", + "min_max2", + "regex_str1", + "regex_str2", + "regex_str3", + "contains_str1", + "contains_str2", + "email_address", + "tel", + "homepage", + "non_empty_str", + "non_empty_str2", + "pair", + "map_contains", + "required_option", + "x" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/validate_tuple.json b/schemars/tests/expected/validate_tuple.json index 8ab6eaa..fa81224 100644 --- a/schemars/tests/expected/validate_tuple.json +++ b/schemars/tests/expected/validate_tuple.json @@ -1,18 +1,18 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Tuple", "type": "array", - "items": [ + "prefixItems": [ { "type": "integer", "format": "uint8", - "maximum": 10.0, - "minimum": 0.0 + "minimum": 0, + "maximum": 10 }, { "type": "boolean" } ], - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 } \ No newline at end of file diff --git a/schemars/tests/extend.rs b/schemars/tests/extend.rs new file mode 100644 index 0000000..2b44f5e --- /dev/null +++ b/schemars/tests/extend.rs @@ -0,0 +1,96 @@ +mod util; +use schemars::JsonSchema; +use serde_json::Value; +use util::*; + +const THREE: f64 = 3.0; + +#[allow(dead_code)] +#[derive(JsonSchema)] +#[schemars(extend("msg" = concat!("hello ", "world"), "obj" = {"array": [null, ()]}))] +#[schemars(extend("3" = THREE), extend("pi" = THREE + 0.14))] +struct Struct { + #[schemars(extend("foo" = "bar"))] + value: Value, + #[schemars(extend("type" = "overridden"))] + int: i32, +} + +#[test] +fn extend_struct() -> TestResult { + test_default_generated_schema::("extend_struct") +} + +#[allow(dead_code)] +#[derive(JsonSchema)] +#[schemars(extend("foo" = "bar"))] +enum External { + #[schemars(extend("foo" = "bar"))] + Unit, + #[schemars(extend("foo" = "bar"))] + NewType(Value), + #[schemars(extend("foo" = "bar"))] + Tuple(i32, bool), + #[schemars(extend("foo" = "bar"))] + Struct { i: i32, b: bool }, +} + +#[test] +fn extend_enum_external() -> TestResult { + test_default_generated_schema::("extend_enum_external") +} + +#[allow(dead_code)] +#[derive(JsonSchema)] +#[schemars(tag = "typeProperty", extend("foo" = "bar"))] +enum Internal { + #[schemars(extend("foo" = "bar"))] + Unit, + #[schemars(extend("foo" = "bar"))] + NewType(Value), + #[schemars(extend("foo" = "bar"))] + Struct { i: i32, b: bool }, +} + +#[test] +fn extend_enum_internal() -> TestResult { + test_default_generated_schema::("extend_enum_internal") +} + +#[allow(dead_code)] +#[derive(JsonSchema)] +#[schemars(untagged, extend("foo" = "bar"))] +enum Untagged { + #[schemars(extend("foo" = "bar"))] + Unit, + #[schemars(extend("foo" = "bar"))] + NewType(Value), + #[schemars(extend("foo" = "bar"))] + Tuple(i32, bool), + #[schemars(extend("foo" = "bar"))] + Struct { i: i32, b: bool }, +} + +#[test] +fn extend_enum_untagged() -> TestResult { + test_default_generated_schema::("extend_enum_untagged") +} + +#[allow(dead_code)] +#[derive(JsonSchema)] +#[schemars(tag = "t", content = "c", extend("foo" = "bar"))] +enum Adjacent { + #[schemars(extend("foo" = "bar"))] + Unit, + #[schemars(extend("foo" = "bar"))] + NewType(Value), + #[schemars(extend("foo" = "bar"))] + Tuple(i32, bool), + #[schemars(extend("foo" = "bar"))] + Struct { i: i32, b: bool }, +} + +#[test] +fn extend_enum_adjacent() -> TestResult { + test_default_generated_schema::("extend_enum_adjacent") +} diff --git a/schemars/tests/indexmap.rs b/schemars/tests/indexmap.rs index 9501b67..e243f35 100644 --- a/schemars/tests/indexmap.rs +++ b/schemars/tests/indexmap.rs @@ -1,13 +1,15 @@ mod util; -use indexmap::{IndexMap, IndexSet}; +use std::hash::RandomState; + +use indexmap2::{IndexMap, IndexSet}; use schemars::JsonSchema; use util::*; #[allow(dead_code)] #[derive(JsonSchema)] struct IndexMapTypes { - map: IndexMap, - set: IndexSet, + map: IndexMap, + set: IndexSet, } #[test] diff --git a/schemars/tests/indexmap2.rs b/schemars/tests/indexmap2.rs deleted file mode 100644 index efc77dd..0000000 --- a/schemars/tests/indexmap2.rs +++ /dev/null @@ -1,16 +0,0 @@ -mod util; -use indexmap2::{IndexMap, IndexSet}; -use schemars::JsonSchema; -use util::*; - -#[allow(dead_code)] -#[derive(JsonSchema)] -struct IndexMapTypes { - map: IndexMap, - set: IndexSet, -} - -#[test] -fn indexmap_types() -> TestResult { - test_default_generated_schema::("indexmap") -} diff --git a/schemars/tests/schema_for_schema.rs b/schemars/tests/schema_for_schema.rs deleted file mode 100644 index d175505..0000000 --- a/schemars/tests/schema_for_schema.rs +++ /dev/null @@ -1,19 +0,0 @@ -mod util; -use schemars::gen::SchemaSettings; -use schemars::schema::RootSchema; -use util::*; - -#[test] -fn schema_matches_draft07() -> TestResult { - test_generated_schema::("schema", SchemaSettings::draft07()) -} - -#[test] -fn schema_matches_2019_09() -> TestResult { - test_generated_schema::("schema-2019_09", SchemaSettings::draft2019_09()) -} - -#[test] -fn schema_matches_openapi3() -> TestResult { - test_generated_schema::("schema-openapi3", SchemaSettings::openapi3()) -} diff --git a/schemars/tests/schema_settings.rs b/schemars/tests/schema_settings.rs index d00042b..0c8741c 100644 --- a/schemars/tests/schema_settings.rs +++ b/schemars/tests/schema_settings.rs @@ -1,6 +1,6 @@ mod util; use schemars::gen::SchemaSettings; -use schemars::JsonSchema; +use schemars::{JsonSchema, Schema}; use serde_json::Value; use std::collections::BTreeMap; use util::*; @@ -12,6 +12,7 @@ pub struct Outer { pub values: BTreeMap<&'static str, Value>, pub value: Value, pub inner: Option, + pub tuples: Vec<(u8, i64)>, } #[derive(JsonSchema)] @@ -40,7 +41,28 @@ fn schema_matches_2019_09() -> TestResult { } #[test] -#[ignore = "Fails due to default/empty `Metadata` not being considered equal to `Option::None`, although they're conceptually the same and serialize to identical JSON"] -fn schema_matches_openapi3() -> TestResult { - test_generated_schema::("schema_settings-openapi3", SchemaSettings::openapi3()) +fn schema_matches_2020_12() -> TestResult { + test_generated_schema::("schema_settings-2020_12", SchemaSettings::draft2020_12()) +} + +#[test] +fn schema_matches_openapi3() -> TestResult { + let mut settings = SchemaSettings::openapi3(); + + // Hack to apply recursive transforms to schemas at components.schemas: + // First, move them to $defs, then run the transforms, then move them back again. + settings.transforms.insert( + 0, + Box::new(|s: &mut Schema| { + let obj = s.ensure_object(); + let defs = obj["components"]["schemas"].take(); + obj.insert("$defs".to_owned(), defs); + }), + ); + settings.transforms.push(Box::new(|s: &mut Schema| { + let obj = s.ensure_object(); + obj["components"]["schemas"] = obj.remove("$defs").unwrap(); + })); + + test_generated_schema::("schema_settings-openapi3", settings) } diff --git a/schemars/tests/schema_with_enum.rs b/schemars/tests/schema_with_enum.rs index a91aa4d..5cf419c 100644 --- a/schemars/tests/schema_with_enum.rs +++ b/schemars/tests/schema_with_enum.rs @@ -2,7 +2,7 @@ mod util; use schemars::JsonSchema; use util::*; -fn schema_fn(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { +fn schema_fn(gen: &mut schemars::SchemaGenerator) -> schemars::Schema { ::json_schema(gen) } diff --git a/schemars/tests/schema_with_struct.rs b/schemars/tests/schema_with_struct.rs index 9c87abe..5fc8c37 100644 --- a/schemars/tests/schema_with_struct.rs +++ b/schemars/tests/schema_with_struct.rs @@ -2,7 +2,7 @@ mod util; use schemars::JsonSchema; use util::*; -fn schema_fn(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { +fn schema_fn(gen: &mut schemars::SchemaGenerator) -> schemars::Schema { ::json_schema(gen) } diff --git a/schemars/tests/semver.rs b/schemars/tests/semver.rs index 617e508..3c351c6 100644 --- a/schemars/tests/semver.rs +++ b/schemars/tests/semver.rs @@ -1,6 +1,6 @@ mod util; use schemars::JsonSchema; -use semver::Version; +use semver1::Version; use util::*; #[allow(dead_code)] diff --git a/schemars/tests/smallvec.rs b/schemars/tests/smallvec.rs index bc9c6ef..8412a6a 100644 --- a/schemars/tests/smallvec.rs +++ b/schemars/tests/smallvec.rs @@ -1,5 +1,5 @@ mod util; -use smallvec::SmallVec; +use smallvec1::SmallVec; use util::*; #[test] diff --git a/schemars/tests/smol_str.rs b/schemars/tests/smol_str.rs index 1e48196..43fad30 100644 --- a/schemars/tests/smol_str.rs +++ b/schemars/tests/smol_str.rs @@ -1,5 +1,5 @@ mod util; -use smol_str::SmolStr; +use smol_str02::SmolStr; use util::*; #[test] diff --git a/schemars/tests/transform.rs b/schemars/tests/transform.rs new file mode 100644 index 0000000..13ac30b --- /dev/null +++ b/schemars/tests/transform.rs @@ -0,0 +1,51 @@ +mod util; +use schemars::{transform::RecursiveTransform, JsonSchema, Schema}; +use serde_json::Value; +use util::*; + +fn capitalize_type(schema: &mut Schema) { + if let Some(obj) = schema.as_object_mut() { + if let Some(Value::String(ty)) = obj.get("type") { + obj.insert("upperType".to_owned(), ty.to_uppercase().into()); + } + } +} + +fn insert_property_count(schema: &mut Schema) { + if let Some(obj) = schema.as_object_mut() { + let count = obj + .get("properties") + .and_then(|p| p.as_object()) + .map_or(0, |p| p.len()); + obj.insert("propertyCount".to_owned(), count.into()); + } +} + +#[allow(dead_code)] +#[derive(JsonSchema)] +#[schemars(transform = RecursiveTransform(capitalize_type), transform = insert_property_count)] +struct Struct { + value: Value, + #[schemars(transform = insert_property_count)] + int: i32, +} + +#[test] +fn transform_struct() -> TestResult { + test_default_generated_schema::("transform_struct") +} + +#[allow(dead_code)] +#[derive(JsonSchema)] +#[schemars(transform = RecursiveTransform(capitalize_type), transform = insert_property_count)] +enum External { + #[schemars(transform = insert_property_count)] + Unit, + #[schemars(transform = insert_property_count)] + NewType(Value), +} + +#[test] +fn transform_enum_external() -> TestResult { + test_default_generated_schema::("transform_enum_external") +} diff --git a/schemars/tests/ui/invalid_extend.rs b/schemars/tests/ui/invalid_extend.rs new file mode 100644 index 0000000..b7295f7 --- /dev/null +++ b/schemars/tests/ui/invalid_extend.rs @@ -0,0 +1,11 @@ +use schemars::JsonSchema; + +#[derive(JsonSchema)] +#[schemars(extend(x))] +#[schemars(extend("x"))] +#[schemars(extend("x" = ))] +#[schemars(extend("y" = "ok!", "y" = "duplicated!"), extend("y" = "duplicated!"))] +#[schemars(extend("y" = "duplicated!"))] +pub struct Struct; + +fn main() {} diff --git a/schemars/tests/ui/invalid_extend.stderr b/schemars/tests/ui/invalid_extend.stderr new file mode 100644 index 0000000..d7d2179 --- /dev/null +++ b/schemars/tests/ui/invalid_extend.stderr @@ -0,0 +1,35 @@ +error: expected string literal + --> tests/ui/invalid_extend.rs:4:19 + | +4 | #[schemars(extend(x))] + | ^ + +error: expected `=` + --> tests/ui/invalid_extend.rs:5:22 + | +5 | #[schemars(extend("x"))] + | ^ + +error: Expected extension value + --> tests/ui/invalid_extend.rs:6:25 + | +6 | #[schemars(extend("x" = ))] + | ^ + +error: Duplicate extension key 'y' + --> tests/ui/invalid_extend.rs:7:32 + | +7 | #[schemars(extend("y" = "ok!", "y" = "duplicated!"), extend("y" = "duplicated!"))] + | ^^^ + +error: Duplicate extension key 'y' + --> tests/ui/invalid_extend.rs:7:61 + | +7 | #[schemars(extend("y" = "ok!", "y" = "duplicated!"), extend("y" = "duplicated!"))] + | ^^^ + +error: Duplicate extension key 'y' + --> tests/ui/invalid_extend.rs:8:19 + | +8 | #[schemars(extend("y" = "duplicated!"))] + | ^^^ diff --git a/schemars/tests/ui/transform_str.rs b/schemars/tests/ui/transform_str.rs new file mode 100644 index 0000000..6570acc --- /dev/null +++ b/schemars/tests/ui/transform_str.rs @@ -0,0 +1,7 @@ +use schemars::JsonSchema; + +#[derive(JsonSchema)] +#[schemars(transform = "x")] +pub struct Struct; + +fn main() {} diff --git a/schemars/tests/ui/transform_str.stderr b/schemars/tests/ui/transform_str.stderr new file mode 100644 index 0000000..6ee3698 --- /dev/null +++ b/schemars/tests/ui/transform_str.stderr @@ -0,0 +1,6 @@ +error: Expected a `fn(&mut Schema)` or other value implementing `schemars::transform::Transform`, found `&str`. + Did you mean `[schemars(transform = x)]`? + --> tests/ui/transform_str.rs:4:24 + | +4 | #[schemars(transform = "x")] + | ^^^ diff --git a/schemars/tests/url.rs b/schemars/tests/url.rs index 0e9499a..018a66b 100644 --- a/schemars/tests/url.rs +++ b/schemars/tests/url.rs @@ -1,6 +1,6 @@ mod util; use schemars::JsonSchema; -use url::Url; +use url2::Url; use util::*; #[allow(dead_code)] diff --git a/schemars/tests/util/mod.rs b/schemars/tests/util/mod.rs index 595d68b..3376c02 100644 --- a/schemars/tests/util/mod.rs +++ b/schemars/tests/util/mod.rs @@ -1,5 +1,5 @@ use pretty_assertions::assert_eq; -use schemars::{gen::SchemaSettings, schema::RootSchema, schema_for, JsonSchema}; +use schemars::{gen::SchemaSettings, schema_for, JsonSchema, Schema}; use std::error::Error; use std::fs; @@ -17,7 +17,7 @@ pub fn test_default_generated_schema(file: &str) -> TestResult { test_schema(&actual, file) } -pub fn test_schema(actual: &RootSchema, file: &str) -> TestResult { +pub fn test_schema(actual: &Schema, file: &str) -> TestResult { let expected_json = match fs::read_to_string(format!("tests/expected/{}.json", file)) { Ok(j) => j, Err(e) => { @@ -35,7 +35,7 @@ pub fn test_schema(actual: &RootSchema, file: &str) -> TestResult { Ok(()) } -fn write_actual_to_file(schema: &RootSchema, file: &str) -> TestResult { +fn write_actual_to_file(schema: &Schema, file: &str) -> TestResult { let actual_json = serde_json::to_string_pretty(&schema)?; fs::write(format!("tests/actual/{}.json", file), actual_json)?; Ok(()) diff --git a/schemars/tests/uuid.rs b/schemars/tests/uuid.rs index bd673b5..77e92c2 100644 --- a/schemars/tests/uuid.rs +++ b/schemars/tests/uuid.rs @@ -1,11 +1,6 @@ mod util; use util::*; -#[test] -fn uuid08() -> TestResult { - test_default_generated_schema::("uuid") -} - #[test] fn uuid1() -> TestResult { test_default_generated_schema::("uuid") diff --git a/schemars_derive/Cargo.toml b/schemars_derive/Cargo.toml index 94ef0fd..2c1b398 100644 --- a/schemars_derive/Cargo.toml +++ b/schemars_derive/Cargo.toml @@ -3,7 +3,7 @@ name = "schemars_derive" description = "Macros for #[derive(JsonSchema)], for use with schemars" homepage = "https://graham.cool/schemars/" repository = "https://github.com/GREsau/schemars" -version = "0.8.21" +version = "1.0.0-alpha.3" authors = ["Graham Esau "] edition = "2021" license = "MIT" diff --git a/schemars_derive/src/attr/doc.rs b/schemars_derive/src/attr/doc.rs index aab15d2..df9daca 100644 --- a/schemars_derive/src/attr/doc.rs +++ b/schemars_derive/src/attr/doc.rs @@ -14,25 +14,15 @@ pub fn get_title_and_desc_from_doc(attrs: &[Attribute]) -> (Option, Opti .trim_start_matches('#') .trim() .to_owned(); - let maybe_desc = split.next().and_then(merge_description_lines); + let maybe_desc = split.next().map(|s| s.trim().to_owned()); (none_if_empty(title), maybe_desc) } else { - (None, merge_description_lines(&doc)) + (None, Some(doc)) } } -fn merge_description_lines(doc: &str) -> Option { - let desc = doc - .trim() - .split("\n\n") - .filter_map(|line| none_if_empty(line.trim().replace('\n', " "))) - .collect::>() - .join("\n\n"); - none_if_empty(desc) -} - fn get_doc(attrs: &[Attribute]) -> Option { - let attrs = attrs + let lines = attrs .iter() .filter_map(|attr| { if !attr.path().is_ident("doc") { @@ -52,29 +42,7 @@ fn get_doc(attrs: &[Attribute]) -> Option { }) .collect::>(); - let mut lines = attrs - .iter() - .flat_map(|a| a.split('\n')) - .map(str::trim) - .skip_while(|s| s.is_empty()) - .collect::>(); - - if let Some(&"") = lines.last() { - lines.pop(); - } - - // Added for backward-compatibility, but perhaps we shouldn't do this - // https://github.com/rust-lang/rust/issues/32088 - if lines.iter().all(|l| l.starts_with('*')) { - for line in lines.iter_mut() { - *line = line[1..].trim() - } - while let Some(&"") = lines.first() { - lines.remove(0); - } - }; - - none_if_empty(lines.join("\n")) + none_if_empty(lines.join("\n").trim().to_owned()) } fn none_if_empty(s: String) -> Option { diff --git a/schemars_derive/src/attr/mod.rs b/schemars_derive/src/attr/mod.rs index 86c9349..6b6e676 100644 --- a/schemars_derive/src/attr/mod.rs +++ b/schemars_derive/src/attr/mod.rs @@ -10,7 +10,7 @@ use proc_macro2::{Group, Span, TokenStream, TokenTree}; use quote::ToTokens; use serde_derive_internals::Ctxt; use syn::parse::{self, Parse}; -use syn::{Meta, MetaNameValue}; +use syn::{LitStr, Meta, MetaNameValue}; // FIXME using the same struct for containers+variants+fields means that // with/schema_with are accepted (but ignored) on containers, and @@ -26,6 +26,8 @@ pub struct Attrs { pub repr: Option, pub crate_name: Option, pub is_renamed: bool, + pub extensions: Vec<(String, TokenStream)>, + pub transforms: Vec, } #[derive(Debug)] @@ -68,6 +70,8 @@ impl Attrs { description: self.description.as_ref().and_then(none_if_empty), deprecated: self.deprecated, examples: &self.examples, + extensions: &self.extensions, + transforms: &self.transforms, read_only: false, write_only: false, default: None, @@ -162,6 +166,48 @@ impl Attrs { } } + Meta::NameValue(m) if m.path.is_ident("transform") && attr_type == "schemars" => { + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit_str), + .. + }) = &m.value + { + if parse_lit_str::(lit_str).is_ok() { + errors.error_spanned_by( + &m.value, + format!( + "Expected a `fn(&mut Schema)` or other value implementing `schemars::transform::Transform`, found `&str`.\nDid you mean `[schemars(transform = {})]`?", + lit_str.value() + ), + ) + } + } + self.transforms.push(m.value.clone()); + } + + Meta::List(m) if m.path.is_ident("extend") && attr_type == "schemars" => { + let parser = + syn::punctuated::Punctuated::::parse_terminated; + match m.parse_args_with(parser) { + Ok(extensions) => { + for extension in extensions { + let key = extension.key.value(); + // This is O(n^2) but should be fine with the typically small number of extensions. + // If this does become a problem, it can be changed to use IndexMap, or a separate Map with cloned keys. + if self.extensions.iter().any(|e| e.0 == key) { + errors.error_spanned_by( + extension.key, + format!("Duplicate extension key '{}'", key), + ); + } else { + self.extensions.push((key, extension.value)); + } + } + } + Err(err) => errors.syn_error(err), + } + } + _ if ignore_errors => {} Meta::List(m) if m.path.is_ident("inner") && attr_type == "schemars" => { @@ -198,7 +244,9 @@ impl Attrs { repr: None, crate_name: None, is_renamed: _, - } if examples.is_empty()) + extensions, + transforms + } if examples.is_empty() && extensions.is_empty() && transforms.is_empty()) } } @@ -322,3 +370,27 @@ fn respan_token_tree(mut token: TokenTree, span: Span) -> TokenTree { token.set_span(span); token } + +#[derive(Debug)] +struct Extension { + key: LitStr, + value: TokenStream, +} + +impl Parse for Extension { + fn parse(input: parse::ParseStream) -> syn::Result { + let key = input.parse::()?; + input.parse::()?; + let mut value = TokenStream::new(); + + while !input.is_empty() && !input.peek(Token![,]) { + value.extend([input.parse::()?]); + } + + if value.is_empty() { + return Err(syn::Error::new(input.span(), "Expected extension value")); + } + + Ok(Extension { key, value }) + } +} diff --git a/schemars_derive/src/attr/validation.rs b/schemars_derive/src/attr/validation.rs index a511ab4..8d90302 100644 --- a/schemars_derive/src/attr/validation.rs +++ b/schemars_derive/src/attr/validation.rs @@ -325,119 +325,85 @@ impl ValidationAttrs { } pub fn apply_to_schema(&self, schema_expr: &mut TokenStream) { - if let Some(apply_expr) = self.apply_to_schema_expr() { - *schema_expr = quote! { - { - let mut schema = #schema_expr; - #apply_expr - schema - } - } + let setters = self.make_setters(quote!(&mut schema)); + if !setters.is_empty() { + *schema_expr = quote!({ + let mut schema = #schema_expr; + #(#setters)* + schema + }); } } - fn apply_to_schema_expr(&self) -> Option { - let mut array_validation = Vec::new(); - let mut number_validation = Vec::new(); - let mut object_validation = Vec::new(); - let mut string_validation = Vec::new(); + fn make_setters(&self, mut_schema: impl ToTokens) -> Vec { + let mut result = Vec::new(); if let Some(length_min) = self.length_min.as_ref().or(self.length_equal.as_ref()) { - string_validation.push(quote! { - validation.min_length = Some(#length_min as u32); + result.push(quote! { + schemars::_private::insert_validation_property(#mut_schema, "string", "minLength", #length_min); }); - array_validation.push(quote! { - validation.min_items = Some(#length_min as u32); + result.push(quote! { + schemars::_private::insert_validation_property(#mut_schema, "array", "minItems", #length_min); }); } if let Some(length_max) = self.length_max.as_ref().or(self.length_equal.as_ref()) { - string_validation.push(quote! { - validation.max_length = Some(#length_max as u32); + result.push(quote! { + schemars::_private::insert_validation_property(#mut_schema, "string", "maxLength", #length_max); }); - array_validation.push(quote! { - validation.max_items = Some(#length_max as u32); + result.push(quote! { + schemars::_private::insert_validation_property(#mut_schema, "array", "maxItems", #length_max); }); } if let Some(range_min) = &self.range_min { - number_validation.push(quote! { - validation.minimum = Some(#range_min as f64); + result.push(quote! { + schemars::_private::insert_validation_property(#mut_schema, "number", "minimum", #range_min); }); } if let Some(range_max) = &self.range_max { - number_validation.push(quote! { - validation.maximum = Some(#range_max as f64); + result.push(quote! { + schemars::_private::insert_validation_property(#mut_schema, "number", "maximum", #range_max); }); } if let Some(regex) = &self.regex { - string_validation.push(quote! { - validation.pattern = Some(#regex.to_string()); + result.push(quote! { + schemars::_private::insert_validation_property(#mut_schema, "string", "pattern", #regex); }); } if let Some(contains) = &self.contains { - object_validation.push(quote! { - validation.required.insert(#contains.to_string()); + result.push(quote! { + schemars::_private::append_required(#mut_schema, #contains); }); if self.regex.is_none() { let pattern = crate::regex_syntax::escape(contains); - string_validation.push(quote! { - validation.pattern = Some(#pattern.to_string()); + result.push(quote! { + schemars::_private::insert_validation_property(#mut_schema, "string", "pattern", #pattern); }); } } - let format = self.format.as_ref().map(|f| { - let f = f.schema_str(); - quote! { - schema_object.format = Some(#f.to_string()); - } - }); - - let inner_validation = self - .inner - .as_deref() - .and_then(|inner| inner.apply_to_schema_expr()) - .map(|apply_expr| { - quote! { - if schema_object.has_type(schemars::schema::InstanceType::Array) { - if let Some(schemars::schema::SingleOrVec::Single(inner_schema)) = &mut schema_object.array().items { - let mut schema = &mut **inner_schema; - #apply_expr - } - } - } - }); - - let array_validation = wrap_array_validation(array_validation); - let number_validation = wrap_number_validation(number_validation); - let object_validation = wrap_object_validation(object_validation); - let string_validation = wrap_string_validation(string_validation); - - if array_validation.is_some() - || number_validation.is_some() - || object_validation.is_some() - || string_validation.is_some() - || format.is_some() - || inner_validation.is_some() - { - Some(quote! { - if let schemars::schema::Schema::Object(schema_object) = &mut schema { - #array_validation - #number_validation - #object_validation - #string_validation - #format - #inner_validation - } + if let Some(format) = &self.format { + let f = format.schema_str(); + result.push(quote! { + schema.ensure_object().insert("format".to_owned(), #f.into()); }) - } else { - None + }; + + if let Some(inner) = &self.inner { + let inner_setters = inner.make_setters(quote!(schema)); + if !inner_setters.is_empty() { + result.push(quote! { + schemars::_private::apply_inner_validation(#mut_schema, |schema| { #(#inner_setters)* }); + }) + } } + + result } } @@ -456,59 +422,6 @@ fn parse_lit_into_expr_path( }) } -fn wrap_array_validation(v: Vec) -> Option { - if v.is_empty() { - None - } else { - Some(quote! { - if schema_object.has_type(schemars::schema::InstanceType::Array) { - let validation = schema_object.array(); - #(#v)* - } - }) - } -} - -fn wrap_number_validation(v: Vec) -> Option { - if v.is_empty() { - None - } else { - Some(quote! { - if schema_object.has_type(schemars::schema::InstanceType::Integer) - || schema_object.has_type(schemars::schema::InstanceType::Number) { - let validation = schema_object.number(); - #(#v)* - } - }) - } -} - -fn wrap_object_validation(v: Vec) -> Option { - if v.is_empty() { - None - } else { - Some(quote! { - if schema_object.has_type(schemars::schema::InstanceType::Object) { - let validation = schema_object.object(); - #(#v)* - } - }) - } -} - -fn wrap_string_validation(v: Vec) -> Option { - if v.is_empty() { - None - } else { - Some(quote! { - if schema_object.has_type(schemars::schema::InstanceType::String) { - let validation = schema_object.string(); - #(#v)* - } - }) - } -} - fn str_or_num_to_expr(cx: &Ctxt, meta_item_name: &str, expr: Expr) -> Option { // this odd double-parsing is to make `-10` parsed as an Lit instead of an Expr::Unary let lit: Lit = match syn::parse2(expr.to_token_stream()) { diff --git a/schemars_derive/src/lib.rs b/schemars_derive/src/lib.rs index ea6c0a6..bd427bd 100644 --- a/schemars_derive/src/lib.rs +++ b/schemars_derive/src/lib.rs @@ -56,11 +56,11 @@ fn derive_json_schema(mut input: syn::DeriveInput, repr: bool) -> syn::Result bool { - <#ty as schemars::JsonSchema>::is_referenceable() + fn always_inline_schema() -> bool { + <#ty as schemars::JsonSchema>::always_inline_schema() } - fn schema_name() -> std::string::String { + fn schema_name() -> std::borrow::Cow<'static, str> { <#ty as schemars::JsonSchema>::schema_name() } @@ -68,11 +68,11 @@ fn derive_json_schema(mut input: syn::DeriveInput, repr: bool) -> syn::Result::schema_id() } - fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema { <#ty as schemars::JsonSchema>::json_schema(gen) } - fn _schemars_private_non_optional_json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + fn _schemars_private_non_optional_json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema { <#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(gen) } @@ -104,7 +104,7 @@ fn derive_json_schema(mut input: syn::DeriveInput, repr: bool) -> syn::Result syn::Result syn::Result syn::Result std::string::String { + fn schema_name() -> std::borrow::Cow<'static, str> { #schema_name } @@ -182,7 +186,7 @@ fn derive_json_schema(mut input: syn::DeriveInput, repr: bool) -> syn::Result schemars::schema::Schema { + fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema { #schema_expr } }; diff --git a/schemars_derive/src/metadata.rs b/schemars_derive/src/metadata.rs index 3dd77a0..c6b5dfe 100644 --- a/schemars_derive/src/metadata.rs +++ b/schemars_derive/src/metadata.rs @@ -1,4 +1,5 @@ use proc_macro2::TokenStream; +use syn::spanned::Spanned; #[derive(Debug, Clone)] pub struct SchemaMetadata<'a> { @@ -9,36 +10,64 @@ pub struct SchemaMetadata<'a> { pub write_only: bool, pub examples: &'a [syn::Path], pub default: Option, + pub extensions: &'a [(String, TokenStream)], + pub transforms: &'a [syn::Expr], } impl<'a> SchemaMetadata<'a> { pub fn apply_to_schema(&self, schema_expr: &mut TokenStream) { + let setters = self.make_setters(); + if !setters.is_empty() { + *schema_expr = quote! {{ + let mut schema = #schema_expr; + let obj = schema.ensure_object(); + #(#setters)* + schema + }} + } + if !self.transforms.is_empty() { + let apply_transforms = self.transforms.iter().map(|t| { + quote_spanned! {t.span()=> + schemars::transform::Transform::transform(&mut #t, &mut schema); + } + }); + *schema_expr = quote! {{ + let mut schema = #schema_expr; + #(#apply_transforms)* + schema + }}; + } + } + + fn make_setters(&self) -> Vec { + let mut setters = Vec::::new(); + if let Some(title) = &self.title { - *schema_expr = quote! { - schemars::_private::metadata::add_title(#schema_expr, #title) - }; + setters.push(quote! { + obj.insert("title".to_owned(), #title.into()); + }); } if let Some(description) = &self.description { - *schema_expr = quote! { - schemars::_private::metadata::add_description(#schema_expr, #description) - }; + setters.push(quote! { + obj.insert("description".to_owned(), #description.into()); + }); } if self.deprecated { - *schema_expr = quote! { - schemars::_private::metadata::add_deprecated(#schema_expr, true) - }; + setters.push(quote! { + obj.insert("deprecated".to_owned(), true.into()); + }); } if self.read_only { - *schema_expr = quote! { - schemars::_private::metadata::add_read_only(#schema_expr, true) - }; + setters.push(quote! { + obj.insert("readOnly".to_owned(), true.into()); + }); } if self.write_only { - *schema_expr = quote! { - schemars::_private::metadata::add_write_only(#schema_expr, true) - }; + setters.push(quote! { + obj.insert("writeOnly".to_owned(), true.into()); + }); } if !self.examples.is_empty() { @@ -47,16 +76,25 @@ impl<'a> SchemaMetadata<'a> { schemars::_serde_json::value::to_value(#eg()) } }); - - *schema_expr = quote! { - schemars::_private::metadata::add_examples(#schema_expr, [#(#examples),*].into_iter().flatten()) - }; + setters.push(quote! { + obj.insert("examples".to_owned(), schemars::_serde_json::Value::Array([#(#examples),*].into_iter().flatten().collect())); + }); } if let Some(default) = &self.default { - *schema_expr = quote! { - schemars::_private::metadata::add_default(#schema_expr, #default.and_then(|d| schemars::_schemars_maybe_to_value!(d))) - }; + setters.push(quote! { + if let Some(default) = #default.and_then(|d| schemars::_schemars_maybe_to_value!(d)) { + obj.insert("default".to_owned(), default); + } + }); } + + for (k, v) in self.extensions { + setters.push(quote! { + obj.insert(#k.to_owned(), schemars::_serde_json::json!(#v)); + }); + } + + setters } } diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index b625114..1f5df99 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -49,9 +49,18 @@ pub fn expr_for_repr(cont: &Container) -> Result { let enum_ident = &cont.ident; let variant_idents = variants.iter().map(|v| &v.ident); - let mut schema_expr = schema_object(quote! { - instance_type: Some(schemars::schema::InstanceType::Integer.into()), - enum_values: Some(vec![#((#enum_ident::#variant_idents as #repr_type).into()),*]), + let mut schema_expr = quote!({ + let mut map = schemars::_serde_json::Map::new(); + map.insert("type".to_owned(), "integer".into()); + map.insert( + "enum".to_owned(), + schemars::_serde_json::Value::Array({ + let mut enum_values = Vec::new(); + #(enum_values.push((#enum_ident::#variant_idents as #repr_type).into());)* + enum_values + }), + ); + schemars::Schema::from(map) }); cont.attrs.as_metadata().apply_to_schema(&mut schema_expr); @@ -101,12 +110,12 @@ fn type_for_schema(with_attr: &WithAttr) -> (syn::Type, Option) { struct #ty_name; impl schemars::JsonSchema for #ty_name { - fn is_referenceable() -> bool { - false + fn always_inline_schema() -> bool { + true } - fn schema_name() -> std::string::String { - #fn_name.to_string() + fn schema_name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed(#fn_name) } fn schema_id() -> std::borrow::Cow<'static, str> { @@ -118,7 +127,7 @@ fn type_for_schema(with_attr: &WithAttr) -> (syn::Type, Option) { )) } - fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema { #fun(gen) } } @@ -160,9 +169,18 @@ fn expr_for_external_tagged_enum<'a>( }) .partition(|v| v.is_unit() && v.attrs.is_default()); let unit_names = unit_variants.iter().map(|v| v.name()); - let unit_schema = schema_object(quote! { - instance_type: Some(schemars::schema::InstanceType::String.into()), - enum_values: Some(vec![#(#unit_names.into()),*]), + let unit_schema = quote!({ + let mut map = schemars::_serde_json::Map::new(); + map.insert("type".to_owned(), "string".into()); + map.insert( + "enum".to_owned(), + schemars::_serde_json::Value::Array({ + let mut enum_values = Vec::new(); + #(enum_values.push((#unit_names).into());)* + enum_values + }), + ); + schemars::Schema::from(map) }); if complex_variants.is_empty() { @@ -179,12 +197,12 @@ fn expr_for_external_tagged_enum<'a>( let mut schema_expr = if variant.is_unit() && variant.attrs.with.is_none() { quote! { - schemars::_private::new_unit_enum(#name) + schemars::_private::new_unit_enum_variant(#name) } } else { let sub_schema = expr_for_untagged_enum_variant(variant, deny_unknown_fields); quote! { - schemars::_private::new_externally_tagged_enum(#name, #sub_schema) + schemars::_private::new_externally_tagged_enum_variant(#name, #sub_schema) } }; @@ -213,19 +231,14 @@ fn expr_for_internal_tagged_enum<'a>( let name = variant.name(); - let mut tag_schema = quote! { - schemars::_private::new_internally_tagged_enum(#tag_name, #name, #deny_unknown_fields) - }; - - variant.attrs.as_metadata().apply_to_schema(&mut tag_schema); - - if let Some(variant_schema) = - expr_for_untagged_enum_variant_for_flatten(variant, deny_unknown_fields) - { - tag_schema.extend(quote!(.flatten(#variant_schema))) - } - - tag_schema + let mut schema_expr = expr_for_internal_tagged_enum_variant(variant, deny_unknown_fields); + schema_expr = quote!({ + let mut schema = #schema_expr; + schemars::_private::apply_internal_enum_variant_tag(&mut schema, #tag_name, #name, #deny_unknown_fields); + schema + }); + variant.attrs.as_metadata().apply_to_schema(&mut schema_expr); + schema_expr }) .collect(); @@ -276,47 +289,44 @@ fn expr_for_adjacent_tagged_enum<'a>( let (add_content_to_props, add_content_to_required) = content_schema .map(|content_schema| { ( - quote!(props.insert(#content_name.to_owned(), #content_schema);), - quote!(required.insert(#content_name.to_owned());), + quote!(#content_name: (#content_schema),), + quote!(#content_name,), ) }) .unwrap_or_default(); let name = variant.name(); - let tag_schema = schema_object(quote! { - instance_type: Some(schemars::schema::InstanceType::String.into()), - enum_values: Some(vec![#name.into()]), - }); + let tag_schema = quote! { + schemars::json_schema!({ + "type": "string", + "enum": [#name], + }) + }; let set_additional_properties = if deny_unknown_fields { quote! { - additional_properties: Some(Box::new(false.into())), + "additionalProperties": false, } } else { TokenStream::new() }; - let mut outer_schema = schema_object(quote! { - instance_type: Some(schemars::schema::InstanceType::Object.into()), - object: Some(Box::new(schemars::schema::ObjectValidation { - properties: { - let mut props = schemars::Map::new(); - props.insert(#tag_name.to_owned(), #tag_schema); + let mut outer_schema = quote! { + schemars::json_schema!({ + "type": "object", + "properties": { + #tag_name: (#tag_schema), #add_content_to_props - props }, - required: { - let mut required = schemars::Set::new(); - required.insert(#tag_name.to_owned()); + "required": [ + #tag_name, #add_content_to_required - required - }, + ], // As we're creating a "wrapper" object, we can honor the // disposition of deny_unknown_fields. #set_additional_properties - ..Default::default() - })), - }); + }) + }; variant .attrs @@ -333,21 +343,19 @@ fn expr_for_adjacent_tagged_enum<'a>( /// Callers must determine if all subschemas are mutually exclusive. This can /// be done for most tagging regimes by checking that all tag names are unique. fn variant_subschemas(unique: bool, schemas: Vec) -> TokenStream { - if unique { - schema_object(quote! { - subschemas: Some(Box::new(schemars::schema::SubschemaValidation { - one_of: Some(vec![#(#schemas),*]), - ..Default::default() - })), - }) - } else { - schema_object(quote! { - subschemas: Some(Box::new(schemars::schema::SubschemaValidation { - any_of: Some(vec![#(#schemas),*]), - ..Default::default() - })), - }) - } + let keyword = if unique { "oneOf" } else { "anyOf" }; + quote!({ + let mut map = schemars::_serde_json::Map::new(); + map.insert( + #keyword.to_owned(), + schemars::_serde_json::Value::Array({ + let mut enum_values = Vec::new(); + #(enum_values.push(#schemas.to_value());)* + enum_values + }), + ); + schemars::Schema::from(map) + }) } fn expr_for_untagged_enum_variant(variant: &Variant, deny_unknown_fields: bool) -> TokenStream { @@ -370,10 +378,10 @@ fn expr_for_untagged_enum_variant(variant: &Variant, deny_unknown_fields: bool) } } -fn expr_for_untagged_enum_variant_for_flatten( +fn expr_for_internal_tagged_enum_variant( variant: &Variant, deny_unknown_fields: bool, -) -> Option { +) -> TokenStream { if let Some(with_attr) = &variant.attrs.with { let (ty, type_def) = type_for_schema(with_attr); let gen = quote!(gen); @@ -382,15 +390,15 @@ fn expr_for_untagged_enum_variant_for_flatten( }; prepend_type_def(type_def, &mut schema_expr); - return Some(schema_expr); + return schema_expr; } - Some(match variant.style { - Style::Unit => return None, + match variant.style { + Style::Unit => expr_for_unit_struct(), Style::Newtype => expr_for_field(&variant.fields[0], false), Style::Tuple => expr_for_tuple_struct(&variant.fields), Style::Struct => expr_for_struct(&variant.fields, &SerdeDefault::None, deny_unknown_fields), - }) + } } fn expr_for_unit_struct() -> TokenStream { @@ -412,16 +420,11 @@ fn expr_for_tuple_struct(fields: &[Field]) -> TokenStream { let len = fields.len() as u32; quote! { - schemars::schema::Schema::Object( - schemars::schema::SchemaObject { - instance_type: Some(schemars::schema::InstanceType::Array.into()), - array: Some(Box::new(schemars::schema::ArrayValidation { - items: Some(vec![#(#fields),*].into()), - max_items: Some(#len), - min_items: Some(#len), - ..Default::default() - })), - ..Default::default() + schemars::json_schema!({ + "type": "array", + "prefixItems": [#((#fields)),*], + "minItems": #len, + "maxItems": #len, }) } } @@ -431,96 +434,87 @@ fn expr_for_struct( default: &SerdeDefault, deny_unknown_fields: bool, ) -> TokenStream { - let (flattened_fields, property_fields): (Vec<_>, Vec<_>) = fields - .iter() - .filter(|f| !f.serde_attrs.skip_deserializing() || !f.serde_attrs.skip_serializing()) - .partition(|f| f.serde_attrs.flatten()); - let set_container_default = match default { SerdeDefault::None => None, SerdeDefault::Default => Some(quote!(let container_default = Self::default();)), SerdeDefault::Path(path) => Some(quote!(let container_default = #path();)), }; - let properties: Vec<_> = property_fields - .into_iter() + let properties: Vec<_> = fields + .iter() + .filter(|f| !f.serde_attrs.skip_deserializing() || !f.serde_attrs.skip_serializing()) .map(|field| { - let name = field.name(); - let default = field_default_expr(field, set_container_default.is_some()); + if field.serde_attrs.flatten() { + let (ty, type_def) = type_for_field_schema(field); - let (ty, type_def) = type_for_field_schema(field); + let required = field.validation_attrs.required(); - let has_default = default.is_some(); - let required = field.validation_attrs.required(); + let args = quote!(gen, #required); + let mut schema_expr = quote_spanned! {ty.span()=> + schemars::_private::json_schema_for_flatten::<#ty>(#args) + }; - let metadata = SchemaMetadata { - read_only: field.serde_attrs.skip_deserializing(), - write_only: field.serde_attrs.skip_serializing(), - default, - ..field.attrs.as_metadata() - }; + prepend_type_def(type_def, &mut schema_expr); - let gen = quote!(gen); - let mut schema_expr = if field.validation_attrs.required() { - quote_spanned! {ty.span()=> - <#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(#gen) + quote! { + schemars::_private::flatten(&mut schema, #schema_expr); } } else { - quote_spanned! {ty.span()=> - #gen.subschema_for::<#ty>() - } - }; + let name = field.name(); + let default = field_default_expr(field, set_container_default.is_some()); - metadata.apply_to_schema(&mut schema_expr); - field.validation_attrs.apply_to_schema(&mut schema_expr); + let (ty, type_def) = type_for_field_schema(field); - quote! { - { - #type_def - schemars::_private::insert_object_property::<#ty>(object_validation, #name, #has_default, #required, #schema_expr); - } - } - }) - .collect(); + let has_default = default.is_some(); + let required = field.validation_attrs.required(); - let flattens: Vec<_> = flattened_fields - .into_iter() - .map(|field| { - let (ty, type_def) = type_for_field_schema(field); + let metadata = SchemaMetadata { + read_only: field.serde_attrs.skip_deserializing(), + write_only: field.serde_attrs.skip_serializing(), + default, + ..field.attrs.as_metadata() + }; - let required = field.validation_attrs.required(); + let gen = quote!(gen); + let mut schema_expr = if field.validation_attrs.required() { + quote_spanned! {ty.span()=> + <#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(#gen) + } + } else { + quote_spanned! {ty.span()=> + #gen.subschema_for::<#ty>() + } + }; - let args = quote!(gen, #required); - let mut schema_expr = quote_spanned! {ty.span()=> - schemars::_private::json_schema_for_flatten::<#ty>(#args) - }; + metadata.apply_to_schema(&mut schema_expr); + field.validation_attrs.apply_to_schema(&mut schema_expr); - prepend_type_def(type_def, &mut schema_expr); - schema_expr - }) + quote! { + { + #type_def + schemars::_private::insert_object_property::<#ty>(&mut schema, #name, #has_default, #required, #schema_expr); + } + }} + }) .collect(); let set_additional_properties = if deny_unknown_fields { quote! { - object_validation.additional_properties = Some(Box::new(false.into())); + "additionalProperties": false, } } else { TokenStream::new() }; - quote! { - { - #set_container_default - let mut schema_object = schemars::schema::SchemaObject { - instance_type: Some(schemars::schema::InstanceType::Object.into()), - ..Default::default() - }; - let object_validation = schema_object.object(); + + quote! ({ + #set_container_default + let mut schema = schemars::json_schema!({ + "type": "object", #set_additional_properties - #(#properties)* - schemars::schema::Schema::Object(schema_object) - #(.flatten(#flattens))* - } - } + }); + #(#properties)* + schema + }) } fn field_default_expr(field: &Field, container_has_default: bool) -> Option { @@ -578,16 +572,6 @@ fn field_default_expr(field: &Field, container_has_default: bool) -> Option TokenStream { - quote! { - schemars::schema::Schema::Object( - schemars::schema::SchemaObject { - #properties - ..Default::default() - }) - } -} - fn prepend_type_def(type_def: Option, schema_expr: &mut TokenStream) { if let Some(type_def) = type_def { *schema_expr = quote! {