From 342cd5fd09e52572aad8f0032cd4cfc30000e203 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 12 May 2024 19:23:54 +0100 Subject: [PATCH 01/40] Define `Schema` as a newtype around `serde_json::Value` (#289) --- .github/workflows/ci.yml | 2 +- Cargo.lock | 100 +-- schemars/Cargo.toml | 84 +-- schemars/examples/custom_serialization.rs | 10 +- schemars/src/_private.rs | 172 +++-- schemars/src/flatten.rs | 247 +++---- schemars/src/gen.rs | 236 +++---- schemars/src/json_schema_impls/array.rs | 70 +- schemars/src/json_schema_impls/arrayvec05.rs | 37 - schemars/src/json_schema_impls/arrayvec07.rs | 19 +- schemars/src/json_schema_impls/atomic.rs | 12 +- schemars/src/json_schema_impls/bytes.rs | 7 - .../{chrono.rs => chrono04.rs} | 42 +- schemars/src/json_schema_impls/core.rs | 235 +++--- schemars/src/json_schema_impls/decimal.rs | 22 +- .../{either.rs => either1.rs} | 11 +- schemars/src/json_schema_impls/enumset.rs | 6 - schemars/src/json_schema_impls/ffi.rs | 42 +- schemars/src/json_schema_impls/indexmap.rs | 8 - schemars/src/json_schema_impls/indexmap2.rs | 2 - schemars/src/json_schema_impls/maps.rs | 17 +- schemars/src/json_schema_impls/mod.rs | 82 ++- .../src/json_schema_impls/nonzero_signed.rs | 17 +- .../src/json_schema_impls/nonzero_unsigned.rs | 24 +- schemars/src/json_schema_impls/primitives.rs | 129 ++-- schemars/src/json_schema_impls/semver.rs | 30 - schemars/src/json_schema_impls/semver1.rs | 24 + schemars/src/json_schema_impls/sequences.rs | 31 +- schemars/src/json_schema_impls/serdejson.rs | 13 +- schemars/src/json_schema_impls/smallvec.rs | 6 - schemars/src/json_schema_impls/smol_str.rs | 6 - schemars/src/json_schema_impls/time.rs | 43 +- schemars/src/json_schema_impls/tuple.rs | 51 +- .../src/json_schema_impls/{url.rs => url2.rs} | 15 +- schemars/src/json_schema_impls/uuid08.rs | 26 - schemars/src/json_schema_impls/uuid1.rs | 13 +- schemars/src/json_schema_impls/wrapper.rs | 2 - schemars/src/lib.rs | 55 +- schemars/src/macros.rs | 16 + schemars/src/schema.rs | 667 +++++------------- schemars/src/ser.rs | 215 +++--- schemars/src/visit.rs | 221 +++--- schemars/tests/arrayvec.rs | 10 - schemars/tests/bytes.rs | 2 +- schemars/tests/chrono.rs | 2 +- schemars/tests/decimal.rs | 7 +- schemars/tests/dereference.rs | 23 - schemars/tests/either.rs | 2 +- schemars/tests/enum.rs | 16 +- schemars/tests/enum_deny_unknown_fields.rs | 16 +- schemars/tests/enumset.rs | 5 +- schemars/tests/expected/arrayvec_string.json | 2 +- schemars/tests/expected/bigdecimal03.json | 6 - schemars/tests/expected/either.json | 2 +- .../tests/expected/enum-internal-duf.json | 3 +- schemars/tests/expected/range.json | 4 +- .../tests/expected/remote_derive_generic.json | 8 +- schemars/tests/expected/result.json | 8 +- .../tests/expected/schema-name-custom.json | 2 +- .../tests/expected/schema-name-default.json | 30 +- .../expected/schema-name-mixed-generics.json | 6 +- .../expected/schema_with-enum-internal.json | 8 +- schemars/tests/expected/smallvec.json | 2 +- schemars/tests/expected/smol_str.json | 2 +- schemars/tests/indexmap.rs | 8 +- schemars/tests/indexmap2.rs | 16 - schemars/tests/schema_for_schema.rs | 19 - schemars/tests/schema_with_enum.rs | 6 +- schemars/tests/schema_with_struct.rs | 2 +- schemars/tests/semver.rs | 2 +- schemars/tests/smallvec.rs | 2 +- schemars/tests/smol_str.rs | 2 +- schemars/tests/url.rs | 2 +- schemars/tests/util/mod.rs | 38 +- schemars/tests/uuid.rs | 5 - schemars_derive/src/attr/validation.rs | 171 ++--- schemars_derive/src/lib.rs | 6 +- schemars_derive/src/metadata.rs | 61 +- schemars_derive/src/schema_exprs.rs | 143 ++-- 79 files changed, 1366 insertions(+), 2350 deletions(-) delete mode 100644 schemars/src/json_schema_impls/arrayvec05.rs delete mode 100644 schemars/src/json_schema_impls/bytes.rs rename schemars/src/json_schema_impls/{chrono.rs => chrono04.rs} (61%) rename schemars/src/json_schema_impls/{either.rs => either1.rs} (67%) delete mode 100644 schemars/src/json_schema_impls/enumset.rs delete mode 100644 schemars/src/json_schema_impls/indexmap.rs delete mode 100644 schemars/src/json_schema_impls/semver.rs create mode 100644 schemars/src/json_schema_impls/semver1.rs delete mode 100644 schemars/src/json_schema_impls/smallvec.rs delete mode 100644 schemars/src/json_schema_impls/smol_str.rs rename schemars/src/json_schema_impls/{url.rs => url2.rs} (56%) delete mode 100644 schemars/src/json_schema_impls/uuid08.rs delete mode 100644 schemars/tests/dereference.rs delete mode 100644 schemars/tests/expected/bigdecimal03.json delete mode 100644 schemars/tests/indexmap2.rs delete mode 100644 schemars/tests/schema_for_schema.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d37634e9..7c723066 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/Cargo.lock b/Cargo.lock index 516f9798..757013a3 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", ] @@ -320,18 +305,16 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" name = "schemars" version = "0.8.19" 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,8 +324,7 @@ dependencies = [ "smol_str", "trybuild", "url", - "uuid 0.8.2", - "uuid 1.5.0", + "uuid", ] [[package]] @@ -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" @@ -415,18 +394,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 +480,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/schemars/Cargo.toml b/schemars/Cargo.toml index 579e8c04..47dc643a 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -15,117 +15,91 @@ rust-version = "1.60" [dependencies] schemars_derive = { version = "=0.8.19", optional = true, path = "../schemars_derive" } -serde = { version = "1.0", features = ["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" } +# optional dependencies +chrono04 = { version = "0.4", default-features = false, optional = true, package = "chrono" } +indexmap2 = { version = "2.0", default-features = false, optional = true, package = "indexmap" } +either1 = { version = "1.3", default-features = false, optional = true, package = "either" } 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" } +smallvec1 = { version = "1.0", default-features = false, optional = true, package = "smallvec" } 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" } +url2 = { version = "2.0", default-features = false, optional = true, package = "url" } +bytes1 = { version = "1.0", default-features = false, optional = true, package = "bytes" } +rust_decimal1 = { version = "1", default-features = false, optional = true, package = "rust_decimal"} 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 } +enumset1 = { version = "1.0", default-features = false, optional = true, package = "enumset" } +smol_str02 = { version = "0.2.1", default-features = false, optional = true, package = "smol_str" } +semver1 = { version = "1.0.9", default-features = false, optional = true, package = "semver" } [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"] - 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"] +name = "ui" +required-features = ["ui_test"] [[test]] -name = "indexmap" -required-features = ["indexmap"] +name = "chrono" +required-features = ["chrono04"] [[test]] -name = "indexmap2" +name = "indexmap" required-features = ["indexmap2"] [[test]] name = "either" -required-features = ["either"] +required-features = ["either1"] [[test]] name = "uuid" -required-features = ["uuid08", "uuid1"] +required-features = ["uuid1"] [[test]] name = "smallvec" -required-features = ["smallvec"] +required-features = ["smallvec1"] [[test]] name = "bytes" -required-features = ["bytes"] +required-features = ["bytes1"] [[test]] name = "arrayvec" -required-features = ["arrayvec05", "arrayvec07"] - -[[test]] -name = "schema_for_schema" -required-features = ["impl_json_schema"] - -[[test]] -name = "ui" -required-features = ["ui_test"] +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 53c78fa6..c119ea5d 100644 --- a/schemars/examples/custom_serialization.rs +++ b/schemars/examples/custom_serialization.rs @@ -1,4 +1,4 @@ -use schemars::schema::{Schema, SchemaObject}; +use schemars::Schema; use schemars::{gen::SchemaGenerator, schema_for, JsonSchema}; use serde::{Deserialize, Serialize}; @@ -21,9 +21,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/src/_private.rs b/schemars/src/_private.rs index c61ffc15..d4ac4a7b 100644 --- a/schemars/src/_private.rs +++ b/schemars/src/_private.rs @@ -1,7 +1,8 @@ use crate::gen::SchemaGenerator; -use crate::schema::{InstanceType, ObjectValidation, Schema, SchemaObject}; -use crate::{JsonSchema, Map, Set}; +use crate::JsonSchema; +use crate::Schema; use serde::Serialize; +use serde_json::Map; use serde_json::Value; // Helper for generating schemas for flattened `Option` fields. @@ -12,12 +13,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"); } } @@ -57,38 +54,22 @@ 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() + // TODO switch from single-valued "enum" to "const" + json_schema!({ + "type": "string", + "enum": [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() + json_schema!({ + "type": "object", + "properties": { + variant: sub_schema + }, + "required": [variant], + "additionalProperties": false, }) } @@ -98,74 +79,87 @@ pub fn new_internally_tagged_enum( 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() + // TODO switch from single-valued "enum" to "const" + let mut schema = json_schema!({ + "type": "object", + "properties": { + tag_name: { + "type": "string", + "enum": [variant], + } + }, + "required": [tag_name], }); - 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() - }) + + if deny_unknown_fields { + schema + .as_object_mut() + .unwrap() + .insert("additionalProperties".into(), false.into()); + } + + schema } 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 required || !(has_default || 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; - - 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 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()); + } +} + +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); } - }; + } } +} - 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, Value); - - 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) +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); } } diff --git a/schemars/src/flatten.rs b/schemars/src/flatten.rs index e55cb7fc..fc66b74d 100644 --- a/schemars/src/flatten.rs +++ b/schemars/src/flatten.rs @@ -1,180 +1,111 @@ -use crate::schema::*; -use crate::{Map, Set}; +use serde_json::map::Entry; +use serde_json::Value; + +use crate::Schema; 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) { + pub fn flatten(mut self, other: Self) -> Schema { + // This special null-type-schema handling is here for backward-compatibility, but needs reviewing. + // I think it's only needed to make internally-tagged enum unit variants behave correctly, but that + // should be handled entirely within schemars_derive. + if other + .as_object() + .and_then(|o| o.get("type")) + .and_then(|t| t.as_str()) + == Some("null") + { 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),)* + if let Value::Object(mut obj2) = other.to_value() { + let obj1 = self.ensure_object(); + + let ap2 = obj2.remove("additionalProperties"); + if let Entry::Occupied(mut ap1) = obj1.entry("additionalProperties") { + match ap2 { + Some(ap2) => { + flatten_additional_properties(ap1.get_mut(), ap2); + } + None => { + ap1.remove(); + } } } - } - }; - ($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)))) + 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 `self`) + } + }; + } + } } - - // 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; +// TODO validate behaviour when flattening a normal struct into a struct with deny_unknown_fields +fn flatten_additional_properties(v1: &mut Value, v2: Value) { + match (v1, v2) { + (v1, Value::Bool(true)) => { + *v1 = Value::Bool(true); } - 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) + (v1 @ Value::Bool(false), v2) => { + *v1 = v2; + } + (Value::Object(o1), Value::Object(o2)) => { + o1.extend(o2); + } + _ => {} } } - -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 a31839a0..d19f80b9 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -7,12 +7,12 @@ 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::{visit::*, JsonSchema}; use dyn_clone::DynClone; use serde::Serialize; use std::borrow::Cow; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::{any::Any, collections::HashSet, fmt::Debug}; /// Settings to customize how Schemas are generated. @@ -76,7 +76,7 @@ impl SchemaSettings { option_add_null_type: true, definitions_path: "#/definitions/".to_owned(), meta_schema: Some("https://json-schema.org/draft/2019-09/schema".to_owned()), - visitors: Vec::default(), + visitors: Vec::new(), inline_subschemas: false, } } @@ -96,9 +96,7 @@ impl SchemaSettings { Box::new(ReplaceBoolSchemas { skip_additional_properties: true, }), - Box::new(SetSingleExample { - retain_examples: false, - }), + Box::new(SetSingleExample), ], inline_subschemas: false, } @@ -150,7 +148,7 @@ impl SchemaSettings { #[derive(Debug, Default)] pub struct SchemaGenerator { settings: SchemaSettings, - definitions: Map, + definitions: BTreeMap, pending_schema_ids: HashSet>, schema_id_to_name: HashMap, String>, used_schema_names: HashSet, @@ -198,19 +196,6 @@ 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 @@ -262,7 +247,7 @@ impl SchemaGenerator { name: String, id: Cow<'static, str>, ) { - let dummy = Schema::Bool(false); + let dummy = false.into(); // insert into definitions BEFORE calling json_schema to avoid infinite recursion self.definitions.insert(name.clone(), dummy); @@ -273,26 +258,26 @@ impl SchemaGenerator { /// Borrows the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated. /// - /// The keys of the returned `Map` are the [schema names](JsonSchema::schema_name), and the values are the schemas + /// The keys of the returned `BTreeMap` are the [schema names](JsonSchema::schema_name), and the values are the schemas /// themselves. - pub fn definitions(&self) -> &Map { + pub fn definitions(&self) -> &BTreeMap { &self.definitions } /// Mutably borrows the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated. /// - /// The keys of the returned `Map` are the [schema names](JsonSchema::schema_name), and the values are the schemas + /// The keys of the returned `BTreeMap` 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 BTreeMap { &mut self.definitions } /// Returns the collection of all [referenceable](JsonSchema::is_referenceable) 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 + /// The keys of the returned `BTreeMap` 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) -> BTreeMap { std::mem::take(&mut self.definitions) } @@ -306,40 +291,72 @@ impl SchemaGenerator { /// 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, - }; + pub fn root_schema_for(&mut self) -> Schema { + let mut schema = self.json_schema_internal::(T::schema_id()); + + 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()); + } + + if !self.definitions.is_empty() { + object.insert( + "definitions".into(), + serde_json::Value::Object( + self.definitions + .iter() + .map(|(k, v)| (k.clone(), v.clone().into())) + .collect(), + ), + ); + } for visitor in &mut self.settings.visitors { - visitor.visit_root_schema(&mut root) + visitor.visit_schema(&mut schema); } - root + schema } /// Consumes `self` and generates a root 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, - }; + pub fn into_root_schema_for(mut self) -> Schema { + let mut schema = self.json_schema_internal::(T::schema_id()); + + let object = schema.ensure_object(); + + object + .entry("title") + .or_insert_with(|| T::schema_name().into()); + + if let Some(meta_schema) = self.settings.meta_schema { + object.insert("$schema".into(), meta_schema.into()); + } + + if !self.definitions.is_empty() { + object.insert( + "definitions".into(), + serde_json::Value::Object( + self.definitions + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect(), + ), + ); + } for visitor in &mut self.settings.visitors { - visitor.visit_root_schema(&mut root) + visitor.visit_schema(&mut schema); } - root + schema } /// Generates a root JSON Schema for the given example value. @@ -349,29 +366,39 @@ impl SchemaGenerator { 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, - }; + if let Some(meta_schema) = self.settings.meta_schema.as_deref() { + object.insert("$schema".into(), meta_schema.into()); + } + + if !self.definitions.is_empty() { + object.insert( + "definitions".into(), + serde_json::Value::Object( + self.definitions + .iter() + .map(|(k, v)| (k.clone(), v.clone().into())) + .collect(), + ), + ); + } for visitor in &mut self.settings.visitors { - visitor.visit_root_schema(&mut root) + visitor.visit_schema(&mut schema); } - Ok(root) + Ok(schema) } /// Consumes `self` and generates a root JSON Schema for the given example value. @@ -381,72 +408,39 @@ impl SchemaGenerator { 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) = self.settings.meta_schema { + object.insert("$schema".into(), meta_schema.into()); } - Ok(root) - } + if !self.definitions.is_empty() { + object.insert( + "definitions".into(), + serde_json::Value::Object( + self.definitions + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect(), + ), + ); + } - /// 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, + for visitor in &mut self.settings.visitors { + visitor.visit_schema(&mut schema); } + + Ok(schema) } fn json_schema_internal(&mut self, id: Cow<'static, str>) -> Schema { diff --git a/schemars/src/json_schema_impls/array.rs b/schemars/src/json_schema_impls/array.rs index 036d0423..bacc079d 100644 --- a/schemars/src/json_schema_impls/array.rs +++ b/schemars/src/json_schema_impls/array.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; // Does not require T: JsonSchema. @@ -16,15 +15,10 @@ impl JsonSchema for [T; 0] { } 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, + }) } } @@ -44,17 +38,12 @@ macro_rules! array_impls { } 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 +56,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 281bde79..00000000 --- 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 e2d92c5b..3d4d0505 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. @@ -19,15 +17,10 @@ where } 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 5d8f6d91..53d55c62 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 f1a0f290..00000000 --- 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/chrono04.rs similarity index 61% rename from schemars/src/json_schema_impls/chrono.rs rename to schemars/src/json_schema_impls/chrono04.rs index 3a1731b5..5fd23e8b 100644 --- a/schemars/src/json_schema_impls/chrono.rs +++ b/schemars/src/json_schema_impls/chrono04.rs @@ -1,8 +1,6 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use chrono::prelude::*; -use serde_json::json; +use crate::{json_schema, JsonSchema, Schema}; +use chrono04::prelude::*; use std::borrow::Cow; impl JsonSchema for Weekday { @@ -17,20 +15,18 @@ impl JsonSchema for 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() + json_schema!({ + "type": "string", + "enum": [ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + ] + }) } } @@ -51,12 +47,10 @@ macro_rules! formatted_string_impl { } fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::String.into()), - format: Some($format.to_owned()), - ..Default::default() - } - .into() + json_schema!({ + "type": "string", + "format": $format + }) } } }; diff --git a/schemars/src/json_schema_impls/core.rs b/schemars/src/json_schema_impls/core.rs index 955ead67..16104f80 100644 --- a/schemars/src/json_schema_impls/core.rs +++ b/schemars/src/json_schema_impls/core.rs @@ -1,7 +1,6 @@ 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}; @@ -18,35 +17,45 @@ impl JsonSchema for Option { 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 => 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() + 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) + ] + }), + } } - .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,16 +68,6 @@ 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()) @@ -79,27 +78,24 @@ impl JsonSchema for Result { } 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"] + } + ] + }) } } @@ -113,37 +109,28 @@ impl JsonSchema for Bound { } 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" + } + ] + }) } } @@ -157,18 +144,15 @@ impl JsonSchema for Range { } 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 643ca44d..462e3d52 100644 --- a/schemars/src/json_schema_impls/decimal.rs +++ b/schemars/src/json_schema_impls/decimal.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! decimal_impl { @@ -17,23 +16,16 @@ macro_rules! decimal_impl { } 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/either1.rs similarity index 67% rename from schemars/src/json_schema_impls/either.rs rename to schemars/src/json_schema_impls/either1.rs index 957fdd16..96ed8a88 100644 --- a/schemars/src/json_schema_impls/either.rs +++ b/schemars/src/json_schema_impls/either1.rs @@ -1,7 +1,6 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use either::Either; +use crate::{json_schema, JsonSchema, Schema}; +use either1::Either; use std::borrow::Cow; impl JsonSchema for Either { @@ -20,8 +19,8 @@ impl JsonSchema for Either { } 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() + 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 22a3ebcf..00000000 --- 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 55b50125..4ad85c24 100644 --- a/schemars/src/json_schema_impls/ffi.rs +++ b/schemars/src/json_schema_impls/ffi.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; use std::ffi::{CStr, CString, OsStr, OsString}; @@ -14,27 +13,24 @@ impl JsonSchema for OsString { } 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 3bd3e545..00000000 --- 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 44eeedb3..9473dead 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 7c27808f..b934f8bb 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 { @@ -20,16 +19,10 @@ macro_rules! map_impl { } 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 0c548eb3..ebcb61b2 100644 --- a/schemars/src/json_schema_impls/mod.rs +++ b/schemars/src/json_schema_impls/mod.rs @@ -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 c2fba2b2..d1b51429 100644 --- a/schemars/src/json_schema_impls/nonzero_signed.rs +++ b/schemars/src/json_schema_impls/nonzero_signed.rs @@ -1,6 +1,5 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; +use crate::{JsonSchema, Schema}; use std::borrow::Cow; use std::num::*; @@ -18,14 +17,12 @@ macro_rules! nonzero_unsigned_impl { } 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 1963d56e..e3c6d1d5 100644 --- a/schemars/src/json_schema_impls/nonzero_unsigned.rs +++ b/schemars/src/json_schema_impls/nonzero_unsigned.rs @@ -1,5 +1,5 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; +use crate::Schema; use crate::JsonSchema; use std::borrow::Cow; use std::num::*; @@ -18,9 +18,10 @@ macro_rules! nonzero_unsigned_impl { } 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 d1829848..63121fe6 100644 --- a/schemars/src/json_schema_impls/primitives.rs +++ b/schemars/src/json_schema_impls/primitives.rs @@ -1,67 +1,77 @@ 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:literal) => { + impl JsonSchema for $type { + no_ref_schema!(); + + fn schema_name() -> String { + $instance_type.to_owned() + } + + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed($instance_type) + } + + fn json_schema(_: &mut SchemaGenerator) -> Schema { + json_schema!({ + "type": $instance_type + }) + } + } }; - ($type:ty => $instance_type:ident, $name:expr, $format:expr) => { + ($type:ty => $instance_type:literal, $format:literal) => { impl JsonSchema for $type { no_ref_schema!(); fn schema_name() -> String { - $name.to_owned() + $format.to_owned() } fn schema_id() -> Cow<'static, str> { - Cow::Borrowed($name) + Cow::Borrowed($format) } fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::$instance_type.into()), - format: $format, - ..Default::default() - } - .into() + 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!(Path => String); -simple_impl!(PathBuf => String); - -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!(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!(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"); 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!(); @@ -74,24 +84,22 @@ macro_rules! unsigned_impl { } 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!(); @@ -105,15 +113,10 @@ impl JsonSchema for char { } 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 dd8ed8ec..00000000 --- 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 00000000..fb47f788 --- /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 { + 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 { + 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 780307fa..d14d4d33 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 { @@ -21,15 +20,10 @@ macro_rules! seq_impl { } 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::(), + }) } } }; @@ -53,16 +47,11 @@ macro_rules! set_impl { } 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 41eafd54..5c6b58d4 100644 --- a/schemars/src/json_schema_impls/serdejson.rs +++ b/schemars/src/json_schema_impls/serdejson.rs @@ -1,6 +1,5 @@ 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; @@ -17,7 +16,7 @@ impl JsonSchema for Value { } fn json_schema(_: &mut SchemaGenerator) -> Schema { - Schema::Bool(true) + true.into() } } @@ -35,11 +34,9 @@ impl JsonSchema for Number { } 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 f7a75e37..00000000 --- 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 cbca4a15..00000000 --- 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 767a1d2a..7317cfdc 100644 --- a/schemars/src/json_schema_impls/time.rs +++ b/schemars/src/json_schema_impls/time.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; use std::time::{Duration, SystemTime}; @@ -14,18 +13,14 @@ impl JsonSchema for Duration { } 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), + } + }) } } @@ -39,17 +34,13 @@ impl JsonSchema for SystemTime { } 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 f67f06a6..ab9a36d2 100644 --- a/schemars/src/json_schema_impls/tuple.rs +++ b/schemars/src/json_schema_impls/tuple.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! tuple_impls { @@ -24,20 +23,14 @@ macro_rules! tuple_impls { } 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", + "items": [ + $(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/url2.rs similarity index 56% rename from schemars/src/json_schema_impls/url.rs rename to schemars/src/json_schema_impls/url2.rs index be186121..2fcfd04d 100644 --- a/schemars/src/json_schema_impls/url.rs +++ b/schemars/src/json_schema_impls/url2.rs @@ -1,8 +1,7 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; +use crate::{json_schema, JsonSchema, Schema}; use std::borrow::Cow; -use url::Url; +use url2::Url; impl JsonSchema for Url { no_ref_schema!(); @@ -16,11 +15,9 @@ impl JsonSchema for Url { } fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::String.into()), - format: Some("uri".to_owned()), - ..Default::default() - } - .into() + 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 b3b18f83..00000000 --- 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 2e0c6e95..825f7a24 100644 --- a/schemars/src/json_schema_impls/uuid1.rs +++ b/schemars/src/json_schema_impls/uuid1.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; use uuid1::Uuid; @@ -16,11 +15,9 @@ impl JsonSchema for Uuid { } 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 243a06b5..e7e233c2 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 f2332e58..fbe5f1db 100644 --- a/schemars/src/lib.rs +++ b/schemars/src/lib.rs @@ -1,32 +1,9 @@ -#![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; @@ -36,7 +13,6 @@ mod macros; #[doc(hidden)] pub mod _private; pub mod gen; -pub mod schema; pub mod visit; #[cfg(feature = "schemars_derive")] @@ -50,7 +26,7 @@ pub use schemars_derive::*; #[doc(hidden)] pub use serde_json as _serde_json; -use schema::Schema; +pub use schema::Schema; /// A type which can be described as a JSON Schema document. /// @@ -75,7 +51,7 @@ 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::{gen::SchemaGenerator, Schema, JsonSchema}; /// use std::borrow::Cow; /// /// struct NonGenericType; @@ -101,7 +77,7 @@ 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::{gen::SchemaGenerator, Schema, JsonSchema}; /// use std::{borrow::Cow, marker::PhantomData}; /// /// struct GenericType(PhantomData); @@ -175,24 +151,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 18a6810c..baa144d3 100644 --- a/schemars/src/macros.rs +++ b/schemars/src/macros.rs @@ -76,3 +76,19 @@ macro_rules! schema_for_value { .unwrap() }; } + +// TODO doc +#[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 4bef66e0..6a881589 100644 --- a/schemars/src/schema.rs +++ b/schemars/src/schema.rs @@ -2,550 +2,229 @@ 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; +use ref_cast::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), +#[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 Schema { - /// 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::new_ref(reference).into() +impl Serialize for Schema { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + ser::OrderedKeywordWrapper(&self.0).serialize(serializer) } +} - /// Returns `true` if `self` is a `$ref` schema. - /// - /// 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(), - _ => false, - } +impl Schema { + pub fn new() -> Self { + Self(Value::Object(Map::new())) } - /// 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() - }, - } + pub fn new_ref(reference: String) -> Self { + let mut map = Map::new(); + map.insert("$ref".to_owned(), Value::String(reference)); + Self(Value::Object(map)) } -} -impl From for Schema { - fn from(o: SchemaObject) -> Self { - Schema::Object(o) + pub fn as_value(&self) -> &Value { + &self.0 } -} -impl From for Schema { - fn from(b: bool) -> Self { - Schema::Bool(b) + pub fn as_bool(&self) -> Option { + self.0.as_bool() } -} -/// 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, -} + pub fn as_object(&self) -> Option<&Map> { + self.0.as_object() + } -/// 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, -} + pub fn as_object_mut(&mut self) -> Option<&mut Map> { + self.0.as_object_mut() + } -// 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) -} + pub(crate) fn try_to_object(self) -> Result, bool> { + match self.0 { + Value::Object(m) => Ok(m), + Value::Bool(b) => Err(b), + _ => unreachable!(), + } + } -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))) + pub fn to_value(self) -> Value { + self.0 } -} -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) + 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); } - }; -} -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() - } + self.as_object_mut() + .expect("Schema value should be of type Object.") } - /// 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(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, + } } - /// 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)) + 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")) } - 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); -} - -impl From for SchemaObject { - fn from(schema: Schema) -> Self { - schema.into_object() - } -} + #[allow(unsafe_code)] + #[ref_cast_custom] + fn ref_cast(value: &Value) -> &Self; -/// 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, + #[allow(unsafe_code)] + #[ref_cast_custom] + fn ref_cast_mut(value: &mut Value) -> &mut Self; } -#[allow(clippy::trivially_copy_pass_by_ref)] -fn is_false(b: &bool) -> bool { - !b +impl From for Value { + fn from(v: Schema) -> Value { + v.0 + } } -/// 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>, -} +impl std::convert::TryFrom for Schema { + type Error = serde_json::Error; -/// 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, + fn try_from(value: Value) -> serde_json::Result { + Schema::validate(&value)?; + Ok(Schema(value)) + } } -/// 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, -} +impl<'a> std::convert::TryFrom<&'a Value> for &'a Schema { + type Error = serde_json::Error; -/// 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>, + fn try_from(value: &Value) -> serde_json::Result<&Schema> { + Schema::validate(value)?; + Ok(Schema::ref_cast(value)) + } } -/// 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>, -} +impl<'a> std::convert::TryFrom<&'a mut Value> for &'a mut Schema { + type Error = serde_json::Error; -/// 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, + fn try_from(value: &mut Value) -> serde_json::Result<&mut Schema> { + Schema::validate(value)?; + Ok(Schema::ref_cast_mut(value)) + } } -/// 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 Default for Schema { + fn default() -> Self { + Self(Value::Object(Map::new())) + } } -impl From for SingleOrVec { - fn from(single: T) -> Self { - SingleOrVec::Single(Box::new(single)) +impl From> for Schema { + fn from(o: Map) -> Self { + Schema(Value::Object(o)) } } -impl From> for SingleOrVec { - fn from(vec: Vec) -> Self { - SingleOrVec::Vec(vec) +impl From for Schema { + fn from(b: bool) -> Self { + Schema(Value::Bool(b)) } } -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), +mod ser { + use serde::ser::{Serialize, SerializeMap, SerializeSeq}; + use serde_json::Value; + + const ORDERED_KEYWORDS_START: [&str; 6] = + ["$id", "$schema", "title", "description", "type", "format"]; + const ORDERED_KEYWORDS_END: [&str; 2] = ["$defs", "definitions"]; + + pub(super) struct OrderedKeywordWrapper<'a>(pub &'a Value); + + 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()))?; + + for key in ORDERED_KEYWORDS_START { + if let Some(value) = object.get(key) { + map.serialize_entry(key, &OrderedKeywordWrapper(value))?; + } + } + + 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))?; + } + } + + for key in ORDERED_KEYWORDS_END { + if let Some(value) = object.get(key) { + map.serialize_entry(key, &OrderedKeywordWrapper(value))?; + } + } + + map.end() + } + _ => self.0.serialize(serializer), + } } } } diff --git a/schemars/src/ser.rs b/schemars/src/ser.rs index 3f69bef5..8be7b343 100644 --- a/schemars/src/ser.rs +++ b/schemars/src/ser.rs @@ -1,8 +1,7 @@ -use crate::schema::*; -use crate::JsonSchema; -use crate::{gen::SchemaGenerator, Map}; -use serde_json::{Error, Value}; -use std::{convert::TryInto, fmt::Display}; +use crate::gen::SchemaGenerator; +use crate::{json_schema, JsonSchema, Schema}; +use serde_json::{Error, Map, Value}; +use std::fmt::Display; pub(crate) struct Serializer<'a> { pub(crate) gen: &'a mut SchemaGenerator, @@ -22,7 +21,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 +35,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 +56,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 +90,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 +100,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)); - - 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()) + .unwrap_or(true.into()); + + Ok(json_schema!({ + "type": "object", + "additionalProperties": value_schema, + })) } fn serialize_none(self) -> Result { @@ -132,52 +124,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 => SchemaObject { - subschemas: Some(Box::new(SubschemaValidation { - any_of: Some(vec![schema, <()>::json_schema(self.gen)]), - ..Default::default() - })), - ..Default::default() + 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) + ] + }), + } } - .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 +180,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 +192,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 +211,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 +298,7 @@ impl serde::ser::SerializeTupleVariant for Serializer<'_> { } fn end(self) -> Result { - Ok(Schema::Bool(true)) + Ok(true.into()) } } @@ -333,7 +318,7 @@ impl serde::ser::SerializeStructVariant for Serializer<'_> { } fn end(self) -> Result { - Ok(Schema::Bool(true)) + Ok(true.into()) } } @@ -345,7 +330,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 +339,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 +349,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 +375,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", + "items": 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 +438,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 +475,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/visit.rs b/schemars/src/visit.rs index 182b5722..cfe11dfa 100644 --- a/schemars/src/visit.rs +++ b/schemars/src/visit.rs @@ -7,116 +7,87 @@ All methods of `Visitor` have a default implementation that makes no change but 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: +To add a custom property to all object schemas: ``` -use schemars::schema::SchemaObject; -use schemars::visit::{Visitor, visit_schema_object}; +use schemars::Schema; +use schemars::visit::{Visitor, visit_schema}; pub struct MyVisitor; impl Visitor for MyVisitor { - fn visit_schema_object(&mut self, schema: &mut SchemaObject) { + fn visit_schema(&mut self, schema: &mut Schema) { // First, make our change to this schema - schema.extensions.insert("my_property".to_string(), serde_json::json!("hello world")); + if let Some(obj) = schema.as_object_mut() { + obj.insert("my_property".to_string(), serde_json::json!("hello world")); + } // Then delegate to default implementation to visit any subschemas - visit_schema_object(self, schema); + visit_schema(self, schema); } } ``` */ -use crate::schema::{RootSchema, Schema, SchemaObject, SingleOrVec}; +use serde_json::{json, Value}; + +use crate::Schema; /// 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) + if let Some(obj) = schema.as_object_mut() { + for (key, value) in obj { + match key.as_str() { + "not" + | "if" + | "then" + | "else" + | "additionalItems" + | "contains" + | "additionalProperties" + | "propertyNames" => { + if let Ok(subschema) = value.try_into() { + v.visit_schema(subschema) + } + } + "allOf" | "anyOf" | "oneOf" => { + if let Some(array) = value.as_array_mut() { + for value in array { + if let Ok(subschema) = value.try_into() { + v.visit_schema(subschema) + } + } + } + } + "items" => { + if let Some(array) = value.as_array_mut() { + for value in array { + if let Ok(subschema) = value.try_into() { + v.visit_schema(subschema) + } + } + } else if let Ok(subschema) = value.try_into() { + v.visit_schema(subschema) + } + } + "properties" | "patternProperties" | "definitions" | "$defs" => { + if let Some(obj) = value.as_object_mut() { + for value in obj.values_mut() { + if let Ok(subschema) = value.try_into() { + v.visit_schema(subschema) + } + } + } + } + _ => {} } } } @@ -133,29 +104,23 @@ pub struct ReplaceBoolSchemas { 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() - } - } + if let Some(obj) = schema.as_object_mut() { + if self.skip_additional_properties { + if let Some((ap_key, ap_value)) = obj.remove_entry("additionalProperties") { + visit_schema(self, schema); - 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; + if let Some(obj) = schema.as_object_mut() { + obj.insert(ap_key, ap_value); } + + return; } } - } - visit_schema_object(self, schema); + visit_schema(self, schema); + } else { + schema.ensure_object(); + } } } @@ -166,18 +131,19 @@ impl Visitor for ReplaceBoolSchemas { 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]), + fn visit_schema(&mut self, schema: &mut Schema) { + visit_schema(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 + })); + } } } } @@ -188,25 +154,18 @@ impl Visitor for RemoveRefSiblings { /// /// 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, -} +pub struct SetSingleExample; 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() - } - }); + fn visit_schema(&mut self, schema: &mut Schema) { + visit_schema(self, schema); - if let Some(example) = first_example { - schema.extensions.insert("example".to_owned(), example); + 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); + } + } } } } diff --git a/schemars/tests/arrayvec.rs b/schemars/tests/arrayvec.rs index b00b3955..e32d1a72 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 688ab214..57974117 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 7f518d79..da6f8d4d 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 d245583b..246e8138 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 b1abab0f..00000000 --- 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/either.rs b/schemars/tests/either.rs index 16a2e591..5dcd0796 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 1ed60462..70bd9d21 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 { @@ -29,6 +31,7 @@ enum External { }, UnitTwo, Tuple(i32, bool), + // FIXME this should probably only replace the "payload" of the enum #[schemars(with = "i32")] WithInt, } @@ -43,7 +46,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 { @@ -51,6 +54,7 @@ enum Internal { bar: bool, }, UnitTwo, + // FIXME this should probably only replace the "payload" of the enum #[schemars(with = "i32")] WithInt, } @@ -65,7 +69,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 { @@ -73,6 +77,7 @@ enum Untagged { bar: bool, }, Tuple(i32, bool), + // FIXME this should probably only replace the "payload" of the enum #[schemars(with = "i32")] WithInt, } @@ -87,7 +92,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 { @@ -96,6 +101,7 @@ enum Adjacent { }, Tuple(i32, bool), UnitTwo, + // FIXME this should probably only replace the "payload" of the enum #[schemars(with = "i32")] WithInt, } diff --git a/schemars/tests/enum_deny_unknown_fields.rs b/schemars/tests/enum_deny_unknown_fields.rs index 62017840..62c1a455 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 { @@ -31,6 +33,7 @@ enum External { }, UnitTwo, Tuple(i32, bool), + // FIXME this should probably only replace the "payload" of the enum #[schemars(with = "i32")] WithInt, } @@ -46,7 +49,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 { @@ -54,6 +57,7 @@ enum Internal { bar: bool, }, UnitTwo, + // FIXME this should only replace the "payload" of the enum (which doesn't even make sense for unit enums!) #[schemars(with = "i32")] WithInt, } @@ -69,7 +73,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 { @@ -77,6 +81,7 @@ enum Untagged { bar: bool, }, Tuple(i32, bool), + // FIXME this should probably only replace the "payload" of the enum #[schemars(with = "i32")] WithInt, } @@ -92,7 +97,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 { @@ -101,6 +106,7 @@ enum Adjacent { }, Tuple(i32, bool), UnitTwo, + // FIXME this should probably only replace the "payload" of the enum #[schemars(with = "i32")] WithInt, } diff --git a/schemars/tests/enumset.rs b/schemars/tests/enumset.rs index 27f20307..c04ba0e1 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_string.json b/schemars/tests/expected/arrayvec_string.json index 42f099a3..ad174d81 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", + "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 855db6f7..00000000 --- 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/either.json b/schemars/tests/expected/either.json index b028057a..807c9c92 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", + "title": "Either_int32_or_Either_boolean_or_null", "anyOf": [ { "type": "integer", diff --git a/schemars/tests/expected/enum-internal-duf.json b/schemars/tests/expected/enum-internal-duf.json index fc36644f..501f13b8 100644 --- a/schemars/tests/expected/enum-internal-duf.json +++ b/schemars/tests/expected/enum-internal-duf.json @@ -127,8 +127,7 @@ "WithInt" ] } - }, - "additionalProperties": false + } } ] } \ No newline at end of file diff --git a/schemars/tests/expected/range.json b/schemars/tests/expected/range.json index a3b14afa..0bb3aeb1 100644 --- a/schemars/tests/expected/range.json +++ b/schemars/tests/expected/range.json @@ -15,7 +15,7 @@ "$ref": "#/definitions/Range_of_double" }, "bound": { - "$ref": "#/definitions/Bound_of_String" + "$ref": "#/definitions/Bound_of_string" } }, "definitions": { @@ -55,7 +55,7 @@ } } }, - "Bound_of_String": { + "Bound_of_string": { "oneOf": [ { "type": "object", diff --git a/schemars/tests/expected/remote_derive_generic.json b/schemars/tests/expected/remote_derive_generic.json index 2fea80a3..5a65f3bc 100644 --- a/schemars/tests/expected/remote_derive_generic.json +++ b/schemars/tests/expected/remote_derive_generic.json @@ -10,10 +10,10 @@ ], "properties": { "byte_or_bool2": { - "$ref": "#/definitions/Or_for_uint8_and_Boolean" + "$ref": "#/definitions/Or_for_uint8_and_boolean" }, "unit_or_t2": { - "$ref": "#/definitions/Or_for_Null_and_int32" + "$ref": "#/definitions/Or_for_null_and_int32" }, "s": { "$ref": "#/definitions/Str" @@ -30,7 +30,7 @@ } }, "definitions": { - "Or_for_uint8_and_Boolean": { + "Or_for_uint8_and_boolean": { "anyOf": [ { "type": "integer", @@ -42,7 +42,7 @@ } ] }, - "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 d8d6ec17..2a015736 100644 --- a/schemars/tests/expected/result.json +++ b/schemars/tests/expected/result.json @@ -8,14 +8,14 @@ ], "properties": { "result1": { - "$ref": "#/definitions/Result_of_MyStruct_or_Array_of_String" + "$ref": "#/definitions/Result_of_MyStruct_or_Array_of_string" }, "result2": { - "$ref": "#/definitions/Result_of_Boolean_or_Null" + "$ref": "#/definitions/Result_of_boolean_or_null" } }, "definitions": { - "Result_of_MyStruct_or_Array_of_String": { + "Result_of_MyStruct_or_Array_of_string": { "oneOf": [ { "type": "object", @@ -56,7 +56,7 @@ } } }, - "Result_of_Boolean_or_Null": { + "Result_of_boolean_or_null": { "oneOf": [ { "type": "object", diff --git a/schemars/tests/expected/schema-name-custom.json b/schemars/tests/expected/schema-name-custom.json index 866fb9d3..844699a0 100644 --- a/schemars/tests/expected/schema-name-custom.json +++ b/schemars/tests/expected/schema-name-custom.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "a-new-name-Array_of_String-int32-int32", + "title": "a-new-name-Array_of_string-int32-int32", "type": "object", "required": [ "inner", diff --git a/schemars/tests/expected/schema-name-default.json b/schemars/tests/expected/schema-name-default.json index 31f7f267..39c39a97 100644 --- a/schemars/tests/expected/schema-name-default.json +++ b/schemars/tests/expected/schema-name-default.json @@ -1,15 +1,11 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MyStruct_for_int32_and_Null_and_Boolean_and_Array_of_String", + "title": "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" @@ -25,23 +21,27 @@ "items": { "type": "string" } - }, - "inner": { - "$ref": "#/definitions/MySimpleStruct" } }, + "required": [ + "inner", + "t", + "u", + "v", + "w" + ], "definitions": { "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 32ac797e..bddd083c 100644 --- a/schemars/tests/expected/schema-name-mixed-generics.json +++ b/schemars/tests/expected/schema-name-mixed-generics.json @@ -1,6 +1,6 @@ { "$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", + "title": "MixedGenericStruct_for_MyStruct_for_int32_and_null_and_boolean_and_Array_of_string_and_42_and_z", "type": "object", "required": [ "foo", @@ -12,7 +12,7 @@ "format": "int32" }, "generic": { - "$ref": "#/definitions/MyStruct_for_int32_and_Null_and_Boolean_and_Array_of_String" + "$ref": "#/definitions/MyStruct_for_int32_and_null_and_boolean_and_Array_of_string" } }, "definitions": { @@ -28,7 +28,7 @@ } } }, - "MyStruct_for_int32_and_Null_and_Boolean_and_Array_of_String": { + "MyStruct_for_int32_and_null_and_boolean_and_Array_of_string": { "type": "object", "required": [ "inner", diff --git a/schemars/tests/expected/schema_with-enum-internal.json b/schemars/tests/expected/schema_with-enum-internal.json index 7ede7e6e..75b28dcc 100644 --- a/schemars/tests/expected/schema_with-enum-internal.json +++ b/schemars/tests/expected/schema_with-enum-internal.json @@ -22,8 +22,8 @@ }, { "type": [ - "boolean", - "object" + "object", + "boolean" ], "required": [ "typeProperty" @@ -39,8 +39,8 @@ }, { "type": [ - "boolean", - "object" + "object", + "boolean" ], "required": [ "typeProperty" diff --git a/schemars/tests/expected/smallvec.json b/schemars/tests/expected/smallvec.json index eab45c0a..7ce011d4 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", + "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 42f099a3..ad174d81 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", + "title": "string", "type": "string" } \ No newline at end of file diff --git a/schemars/tests/indexmap.rs b/schemars/tests/indexmap.rs index 9501b67c..e243f35a 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 efc77ddf..00000000 --- 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 d175505c..00000000 --- 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_with_enum.rs b/schemars/tests/schema_with_enum.rs index a91aa4d5..5a6c575b 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::gen::SchemaGenerator) -> schemars::Schema { ::json_schema(gen) } @@ -21,6 +21,7 @@ pub enum External { #[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema, i32, ), + // FIXME this should probably only replace the "payload" of the enum #[schemars(schema_with = "schema_fn")] Unit, } @@ -38,6 +39,7 @@ pub enum Internal { foo: DoesntImplementJsonSchema, }, NewType(#[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema), + // FIXME this should probably only replace the "payload" of the enum #[schemars(schema_with = "schema_fn")] Unit, } @@ -59,6 +61,7 @@ pub enum Untagged { #[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema, i32, ), + // FIXME this should probably only replace the "payload" of the enum #[schemars(schema_with = "schema_fn")] Unit, } @@ -80,6 +83,7 @@ pub enum Adjacent { #[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema, i32, ), + // FIXME this should probably only replace the "payload" of the enum #[schemars(schema_with = "schema_fn")] Unit, } diff --git a/schemars/tests/schema_with_struct.rs b/schemars/tests/schema_with_struct.rs index 9c87abeb..70ba24d6 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::gen::SchemaGenerator) -> schemars::Schema { ::json_schema(gen) } diff --git a/schemars/tests/semver.rs b/schemars/tests/semver.rs index 617e5086..3c351c65 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 bc9c6ef6..8412a6a2 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 1e481963..43fad304 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/url.rs b/schemars/tests/url.rs index 0e9499a2..018a66b5 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 595d68bf..99cf6775 100644 --- a/schemars/tests/util/mod.rs +++ b/schemars/tests/util/mod.rs @@ -1,5 +1,6 @@ use pretty_assertions::assert_eq; -use schemars::{gen::SchemaSettings, schema::RootSchema, schema_for, JsonSchema}; +use schemars::visit::Visitor; +use schemars::{gen::SchemaSettings, schema_for, JsonSchema, Schema}; use std::error::Error; use std::fs; @@ -17,7 +18,16 @@ 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 { + // TEMP for easier comparison of schemas handling changes that don't actually affect a schema: + // - `required` ordering has changed + // - previously `f64` properties may now be integers + let actual = &{ + let mut actual = actual.clone(); + TempFixupForTests.visit_schema(&mut actual); + actual + }; + let expected_json = match fs::read_to_string(format!("tests/expected/{}.json", file)) { Ok(j) => j, Err(e) => { @@ -35,8 +45,30 @@ 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(()) } + +struct TempFixupForTests; + +impl schemars::visit::Visitor for TempFixupForTests { + fn visit_schema(&mut self, schema: &mut Schema) { + schemars::visit::visit_schema(self, schema); + + if let Some(object) = schema.as_object_mut() { + if let Some(serde_json::Value::Array(required)) = object.get_mut("required") { + required.sort_unstable_by(|a, b| a.as_str().cmp(&b.as_str())); + } + + for (key, value) in object { + if key == "multipleOf" || key.ends_with("aximum") || key.ends_with("inimum") { + if let Some(f) = value.as_f64() { + *value = f.into(); + } + } + } + } + } +} diff --git a/schemars/tests/uuid.rs b/schemars/tests/uuid.rs index bd673b51..77e92c2c 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/src/attr/validation.rs b/schemars_derive/src/attr/validation.rs index a511ab41..8d90302c 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 ea6c0a65..c890f75c 100644 --- a/schemars_derive/src/lib.rs +++ b/schemars_derive/src/lib.rs @@ -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::gen::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::gen::SchemaGenerator) -> schemars::Schema { <#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(gen) } @@ -182,7 +182,7 @@ fn derive_json_schema(mut input: syn::DeriveInput, repr: bool) -> syn::Result schemars::schema::Schema { + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Schema { #schema_expr } }; diff --git a/schemars_derive/src/metadata.rs b/schemars_derive/src/metadata.rs index 3dd77a05..6de68343 100644 --- a/schemars_derive/src/metadata.rs +++ b/schemars_derive/src/metadata.rs @@ -13,32 +13,46 @@ pub struct SchemaMetadata<'a> { 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 + }} + } + } + + 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 +61,19 @@ 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); + } + }); } + + setters } } diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index b625114b..585184c5 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); @@ -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::gen::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() { @@ -276,47 +294,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 +348,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 { @@ -412,16 +425,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", + "items": [#((#fields)),*], + "minItems": #len, + "maxItems": #len, }) } } @@ -477,7 +485,7 @@ fn expr_for_struct( quote! { { #type_def - schemars::_private::insert_object_property::<#ty>(object_validation, #name, #has_default, #required, #schema_expr); + schemars::_private::insert_object_property::<#ty>(&mut schema, #name, #has_default, #required, #schema_expr); } } }) @@ -502,7 +510,7 @@ fn expr_for_struct( let set_additional_properties = if deny_unknown_fields { quote! { - object_validation.additional_properties = Some(Box::new(false.into())); + "additionalProperties": false, } } else { TokenStream::new() @@ -510,15 +518,12 @@ fn expr_for_struct( 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(); - #set_additional_properties + let mut schema = schemars::json_schema!({ + "type": "object", + #set_additional_properties + }); #(#properties)* - schemars::schema::Schema::Object(schema_object) - #(.flatten(#flattens))* + schema #(.flatten(#flattens))* } } } @@ -578,16 +583,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! { From 3b3870ca823f13538a3a284ae4e19fe91f482845 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Mon, 13 May 2024 10:52:42 +0100 Subject: [PATCH 02/40] Simplify `flatten` No longer use it for internally-tagged enums. Instead, use a private helper that adds the tag property. --- schemars/src/_private.rs | 40 ++++++++++++++++++ schemars/src/flatten.rs | 42 +------------------ .../tests/expected/enum-internal-duf.json | 5 +-- schemars/tests/expected/enum-internal.json | 8 ++-- .../expected/schema_with-enum-internal.json | 10 +---- schemars_derive/src/schema_exprs.rs | 33 +++++++-------- 6 files changed, 62 insertions(+), 76 deletions(-) diff --git a/schemars/src/_private.rs b/schemars/src/_private.rs index d4ac4a7b..3de96145 100644 --- a/schemars/src/_private.rs +++ b/schemars/src/_private.rs @@ -2,6 +2,7 @@ use crate::gen::SchemaGenerator; use crate::JsonSchema; use crate::Schema; use serde::Serialize; +use serde_json::json; use serde_json::Map; use serde_json::Value; @@ -73,6 +74,45 @@ pub fn new_externally_tagged_enum(variant: &str, sub_schema: Schema) -> Schema { }) } +pub fn apply_internal_enum_tag( + schema: &mut Schema, + tag_name: &str, + variant: &str, + deny_unknown_fields: bool, +) { + let obj = schema.ensure_object(); + let is_unit = obj.get("type").is_some_and(|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", + // TODO switch from single-valued "enum" to "const" + "enum": [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()); + } +} + /// Create a schema for an internally tagged enum pub fn new_internally_tagged_enum( tag_name: &str, diff --git a/schemars/src/flatten.rs b/schemars/src/flatten.rs index fc66b74d..69b03617 100644 --- a/schemars/src/flatten.rs +++ b/schemars/src/flatten.rs @@ -9,33 +9,9 @@ impl Schema { /// It should not be considered part of the public API. #[doc(hidden)] pub fn flatten(mut self, other: Self) -> Schema { - // This special null-type-schema handling is here for backward-compatibility, but needs reviewing. - // I think it's only needed to make internally-tagged enum unit variants behave correctly, but that - // should be handled entirely within schemars_derive. - if other - .as_object() - .and_then(|o| o.get("type")) - .and_then(|t| t.as_str()) - == Some("null") - { - return self; - } - - if let Value::Object(mut obj2) = other.to_value() { + if let Value::Object(obj2) = other.to_value() { let obj1 = self.ensure_object(); - let ap2 = obj2.remove("additionalProperties"); - if let Entry::Occupied(mut ap1) = obj1.entry("additionalProperties") { - match ap2 { - Some(ap2) => { - flatten_additional_properties(ap1.get_mut(), ap2); - } - None => { - ap1.remove(); - } - } - } - for (key, value2) in obj2 { match obj1.entry(key) { Entry::Vacant(vacant) => { @@ -93,19 +69,3 @@ impl Schema { self } } - -// TODO validate behaviour when flattening a normal struct into a struct with deny_unknown_fields -fn flatten_additional_properties(v1: &mut Value, v2: Value) { - match (v1, v2) { - (v1, Value::Bool(true)) => { - *v1 = Value::Bool(true); - } - (v1 @ Value::Bool(false), v2) => { - *v1 = v2; - } - (Value::Object(o1), Value::Object(o2)) => { - o1.extend(o2); - } - _ => {} - } -} diff --git a/schemars/tests/expected/enum-internal-duf.json b/schemars/tests/expected/enum-internal-duf.json index 501f13b8..7a9bcd75 100644 --- a/schemars/tests/expected/enum-internal-duf.json +++ b/schemars/tests/expected/enum-internal-duf.json @@ -112,10 +112,7 @@ "additionalProperties": false }, { - "type": [ - "object", - "integer" - ], + "type": "object", "format": "int32", "required": [ "typeProperty" diff --git a/schemars/tests/expected/enum-internal.json b/schemars/tests/expected/enum-internal.json index 37739b09..115dbf31 100644 --- a/schemars/tests/expected/enum-internal.json +++ b/schemars/tests/expected/enum-internal.json @@ -28,6 +28,9 @@ "StringMap" ] } + }, + "additionalProperties": { + "type": "string" } }, { @@ -105,10 +108,7 @@ } }, { - "type": [ - "object", - "integer" - ], + "type": "object", "format": "int32", "required": [ "typeProperty" diff --git a/schemars/tests/expected/schema_with-enum-internal.json b/schemars/tests/expected/schema_with-enum-internal.json index 75b28dcc..c4a0cc10 100644 --- a/schemars/tests/expected/schema_with-enum-internal.json +++ b/schemars/tests/expected/schema_with-enum-internal.json @@ -21,10 +21,7 @@ } }, { - "type": [ - "object", - "boolean" - ], + "type": "object", "required": [ "typeProperty" ], @@ -38,10 +35,7 @@ } }, { - "type": [ - "object", - "boolean" - ], + "type": "object", "required": [ "typeProperty" ], diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index 585184c5..f22831e3 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -231,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); + variant.attrs.as_metadata().apply_to_schema(&mut schema_expr); + + quote!({ + let mut schema = #schema_expr; + schemars::_private::apply_internal_enum_tag(&mut schema, #tag_name, #name, #deny_unknown_fields); + schema + }) }) .collect(); @@ -383,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); @@ -395,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 { From b87b6ebb6c564cf1313fe65a6631927e18fa3a4d Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Mon, 13 May 2024 10:56:55 +0100 Subject: [PATCH 03/40] Remove usage of `is_some_and` (not supported in rustc 1.60) --- schemars/src/_private.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemars/src/_private.rs b/schemars/src/_private.rs index 3de96145..0ba27ce9 100644 --- a/schemars/src/_private.rs +++ b/schemars/src/_private.rs @@ -81,7 +81,7 @@ pub fn apply_internal_enum_tag( deny_unknown_fields: bool, ) { let obj = schema.ensure_object(); - let is_unit = obj.get("type").is_some_and(|t| t.as_str() == Some("null")); + let is_unit = obj.get("type").and_then(|t| t.as_str()) == Some("null"); obj.insert("type".to_owned(), "object".into()); From dec3c67e878bee8c52c0744f7ba5095c985412a7 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Mon, 13 May 2024 17:58:09 +0100 Subject: [PATCH 04/40] Remove test schemas for now-removed `RootSchema` --- schemars/tests/expected/schema-2019_09.json | 758 ------------------ schemars/tests/expected/schema-openapi3.json | 636 ---------------- schemars/tests/expected/schema.json | 762 ------------------- 3 files changed, 2156 deletions(-) delete mode 100644 schemars/tests/expected/schema-2019_09.json delete mode 100644 schemars/tests/expected/schema-openapi3.json delete mode 100644 schemars/tests/expected/schema.json diff --git a/schemars/tests/expected/schema-2019_09.json b/schemars/tests/expected/schema-2019_09.json deleted file mode 100644 index cf11d352..00000000 --- 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-openapi3.json b/schemars/tests/expected/schema-openapi3.json deleted file mode 100644 index 0548828a..00000000 --- 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 6cf840bd..00000000 --- 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 From 4dde683358e5e70d11b454e1f7c321ac2f52816a Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Mon, 13 May 2024 18:17:21 +0100 Subject: [PATCH 05/40] Update OpenAPI 3.0 schema URI --- schemars/src/gen.rs | 2 +- .../tests/expected/from_value_openapi3.json | 78 +++++++++---------- .../expected/schema_settings-openapi3.json | 38 ++++----- 3 files changed, 59 insertions(+), 59 deletions(-) diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index d19f80b9..8336eccc 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -88,7 +88,7 @@ impl SchemaSettings { option_add_null_type: false, 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![ diff --git a/schemars/tests/expected/from_value_openapi3.json b/schemars/tests/expected/from_value_openapi3.json index 132a59a2..858d2862 100644 --- a/schemars/tests/expected/from_value_openapi3.json +++ b/schemars/tests/expected/from_value_openapi3.json @@ -1,32 +1,34 @@ { - "$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": { - "myInt": { - "type": "integer" + "example": { + "myBool": true, + "myInnerStruct": { + "my_empty_map": {}, + "my_empty_vec": [], + "my_map": { + "": 0.0 + }, + "my_tuple": [ + "💩", + 42 + ], + "my_vec": [ + "hello", + "world" + ] }, + "myInt": 123, + "myNullableEnum": null + }, + "properties": { "myBool": { "type": "boolean" }, - "myNullableEnum": { - "nullable": true - }, "myInnerStruct": { "type": "object", "properties": { - "my_map": { - "type": "object", - "additionalProperties": { - "type": "number" - } - }, - "my_vec": { - "type": "array", - "items": { - "type": "string" - } - }, "my_empty_map": { "type": "object", "additionalProperties": true @@ -35,6 +37,12 @@ "type": "array", "items": {} }, + "my_map": { + "type": "object", + "additionalProperties": { + "type": "number" + } + }, "my_tuple": { "type": "array", "items": [ @@ -49,28 +57,20 @@ ], "maxItems": 2, "minItems": 2 + }, + "my_vec": { + "type": "array", + "items": { + "type": "string" + } } } - } - }, - "example": { - "myBool": true, - "myInnerStruct": { - "my_empty_map": {}, - "my_empty_vec": [], - "my_map": { - "": 0.0 - }, - "my_tuple": [ - "💩", - 42 - ], - "my_vec": [ - "hello", - "world" - ] }, - "myInt": 123, - "myNullableEnum": null + "myInt": { + "type": "integer" + }, + "myNullableEnum": { + "nullable": true + } } } \ 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 17fe8051..15b752d5 100644 --- a/schemars/tests/expected/schema_settings-openapi3.json +++ b/schemars/tests/expected/schema_settings-openapi3.json @@ -1,32 +1,32 @@ { - "$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": { + "inner": { + "allOf": [ + { + "$ref": "#/components/schemas/Inner" + } + ], + "nullable": true + }, "int": { "type": "integer", "format": "int32", "example": 8 }, + "value": {}, "values": { "type": "object", "additionalProperties": true - }, - "value": {}, - "inner": { - "allOf": [ - { - "$ref": "#/components/schemas/Inner" - } - ], - "nullable": true } }, + "required": [ + "int", + "value", + "values" + ], "definitions": { "Inner": { "oneOf": [ @@ -46,13 +46,13 @@ }, { "type": "object", - "required": [ - "ValueNewType" - ], + "additionalProperties": false, "properties": { "ValueNewType": {} }, - "additionalProperties": false + "required": [ + "ValueNewType" + ] } ] } From f5d2142714d4fca25021f0df6825b0a5e40aea3b Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Mon, 13 May 2024 18:33:48 +0100 Subject: [PATCH 06/40] Regenerate test schemas --- schemars/src/schema.rs | 13 ++- schemars/tests/expected/bound.json | 10 +- schemars/tests/expected/chrono-types.json | 40 +++---- schemars/tests/expected/crate_alias.json | 16 +-- schemars/tests/expected/default.json | 28 ++--- schemars/tests/expected/deprecated-enum.json | 26 ++--- .../tests/expected/deprecated-struct.json | 20 ++-- .../tests/expected/doc_comments_enum.json | 8 +- .../tests/expected/doc_comments_override.json | 10 +- .../tests/expected/doc_comments_struct.json | 10 +- .../doc_comments_struct_ref_siblings.json | 10 +- .../expected/duration_and_systemtime.json | 44 +++---- .../expected/enum-adjacent-tagged-duf.json | 96 +++++++-------- .../tests/expected/enum-adjacent-tagged.json | 96 +++++++-------- .../tests/expected/enum-external-duf.json | 68 +++++------ schemars/tests/expected/enum-external.json | 68 +++++------ .../tests/expected/enum-internal-duf.json | 62 +++++----- schemars/tests/expected/enum-internal.json | 62 +++++----- .../expected/enum-simple-internal-duf.json | 24 ++-- .../tests/expected/enum-simple-internal.json | 24 ++-- .../tests/expected/enum-untagged-duf.json | 38 +++--- schemars/tests/expected/enum-untagged.json | 38 +++--- schemars/tests/expected/enumset.json | 2 +- schemars/tests/expected/examples.json | 48 ++++---- schemars/tests/expected/flatten.json | 26 ++--- schemars/tests/expected/from_json_value.json | 34 +++--- .../tests/expected/from_value_2019_09.json | 78 ++++++------- .../tests/expected/from_value_draft07.json | 78 ++++++------- .../tests/expected/from_value_openapi3.json | 40 +++---- schemars/tests/expected/indexmap.json | 10 +- .../expected/inline-subschemas-recursive.json | 16 +-- .../tests/expected/inline-subschemas.json | 16 +-- schemars/tests/expected/macro_built_enum.json | 16 +-- .../tests/expected/macro_built_struct.json | 18 +-- schemars/tests/expected/nonzero_ints.json | 30 ++--- schemars/tests/expected/os_strings.json | 28 ++--- .../tests/expected/property-name-struct.json | 12 +- schemars/tests/expected/range.json | 106 ++++++++--------- schemars/tests/expected/remote_derive.json | 50 ++++---- .../tests/expected/remote_derive_generic.json | 42 +++---- schemars/tests/expected/result.json | 64 +++++----- schemars/tests/expected/same_name.json | 24 ++-- .../expected/schema-name-const-generics.json | 8 +- .../tests/expected/schema-name-custom.json | 28 ++--- .../expected/schema-name-mixed-generics.json | 32 ++--- .../expected/schema_settings-2019_09.json | 44 +++---- .../expected/schema_settings-openapi3.json | 2 +- schemars/tests/expected/schema_settings.json | 44 +++---- .../schema_with-enum-adjacent-tagged.json | 48 ++++---- .../expected/schema_with-enum-external.json | 40 +++---- .../expected/schema_with-enum-internal.json | 26 ++--- .../expected/schema_with-enum-untagged.json | 8 +- .../tests/expected/schema_with-struct.json | 18 +-- schemars/tests/expected/semver.json | 8 +- .../tests/expected/skip_enum_variants.json | 8 +- .../tests/expected/skip_struct_fields.json | 24 ++-- .../struct-normal-additional-properties.json | 18 +-- schemars/tests/expected/struct-normal.json | 18 +-- schemars/tests/expected/url.json | 10 +- schemars/tests/expected/validate.json | 110 +++++++++--------- schemars/tests/expected/validate_inner.json | 20 ++-- .../expected/validate_schemars_attrs.json | 110 +++++++++--------- 62 files changed, 1092 insertions(+), 1081 deletions(-) diff --git a/schemars/src/schema.rs b/schemars/src/schema.rs index 6a881589..82be8211 100644 --- a/schemars/src/schema.rs +++ b/schemars/src/schema.rs @@ -179,8 +179,17 @@ mod ser { use serde::ser::{Serialize, SerializeMap, SerializeSeq}; use serde_json::Value; - const ORDERED_KEYWORDS_START: [&str; 6] = - ["$id", "$schema", "title", "description", "type", "format"]; + // 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. + const ORDERED_KEYWORDS_START: [&str; 7] = [ + "$id", + "$schema", + "title", + "description", + "type", + "format", + "properties", + ]; const ORDERED_KEYWORDS_END: [&str; 2] = ["$defs", "definitions"]; pub(super) struct OrderedKeywordWrapper<'a>(pub &'a Value); diff --git a/schemars/tests/expected/bound.json b/schemars/tests/expected/bound.json index 3c022dcf..e4ceb13b 100644 --- a/schemars/tests/expected/bound.json +++ b/schemars/tests/expected/bound.json @@ -2,10 +2,6 @@ "$schema": "http://json-schema.org/draft-07/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/chrono-types.json b/schemars/tests/expected/chrono-types.json index e3e788f7..b96b00c0 100644 --- a/schemars/tests/expected/chrono-types.json +++ b/schemars/tests/expected/chrono-types.json @@ -2,26 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "ChronoTypes", "type": "object", - "required": [ - "date_time", - "naive_date", - "naive_date_time", - "naive_time", - "weekday" - ], "properties": { - "weekday": { - "type": "string", - "enum": [ - "Mon", - "Tue", - "Wed", - "Thu", - "Fri", - "Sat", - "Sun" - ] - }, "date_time": { "type": "string", "format": "date-time" @@ -37,6 +18,25 @@ "naive_time": { "type": "string", "format": "partial-date-time" + }, + "weekday": { + "type": "string", + "enum": [ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun" + ] } - } + }, + "required": [ + "date_time", + "naive_date", + "naive_date_time", + "naive_time", + "weekday" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/crate_alias.json b/schemars/tests/expected/crate_alias.json index 66bf749b..d37d4826 100644 --- a/schemars/tests/expected/crate_alias.json +++ b/schemars/tests/expected/crate_alias.json @@ -2,18 +2,18 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Struct", "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { + "bar": { + "type": "boolean" + }, "foo": { "description": "This is a document", "type": "integer", "format": "int32" - }, - "bar": { - "type": "boolean" } - } + }, + "required": [ + "bar", + "foo" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/default.json b/schemars/tests/expected/default.json index aefef83d..36f16e14 100644 --- a/schemars/tests/expected/default.json +++ b/schemars/tests/expected/default.json @@ -3,22 +3,22 @@ "title": "MyStruct", "type": "object", "properties": { + "my_bool": { + "type": "boolean", + "default": false + }, "my_int": { - "default": 0, "type": "integer", - "format": "int32" - }, - "my_bool": { - "default": false, - "type": "boolean" + "format": "int32", + "default": 0 }, "my_struct2": { - "default": "i:0 b:false", "allOf": [ { "$ref": "#/definitions/MyStruct2" } - ] + ], + "default": "i:0 b:false" }, "my_struct2_default_skipped": { "$ref": "#/definitions/MyStruct2" @@ -31,14 +31,14 @@ "MyStruct2": { "type": "object", "properties": { + "my_bool": { + "type": "boolean", + "default": true + }, "my_int": { - "default": 6, "type": "integer", - "format": "int32" - }, - "my_bool": { - "default": true, - "type": "boolean" + "format": "int32", + "default": 6 } } }, diff --git a/schemars/tests/expected/deprecated-enum.json b/schemars/tests/expected/deprecated-enum.json index 825ae50d..55a98ef6 100644 --- a/schemars/tests/expected/deprecated-enum.json +++ b/schemars/tests/expected/deprecated-enum.json @@ -10,38 +10,38 @@ ] }, { - "deprecated": true, "type": "string", + "deprecated": true, "enum": [ "DeprecatedUnitVariant" ] }, { - "deprecated": true, "type": "object", - "required": [ - "DeprecatedStructVariant" - ], "properties": { "DeprecatedStructVariant": { "type": "object", - "required": [ - "deprecated_field", - "foo" - ], "properties": { "deprecated_field": { - "deprecated": true, - "type": "boolean" + "type": "boolean", + "deprecated": true }, "foo": { "type": "integer", "format": "int32" } - } + }, + "required": [ + "deprecated_field", + "foo" + ] } }, - "additionalProperties": false + "additionalProperties": false, + "deprecated": true, + "required": [ + "DeprecatedStructVariant" + ] } ] } \ No newline at end of file diff --git a/schemars/tests/expected/deprecated-struct.json b/schemars/tests/expected/deprecated-struct.json index 8f7ba711..b915eb14 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#", "title": "DeprecatedStruct", - "deprecated": true, "type": "object", - "required": [ - "deprecated_field", - "foo" - ], "properties": { + "deprecated_field": { + "type": "boolean", + "deprecated": true + }, "foo": { "type": "integer", "format": "int32" - }, - "deprecated_field": { - "deprecated": true, - "type": "boolean" } - } + }, + "deprecated": true, + "required": [ + "deprecated_field", + "foo" + ] } \ 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 53aaa86b..c983c1d3 100644 --- a/schemars/tests/expected/doc_comments_enum.json +++ b/schemars/tests/expected/doc_comments_enum.json @@ -21,9 +21,6 @@ "title": "Complex variant", "description": "This is a struct-like variant.", "type": "object", - "required": [ - "Complex" - ], "properties": { "Complex": { "type": "object", @@ -39,7 +36,10 @@ } } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "Complex" + ] } ] } \ No newline at end of file diff --git a/schemars/tests/expected/doc_comments_override.json b/schemars/tests/expected/doc_comments_override.json index 83dce791..0f904331 100644 --- a/schemars/tests/expected/doc_comments_override.json +++ b/schemars/tests/expected/doc_comments_override.json @@ -3,10 +3,6 @@ "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 9f1b4bfa..103a71a1 100644 --- a/schemars/tests/expected/doc_comments_struct.json +++ b/schemars/tests/expected/doc_comments_struct.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", @@ -26,6 +21,11 @@ ] } }, + "required": [ + "my_int", + "my_undocumented_bool", + "my_unit" + ], "definitions": { "MyUnitStruct": { "title": "A Unit", diff --git a/schemars/tests/expected/doc_comments_struct_ref_siblings.json b/schemars/tests/expected/doc_comments_struct_ref_siblings.json index 35bb648e..a3dcbe02 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", @@ -22,6 +17,11 @@ "$ref": "#/definitions/MyUnitStruct" } }, + "required": [ + "my_int", + "my_undocumented_bool", + "my_unit" + ], "definitions": { "MyUnitStruct": { "title": "A Unit", diff --git a/schemars/tests/expected/duration_and_systemtime.json b/schemars/tests/expected/duration_and_systemtime.json index d01f7d56..b2125996 100644 --- a/schemars/tests/expected/duration_and_systemtime.json +++ b/schemars/tests/expected/duration_and_systemtime.json @@ -2,10 +2,6 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "MyStruct", "type": "object", - "required": [ - "duration", - "time" - ], "properties": { "duration": { "$ref": "#/definitions/Duration" @@ -14,44 +10,48 @@ "$ref": "#/definitions/SystemTime" } }, + "required": [ + "duration", + "time" + ], "definitions": { "Duration": { "type": "object", - "required": [ - "nanos", - "secs" - ], "properties": { - "secs": { + "nanos": { "type": "integer", - "format": "uint64", + "format": "uint32", "minimum": 0.0 }, - "nanos": { + "secs": { "type": "integer", - "format": "uint32", + "format": "uint64", "minimum": 0.0 } - } + }, + "required": [ + "nanos", + "secs" + ] }, "SystemTime": { "type": "object", - "required": [ - "nanos_since_epoch", - "secs_since_epoch" - ], "properties": { - "secs_since_epoch": { + "nanos_since_epoch": { "type": "integer", - "format": "uint64", + "format": "uint32", "minimum": 0.0 }, - "nanos_since_epoch": { + "secs_since_epoch": { "type": "integer", - "format": "uint32", + "format": "uint64", "minimum": 0.0 } - } + }, + "required": [ + "nanos_since_epoch", + "secs_since_epoch" + ] } } } \ No newline at end of file diff --git a/schemars/tests/expected/enum-adjacent-tagged-duf.json b/schemars/tests/expected/enum-adjacent-tagged-duf.json index c5b54c81..a89ab50c 100644 --- a/schemars/tests/expected/enum-adjacent-tagged-duf.json +++ b/schemars/tests/expected/enum-adjacent-tagged-duf.json @@ -4,9 +4,6 @@ "oneOf": [ { "type": "object", - "required": [ - "t" - ], "properties": { "t": { "type": "string", @@ -15,14 +12,13 @@ ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "t" + ] }, { "type": "object", - "required": [ - "c", - "t" - ], "properties": { "c": { "type": "object", @@ -37,14 +33,14 @@ ] } }, - "additionalProperties": false - }, - { - "type": "object", + "additionalProperties": false, "required": [ "c", "t" - ], + ] + }, + { + "type": "object", "properties": { "c": { "$ref": "#/definitions/UnitStruct" @@ -56,14 +52,14 @@ ] } }, - "additionalProperties": false - }, - { - "type": "object", + "additionalProperties": false, "required": [ "c", "t" - ], + ] + }, + { + "type": "object", "properties": { "c": { "$ref": "#/definitions/Struct" @@ -75,21 +71,17 @@ ] } }, - "additionalProperties": false - }, - { - "type": "object", + "additionalProperties": false, "required": [ "c", "t" - ], + ] + }, + { + "type": "object", "properties": { "c": { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { "bar": { "type": "boolean" @@ -99,7 +91,11 @@ "format": "int32" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "bar", + "foo" + ] }, "t": { "type": "string", @@ -108,14 +104,14 @@ ] } }, - "additionalProperties": false - }, - { - "type": "object", + "additionalProperties": false, "required": [ "c", "t" - ], + ] + }, + { + "type": "object", "properties": { "c": { "type": "array", @@ -138,13 +134,14 @@ ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "c", + "t" + ] }, { "type": "object", - "required": [ - "t" - ], "properties": { "t": { "type": "string", @@ -153,14 +150,13 @@ ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "t" + ] }, { "type": "object", - "required": [ - "c", - "t" - ], "properties": { "c": { "type": "integer", @@ -173,16 +169,16 @@ ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "c", + "t" + ] } ], "definitions": { "Struct": { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { "bar": { "type": "boolean" @@ -191,7 +187,11 @@ "type": "integer", "format": "int32" } - } + }, + "required": [ + "bar", + "foo" + ] }, "UnitStruct": { "type": "null" diff --git a/schemars/tests/expected/enum-adjacent-tagged.json b/schemars/tests/expected/enum-adjacent-tagged.json index efe19dc8..9380c5b7 100644 --- a/schemars/tests/expected/enum-adjacent-tagged.json +++ b/schemars/tests/expected/enum-adjacent-tagged.json @@ -4,9 +4,6 @@ "oneOf": [ { "type": "object", - "required": [ - "t" - ], "properties": { "t": { "type": "string", @@ -14,14 +11,13 @@ "UnitOne" ] } - } + }, + "required": [ + "t" + ] }, { "type": "object", - "required": [ - "c", - "t" - ], "properties": { "c": { "type": "object", @@ -35,14 +31,14 @@ "StringMap" ] } - } - }, - { - "type": "object", + }, "required": [ "c", "t" - ], + ] + }, + { + "type": "object", "properties": { "c": { "$ref": "#/definitions/UnitStruct" @@ -53,14 +49,14 @@ "UnitStructNewType" ] } - } - }, - { - "type": "object", + }, "required": [ "c", "t" - ], + ] + }, + { + "type": "object", "properties": { "c": { "$ref": "#/definitions/Struct" @@ -71,21 +67,17 @@ "StructNewType" ] } - } - }, - { - "type": "object", + }, "required": [ "c", "t" - ], + ] + }, + { + "type": "object", "properties": { "c": { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { "bar": { "type": "boolean" @@ -94,7 +86,11 @@ "type": "integer", "format": "int32" } - } + }, + "required": [ + "bar", + "foo" + ] }, "t": { "type": "string", @@ -102,14 +98,14 @@ "Struct" ] } - } - }, - { - "type": "object", + }, "required": [ "c", "t" - ], + ] + }, + { + "type": "object", "properties": { "c": { "type": "array", @@ -131,13 +127,14 @@ "Tuple" ] } - } + }, + "required": [ + "c", + "t" + ] }, { "type": "object", - "required": [ - "t" - ], "properties": { "t": { "type": "string", @@ -145,14 +142,13 @@ "UnitTwo" ] } - } + }, + "required": [ + "t" + ] }, { "type": "object", - "required": [ - "c", - "t" - ], "properties": { "c": { "type": "integer", @@ -164,16 +160,16 @@ "WithInt" ] } - } + }, + "required": [ + "c", + "t" + ] } ], "definitions": { "Struct": { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { "bar": { "type": "boolean" @@ -182,7 +178,11 @@ "type": "integer", "format": "int32" } - } + }, + "required": [ + "bar", + "foo" + ] }, "UnitStruct": { "type": "null" diff --git a/schemars/tests/expected/enum-external-duf.json b/schemars/tests/expected/enum-external-duf.json index b6b7b99a..d4928080 100644 --- a/schemars/tests/expected/enum-external-duf.json +++ b/schemars/tests/expected/enum-external-duf.json @@ -11,9 +11,6 @@ }, { "type": "object", - "required": [ - "stringMap" - ], "properties": { "stringMap": { "type": "object", @@ -22,44 +19,40 @@ } } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "stringMap" + ] }, { "type": "object", - "required": [ - "unitStructNewType" - ], "properties": { "unitStructNewType": { "$ref": "#/definitions/UnitStruct" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "unitStructNewType" + ] }, { "type": "object", - "required": [ - "structNewType" - ], "properties": { "structNewType": { "$ref": "#/definitions/Struct" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "structNewType" + ] }, { "type": "object", - "required": [ - "struct" - ], "properties": { "struct": { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { "bar": { "type": "boolean" @@ -69,16 +62,20 @@ "format": "int32" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "bar", + "foo" + ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "struct" + ] }, { "type": "object", - "required": [ - "tuple" - ], "properties": { "tuple": { "type": "array", @@ -95,29 +92,28 @@ "minItems": 2 } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "tuple" + ] }, { "type": "object", - "required": [ - "withInt" - ], "properties": { "withInt": { "type": "integer", "format": "int32" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "withInt" + ] } ], "definitions": { "Struct": { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { "bar": { "type": "boolean" @@ -126,7 +122,11 @@ "type": "integer", "format": "int32" } - } + }, + "required": [ + "bar", + "foo" + ] }, "UnitStruct": { "type": "null" diff --git a/schemars/tests/expected/enum-external.json b/schemars/tests/expected/enum-external.json index cc721dfd..b09f1072 100644 --- a/schemars/tests/expected/enum-external.json +++ b/schemars/tests/expected/enum-external.json @@ -11,9 +11,6 @@ }, { "type": "object", - "required": [ - "stringMap" - ], "properties": { "stringMap": { "type": "object", @@ -22,44 +19,40 @@ } } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "stringMap" + ] }, { "type": "object", - "required": [ - "unitStructNewType" - ], "properties": { "unitStructNewType": { "$ref": "#/definitions/UnitStruct" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "unitStructNewType" + ] }, { "type": "object", - "required": [ - "structNewType" - ], "properties": { "structNewType": { "$ref": "#/definitions/Struct" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "structNewType" + ] }, { "type": "object", - "required": [ - "struct" - ], "properties": { "struct": { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { "bar": { "type": "boolean" @@ -68,16 +61,20 @@ "type": "integer", "format": "int32" } - } + }, + "required": [ + "bar", + "foo" + ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "struct" + ] }, { "type": "object", - "required": [ - "tuple" - ], "properties": { "tuple": { "type": "array", @@ -94,29 +91,28 @@ "minItems": 2 } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "tuple" + ] }, { "type": "object", - "required": [ - "withInt" - ], "properties": { "withInt": { "type": "integer", "format": "int32" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "withInt" + ] } ], "definitions": { "Struct": { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { "bar": { "type": "boolean" @@ -125,7 +121,11 @@ "type": "integer", "format": "int32" } - } + }, + "required": [ + "bar", + "foo" + ] }, "UnitStruct": { "type": "null" diff --git a/schemars/tests/expected/enum-internal-duf.json b/schemars/tests/expected/enum-internal-duf.json index 7a9bcd75..6db4d31b 100644 --- a/schemars/tests/expected/enum-internal-duf.json +++ b/schemars/tests/expected/enum-internal-duf.json @@ -4,9 +4,6 @@ "oneOf": [ { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", @@ -15,13 +12,13 @@ ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "typeProperty" + ] }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", @@ -32,13 +29,13 @@ }, "additionalProperties": { "type": "string" - } + }, + "required": [ + "typeProperty" + ] }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", @@ -47,15 +44,13 @@ ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "typeProperty" + ] }, { "type": "object", - "required": [ - "bar", - "foo", - "typeProperty" - ], "properties": { "bar": { "type": "boolean" @@ -70,15 +65,15 @@ "StructNewType" ] } - } - }, - { - "type": "object", + }, "required": [ "bar", "foo", "typeProperty" - ], + ] + }, + { + "type": "object", "properties": { "bar": { "type": "boolean" @@ -94,13 +89,15 @@ ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "bar", + "foo", + "typeProperty" + ] }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", @@ -109,14 +106,14 @@ ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "typeProperty" + ] }, { "type": "object", "format": "int32", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", @@ -124,7 +121,10 @@ "WithInt" ] } - } + }, + "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 115dbf31..2382f2d2 100644 --- a/schemars/tests/expected/enum-internal.json +++ b/schemars/tests/expected/enum-internal.json @@ -4,9 +4,6 @@ "oneOf": [ { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", @@ -14,13 +11,13 @@ "UnitOne" ] } - } + }, + "required": [ + "typeProperty" + ] }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", @@ -31,13 +28,13 @@ }, "additionalProperties": { "type": "string" - } + }, + "required": [ + "typeProperty" + ] }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", @@ -45,15 +42,13 @@ "UnitStructNewType" ] } - } + }, + "required": [ + "typeProperty" + ] }, { "type": "object", - "required": [ - "bar", - "foo", - "typeProperty" - ], "properties": { "bar": { "type": "boolean" @@ -68,15 +63,15 @@ "StructNewType" ] } - } - }, - { - "type": "object", + }, "required": [ "bar", "foo", "typeProperty" - ], + ] + }, + { + "type": "object", "properties": { "bar": { "type": "boolean" @@ -91,13 +86,15 @@ "Struct" ] } - } + }, + "required": [ + "bar", + "foo", + "typeProperty" + ] }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", @@ -105,14 +102,14 @@ "UnitTwo" ] } - } + }, + "required": [ + "typeProperty" + ] }, { "type": "object", "format": "int32", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", @@ -120,7 +117,10 @@ "WithInt" ] } - } + }, + "required": [ + "typeProperty" + ] } ] } \ No newline at end of file diff --git a/schemars/tests/expected/enum-simple-internal-duf.json b/schemars/tests/expected/enum-simple-internal-duf.json index 833f7b73..9e6e5d4a 100644 --- a/schemars/tests/expected/enum-simple-internal-duf.json +++ b/schemars/tests/expected/enum-simple-internal-duf.json @@ -4,9 +4,6 @@ "oneOf": [ { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", @@ -15,13 +12,13 @@ ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "typeProperty" + ] }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", @@ -30,13 +27,13 @@ ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "typeProperty" + ] }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", @@ -45,7 +42,10 @@ ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "typeProperty" + ] } ] } \ No newline at end of file diff --git a/schemars/tests/expected/enum-simple-internal.json b/schemars/tests/expected/enum-simple-internal.json index 50cd62c1..a549c8cb 100644 --- a/schemars/tests/expected/enum-simple-internal.json +++ b/schemars/tests/expected/enum-simple-internal.json @@ -4,9 +4,6 @@ "oneOf": [ { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", @@ -14,13 +11,13 @@ "A" ] } - } + }, + "required": [ + "typeProperty" + ] }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", @@ -28,13 +25,13 @@ "B" ] } - } + }, + "required": [ + "typeProperty" + ] }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", @@ -42,7 +39,10 @@ "C" ] } - } + }, + "required": [ + "typeProperty" + ] } ] } \ 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 24397bf2..7ce7d3d4 100644 --- a/schemars/tests/expected/enum-untagged-duf.json +++ b/schemars/tests/expected/enum-untagged-duf.json @@ -19,20 +19,20 @@ }, { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { + "bar": { + "type": "boolean" + }, "foo": { "type": "integer", "format": "int32" - }, - "bar": { - "type": "boolean" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "bar", + "foo" + ] }, { "type": "array", @@ -54,24 +54,24 @@ } ], "definitions": { - "UnitStruct": { - "type": "null" - }, "Struct": { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { + "bar": { + "type": "boolean" + }, "foo": { "type": "integer", "format": "int32" - }, - "bar": { - "type": "boolean" } - } + }, + "required": [ + "bar", + "foo" + ] + }, + "UnitStruct": { + "type": "null" } } } \ No newline at end of file diff --git a/schemars/tests/expected/enum-untagged.json b/schemars/tests/expected/enum-untagged.json index 7eccbe28..add81e49 100644 --- a/schemars/tests/expected/enum-untagged.json +++ b/schemars/tests/expected/enum-untagged.json @@ -19,19 +19,19 @@ }, { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { + "bar": { + "type": "boolean" + }, "foo": { "type": "integer", "format": "int32" - }, - "bar": { - "type": "boolean" } - } + }, + "required": [ + "bar", + "foo" + ] }, { "type": "array", @@ -53,24 +53,24 @@ } ], "definitions": { - "UnitStruct": { - "type": "null" - }, "Struct": { "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { + "bar": { + "type": "boolean" + }, "foo": { "type": "integer", "format": "int32" - }, - "bar": { - "type": "boolean" } - } + }, + "required": [ + "bar", + "foo" + ] + }, + "UnitStruct": { + "type": "null" } } } \ No newline at end of file diff --git a/schemars/tests/expected/enumset.json b/schemars/tests/expected/enumset.json index 4950c939..b4dc26a6 100644 --- a/schemars/tests/expected/enumset.json +++ b/schemars/tests/expected/enumset.json @@ -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 c02a139a..9c5c5ab1 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#", "title": "Struct", - "examples": [ - { - "bar": false, - "baz": null, - "foo": 0 - }, - null - ], "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { - "foo": { - "examples": [ - 8, - null - ], - "type": "integer", - "format": "int32" - }, "bar": { "type": "boolean" }, "baz": { - "examples": [ - null - ], "type": [ "string", "null" + ], + "examples": [ + null + ] + }, + "foo": { + "type": "integer", + "format": "int32", + "examples": [ + 8, + null ] } - } + }, + "examples": [ + { + "bar": false, + "baz": null, + "foo": 0 + }, + null + ], + "required": [ + "bar", + "foo" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/flatten.json b/schemars/tests/expected/flatten.json index fedf571a..ec0aff3f 100644 --- a/schemars/tests/expected/flatten.json +++ b/schemars/tests/expected/flatten.json @@ -2,27 +2,21 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Flat", "type": "object", - "required": [ - "b", - "f", - "s", - "v" - ], "properties": { + "b": { + "type": "boolean" + }, "f": { "type": "number", "format": "float" }, - "b": { - "type": "boolean" + "os": { + "type": "string", + "default": "" }, "s": { "type": "string" }, - "os": { - "default": "", - "type": "string" - }, "v": { "type": "array", "items": { @@ -30,5 +24,11 @@ "format": "int32" } } - } + }, + "required": [ + "b", + "f", + "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 0d94b66a..cd1c6349 100644 --- a/schemars/tests/expected/from_json_value.json +++ b/schemars/tests/expected/from_json_value.json @@ -1,21 +1,5 @@ { "$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 - } - ], "type": "object", "properties": { "bool": { @@ -45,5 +29,21 @@ "zeroPointZero": { "type": "number" } - } + }, + "examples": [ + { + "bool": true, + "minusOne": -1, + "null": null, + "object": { + "array": [ + "foo", + "bar" + ] + }, + "one": 1, + "zero": 0, + "zeroPointZero": 0.0 + } + ] } \ 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 9cea709c..4c6adcfe 100644 --- a/schemars/tests/expected/from_value_2019_09.json +++ b/schemars/tests/expected/from_value_2019_09.json @@ -1,52 +1,14 @@ { "$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": { - "type": "integer" - }, "myBool": { "type": "boolean" }, - "myNullableEnum": true, "myInnerStruct": { "type": "object", "properties": { - "my_map": { - "type": "object", - "additionalProperties": { - "type": "number" - } - }, - "my_vec": { - "type": "array", - "items": { - "type": "string" - } - }, "my_empty_map": { "type": "object", "additionalProperties": true @@ -55,6 +17,12 @@ "type": "array", "items": true }, + "my_map": { + "type": "object", + "additionalProperties": { + "type": "number" + } + }, "my_tuple": { "type": "array", "items": [ @@ -69,8 +37,40 @@ ], "maxItems": 2, "minItems": 2 + }, + "my_vec": { + "type": "array", + "items": { + "type": "string" + } } } + }, + "myInt": { + "type": "integer" + }, + "myNullableEnum": true + }, + "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 } - } + ] } \ 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 dec39962..5d87f028 100644 --- a/schemars/tests/expected/from_value_draft07.json +++ b/schemars/tests/expected/from_value_draft07.json @@ -1,52 +1,14 @@ { "$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": { - "type": "integer" - }, "myBool": { "type": "boolean" }, - "myNullableEnum": true, "myInnerStruct": { "type": "object", "properties": { - "my_map": { - "type": "object", - "additionalProperties": { - "type": "number" - } - }, - "my_vec": { - "type": "array", - "items": { - "type": "string" - } - }, "my_empty_map": { "type": "object", "additionalProperties": true @@ -55,6 +17,12 @@ "type": "array", "items": true }, + "my_map": { + "type": "object", + "additionalProperties": { + "type": "number" + } + }, "my_tuple": { "type": "array", "items": [ @@ -69,8 +37,40 @@ ], "maxItems": 2, "minItems": 2 + }, + "my_vec": { + "type": "array", + "items": { + "type": "string" + } } } + }, + "myInt": { + "type": "integer" + }, + "myNullableEnum": true + }, + "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 } - } + ] } \ 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 858d2862..04059354 100644 --- a/schemars/tests/expected/from_value_openapi3.json +++ b/schemars/tests/expected/from_value_openapi3.json @@ -2,26 +2,6 @@ "$schema": "https://spec.openapis.org/oas/3.0/schema/2021-09-28#/definitions/Schema", "title": "MyStruct", "type": "object", - "example": { - "myBool": true, - "myInnerStruct": { - "my_empty_map": {}, - "my_empty_vec": [], - "my_map": { - "": 0.0 - }, - "my_tuple": [ - "💩", - 42 - ], - "my_vec": [ - "hello", - "world" - ] - }, - "myInt": 123, - "myNullableEnum": null - }, "properties": { "myBool": { "type": "boolean" @@ -72,5 +52,25 @@ "myNullableEnum": { "nullable": true } + }, + "example": { + "myBool": true, + "myInnerStruct": { + "my_empty_map": {}, + "my_empty_vec": [], + "my_map": { + "": 0.0 + }, + "my_tuple": [ + "💩", + 42 + ], + "my_vec": [ + "hello", + "world" + ] + }, + "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 98065d9d..9c209e62 100644 --- a/schemars/tests/expected/indexmap.json +++ b/schemars/tests/expected/indexmap.json @@ -2,10 +2,6 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "IndexMapTypes", "type": "object", - "required": [ - "map", - "set" - ], "properties": { "map": { "type": "object", @@ -21,5 +17,9 @@ }, "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 e051e5df..0c1eebee 100644 --- a/schemars/tests/expected/inline-subschemas-recursive.json +++ b/schemars/tests/expected/inline-subschemas-recursive.json @@ -18,14 +18,14 @@ "object", "null" ], - "required": [ - "recursive" - ], "properties": { "recursive": { "$ref": "#/definitions/RecursiveOuter" } - } + }, + "required": [ + "recursive" + ] } }, "definitions": { @@ -47,14 +47,14 @@ "object", "null" ], - "required": [ - "recursive" - ], "properties": { "recursive": { "$ref": "#/definitions/RecursiveOuter" } - } + }, + "required": [ + "recursive" + ] } } } diff --git a/schemars/tests/expected/inline-subschemas.json b/schemars/tests/expected/inline-subschemas.json index b7315534..fe4e83e2 100644 --- a/schemars/tests/expected/inline-subschemas.json +++ b/schemars/tests/expected/inline-subschemas.json @@ -2,22 +2,22 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "MyJob", "type": "object", - "required": [ - "spec" - ], "properties": { "spec": { "type": "object", - "required": [ - "replicas" - ], "properties": { "replicas": { "type": "integer", "format": "uint32", "minimum": 0.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 d030787d..51a9bb73 100644 --- a/schemars/tests/expected/macro_built_enum.json +++ b/schemars/tests/expected/macro_built_enum.json @@ -4,29 +4,29 @@ "oneOf": [ { "type": "object", - "required": [ - "InnerStruct" - ], "properties": { "InnerStruct": { "$ref": "#/definitions/InnerStruct" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "InnerStruct" + ] } ], "definitions": { "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 811eae31..0c5840c4 100644 --- a/schemars/tests/expected/macro_built_struct.json +++ b/schemars/tests/expected/macro_built_struct.json @@ -2,19 +2,19 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "A", "type": "object", - "required": [ - "v", - "x" - ], "properties": { + "v": { + "type": "integer", + "format": "int32" + }, "x": { "type": "integer", "format": "uint8", "minimum": 0.0 - }, - "v": { - "type": "integer", - "format": "int32" } - } + }, + "required": [ + "v", + "x" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/nonzero_ints.json b/schemars/tests/expected/nonzero_ints.json index cbfcfd97..3b839291 100644 --- a/schemars/tests/expected/nonzero_ints.json +++ b/schemars/tests/expected/nonzero_ints.json @@ -2,17 +2,13 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "MyStruct", "type": "object", - "required": [ - "nonzero_signed", - "nonzero_unsigned", - "signed", - "unsigned" - ], "properties": { - "unsigned": { + "nonzero_signed": { "type": "integer", - "format": "uint32", - "minimum": 0.0 + "format": "int32", + "not": { + "const": 0 + } }, "nonzero_unsigned": { "type": "integer", @@ -23,12 +19,16 @@ "type": "integer", "format": "int32" }, - "nonzero_signed": { + "unsigned": { "type": "integer", - "format": "int32", - "not": { - "const": 0 - } + "format": "uint32", + "minimum": 0.0 } - } + }, + "required": [ + "nonzero_signed", + "nonzero_unsigned", + "signed", + "unsigned" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/os_strings.json b/schemars/tests/expected/os_strings.json index d3336757..50448edb 100644 --- a/schemars/tests/expected/os_strings.json +++ b/schemars/tests/expected/os_strings.json @@ -2,26 +2,23 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "OsStrings", "type": "object", - "required": [ - "borrowed", - "owned" - ], "properties": { - "owned": { + "borrowed": { "$ref": "#/definitions/OsString" }, - "borrowed": { + "owned": { "$ref": "#/definitions/OsString" } }, + "required": [ + "borrowed", + "owned" + ], "definitions": { "OsString": { "oneOf": [ { "type": "object", - "required": [ - "Unix" - ], "properties": { "Unix": { "type": "array", @@ -31,13 +28,13 @@ "minimum": 0.0 } } - } + }, + "required": [ + "Unix" + ] }, { "type": "object", - "required": [ - "Windows" - ], "properties": { "Windows": { "type": "array", @@ -47,7 +44,10 @@ "minimum": 0.0 } } - } + }, + "required": [ + "Windows" + ] } ] } diff --git a/schemars/tests/expected/property-name-struct.json b/schemars/tests/expected/property-name-struct.json index aa708fde..1aa6e781 100644 --- a/schemars/tests/expected/property-name-struct.json +++ b/schemars/tests/expected/property-name-struct.json @@ -2,11 +2,6 @@ "$schema": "http://json-schema.org/draft-07/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 0bb3aeb1..bb68e3a1 100644 --- a/schemars/tests/expected/range.json +++ b/schemars/tests/expected/range.json @@ -2,88 +2,88 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "MyStruct", "type": "object", - "required": [ - "bound", - "inclusive", - "range" - ], "properties": { - "range": { - "$ref": "#/definitions/Range_of_uint" + "bound": { + "$ref": "#/definitions/Bound_of_string" }, "inclusive": { "$ref": "#/definitions/Range_of_double" }, - "bound": { - "$ref": "#/definitions/Bound_of_string" + "range": { + "$ref": "#/definitions/Range_of_uint" } }, + "required": [ + "bound", + "inclusive", + "range" + ], "definitions": { - "Range_of_uint": { - "type": "object", - "required": [ - "end", - "start" - ], - "properties": { - "start": { - "type": "integer", - "format": "uint", - "minimum": 0.0 - }, - "end": { - "type": "integer", - "format": "uint", - "minimum": 0.0 - } - } - }, - "Range_of_double": { - "type": "object", - "required": [ - "end", - "start" - ], - "properties": { - "start": { - "type": "number", - "format": "double" - }, - "end": { - "type": "number", - "format": "double" - } - } - }, "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", "const": "Unbounded" } ] + }, + "Range_of_double": { + "type": "object", + "properties": { + "end": { + "type": "number", + "format": "double" + }, + "start": { + "type": "number", + "format": "double" + } + }, + "required": [ + "end", + "start" + ] + }, + "Range_of_uint": { + "type": "object", + "properties": { + "end": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + } + }, + "required": [ + "end", + "start" + ] } } } \ No newline at end of file diff --git a/schemars/tests/expected/remote_derive.json b/schemars/tests/expected/remote_derive.json index 6f09eb22..76df9b69 100644 --- a/schemars/tests/expected/remote_derive.json +++ b/schemars/tests/expected/remote_derive.json @@ -2,54 +2,54 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Process", "type": "object", - "required": [ - "command_line", - "wall_time" - ], "properties": { "command_line": { "type": "string" }, - "wall_time": { - "$ref": "#/definitions/Duration" - }, - "user_cpu_time": { - "default": { - "nanos": 0, - "secs": 0 - }, + "system_cpu_time": { "allOf": [ { "$ref": "#/definitions/Duration" } - ] + ], + "default": "0.000000000s" }, - "system_cpu_time": { - "default": "0.000000000s", + "user_cpu_time": { "allOf": [ { "$ref": "#/definitions/Duration" } - ] + ], + "default": { + "nanos": 0, + "secs": 0 + } + }, + "wall_time": { + "$ref": "#/definitions/Duration" } }, + "required": [ + "command_line", + "wall_time" + ], "definitions": { "Duration": { "type": "object", - "required": [ - "nanos", - "secs" - ], "properties": { - "secs": { - "type": "integer", - "format": "int64" - }, "nanos": { "type": "integer", "format": "int32" + }, + "secs": { + "type": "integer", + "format": "int64" } - } + }, + "required": [ + "nanos", + "secs" + ] } } } \ 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 5a65f3bc..d06320f3 100644 --- a/schemars/tests/expected/remote_derive_generic.json +++ b/schemars/tests/expected/remote_derive_generic.json @@ -2,22 +2,10 @@ "$schema": "http://json-schema.org/draft-07/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" }, - "unit_or_t2": { - "$ref": "#/definitions/Or_for_null_and_int32" - }, - "s": { - "$ref": "#/definitions/Str" - }, "fake_map": { "type": "object", "additionalProperties": { @@ -27,29 +15,41 @@ }, "uniqueItems": true } + }, + "s": { + "$ref": "#/definitions/Str" + }, + "unit_or_t2": { + "$ref": "#/definitions/Or_for_null_and_int32" } }, + "required": [ + "byte_or_bool2", + "fake_map", + "s", + "unit_or_t2" + ], "definitions": { - "Or_for_uint8_and_boolean": { + "Or_for_null_and_int32": { "anyOf": [ { - "type": "integer", - "format": "uint8", - "minimum": 0.0 + "type": "null" }, { - "type": "boolean" + "type": "integer", + "format": "int32" } ] }, - "Or_for_null_and_int32": { + "Or_for_uint8_and_boolean": { "anyOf": [ { - "type": "null" + "type": "integer", + "format": "uint8", + "minimum": 0.0 }, { - "type": "integer", - "format": "int32" + "type": "boolean" } ] }, diff --git a/schemars/tests/expected/result.json b/schemars/tests/expected/result.json index 2a015736..44c079e4 100644 --- a/schemars/tests/expected/result.json +++ b/schemars/tests/expected/result.json @@ -2,10 +2,6 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Container", "type": "object", - "required": [ - "result1", - "result2" - ], "properties": { "result1": { "$ref": "#/definitions/Result_of_MyStruct_or_Array_of_string" @@ -14,25 +10,38 @@ "$ref": "#/definitions/Result_of_boolean_or_null" } }, + "required": [ + "result1", + "result2" + ], "definitions": { + "MyStruct": { + "type": "object", + "properties": { + "foo": { + "type": "integer", + "format": "int32" + } + }, + "required": [ + "foo" + ] + }, "Result_of_MyStruct_or_Array_of_string": { "oneOf": [ { "type": "object", - "required": [ - "Ok" - ], "properties": { "Ok": { "$ref": "#/definitions/MyStruct" } - } + }, + "required": [ + "Ok" + ] }, { "type": "object", - "required": [ - "Err" - ], "properties": { "Err": { "type": "array", @@ -40,45 +49,36 @@ "type": "string" } } - } + }, + "required": [ + "Err" + ] } ] }, - "MyStruct": { - "type": "object", - "required": [ - "foo" - ], - "properties": { - "foo": { - "type": "integer", - "format": "int32" - } - } - }, "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/same_name.json b/schemars/tests/expected/same_name.json index 9e4e6b34..ebea3fed 100644 --- a/schemars/tests/expected/same_name.json +++ b/schemars/tests/expected/same_name.json @@ -2,10 +2,6 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Config2", "type": "object", - "required": [ - "a_cfg", - "b_cfg" - ], "properties": { "a_cfg": { "$ref": "#/definitions/Config" @@ -14,28 +10,32 @@ "$ref": "#/definitions/Config2" } }, + "required": [ + "a_cfg", + "b_cfg" + ], "definitions": { "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-name-const-generics.json b/schemars/tests/expected/schema-name-const-generics.json index 7505b285..6bb3ab1b 100644 --- a/schemars/tests/expected/schema-name-const-generics.json +++ b/schemars/tests/expected/schema-name-const-generics.json @@ -2,13 +2,13 @@ "$schema": "http://json-schema.org/draft-07/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 844699a0..4e6cc36f 100644 --- a/schemars/tests/expected/schema-name-custom.json +++ b/schemars/tests/expected/schema-name-custom.json @@ -2,14 +2,10 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "a-new-name-Array_of_string-int32-int32", "type": "object", - "required": [ - "inner", - "t", - "u", - "v", - "w" - ], "properties": { + "inner": { + "$ref": "#/definitions/another-new-name" + }, "t": { "type": "integer", "format": "int32" @@ -25,23 +21,27 @@ "items": { "type": "string" } - }, - "inner": { - "$ref": "#/definitions/another-new-name" } }, + "required": [ + "inner", + "t", + "u", + "v", + "w" + ], "definitions": { "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-mixed-generics.json b/schemars/tests/expected/schema-name-mixed-generics.json index bddd083c..0da80440 100644 --- a/schemars/tests/expected/schema-name-mixed-generics.json +++ b/schemars/tests/expected/schema-name-mixed-generics.json @@ -2,10 +2,6 @@ "$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", "type": "object", - "required": [ - "foo", - "generic" - ], "properties": { "foo": { "type": "integer", @@ -15,28 +11,25 @@ "$ref": "#/definitions/MyStruct_for_int32_and_null_and_boolean_and_Array_of_string" } }, + "required": [ + "foo", + "generic" + ], "definitions": { "MySimpleStruct": { "type": "object", - "required": [ - "foo" - ], "properties": { "foo": { "type": "integer", "format": "int32" } - } + }, + "required": [ + "foo" + ] }, "MyStruct_for_int32_and_null_and_boolean_and_Array_of_string": { "type": "object", - "required": [ - "inner", - "t", - "u", - "v", - "w" - ], "properties": { "inner": { "$ref": "#/definitions/MySimpleStruct" @@ -57,7 +50,14 @@ "type": "string" } } - } + }, + "required": [ + "inner", + "t", + "u", + "v", + "w" + ] } } } \ 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 fb936a72..4e18426e 100644 --- a/schemars/tests/expected/schema_settings-2019_09.json +++ b/schemars/tests/expected/schema_settings-2019_09.json @@ -2,25 +2,7 @@ "$schema": "https://json-schema.org/draft/2019-09/schema", "title": "Outer", "type": "object", - "required": [ - "int", - "value", - "values" - ], "properties": { - "int": { - "examples": [ - 8, - null - ], - "type": "integer", - "format": "int32" - }, - "values": { - "type": "object", - "additionalProperties": true - }, - "value": true, "inner": { "anyOf": [ { @@ -30,8 +12,26 @@ "type": "null" } ] + }, + "int": { + "type": "integer", + "format": "int32", + "examples": [ + 8, + null + ] + }, + "value": true, + "values": { + "type": "object", + "additionalProperties": true } }, + "required": [ + "int", + "value", + "values" + ], "definitions": { "Inner": { "oneOf": [ @@ -51,13 +51,13 @@ }, { "type": "object", - "required": [ - "ValueNewType" - ], "properties": { "ValueNewType": true }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "ValueNewType" + ] } ] } diff --git a/schemars/tests/expected/schema_settings-openapi3.json b/schemars/tests/expected/schema_settings-openapi3.json index 15b752d5..5318b011 100644 --- a/schemars/tests/expected/schema_settings-openapi3.json +++ b/schemars/tests/expected/schema_settings-openapi3.json @@ -46,10 +46,10 @@ }, { "type": "object", - "additionalProperties": false, "properties": { "ValueNewType": {} }, + "additionalProperties": false, "required": [ "ValueNewType" ] diff --git a/schemars/tests/expected/schema_settings.json b/schemars/tests/expected/schema_settings.json index c876d6f0..8e70c7c6 100644 --- a/schemars/tests/expected/schema_settings.json +++ b/schemars/tests/expected/schema_settings.json @@ -2,25 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Outer", "type": "object", - "required": [ - "int", - "value", - "values" - ], "properties": { - "int": { - "examples": [ - 8, - null - ], - "type": "integer", - "format": "int32" - }, - "values": { - "type": "object", - "additionalProperties": true - }, - "value": true, "inner": { "anyOf": [ { @@ -30,8 +12,26 @@ "type": "null" } ] + }, + "int": { + "type": "integer", + "format": "int32", + "examples": [ + 8, + null + ] + }, + "value": true, + "values": { + "type": "object", + "additionalProperties": true } }, + "required": [ + "int", + "value", + "values" + ], "definitions": { "Inner": { "oneOf": [ @@ -51,13 +51,13 @@ }, { "type": "object", - "required": [ - "ValueNewType" - ], "properties": { "ValueNewType": true }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "ValueNewType" + ] } ] } diff --git a/schemars/tests/expected/schema_with-enum-adjacent-tagged.json b/schemars/tests/expected/schema_with-enum-adjacent-tagged.json index 3ecba4c8..c29c59e1 100644 --- a/schemars/tests/expected/schema_with-enum-adjacent-tagged.json +++ b/schemars/tests/expected/schema_with-enum-adjacent-tagged.json @@ -4,21 +4,17 @@ "oneOf": [ { "type": "object", - "required": [ - "c", - "t" - ], "properties": { "c": { "type": "object", - "required": [ - "foo" - ], "properties": { "foo": { "type": "boolean" } - } + }, + "required": [ + "foo" + ] }, "t": { "type": "string", @@ -26,14 +22,14 @@ "Struct" ] } - } - }, - { - "type": "object", + }, "required": [ "c", "t" - ], + ] + }, + { + "type": "object", "properties": { "c": { "type": "boolean" @@ -44,14 +40,14 @@ "NewType" ] } - } - }, - { - "type": "object", + }, "required": [ "c", "t" - ], + ] + }, + { + "type": "object", "properties": { "c": { "type": "array", @@ -73,14 +69,14 @@ "Tuple" ] } - } - }, - { - "type": "object", + }, "required": [ "c", "t" - ], + ] + }, + { + "type": "object", "properties": { "c": { "type": "boolean" @@ -91,7 +87,11 @@ "Unit" ] } - } + }, + "required": [ + "c", + "t" + ] } ] } \ 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 dea02199..78b6475e 100644 --- a/schemars/tests/expected/schema_with-enum-external.json +++ b/schemars/tests/expected/schema_with-enum-external.json @@ -4,41 +4,38 @@ "oneOf": [ { "type": "object", - "required": [ - "struct" - ], "properties": { "struct": { "type": "object", - "required": [ - "foo" - ], "properties": { "foo": { "type": "boolean" } - } + }, + "required": [ + "foo" + ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "struct" + ] }, { "type": "object", - "required": [ - "newType" - ], "properties": { "newType": { "type": "boolean" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "newType" + ] }, { "type": "object", - "required": [ - "tuple" - ], "properties": { "tuple": { "type": "array", @@ -55,19 +52,22 @@ "minItems": 2 } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "tuple" + ] }, { "type": "object", - "required": [ - "unit" - ], "properties": { "unit": { "type": "boolean" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "unit" + ] } ] } \ No newline at end of file diff --git a/schemars/tests/expected/schema_with-enum-internal.json b/schemars/tests/expected/schema_with-enum-internal.json index c4a0cc10..8b6fbebe 100644 --- a/schemars/tests/expected/schema_with-enum-internal.json +++ b/schemars/tests/expected/schema_with-enum-internal.json @@ -4,10 +4,6 @@ "oneOf": [ { "type": "object", - "required": [ - "foo", - "typeProperty" - ], "properties": { "foo": { "type": "boolean" @@ -18,13 +14,14 @@ "Struct" ] } - } + }, + "required": [ + "foo", + "typeProperty" + ] }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", @@ -32,13 +29,13 @@ "NewType" ] } - } + }, + "required": [ + "typeProperty" + ] }, { "type": "object", - "required": [ - "typeProperty" - ], "properties": { "typeProperty": { "type": "string", @@ -46,7 +43,10 @@ "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 55147ded..b912febe 100644 --- a/schemars/tests/expected/schema_with-enum-untagged.json +++ b/schemars/tests/expected/schema_with-enum-untagged.json @@ -4,14 +4,14 @@ "anyOf": [ { "type": "object", - "required": [ - "foo" - ], "properties": { "foo": { "type": "boolean" } - } + }, + "required": [ + "foo" + ] }, { "type": "boolean" diff --git a/schemars/tests/expected/schema_with-struct.json b/schemars/tests/expected/schema_with-struct.json index feb5b511..40b5c56e 100644 --- a/schemars/tests/expected/schema_with-struct.json +++ b/schemars/tests/expected/schema_with-struct.json @@ -2,21 +2,21 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Struct", "type": "object", - "required": [ - "bar", - "baz", - "foo" - ], "properties": { - "foo": { - "type": "boolean" - }, "bar": { "type": "integer", "format": "int32" }, "baz": { "type": "boolean" + }, + "foo": { + "type": "boolean" } - } + }, + "required": [ + "bar", + "baz", + "foo" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/semver.json b/schemars/tests/expected/semver.json index d87ad047..e8c59b9b 100644 --- a/schemars/tests/expected/semver.json +++ b/schemars/tests/expected/semver.json @@ -2,11 +2,13 @@ "$schema": "http://json-schema.org/draft-07/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 ba1bf23c..b955e779 100644 --- a/schemars/tests/expected/skip_enum_variants.json +++ b/schemars/tests/expected/skip_enum_variants.json @@ -10,16 +10,16 @@ }, { "type": "object", - "required": [ - "Included1" - ], "properties": { "Included1": { "type": "number", "format": "float" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "Included1" + ] } ] } \ No newline at end of file diff --git a/schemars/tests/expected/skip_struct_fields.json b/schemars/tests/expected/skip_struct_fields.json index d4885618..21e01a60 100644 --- a/schemars/tests/expected/skip_struct_fields.json +++ b/schemars/tests/expected/skip_struct_fields.json @@ -2,23 +2,23 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "MyStruct", "type": "object", - "required": [ - "included", - "writable" - ], "properties": { + "included": { + "type": "null" + }, "readable": { + "type": "string", "default": "", - "readOnly": true, - "type": "string" + "readOnly": true }, "writable": { - "writeOnly": true, "type": "number", - "format": "float" - }, - "included": { - "type": "null" + "format": "float", + "writeOnly": true } - } + }, + "required": [ + "included", + "writable" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/struct-normal-additional-properties.json b/schemars/tests/expected/struct-normal-additional-properties.json index 1a8d7494..ed8602a9 100644 --- a/schemars/tests/expected/struct-normal-additional-properties.json +++ b/schemars/tests/expected/struct-normal-additional-properties.json @@ -2,15 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Struct", "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { - "foo": { - "type": "integer", - "format": "int32" - }, "bar": { "type": "boolean" }, @@ -19,7 +11,15 @@ "string", "null" ] + }, + "foo": { + "type": "integer", + "format": "int32" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "bar", + "foo" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/struct-normal.json b/schemars/tests/expected/struct-normal.json index cee25b17..e6fe68b9 100644 --- a/schemars/tests/expected/struct-normal.json +++ b/schemars/tests/expected/struct-normal.json @@ -2,15 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Struct", "type": "object", - "required": [ - "bar", - "foo" - ], "properties": { - "foo": { - "type": "integer", - "format": "int32" - }, "bar": { "type": "boolean" }, @@ -19,6 +11,14 @@ "string", "null" ] + }, + "foo": { + "type": "integer", + "format": "int32" } - } + }, + "required": [ + "bar", + "foo" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/url.json b/schemars/tests/expected/url.json index ddb8c5a1..5722f9e8 100644 --- a/schemars/tests/expected/url.json +++ b/schemars/tests/expected/url.json @@ -2,13 +2,13 @@ "$schema": "http://json-schema.org/draft-07/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/validate.json b/schemars/tests/expected/validate.json index d4a14e3f..38aabccc 100644 --- a/schemars/tests/expected/validate.json +++ b/schemars/tests/expected/validate.json @@ -2,49 +2,7 @@ "$schema": "http://json-schema.org/draft-07/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 - }, - "min_max2": { - "type": "number", - "format": "float", - "maximum": 1000.0, - "minimum": 1.0 - }, - "regex_str1": { - "type": "string", - "pattern": "^[Hh]ello\\b" - }, - "regex_str2": { - "type": "string", - "pattern": "^[Hh]ello\\b" - }, - "regex_str3": { - "type": "string", - "pattern": "^\\d+$" - }, "contains_str1": { "type": "string", "pattern": "substring\\.\\.\\." @@ -57,14 +15,31 @@ "type": "string", "format": "email" }, - "tel": { - "type": "string", - "format": "phone" - }, "homepage": { "type": "string", "format": "uri" }, + "map_contains": { + "type": "object", + "additionalProperties": { + "type": "null" + }, + "required": [ + "map_key" + ] + }, + "min_max": { + "type": "number", + "format": "float", + "maximum": 100.0, + "minimum": 0.01 + }, + "min_max2": { + "type": "number", + "format": "float", + "maximum": 1000.0, + "minimum": 1.0 + }, "non_empty_str": { "type": "string", "maxLength": 100, @@ -84,21 +59,46 @@ "maxItems": 2, "minItems": 2 }, - "map_contains": { - "type": "object", - "required": [ - "map_key" - ], - "additionalProperties": { - "type": "null" - } + "regex_str1": { + "type": "string", + "pattern": "^[Hh]ello\\b" + }, + "regex_str2": { + "type": "string", + "pattern": "^[Hh]ello\\b" + }, + "regex_str3": { + "type": "string", + "pattern": "^\\d+$" }, "required_option": { "type": "boolean" }, + "tel": { + "type": "string", + "format": "phone" + }, "x": { "type": "integer", "format": "int32" } - } + }, + "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" + ] } \ No newline at end of file diff --git a/schemars/tests/expected/validate_inner.json b/schemars/tests/expected/validate_inner.json index 443a9fa6..ffe67923 100644 --- a/schemars/tests/expected/validate_inner.json +++ b/schemars/tests/expected/validate_inner.json @@ -2,15 +2,6 @@ "$schema": "http://json-schema.org/draft-07/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", @@ -70,5 +61,14 @@ "format": "uri" } } - } + }, + "required": [ + "array_str_length", + "slice_str_contains", + "vec_i32_range", + "vec_str_length", + "vec_str_length2", + "vec_str_regex", + "vec_str_url" + ] } \ 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 86658ac4..438b4aae 100644 --- a/schemars/tests/expected/validate_schemars_attrs.json +++ b/schemars/tests/expected/validate_schemars_attrs.json @@ -2,49 +2,7 @@ "$schema": "http://json-schema.org/draft-07/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 - }, - "min_max2": { - "type": "number", - "format": "float", - "maximum": 1000.0, - "minimum": 1.0 - }, - "regex_str1": { - "type": "string", - "pattern": "^[Hh]ello\\b" - }, - "regex_str2": { - "type": "string", - "pattern": "^[Hh]ello\\b" - }, - "regex_str3": { - "type": "string", - "pattern": "^\\d+$" - }, "contains_str1": { "type": "string", "pattern": "substring\\.\\.\\." @@ -57,14 +15,31 @@ "type": "string", "format": "email" }, - "tel": { - "type": "string", - "format": "phone" - }, "homepage": { "type": "string", "format": "uri" }, + "map_contains": { + "type": "object", + "additionalProperties": { + "type": "null" + }, + "required": [ + "map_key" + ] + }, + "min_max": { + "type": "number", + "format": "float", + "maximum": 100.0, + "minimum": 0.01 + }, + "min_max2": { + "type": "number", + "format": "float", + "maximum": 1000.0, + "minimum": 1.0 + }, "non_empty_str": { "type": "string", "maxLength": 100, @@ -84,21 +59,46 @@ "maxItems": 2, "minItems": 2 }, - "map_contains": { - "type": "object", - "required": [ - "map_key" - ], - "additionalProperties": { - "type": "null" - } + "regex_str1": { + "type": "string", + "pattern": "^[Hh]ello\\b" + }, + "regex_str2": { + "type": "string", + "pattern": "^[Hh]ello\\b" + }, + "regex_str3": { + "type": "string", + "pattern": "^\\d+$" }, "required_option": { "type": "boolean" }, + "tel": { + "type": "string", + "format": "phone" + }, "x": { "type": "integer", "format": "int32" } - } + }, + "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" + ] } \ No newline at end of file From 8c2c32bce084b31d195c62f2a40b4d4b0e6b0743 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Mon, 13 May 2024 18:33:55 +0100 Subject: [PATCH 07/40] Regenerate example schemas --- .../examples/custom_serialization.rs | 10 +++-- .../examples/custom_serialization.schema.json | 16 ++++---- .../examples/custom_settings.schema.json | 32 +++++++-------- .../examples/doc_comments.schema.json | 32 +++++++-------- .../_includes/examples/from_value.schema.json | 20 +++++----- docs/_includes/examples/main.schema.json | 32 +++++++-------- .../examples/remote_derive.schema.json | 20 +++++----- .../examples/schemars_attrs.schema.json | 26 ++++++------ .../examples/serde_attrs.schema.json | 20 +++++----- docs/_includes/examples/validate.schema.json | 40 +++++++++---------- .../examples/custom_serialization.schema.json | 16 ++++---- schemars/examples/custom_settings.schema.json | 32 +++++++-------- schemars/examples/doc_comments.schema.json | 32 +++++++-------- schemars/examples/from_value.schema.json | 20 +++++----- schemars/examples/main.schema.json | 32 +++++++-------- schemars/examples/remote_derive.schema.json | 20 +++++----- schemars/examples/schemars_attrs.schema.json | 26 ++++++------ schemars/examples/serde_attrs.schema.json | 20 +++++----- schemars/examples/validate.schema.json | 40 +++++++++---------- 19 files changed, 244 insertions(+), 242 deletions(-) diff --git a/docs/_includes/examples/custom_serialization.rs b/docs/_includes/examples/custom_serialization.rs index 53c78fa6..c119ea5d 100644 --- a/docs/_includes/examples/custom_serialization.rs +++ b/docs/_includes/examples/custom_serialization.rs @@ -1,4 +1,4 @@ -use schemars::schema::{Schema, SchemaObject}; +use schemars::Schema; use schemars::{gen::SchemaGenerator, schema_for, JsonSchema}; use serde::{Deserialize, Serialize}; @@ -21,9 +21,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/docs/_includes/examples/custom_serialization.schema.json b/docs/_includes/examples/custom_serialization.schema.json index 42fda993..1475c253 100644 --- a/docs/_includes/examples/custom_serialization.schema.json +++ b/docs/_includes/examples/custom_serialization.schema.json @@ -4,22 +4,22 @@ "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/docs/_includes/examples/custom_settings.schema.json b/docs/_includes/examples/custom_settings.schema.json index 12ac7d59..8da43482 100644 --- a/docs/_includes/examples/custom_settings.schema.json +++ b/docs/_includes/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/docs/_includes/examples/doc_comments.schema.json b/docs/_includes/examples/doc_comments.schema.json index 121cdb42..501b79c8 100644 --- a/docs/_includes/examples/doc_comments.schema.json +++ b/docs/_includes/examples/doc_comments.schema.json @@ -3,10 +3,6 @@ "title": "My Amazing Struct", "description": "This struct shows off generating a schema with a custom title and description.", "type": "object", - "required": [ - "my_bool", - "my_int" - ], "properties": { "my_bool": { "description": "This bool has a description, but no title.", @@ -30,6 +26,10 @@ ] } }, + "required": [ + "my_int", + "my_bool" + ], "definitions": { "MyEnum": { "title": "My Amazing Enum", @@ -37,28 +37,22 @@ { "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", "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/docs/_includes/examples/from_value.schema.json b/docs/_includes/examples/from_value.schema.json index 4ba77351..237a90e7 100644 --- a/docs/_includes/examples/from_value.schema.json +++ b/docs/_includes/examples/from_value.schema.json @@ -1,15 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/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/docs/_includes/examples/main.schema.json b/docs/_includes/examples/main.schema.json index ddbd9d33..bcfe1372 100644 --- a/docs/_includes/examples/main.schema.json +++ b/docs/_includes/examples/main.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" @@ -25,32 +21,30 @@ ] } }, + "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", @@ -59,10 +53,16 @@ "format": "float" } } - } + }, + "required": [ + "floats" + ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "StructVariant" + ] } ] } diff --git a/docs/_includes/examples/remote_derive.schema.json b/docs/_includes/examples/remote_derive.schema.json index e5841778..b6315cbb 100644 --- a/docs/_includes/examples/remote_derive.schema.json +++ b/docs/_includes/examples/remote_derive.schema.json @@ -2,11 +2,6 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Process", "type": "object", - "required": [ - "command_line", - "durations", - "wall_time" - ], "properties": { "command_line": { "type": "string" @@ -21,13 +16,14 @@ "$ref": "#/definitions/Duration" } }, + "required": [ + "command_line", + "wall_time", + "durations" + ], "definitions": { "Duration": { "type": "object", - "required": [ - "nanos", - "secs" - ], "properties": { "nanos": { "type": "integer", @@ -37,7 +33,11 @@ "type": "integer", "format": "int64" } - } + }, + "required": [ + "secs", + "nanos" + ] } } } diff --git a/docs/_includes/examples/schemars_attrs.schema.json b/docs/_includes/examples/schemars_attrs.schema.json index 9a6a22a6..9637e1b4 100644 --- a/docs/_includes/examples/schemars_attrs.schema.json +++ b/docs/_includes/examples/schemars_attrs.schema.json @@ -2,17 +2,11 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "MyStruct", "type": "object", - "required": [ - "myBool", - "myNumber", - "myVecStr" - ], "properties": { "myBool": { "type": "boolean" }, "myNullableEnum": { - "default": null, "anyOf": [ { "$ref": "#/definitions/MyEnum" @@ -20,13 +14,14 @@ { "type": "null" } - ] + ], + "default": null }, "myNumber": { "type": "integer", "format": "int32", - "maximum": 10.0, - "minimum": 1.0 + "maximum": 10, + "minimum": 1 }, "myVecStr": { "type": "array", @@ -37,6 +32,11 @@ } }, "additionalProperties": false, + "required": [ + "myNumber", + "myBool", + "myVecStr" + ], "definitions": { "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/docs/_includes/examples/serde_attrs.schema.json b/docs/_includes/examples/serde_attrs.schema.json index d0441932..9bb2f82d 100644 --- a/docs/_includes/examples/serde_attrs.schema.json +++ b/docs/_includes/examples/serde_attrs.schema.json @@ -2,16 +2,11 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "MyStruct", "type": "object", - "required": [ - "myBool", - "myNumber" - ], "properties": { "myBool": { "type": "boolean" }, "myNullableEnum": { - "default": null, "anyOf": [ { "$ref": "#/definitions/MyEnum" @@ -19,7 +14,8 @@ { "type": "null" } - ] + ], + "default": null }, "myNumber": { "type": "integer", @@ -27,6 +23,10 @@ } }, "additionalProperties": false, + "required": [ + "myNumber", + "myBool" + ], "definitions": { "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/docs/_includes/examples/validate.schema.json b/docs/_includes/examples/validate.schema.json index 1e45a969..308f631e 100644 --- a/docs/_includes/examples/validate.schema.json +++ b/docs/_includes/examples/validate.schema.json @@ -2,11 +2,6 @@ "$schema": "http://json-schema.org/draft-07/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/examples/custom_serialization.schema.json b/schemars/examples/custom_serialization.schema.json index 42fda993..1475c253 100644 --- a/schemars/examples/custom_serialization.schema.json +++ b/schemars/examples/custom_serialization.schema.json @@ -4,22 +4,22 @@ "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 12ac7d59..8da43482 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 121cdb42..501b79c8 100644 --- a/schemars/examples/doc_comments.schema.json +++ b/schemars/examples/doc_comments.schema.json @@ -3,10 +3,6 @@ "title": "My Amazing Struct", "description": "This struct shows off generating a schema with a custom title and description.", "type": "object", - "required": [ - "my_bool", - "my_int" - ], "properties": { "my_bool": { "description": "This bool has a description, but no title.", @@ -30,6 +26,10 @@ ] } }, + "required": [ + "my_int", + "my_bool" + ], "definitions": { "MyEnum": { "title": "My Amazing Enum", @@ -37,28 +37,22 @@ { "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", "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/from_value.schema.json b/schemars/examples/from_value.schema.json index 4ba77351..237a90e7 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#", "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 ddbd9d33..bcfe1372 100644 --- a/schemars/examples/main.schema.json +++ b/schemars/examples/main.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" @@ -25,32 +21,30 @@ ] } }, + "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", @@ -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 e5841778..b6315cbb 100644 --- a/schemars/examples/remote_derive.schema.json +++ b/schemars/examples/remote_derive.schema.json @@ -2,11 +2,6 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Process", "type": "object", - "required": [ - "command_line", - "durations", - "wall_time" - ], "properties": { "command_line": { "type": "string" @@ -21,13 +16,14 @@ "$ref": "#/definitions/Duration" } }, + "required": [ + "command_line", + "wall_time", + "durations" + ], "definitions": { "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.schema.json b/schemars/examples/schemars_attrs.schema.json index 9a6a22a6..9637e1b4 100644 --- a/schemars/examples/schemars_attrs.schema.json +++ b/schemars/examples/schemars_attrs.schema.json @@ -2,17 +2,11 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "MyStruct", "type": "object", - "required": [ - "myBool", - "myNumber", - "myVecStr" - ], "properties": { "myBool": { "type": "boolean" }, "myNullableEnum": { - "default": null, "anyOf": [ { "$ref": "#/definitions/MyEnum" @@ -20,13 +14,14 @@ { "type": "null" } - ] + ], + "default": null }, "myNumber": { "type": "integer", "format": "int32", - "maximum": 10.0, - "minimum": 1.0 + "maximum": 10, + "minimum": 1 }, "myVecStr": { "type": "array", @@ -37,6 +32,11 @@ } }, "additionalProperties": false, + "required": [ + "myNumber", + "myBool", + "myVecStr" + ], "definitions": { "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 d0441932..9bb2f82d 100644 --- a/schemars/examples/serde_attrs.schema.json +++ b/schemars/examples/serde_attrs.schema.json @@ -2,16 +2,11 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "MyStruct", "type": "object", - "required": [ - "myBool", - "myNumber" - ], "properties": { "myBool": { "type": "boolean" }, "myNullableEnum": { - "default": null, "anyOf": [ { "$ref": "#/definitions/MyEnum" @@ -19,7 +14,8 @@ { "type": "null" } - ] + ], + "default": null }, "myNumber": { "type": "integer", @@ -27,6 +23,10 @@ } }, "additionalProperties": false, + "required": [ + "myNumber", + "myBool" + ], "definitions": { "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 1e45a969..308f631e 100644 --- a/schemars/examples/validate.schema.json +++ b/schemars/examples/validate.schema.json @@ -2,11 +2,6 @@ "$schema": "http://json-schema.org/draft-07/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" + ] } From 18300c67bb49cecd3d9ffd8f77779d8614554027 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Mon, 13 May 2024 21:30:51 +0100 Subject: [PATCH 08/40] Use `const` instead of single-valued `enum` (#291) --- schemars/src/_private.rs | 45 ++++--------------- schemars/src/gen.rs | 1 + schemars/src/visit.rs | 18 ++++++++ schemars/tests/expected/deprecated-enum.json | 6 +-- .../tests/expected/doc_comments_enum.json | 4 +- .../tests/expected/enum-internal-duf.json | 28 +++--------- schemars/tests/expected/enum-internal.json | 28 +++--------- .../expected/enum-simple-internal-duf.json | 12 ++--- .../tests/expected/enum-simple-internal.json | 12 ++--- schemars/tests/expected/enum-unit-doc.json | 12 ++--- .../expected/schema_settings-2019_09.json | 4 +- schemars/tests/expected/schema_settings.json | 4 +- .../expected/schema_with-enum-internal.json | 12 ++--- schemars_derive/src/schema_exprs.rs | 6 +-- 14 files changed, 61 insertions(+), 131 deletions(-) diff --git a/schemars/src/_private.rs b/schemars/src/_private.rs index 0ba27ce9..25104015 100644 --- a/schemars/src/_private.rs +++ b/schemars/src/_private.rs @@ -53,17 +53,16 @@ impl MaybeSerializeWrapper { } } -/// Create a schema for a unit enum -pub fn new_unit_enum(variant: &str) -> Schema { - // TODO switch from single-valued "enum" to "const" +/// Create a schema for a unit enum variant +pub fn new_unit_enum_variant(variant: &str) -> Schema { json_schema!({ "type": "string", - "enum": [variant], + "const": variant, }) } -/// Create a schema for an externally tagged enum -pub fn new_externally_tagged_enum(variant: &str, sub_schema: Schema) -> Schema { +/// 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": { @@ -74,7 +73,8 @@ pub fn new_externally_tagged_enum(variant: &str, sub_schema: Schema) -> Schema { }) } -pub fn apply_internal_enum_tag( +/// Update a schema for an internally tagged enum variant +pub fn apply_internal_enum_variant_tag( schema: &mut Schema, tag_name: &str, variant: &str, @@ -94,8 +94,7 @@ pub fn apply_internal_enum_tag( tag_name.to_string(), json!({ "type": "string", - // TODO switch from single-valued "enum" to "const" - "enum": [variant] + "const": variant }), ); } @@ -113,34 +112,6 @@ pub fn apply_internal_enum_tag( } } -/// Create a schema for an internally tagged enum -pub fn new_internally_tagged_enum( - tag_name: &str, - variant: &str, - deny_unknown_fields: bool, -) -> Schema { - // TODO switch from single-valued "enum" to "const" - let mut schema = json_schema!({ - "type": "object", - "properties": { - tag_name: { - "type": "string", - "enum": [variant], - } - }, - "required": [tag_name], - }); - - if deny_unknown_fields { - schema - .as_object_mut() - .unwrap() - .insert("additionalProperties".into(), false.into()); - } - - schema -} - pub fn insert_object_property( schema: &mut Schema, key: &str, diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index 8336eccc..c06f85a2 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -97,6 +97,7 @@ impl SchemaSettings { skip_additional_properties: true, }), Box::new(SetSingleExample), + Box::new(ReplaceConstValue), ], inline_subschemas: false, } diff --git a/schemars/src/visit.rs b/schemars/src/visit.rs index cfe11dfa..5fd42736 100644 --- a/schemars/src/visit.rs +++ b/schemars/src/visit.rs @@ -169,3 +169,21 @@ impl Visitor for SetSingleExample { } } } + +/// This visitor will replace the `const` schema property with a single-valued `enum` property. +/// +/// 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 Visitor for ReplaceConstValue { + fn visit_schema(&mut self, schema: &mut Schema) { + visit_schema(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])); + } + } + } +} diff --git a/schemars/tests/expected/deprecated-enum.json b/schemars/tests/expected/deprecated-enum.json index 55a98ef6..dab90ecd 100644 --- a/schemars/tests/expected/deprecated-enum.json +++ b/schemars/tests/expected/deprecated-enum.json @@ -11,10 +11,8 @@ }, { "type": "string", - "deprecated": true, - "enum": [ - "DeprecatedUnitVariant" - ] + "const": "DeprecatedUnitVariant", + "deprecated": true }, { "type": "object", diff --git a/schemars/tests/expected/doc_comments_enum.json b/schemars/tests/expected/doc_comments_enum.json index c983c1d3..fc38db18 100644 --- a/schemars/tests/expected/doc_comments_enum.json +++ b/schemars/tests/expected/doc_comments_enum.json @@ -13,9 +13,7 @@ { "description": "This comment is included in the generated schema :)", "type": "string", - "enum": [ - "DocumentedUnit" - ] + "const": "DocumentedUnit" }, { "title": "Complex variant", diff --git a/schemars/tests/expected/enum-internal-duf.json b/schemars/tests/expected/enum-internal-duf.json index 6db4d31b..758bf9b9 100644 --- a/schemars/tests/expected/enum-internal-duf.json +++ b/schemars/tests/expected/enum-internal-duf.json @@ -7,9 +7,7 @@ "properties": { "typeProperty": { "type": "string", - "enum": [ - "UnitOne" - ] + "const": "UnitOne" } }, "additionalProperties": false, @@ -22,9 +20,7 @@ "properties": { "typeProperty": { "type": "string", - "enum": [ - "StringMap" - ] + "const": "StringMap" } }, "additionalProperties": { @@ -39,9 +35,7 @@ "properties": { "typeProperty": { "type": "string", - "enum": [ - "UnitStructNewType" - ] + "const": "UnitStructNewType" } }, "additionalProperties": false, @@ -61,9 +55,7 @@ }, "typeProperty": { "type": "string", - "enum": [ - "StructNewType" - ] + "const": "StructNewType" } }, "required": [ @@ -84,9 +76,7 @@ }, "typeProperty": { "type": "string", - "enum": [ - "Struct" - ] + "const": "Struct" } }, "additionalProperties": false, @@ -101,9 +91,7 @@ "properties": { "typeProperty": { "type": "string", - "enum": [ - "UnitTwo" - ] + "const": "UnitTwo" } }, "additionalProperties": false, @@ -117,9 +105,7 @@ "properties": { "typeProperty": { "type": "string", - "enum": [ - "WithInt" - ] + "const": "WithInt" } }, "required": [ diff --git a/schemars/tests/expected/enum-internal.json b/schemars/tests/expected/enum-internal.json index 2382f2d2..765248f7 100644 --- a/schemars/tests/expected/enum-internal.json +++ b/schemars/tests/expected/enum-internal.json @@ -7,9 +7,7 @@ "properties": { "typeProperty": { "type": "string", - "enum": [ - "UnitOne" - ] + "const": "UnitOne" } }, "required": [ @@ -21,9 +19,7 @@ "properties": { "typeProperty": { "type": "string", - "enum": [ - "StringMap" - ] + "const": "StringMap" } }, "additionalProperties": { @@ -38,9 +34,7 @@ "properties": { "typeProperty": { "type": "string", - "enum": [ - "UnitStructNewType" - ] + "const": "UnitStructNewType" } }, "required": [ @@ -59,9 +53,7 @@ }, "typeProperty": { "type": "string", - "enum": [ - "StructNewType" - ] + "const": "StructNewType" } }, "required": [ @@ -82,9 +74,7 @@ }, "typeProperty": { "type": "string", - "enum": [ - "Struct" - ] + "const": "Struct" } }, "required": [ @@ -98,9 +88,7 @@ "properties": { "typeProperty": { "type": "string", - "enum": [ - "UnitTwo" - ] + "const": "UnitTwo" } }, "required": [ @@ -113,9 +101,7 @@ "properties": { "typeProperty": { "type": "string", - "enum": [ - "WithInt" - ] + "const": "WithInt" } }, "required": [ diff --git a/schemars/tests/expected/enum-simple-internal-duf.json b/schemars/tests/expected/enum-simple-internal-duf.json index 9e6e5d4a..4fdf04e3 100644 --- a/schemars/tests/expected/enum-simple-internal-duf.json +++ b/schemars/tests/expected/enum-simple-internal-duf.json @@ -7,9 +7,7 @@ "properties": { "typeProperty": { "type": "string", - "enum": [ - "A" - ] + "const": "A" } }, "additionalProperties": false, @@ -22,9 +20,7 @@ "properties": { "typeProperty": { "type": "string", - "enum": [ - "B" - ] + "const": "B" } }, "additionalProperties": false, @@ -37,9 +33,7 @@ "properties": { "typeProperty": { "type": "string", - "enum": [ - "C" - ] + "const": "C" } }, "additionalProperties": false, diff --git a/schemars/tests/expected/enum-simple-internal.json b/schemars/tests/expected/enum-simple-internal.json index a549c8cb..050089c9 100644 --- a/schemars/tests/expected/enum-simple-internal.json +++ b/schemars/tests/expected/enum-simple-internal.json @@ -7,9 +7,7 @@ "properties": { "typeProperty": { "type": "string", - "enum": [ - "A" - ] + "const": "A" } }, "required": [ @@ -21,9 +19,7 @@ "properties": { "typeProperty": { "type": "string", - "enum": [ - "B" - ] + "const": "B" } }, "required": [ @@ -35,9 +31,7 @@ "properties": { "typeProperty": { "type": "string", - "enum": [ - "C" - ] + "const": "C" } }, "required": [ diff --git a/schemars/tests/expected/enum-unit-doc.json b/schemars/tests/expected/enum-unit-doc.json index 11a5c2a5..86160e8c 100644 --- a/schemars/tests/expected/enum-unit-doc.json +++ b/schemars/tests/expected/enum-unit-doc.json @@ -6,23 +6,17 @@ "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/schema_settings-2019_09.json b/schemars/tests/expected/schema_settings-2019_09.json index 4e18426e..e51f3977 100644 --- a/schemars/tests/expected/schema_settings-2019_09.json +++ b/schemars/tests/expected/schema_settings-2019_09.json @@ -45,9 +45,7 @@ { "description": "This is a documented unit variant", "type": "string", - "enum": [ - "DocumentedUnit" - ] + "const": "DocumentedUnit" }, { "type": "object", diff --git a/schemars/tests/expected/schema_settings.json b/schemars/tests/expected/schema_settings.json index 8e70c7c6..07cdb0ec 100644 --- a/schemars/tests/expected/schema_settings.json +++ b/schemars/tests/expected/schema_settings.json @@ -45,9 +45,7 @@ { "description": "This is a documented unit variant", "type": "string", - "enum": [ - "DocumentedUnit" - ] + "const": "DocumentedUnit" }, { "type": "object", diff --git a/schemars/tests/expected/schema_with-enum-internal.json b/schemars/tests/expected/schema_with-enum-internal.json index 8b6fbebe..45bc8e94 100644 --- a/schemars/tests/expected/schema_with-enum-internal.json +++ b/schemars/tests/expected/schema_with-enum-internal.json @@ -10,9 +10,7 @@ }, "typeProperty": { "type": "string", - "enum": [ - "Struct" - ] + "const": "Struct" } }, "required": [ @@ -25,9 +23,7 @@ "properties": { "typeProperty": { "type": "string", - "enum": [ - "NewType" - ] + "const": "NewType" } }, "required": [ @@ -39,9 +35,7 @@ "properties": { "typeProperty": { "type": "string", - "enum": [ - "Unit" - ] + "const": "Unit" } }, "required": [ diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index f22831e3..7a1de816 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -197,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) } }; @@ -236,7 +236,7 @@ fn expr_for_internal_tagged_enum<'a>( quote!({ let mut schema = #schema_expr; - schemars::_private::apply_internal_enum_tag(&mut schema, #tag_name, #name, #deny_unknown_fields); + schemars::_private::apply_internal_enum_variant_tag(&mut schema, #tag_name, #name, #deny_unknown_fields); schema }) }) From c4d42ec11a54e445f84d2101e9afd97b98735f69 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Mon, 13 May 2024 22:02:30 +0100 Subject: [PATCH 09/40] Refactor `flatten` and move it to `_private`, remove `TempFixupForTests`, regenerate test schemas --- schemars/src/_private.rs | 60 ++++++++++ schemars/src/flatten.rs | 71 ----------- schemars/src/lib.rs | 1 - schemars/tests/expected/bytes.json | 4 +- schemars/tests/expected/chrono-types.json | 4 +- schemars/tests/expected/crate_alias.json | 4 +- schemars/tests/expected/deprecated-enum.json | 4 +- .../tests/expected/deprecated-struct.json | 4 +- .../expected/duration_and_systemtime.json | 16 +-- .../expected/enum-adjacent-tagged-duf.json | 32 ++--- .../tests/expected/enum-adjacent-tagged.json | 32 ++--- .../tests/expected/enum-external-duf.json | 8 +- schemars/tests/expected/enum-external.json | 8 +- .../tests/expected/enum-internal-duf.json | 8 +- schemars/tests/expected/enum-internal.json | 8 +- .../tests/expected/enum-untagged-duf.json | 8 +- schemars/tests/expected/enum-untagged.json | 8 +- schemars/tests/expected/examples.json | 4 +- schemars/tests/expected/flatten.json | 2 +- .../tests/expected/inline-subschemas.json | 2 +- .../tests/expected/macro_built_struct.json | 6 +- schemars/tests/expected/nonzero_ints.json | 8 +- schemars/tests/expected/os_strings.json | 8 +- schemars/tests/expected/range.json | 16 +-- schemars/tests/expected/remote_derive.json | 4 +- .../tests/expected/remote_derive_generic.json | 6 +- .../tests/expected/schema-name-custom.json | 4 +- .../tests/expected/schema-name-default.json | 4 +- .../expected/schema-name-mixed-generics.json | 8 +- .../expected/schema_settings-2019_09.json | 4 +- .../expected/schema_settings-openapi3.json | 4 +- schemars/tests/expected/schema_settings.json | 4 +- .../schema_with-enum-adjacent-tagged.json | 16 +-- .../expected/schema_with-enum-internal.json | 4 +- .../tests/expected/schema_with-struct.json | 4 +- .../tests/expected/skip_struct_fields.json | 4 +- .../struct-normal-additional-properties.json | 4 +- schemars/tests/expected/struct-normal.json | 4 +- schemars/tests/expected/validate.json | 20 ++-- schemars/tests/expected/validate_inner.json | 10 +- schemars/tests/expected/validate_newtype.json | 4 +- .../expected/validate_schemars_attrs.json | 20 ++-- schemars/tests/expected/validate_tuple.json | 4 +- schemars/tests/util/mod.rs | 32 ----- schemars_derive/src/schema_exprs.rs | 112 +++++++++--------- 45 files changed, 276 insertions(+), 326 deletions(-) delete mode 100644 schemars/src/flatten.rs diff --git a/schemars/src/_private.rs b/schemars/src/_private.rs index 25104015..129d92ea 100644 --- a/schemars/src/_private.rs +++ b/schemars/src/_private.rs @@ -3,6 +3,7 @@ use crate::JsonSchema; use crate::Schema; use serde::Serialize; use serde_json::json; +use serde_json::map::Entry; use serde_json::Map; use serde_json::Value; @@ -174,3 +175,62 @@ pub fn apply_inner_validation(schema: &mut Schema, f: fn(&mut Schema) -> ()) { 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`) + } + }; + } + } + } + } +} diff --git a/schemars/src/flatten.rs b/schemars/src/flatten.rs deleted file mode 100644 index 69b03617..00000000 --- a/schemars/src/flatten.rs +++ /dev/null @@ -1,71 +0,0 @@ -use serde_json::map::Entry; -use serde_json::Value; - -use crate::Schema; - -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(mut self, other: Self) -> Schema { - if let Value::Object(obj2) = other.to_value() { - let obj1 = self.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 `self`) - } - }; - } - } - } - } - - self - } -} diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs index fbe5f1db..32bb8c0b 100644 --- a/schemars/src/lib.rs +++ b/schemars/src/lib.rs @@ -1,7 +1,6 @@ #![deny(unsafe_code)] #![doc = include_str!("../README.md")] -mod flatten; mod json_schema_impls; mod schema; mod ser; diff --git a/schemars/tests/expected/bytes.json b/schemars/tests/expected/bytes.json index 5e1f9a54..06458c6a 100644 --- a/schemars/tests/expected/bytes.json +++ b/schemars/tests/expected/bytes.json @@ -8,7 +8,7 @@ "items": { "type": "integer", "format": "uint8", - "minimum": 0.0 + "minimum": 0 } }, { @@ -16,7 +16,7 @@ "items": { "type": "integer", "format": "uint8", - "minimum": 0.0 + "minimum": 0 } } ], diff --git a/schemars/tests/expected/chrono-types.json b/schemars/tests/expected/chrono-types.json index b96b00c0..b9e333da 100644 --- a/schemars/tests/expected/chrono-types.json +++ b/schemars/tests/expected/chrono-types.json @@ -33,10 +33,10 @@ } }, "required": [ + "weekday", "date_time", "naive_date", "naive_date_time", - "naive_time", - "weekday" + "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 d37d4826..b01fc0dd 100644 --- a/schemars/tests/expected/crate_alias.json +++ b/schemars/tests/expected/crate_alias.json @@ -13,7 +13,7 @@ } }, "required": [ - "bar", - "foo" + "foo", + "bar" ] } \ No newline at end of file diff --git a/schemars/tests/expected/deprecated-enum.json b/schemars/tests/expected/deprecated-enum.json index dab90ecd..ee3de616 100644 --- a/schemars/tests/expected/deprecated-enum.json +++ b/schemars/tests/expected/deprecated-enum.json @@ -30,8 +30,8 @@ } }, "required": [ - "deprecated_field", - "foo" + "foo", + "deprecated_field" ] } }, diff --git a/schemars/tests/expected/deprecated-struct.json b/schemars/tests/expected/deprecated-struct.json index b915eb14..d2af941d 100644 --- a/schemars/tests/expected/deprecated-struct.json +++ b/schemars/tests/expected/deprecated-struct.json @@ -14,7 +14,7 @@ }, "deprecated": true, "required": [ - "deprecated_field", - "foo" + "foo", + "deprecated_field" ] } \ No newline at end of file diff --git a/schemars/tests/expected/duration_and_systemtime.json b/schemars/tests/expected/duration_and_systemtime.json index b2125996..71c17b7c 100644 --- a/schemars/tests/expected/duration_and_systemtime.json +++ b/schemars/tests/expected/duration_and_systemtime.json @@ -21,17 +21,17 @@ "nanos": { "type": "integer", "format": "uint32", - "minimum": 0.0 + "minimum": 0 }, "secs": { "type": "integer", "format": "uint64", - "minimum": 0.0 + "minimum": 0 } }, "required": [ - "nanos", - "secs" + "secs", + "nanos" ] }, "SystemTime": { @@ -40,17 +40,17 @@ "nanos_since_epoch": { "type": "integer", "format": "uint32", - "minimum": 0.0 + "minimum": 0 }, "secs_since_epoch": { "type": "integer", "format": "uint64", - "minimum": 0.0 + "minimum": 0 } }, "required": [ - "nanos_since_epoch", - "secs_since_epoch" + "secs_since_epoch", + "nanos_since_epoch" ] } } diff --git a/schemars/tests/expected/enum-adjacent-tagged-duf.json b/schemars/tests/expected/enum-adjacent-tagged-duf.json index a89ab50c..f746216d 100644 --- a/schemars/tests/expected/enum-adjacent-tagged-duf.json +++ b/schemars/tests/expected/enum-adjacent-tagged-duf.json @@ -35,8 +35,8 @@ }, "additionalProperties": false, "required": [ - "c", - "t" + "t", + "c" ] }, { @@ -54,8 +54,8 @@ }, "additionalProperties": false, "required": [ - "c", - "t" + "t", + "c" ] }, { @@ -73,8 +73,8 @@ }, "additionalProperties": false, "required": [ - "c", - "t" + "t", + "c" ] }, { @@ -93,8 +93,8 @@ }, "additionalProperties": false, "required": [ - "bar", - "foo" + "foo", + "bar" ] }, "t": { @@ -106,8 +106,8 @@ }, "additionalProperties": false, "required": [ - "c", - "t" + "t", + "c" ] }, { @@ -136,8 +136,8 @@ }, "additionalProperties": false, "required": [ - "c", - "t" + "t", + "c" ] }, { @@ -171,8 +171,8 @@ }, "additionalProperties": false, "required": [ - "c", - "t" + "t", + "c" ] } ], @@ -189,8 +189,8 @@ } }, "required": [ - "bar", - "foo" + "foo", + "bar" ] }, "UnitStruct": { diff --git a/schemars/tests/expected/enum-adjacent-tagged.json b/schemars/tests/expected/enum-adjacent-tagged.json index 9380c5b7..b977be00 100644 --- a/schemars/tests/expected/enum-adjacent-tagged.json +++ b/schemars/tests/expected/enum-adjacent-tagged.json @@ -33,8 +33,8 @@ } }, "required": [ - "c", - "t" + "t", + "c" ] }, { @@ -51,8 +51,8 @@ } }, "required": [ - "c", - "t" + "t", + "c" ] }, { @@ -69,8 +69,8 @@ } }, "required": [ - "c", - "t" + "t", + "c" ] }, { @@ -88,8 +88,8 @@ } }, "required": [ - "bar", - "foo" + "foo", + "bar" ] }, "t": { @@ -100,8 +100,8 @@ } }, "required": [ - "c", - "t" + "t", + "c" ] }, { @@ -129,8 +129,8 @@ } }, "required": [ - "c", - "t" + "t", + "c" ] }, { @@ -162,8 +162,8 @@ } }, "required": [ - "c", - "t" + "t", + "c" ] } ], @@ -180,8 +180,8 @@ } }, "required": [ - "bar", - "foo" + "foo", + "bar" ] }, "UnitStruct": { diff --git a/schemars/tests/expected/enum-external-duf.json b/schemars/tests/expected/enum-external-duf.json index d4928080..0351fa20 100644 --- a/schemars/tests/expected/enum-external-duf.json +++ b/schemars/tests/expected/enum-external-duf.json @@ -64,8 +64,8 @@ }, "additionalProperties": false, "required": [ - "bar", - "foo" + "foo", + "bar" ] } }, @@ -124,8 +124,8 @@ } }, "required": [ - "bar", - "foo" + "foo", + "bar" ] }, "UnitStruct": { diff --git a/schemars/tests/expected/enum-external.json b/schemars/tests/expected/enum-external.json index b09f1072..4c1d5a6d 100644 --- a/schemars/tests/expected/enum-external.json +++ b/schemars/tests/expected/enum-external.json @@ -63,8 +63,8 @@ } }, "required": [ - "bar", - "foo" + "foo", + "bar" ] } }, @@ -123,8 +123,8 @@ } }, "required": [ - "bar", - "foo" + "foo", + "bar" ] }, "UnitStruct": { diff --git a/schemars/tests/expected/enum-internal-duf.json b/schemars/tests/expected/enum-internal-duf.json index 758bf9b9..9c2aa9cd 100644 --- a/schemars/tests/expected/enum-internal-duf.json +++ b/schemars/tests/expected/enum-internal-duf.json @@ -59,9 +59,9 @@ } }, "required": [ - "bar", + "typeProperty", "foo", - "typeProperty" + "bar" ] }, { @@ -81,9 +81,9 @@ }, "additionalProperties": false, "required": [ - "bar", + "typeProperty", "foo", - "typeProperty" + "bar" ] }, { diff --git a/schemars/tests/expected/enum-internal.json b/schemars/tests/expected/enum-internal.json index 765248f7..7fd931c0 100644 --- a/schemars/tests/expected/enum-internal.json +++ b/schemars/tests/expected/enum-internal.json @@ -57,9 +57,9 @@ } }, "required": [ - "bar", + "typeProperty", "foo", - "typeProperty" + "bar" ] }, { @@ -78,9 +78,9 @@ } }, "required": [ - "bar", + "typeProperty", "foo", - "typeProperty" + "bar" ] }, { diff --git a/schemars/tests/expected/enum-untagged-duf.json b/schemars/tests/expected/enum-untagged-duf.json index 7ce7d3d4..e3f7c66d 100644 --- a/schemars/tests/expected/enum-untagged-duf.json +++ b/schemars/tests/expected/enum-untagged-duf.json @@ -30,8 +30,8 @@ }, "additionalProperties": false, "required": [ - "bar", - "foo" + "foo", + "bar" ] }, { @@ -66,8 +66,8 @@ } }, "required": [ - "bar", - "foo" + "foo", + "bar" ] }, "UnitStruct": { diff --git a/schemars/tests/expected/enum-untagged.json b/schemars/tests/expected/enum-untagged.json index add81e49..2b23ffb3 100644 --- a/schemars/tests/expected/enum-untagged.json +++ b/schemars/tests/expected/enum-untagged.json @@ -29,8 +29,8 @@ } }, "required": [ - "bar", - "foo" + "foo", + "bar" ] }, { @@ -65,8 +65,8 @@ } }, "required": [ - "bar", - "foo" + "foo", + "bar" ] }, "UnitStruct": { diff --git a/schemars/tests/expected/examples.json b/schemars/tests/expected/examples.json index 9c5c5ab1..3203c4a8 100644 --- a/schemars/tests/expected/examples.json +++ b/schemars/tests/expected/examples.json @@ -33,7 +33,7 @@ null ], "required": [ - "bar", - "foo" + "foo", + "bar" ] } \ No newline at end of file diff --git a/schemars/tests/expected/flatten.json b/schemars/tests/expected/flatten.json index ec0aff3f..8f77dd2e 100644 --- a/schemars/tests/expected/flatten.json +++ b/schemars/tests/expected/flatten.json @@ -26,8 +26,8 @@ } }, "required": [ - "b", "f", + "b", "s", "v" ] diff --git a/schemars/tests/expected/inline-subschemas.json b/schemars/tests/expected/inline-subschemas.json index fe4e83e2..7ba76d58 100644 --- a/schemars/tests/expected/inline-subschemas.json +++ b/schemars/tests/expected/inline-subschemas.json @@ -9,7 +9,7 @@ "replicas": { "type": "integer", "format": "uint32", - "minimum": 0.0 + "minimum": 0 } }, "required": [ diff --git a/schemars/tests/expected/macro_built_struct.json b/schemars/tests/expected/macro_built_struct.json index 0c5840c4..e958c8ef 100644 --- a/schemars/tests/expected/macro_built_struct.json +++ b/schemars/tests/expected/macro_built_struct.json @@ -10,11 +10,11 @@ "x": { "type": "integer", "format": "uint8", - "minimum": 0.0 + "minimum": 0 } }, "required": [ - "v", - "x" + "x", + "v" ] } \ No newline at end of file diff --git a/schemars/tests/expected/nonzero_ints.json b/schemars/tests/expected/nonzero_ints.json index 3b839291..6ee51057 100644 --- a/schemars/tests/expected/nonzero_ints.json +++ b/schemars/tests/expected/nonzero_ints.json @@ -13,7 +13,7 @@ "nonzero_unsigned": { "type": "integer", "format": "uint32", - "minimum": 1.0 + "minimum": 1 }, "signed": { "type": "integer", @@ -22,13 +22,13 @@ "unsigned": { "type": "integer", "format": "uint32", - "minimum": 0.0 + "minimum": 0 } }, "required": [ - "nonzero_signed", + "unsigned", "nonzero_unsigned", "signed", - "unsigned" + "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 50448edb..927f3f93 100644 --- a/schemars/tests/expected/os_strings.json +++ b/schemars/tests/expected/os_strings.json @@ -11,8 +11,8 @@ } }, "required": [ - "borrowed", - "owned" + "owned", + "borrowed" ], "definitions": { "OsString": { @@ -25,7 +25,7 @@ "items": { "type": "integer", "format": "uint8", - "minimum": 0.0 + "minimum": 0 } } }, @@ -41,7 +41,7 @@ "items": { "type": "integer", "format": "uint16", - "minimum": 0.0 + "minimum": 0 } } }, diff --git a/schemars/tests/expected/range.json b/schemars/tests/expected/range.json index bb68e3a1..19e47806 100644 --- a/schemars/tests/expected/range.json +++ b/schemars/tests/expected/range.json @@ -14,9 +14,9 @@ } }, "required": [ - "bound", + "range", "inclusive", - "range" + "bound" ], "definitions": { "Bound_of_string": { @@ -62,8 +62,8 @@ } }, "required": [ - "end", - "start" + "start", + "end" ] }, "Range_of_uint": { @@ -72,17 +72,17 @@ "end": { "type": "integer", "format": "uint", - "minimum": 0.0 + "minimum": 0 }, "start": { "type": "integer", "format": "uint", - "minimum": 0.0 + "minimum": 0 } }, "required": [ - "end", - "start" + "start", + "end" ] } } diff --git a/schemars/tests/expected/remote_derive.json b/schemars/tests/expected/remote_derive.json index 76df9b69..9477e0ff 100644 --- a/schemars/tests/expected/remote_derive.json +++ b/schemars/tests/expected/remote_derive.json @@ -47,8 +47,8 @@ } }, "required": [ - "nanos", - "secs" + "secs", + "nanos" ] } } diff --git a/schemars/tests/expected/remote_derive_generic.json b/schemars/tests/expected/remote_derive_generic.json index d06320f3..b533320b 100644 --- a/schemars/tests/expected/remote_derive_generic.json +++ b/schemars/tests/expected/remote_derive_generic.json @@ -25,9 +25,9 @@ }, "required": [ "byte_or_bool2", - "fake_map", + "unit_or_t2", "s", - "unit_or_t2" + "fake_map" ], "definitions": { "Or_for_null_and_int32": { @@ -46,7 +46,7 @@ { "type": "integer", "format": "uint8", - "minimum": 0.0 + "minimum": 0 }, { "type": "boolean" diff --git a/schemars/tests/expected/schema-name-custom.json b/schemars/tests/expected/schema-name-custom.json index 4e6cc36f..dc5336ba 100644 --- a/schemars/tests/expected/schema-name-custom.json +++ b/schemars/tests/expected/schema-name-custom.json @@ -24,11 +24,11 @@ } }, "required": [ - "inner", "t", "u", "v", - "w" + "w", + "inner" ], "definitions": { "another-new-name": { diff --git a/schemars/tests/expected/schema-name-default.json b/schemars/tests/expected/schema-name-default.json index 39c39a97..7524c633 100644 --- a/schemars/tests/expected/schema-name-default.json +++ b/schemars/tests/expected/schema-name-default.json @@ -24,11 +24,11 @@ } }, "required": [ - "inner", "t", "u", "v", - "w" + "w", + "inner" ], "definitions": { "MySimpleStruct": { diff --git a/schemars/tests/expected/schema-name-mixed-generics.json b/schemars/tests/expected/schema-name-mixed-generics.json index 0da80440..fdadbab7 100644 --- a/schemars/tests/expected/schema-name-mixed-generics.json +++ b/schemars/tests/expected/schema-name-mixed-generics.json @@ -12,8 +12,8 @@ } }, "required": [ - "foo", - "generic" + "generic", + "foo" ], "definitions": { "MySimpleStruct": { @@ -52,11 +52,11 @@ } }, "required": [ - "inner", "t", "u", "v", - "w" + "w", + "inner" ] } } diff --git a/schemars/tests/expected/schema_settings-2019_09.json b/schemars/tests/expected/schema_settings-2019_09.json index e51f3977..947aa2e7 100644 --- a/schemars/tests/expected/schema_settings-2019_09.json +++ b/schemars/tests/expected/schema_settings-2019_09.json @@ -29,8 +29,8 @@ }, "required": [ "int", - "value", - "values" + "values", + "value" ], "definitions": { "Inner": { diff --git a/schemars/tests/expected/schema_settings-openapi3.json b/schemars/tests/expected/schema_settings-openapi3.json index 5318b011..b6de3027 100644 --- a/schemars/tests/expected/schema_settings-openapi3.json +++ b/schemars/tests/expected/schema_settings-openapi3.json @@ -24,8 +24,8 @@ }, "required": [ "int", - "value", - "values" + "values", + "value" ], "definitions": { "Inner": { diff --git a/schemars/tests/expected/schema_settings.json b/schemars/tests/expected/schema_settings.json index 07cdb0ec..6a66ca0c 100644 --- a/schemars/tests/expected/schema_settings.json +++ b/schemars/tests/expected/schema_settings.json @@ -29,8 +29,8 @@ }, "required": [ "int", - "value", - "values" + "values", + "value" ], "definitions": { "Inner": { diff --git a/schemars/tests/expected/schema_with-enum-adjacent-tagged.json b/schemars/tests/expected/schema_with-enum-adjacent-tagged.json index c29c59e1..5b95671e 100644 --- a/schemars/tests/expected/schema_with-enum-adjacent-tagged.json +++ b/schemars/tests/expected/schema_with-enum-adjacent-tagged.json @@ -24,8 +24,8 @@ } }, "required": [ - "c", - "t" + "t", + "c" ] }, { @@ -42,8 +42,8 @@ } }, "required": [ - "c", - "t" + "t", + "c" ] }, { @@ -71,8 +71,8 @@ } }, "required": [ - "c", - "t" + "t", + "c" ] }, { @@ -89,8 +89,8 @@ } }, "required": [ - "c", - "t" + "t", + "c" ] } ] diff --git a/schemars/tests/expected/schema_with-enum-internal.json b/schemars/tests/expected/schema_with-enum-internal.json index 45bc8e94..90871ad5 100644 --- a/schemars/tests/expected/schema_with-enum-internal.json +++ b/schemars/tests/expected/schema_with-enum-internal.json @@ -14,8 +14,8 @@ } }, "required": [ - "foo", - "typeProperty" + "typeProperty", + "foo" ] }, { diff --git a/schemars/tests/expected/schema_with-struct.json b/schemars/tests/expected/schema_with-struct.json index 40b5c56e..7cc55bf6 100644 --- a/schemars/tests/expected/schema_with-struct.json +++ b/schemars/tests/expected/schema_with-struct.json @@ -15,8 +15,8 @@ } }, "required": [ + "foo", "bar", - "baz", - "foo" + "baz" ] } \ No newline at end of file diff --git a/schemars/tests/expected/skip_struct_fields.json b/schemars/tests/expected/skip_struct_fields.json index 21e01a60..78902987 100644 --- a/schemars/tests/expected/skip_struct_fields.json +++ b/schemars/tests/expected/skip_struct_fields.json @@ -18,7 +18,7 @@ } }, "required": [ - "included", - "writable" + "writable", + "included" ] } \ No newline at end of file diff --git a/schemars/tests/expected/struct-normal-additional-properties.json b/schemars/tests/expected/struct-normal-additional-properties.json index ed8602a9..5ff105c7 100644 --- a/schemars/tests/expected/struct-normal-additional-properties.json +++ b/schemars/tests/expected/struct-normal-additional-properties.json @@ -19,7 +19,7 @@ }, "additionalProperties": false, "required": [ - "bar", - "foo" + "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 e6fe68b9..0a5a0afb 100644 --- a/schemars/tests/expected/struct-normal.json +++ b/schemars/tests/expected/struct-normal.json @@ -18,7 +18,7 @@ } }, "required": [ - "bar", - "foo" + "foo", + "bar" ] } \ No newline at end of file diff --git a/schemars/tests/expected/validate.json b/schemars/tests/expected/validate.json index 38aabccc..2d6e43c5 100644 --- a/schemars/tests/expected/validate.json +++ b/schemars/tests/expected/validate.json @@ -31,14 +31,14 @@ "min_max": { "type": "number", "format": "float", - "maximum": 100.0, + "maximum": 100, "minimum": 0.01 }, "min_max2": { "type": "number", "format": "float", - "maximum": 1000.0, - "minimum": 1.0 + "maximum": 1000, + "minimum": 1 }, "non_empty_str": { "type": "string", @@ -84,21 +84,21 @@ } }, "required": [ + "min_max", + "min_max2", + "regex_str1", + "regex_str2", + "regex_str3", "contains_str1", "contains_str2", "email_address", + "tel", "homepage", - "map_contains", - "min_max", - "min_max2", "non_empty_str", "non_empty_str2", "pair", - "regex_str1", - "regex_str2", - "regex_str3", + "map_contains", "required_option", - "tel", "x" ] } \ No newline at end of file diff --git a/schemars/tests/expected/validate_inner.json b/schemars/tests/expected/validate_inner.json index ffe67923..f8a7eca1 100644 --- a/schemars/tests/expected/validate_inner.json +++ b/schemars/tests/expected/validate_inner.json @@ -25,8 +25,8 @@ "items": { "type": "integer", "format": "int32", - "maximum": 10.0, - "minimum": -10.0 + "maximum": 10, + "minimum": -10 } }, "vec_str_length": { @@ -65,10 +65,10 @@ "required": [ "array_str_length", "slice_str_contains", - "vec_i32_range", + "vec_str_regex", "vec_str_length", "vec_str_length2", - "vec_str_regex", - "vec_str_url" + "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 796aecde..4c1146e8 100644 --- a/schemars/tests/expected/validate_newtype.json +++ b/schemars/tests/expected/validate_newtype.json @@ -3,6 +3,6 @@ "title": "NewType", "type": "integer", "format": "uint8", - "maximum": 10.0, - "minimum": 0.0 + "maximum": 10, + "minimum": 0 } \ 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 438b4aae..fa2dbcda 100644 --- a/schemars/tests/expected/validate_schemars_attrs.json +++ b/schemars/tests/expected/validate_schemars_attrs.json @@ -31,14 +31,14 @@ "min_max": { "type": "number", "format": "float", - "maximum": 100.0, + "maximum": 100, "minimum": 0.01 }, "min_max2": { "type": "number", "format": "float", - "maximum": 1000.0, - "minimum": 1.0 + "maximum": 1000, + "minimum": 1 }, "non_empty_str": { "type": "string", @@ -84,21 +84,21 @@ } }, "required": [ + "min_max", + "min_max2", + "regex_str1", + "regex_str2", + "regex_str3", "contains_str1", "contains_str2", "email_address", + "tel", "homepage", - "map_contains", - "min_max", - "min_max2", "non_empty_str", "non_empty_str2", "pair", - "regex_str1", - "regex_str2", - "regex_str3", + "map_contains", "required_option", - "tel", "x" ] } \ No newline at end of file diff --git a/schemars/tests/expected/validate_tuple.json b/schemars/tests/expected/validate_tuple.json index 8ab6eaa6..619b8c4d 100644 --- a/schemars/tests/expected/validate_tuple.json +++ b/schemars/tests/expected/validate_tuple.json @@ -6,8 +6,8 @@ { "type": "integer", "format": "uint8", - "maximum": 10.0, - "minimum": 0.0 + "maximum": 10, + "minimum": 0 }, { "type": "boolean" diff --git a/schemars/tests/util/mod.rs b/schemars/tests/util/mod.rs index 99cf6775..3376c023 100644 --- a/schemars/tests/util/mod.rs +++ b/schemars/tests/util/mod.rs @@ -1,5 +1,4 @@ use pretty_assertions::assert_eq; -use schemars::visit::Visitor; use schemars::{gen::SchemaSettings, schema_for, JsonSchema, Schema}; use std::error::Error; use std::fs; @@ -19,15 +18,6 @@ pub fn test_default_generated_schema(file: &str) -> TestResult { } pub fn test_schema(actual: &Schema, file: &str) -> TestResult { - // TEMP for easier comparison of schemas handling changes that don't actually affect a schema: - // - `required` ordering has changed - // - previously `f64` properties may now be integers - let actual = &{ - let mut actual = actual.clone(); - TempFixupForTests.visit_schema(&mut actual); - actual - }; - let expected_json = match fs::read_to_string(format!("tests/expected/{}.json", file)) { Ok(j) => j, Err(e) => { @@ -50,25 +40,3 @@ fn write_actual_to_file(schema: &Schema, file: &str) -> TestResult { fs::write(format!("tests/actual/{}.json", file), actual_json)?; Ok(()) } - -struct TempFixupForTests; - -impl schemars::visit::Visitor for TempFixupForTests { - fn visit_schema(&mut self, schema: &mut Schema) { - schemars::visit::visit_schema(self, schema); - - if let Some(object) = schema.as_object_mut() { - if let Some(serde_json::Value::Array(required)) = object.get_mut("required") { - required.sort_unstable_by(|a, b| a.as_str().cmp(&b.as_str())); - } - - for (key, value) in object { - if key == "multipleOf" || key.ends_with("aximum") || key.ends_with("inimum") { - if let Some(f) = value.as_f64() { - *value = f.into(); - } - } - } - } - } -} diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index 7a1de816..63571724 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -434,73 +434,68 @@ 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>(&mut schema, #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 { @@ -510,17 +505,16 @@ fn expr_for_struct( } else { TokenStream::new() }; - quote! { - { - #set_container_default - let mut schema = schemars::json_schema!({ - "type": "object", - #set_additional_properties - }); - #(#properties)* - schema #(.flatten(#flattens))* - } - } + + quote! ({ + #set_container_default + let mut schema = schemars::json_schema!({ + "type": "object", + #set_additional_properties + }); + #(#properties)* + schema + }) } fn field_default_expr(field: &Field, container_has_default: bool) -> Option { From d3b6ff5aebb25be88af9f7eecbd5eb1740de3a23 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sat, 18 May 2024 21:55:05 +0100 Subject: [PATCH 10/40] Re-add `preserve_order` feature, to preserve order of struct fields in a schema's `properties` --- Cargo.lock | 1 + schemars/Cargo.toml | 1 + schemars/src/gen.rs | 47 +++----- schemars/src/schema.rs | 3 +- schemars/tests/expected/bytes.json | 4 +- schemars/tests/expected/chrono-types.json | 24 ++-- schemars/tests/expected/crate_alias.json | 6 +- schemars/tests/expected/default.json | 20 ++-- schemars/tests/expected/deprecated-enum.json | 18 +-- .../tests/expected/deprecated-struct.json | 12 +- .../tests/expected/doc_comments_enum.json | 4 +- .../expected/duration_and_systemtime.json | 16 +-- .../expected/enum-adjacent-tagged-duf.json | 110 +++++++++--------- .../tests/expected/enum-adjacent-tagged.json | 78 ++++++------- .../tests/expected/enum-external-duf.json | 46 ++++---- schemars/tests/expected/enum-external.json | 46 ++++---- .../tests/expected/enum-internal-duf.json | 24 ++-- schemars/tests/expected/enum-internal.json | 12 +- .../expected/enum-simple-internal-duf.json | 12 +- .../tests/expected/enum-untagged-duf.json | 22 ++-- schemars/tests/expected/enum-untagged.json | 22 ++-- schemars/tests/expected/enumset.json | 2 +- schemars/tests/expected/examples.json | 28 ++--- schemars/tests/expected/flatten.json | 12 +- schemars/tests/expected/from_json_value.json | 32 ++--- .../tests/expected/from_value_2019_09.json | 56 ++++----- .../tests/expected/from_value_draft07.json | 56 ++++----- .../tests/expected/from_value_openapi3.json | 58 ++++----- schemars/tests/expected/indexmap.json | 4 +- schemars/tests/expected/macro_built_enum.json | 4 +- .../tests/expected/macro_built_struct.json | 8 +- schemars/tests/expected/nonzero_ints.json | 16 +-- schemars/tests/expected/os_strings.json | 4 +- schemars/tests/expected/range.json | 80 ++++++------- schemars/tests/expected/remote_derive.json | 32 ++--- .../tests/expected/remote_derive_generic.json | 34 +++--- schemars/tests/expected/result.json | 24 ++-- .../tests/expected/schema-name-custom.json | 6 +- .../tests/expected/schema-name-default.json | 6 +- .../expected/schema-name-mixed-generics.json | 36 +++--- .../expected/schema_settings-2019_09.json | 26 ++--- .../expected/schema_settings-openapi3.json | 22 ++-- schemars/tests/expected/schema_settings.json | 26 ++--- .../schema_with-enum-adjacent-tagged.json | 40 +++---- .../expected/schema_with-enum-external.json | 20 ++-- .../expected/schema_with-enum-untagged.json | 4 +- .../tests/expected/schema_with-struct.json | 6 +- .../tests/expected/schema_with-tuple.json | 4 +- .../tests/expected/skip_enum_variants.json | 4 +- .../tests/expected/skip_struct_fields.json | 10 +- .../tests/expected/skip_tuple_fields.json | 4 +- .../struct-normal-additional-properties.json | 8 +- schemars/tests/expected/struct-normal.json | 8 +- schemars/tests/expected/struct-tuple.json | 4 +- .../tests/expected/transparent-struct.json | 4 +- schemars/tests/expected/validate.json | 84 ++++++------- schemars/tests/expected/validate_inner.json | 40 +++---- schemars/tests/expected/validate_newtype.json | 4 +- .../expected/validate_schemars_attrs.json | 84 ++++++------- schemars/tests/expected/validate_tuple.json | 8 +- 60 files changed, 710 insertions(+), 726 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 757013a3..c415a5f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -381,6 +381,7 @@ version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ + "indexmap", "itoa", "ryu", "serde", diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 47dc643a..ec9ff005 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -44,6 +44,7 @@ serde = { version = "1.0", features = ["derive"] } default = ["derive"] derive = ["schemars_derive"] +preserve_order = ["serde_json/preserve_order"] raw_value = ["serde_json/raw_value"] diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index c06f85a2..678ee419 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -11,8 +11,9 @@ use crate::Schema; use crate::{visit::*, JsonSchema}; use dyn_clone::DynClone; use serde::Serialize; +use serde_json::{Map, Value}; use std::borrow::Cow; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; use std::{any::Any, collections::HashSet, fmt::Debug}; /// Settings to customize how Schemas are generated. @@ -149,7 +150,7 @@ impl SchemaSettings { #[derive(Debug, Default)] pub struct SchemaGenerator { settings: SchemaSettings, - definitions: BTreeMap, + definitions: Map, pending_schema_ids: HashSet>, schema_id_to_name: HashMap, String>, used_schema_names: HashSet, @@ -254,31 +255,31 @@ impl SchemaGenerator { let schema = self.json_schema_internal::(id); - self.definitions.insert(name, schema); + self.definitions.insert(name, schema.to_value()); } /// Borrows the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated. /// - /// The keys of the returned `BTreeMap` are the [schema names](JsonSchema::schema_name), and the values are the schemas + /// The keys of the returned `Map` are the [schema names](JsonSchema::schema_name), and the values are the schemas /// themselves. - pub fn definitions(&self) -> &BTreeMap { + pub fn definitions(&self) -> &Map { &self.definitions } /// Mutably borrows the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated. /// - /// The keys of the returned `BTreeMap` are the [schema names](JsonSchema::schema_name), and the values are the schemas + /// 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 BTreeMap { + 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. /// - /// The keys of the returned `BTreeMap` are the [schema names](JsonSchema::schema_name), and the values are the schemas + /// 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) -> BTreeMap { + pub fn take_definitions(&mut self) -> Map { std::mem::take(&mut self.definitions) } @@ -308,12 +309,7 @@ impl SchemaGenerator { if !self.definitions.is_empty() { object.insert( "definitions".into(), - serde_json::Value::Object( - self.definitions - .iter() - .map(|(k, v)| (k.clone(), v.clone().into())) - .collect(), - ), + serde_json::Value::Object(self.definitions.clone()), ); } @@ -344,12 +340,7 @@ impl SchemaGenerator { if !self.definitions.is_empty() { object.insert( "definitions".into(), - serde_json::Value::Object( - self.definitions - .into_iter() - .map(|(k, v)| (k, v.into())) - .collect(), - ), + serde_json::Value::Object(self.definitions), ); } @@ -386,12 +377,7 @@ impl SchemaGenerator { if !self.definitions.is_empty() { object.insert( "definitions".into(), - serde_json::Value::Object( - self.definitions - .iter() - .map(|(k, v)| (k.clone(), v.clone().into())) - .collect(), - ), + serde_json::Value::Object(self.definitions.clone()), ); } @@ -428,12 +414,7 @@ impl SchemaGenerator { if !self.definitions.is_empty() { object.insert( "definitions".into(), - serde_json::Value::Object( - self.definitions - .into_iter() - .map(|(k, v)| (k, v.into())) - .collect(), - ), + serde_json::Value::Object(self.definitions), ); } diff --git a/schemars/src/schema.rs b/schemars/src/schema.rs index 82be8211..27cb0480 100644 --- a/schemars/src/schema.rs +++ b/schemars/src/schema.rs @@ -180,7 +180,8 @@ mod ser { use serde_json::Value; // 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. + // 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", diff --git a/schemars/tests/expected/bytes.json b/schemars/tests/expected/bytes.json index 06458c6a..8618a7f9 100644 --- a/schemars/tests/expected/bytes.json +++ b/schemars/tests/expected/bytes.json @@ -20,6 +20,6 @@ } } ], - "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 b9e333da..ce03621c 100644 --- a/schemars/tests/expected/chrono-types.json +++ b/schemars/tests/expected/chrono-types.json @@ -3,6 +3,18 @@ "title": "ChronoTypes", "type": "object", "properties": { + "weekday": { + "type": "string", + "enum": [ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun" + ] + }, "date_time": { "type": "string", "format": "date-time" @@ -18,18 +30,6 @@ "naive_time": { "type": "string", "format": "partial-date-time" - }, - "weekday": { - "type": "string", - "enum": [ - "Mon", - "Tue", - "Wed", - "Thu", - "Fri", - "Sat", - "Sun" - ] } }, "required": [ diff --git a/schemars/tests/expected/crate_alias.json b/schemars/tests/expected/crate_alias.json index b01fc0dd..e88a449b 100644 --- a/schemars/tests/expected/crate_alias.json +++ b/schemars/tests/expected/crate_alias.json @@ -3,13 +3,13 @@ "title": "Struct", "type": "object", "properties": { - "bar": { - "type": "boolean" - }, "foo": { "description": "This is a document", "type": "integer", "format": "int32" + }, + "bar": { + "type": "boolean" } }, "required": [ diff --git a/schemars/tests/expected/default.json b/schemars/tests/expected/default.json index 36f16e14..88ad12ad 100644 --- a/schemars/tests/expected/default.json +++ b/schemars/tests/expected/default.json @@ -3,22 +3,22 @@ "title": "MyStruct", "type": "object", "properties": { - "my_bool": { - "type": "boolean", - "default": false - }, "my_int": { "type": "integer", "format": "int32", "default": 0 }, + "my_bool": { + "type": "boolean", + "default": false + }, "my_struct2": { + "default": "i:0 b:false", "allOf": [ { "$ref": "#/definitions/MyStruct2" } - ], - "default": "i:0 b:false" + ] }, "my_struct2_default_skipped": { "$ref": "#/definitions/MyStruct2" @@ -31,14 +31,14 @@ "MyStruct2": { "type": "object", "properties": { - "my_bool": { - "type": "boolean", - "default": true - }, "my_int": { "type": "integer", "format": "int32", "default": 6 + }, + "my_bool": { + "type": "boolean", + "default": true } } }, diff --git a/schemars/tests/expected/deprecated-enum.json b/schemars/tests/expected/deprecated-enum.json index ee3de616..e8711606 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#", "title": "DeprecatedEnum", - "deprecated": true, "oneOf": [ { "type": "string", @@ -20,13 +19,13 @@ "DeprecatedStructVariant": { "type": "object", "properties": { - "deprecated_field": { - "type": "boolean", - "deprecated": true - }, "foo": { "type": "integer", "format": "int32" + }, + "deprecated_field": { + "type": "boolean", + "deprecated": true } }, "required": [ @@ -35,11 +34,12 @@ ] } }, - "additionalProperties": false, - "deprecated": true, "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 d2af941d..2b25c343 100644 --- a/schemars/tests/expected/deprecated-struct.json +++ b/schemars/tests/expected/deprecated-struct.json @@ -3,18 +3,18 @@ "title": "DeprecatedStruct", "type": "object", "properties": { - "deprecated_field": { - "type": "boolean", - "deprecated": true - }, "foo": { "type": "integer", "format": "int32" + }, + "deprecated_field": { + "type": "boolean", + "deprecated": true } }, - "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 fc38db18..7d9321fd 100644 --- a/schemars/tests/expected/doc_comments_enum.json +++ b/schemars/tests/expected/doc_comments_enum.json @@ -34,10 +34,10 @@ } } }, - "additionalProperties": false, "required": [ "Complex" - ] + ], + "additionalProperties": false } ] } \ No newline at end of file diff --git a/schemars/tests/expected/duration_and_systemtime.json b/schemars/tests/expected/duration_and_systemtime.json index 71c17b7c..bb25dcd7 100644 --- a/schemars/tests/expected/duration_and_systemtime.json +++ b/schemars/tests/expected/duration_and_systemtime.json @@ -18,14 +18,14 @@ "Duration": { "type": "object", "properties": { - "nanos": { + "secs": { "type": "integer", - "format": "uint32", + "format": "uint64", "minimum": 0 }, - "secs": { + "nanos": { "type": "integer", - "format": "uint64", + "format": "uint32", "minimum": 0 } }, @@ -37,14 +37,14 @@ "SystemTime": { "type": "object", "properties": { - "nanos_since_epoch": { + "secs_since_epoch": { "type": "integer", - "format": "uint32", + "format": "uint64", "minimum": 0 }, - "secs_since_epoch": { + "nanos_since_epoch": { "type": "integer", - "format": "uint64", + "format": "uint32", "minimum": 0 } }, diff --git a/schemars/tests/expected/enum-adjacent-tagged-duf.json b/schemars/tests/expected/enum-adjacent-tagged-duf.json index f746216d..b9cb5faf 100644 --- a/schemars/tests/expected/enum-adjacent-tagged-duf.json +++ b/schemars/tests/expected/enum-adjacent-tagged-duf.json @@ -12,83 +12,89 @@ ] } }, - "additionalProperties": false, "required": [ "t" - ] + ], + "additionalProperties": false }, { "type": "object", "properties": { - "c": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, "t": { "type": "string", "enum": [ "StringMap" ] + }, + "c": { + "type": "object", + "additionalProperties": { + "type": "string" + } } }, - "additionalProperties": false, "required": [ "t", "c" - ] + ], + "additionalProperties": false }, { "type": "object", "properties": { - "c": { - "$ref": "#/definitions/UnitStruct" - }, "t": { "type": "string", "enum": [ "UnitStructNewType" ] + }, + "c": { + "$ref": "#/definitions/UnitStruct" } }, - "additionalProperties": false, "required": [ "t", "c" - ] + ], + "additionalProperties": false }, { "type": "object", "properties": { - "c": { - "$ref": "#/definitions/Struct" - }, "t": { "type": "string", "enum": [ "StructNewType" ] + }, + "c": { + "$ref": "#/definitions/Struct" } }, - "additionalProperties": false, "required": [ "t", "c" - ] + ], + "additionalProperties": false }, { "type": "object", "properties": { + "t": { + "type": "string", + "enum": [ + "Struct" + ] + }, "c": { "type": "object", "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" + }, + "bar": { + "type": "boolean" } }, "additionalProperties": false, @@ -96,23 +102,23 @@ "foo", "bar" ] - }, - "t": { - "type": "string", - "enum": [ - "Struct" - ] } }, - "additionalProperties": false, "required": [ "t", "c" - ] + ], + "additionalProperties": false }, { "type": "object", "properties": { + "t": { + "type": "string", + "enum": [ + "Tuple" + ] + }, "c": { "type": "array", "items": [ @@ -124,21 +130,15 @@ "type": "boolean" } ], - "maxItems": 2, - "minItems": 2 - }, - "t": { - "type": "string", - "enum": [ - "Tuple" - ] + "minItems": 2, + "maxItems": 2 } }, - "additionalProperties": false, "required": [ "t", "c" - ] + ], + "additionalProperties": false }, { "type": "object", @@ -150,51 +150,51 @@ ] } }, - "additionalProperties": false, "required": [ "t" - ] + ], + "additionalProperties": false }, { "type": "object", "properties": { - "c": { - "type": "integer", - "format": "int32" - }, "t": { "type": "string", "enum": [ "WithInt" ] + }, + "c": { + "type": "integer", + "format": "int32" } }, - "additionalProperties": false, "required": [ "t", "c" - ] + ], + "additionalProperties": false } ], "definitions": { + "UnitStruct": { + "type": "null" + }, "Struct": { "type": "object", "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" + }, + "bar": { + "type": "boolean" } }, "required": [ "foo", "bar" ] - }, - "UnitStruct": { - "type": "null" } } } \ 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 b977be00..de777139 100644 --- a/schemars/tests/expected/enum-adjacent-tagged.json +++ b/schemars/tests/expected/enum-adjacent-tagged.json @@ -19,17 +19,17 @@ { "type": "object", "properties": { - "c": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, "t": { "type": "string", "enum": [ "StringMap" ] + }, + "c": { + "type": "object", + "additionalProperties": { + "type": "string" + } } }, "required": [ @@ -40,14 +40,14 @@ { "type": "object", "properties": { - "c": { - "$ref": "#/definitions/UnitStruct" - }, "t": { "type": "string", "enum": [ "UnitStructNewType" ] + }, + "c": { + "$ref": "#/definitions/UnitStruct" } }, "required": [ @@ -58,14 +58,14 @@ { "type": "object", "properties": { - "c": { - "$ref": "#/definitions/Struct" - }, "t": { "type": "string", "enum": [ "StructNewType" ] + }, + "c": { + "$ref": "#/definitions/Struct" } }, "required": [ @@ -76,27 +76,27 @@ { "type": "object", "properties": { + "t": { + "type": "string", + "enum": [ + "Struct" + ] + }, "c": { "type": "object", "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" + }, + "bar": { + "type": "boolean" } }, "required": [ "foo", "bar" ] - }, - "t": { - "type": "string", - "enum": [ - "Struct" - ] } }, "required": [ @@ -107,6 +107,12 @@ { "type": "object", "properties": { + "t": { + "type": "string", + "enum": [ + "Tuple" + ] + }, "c": { "type": "array", "items": [ @@ -118,14 +124,8 @@ "type": "boolean" } ], - "maxItems": 2, - "minItems": 2 - }, - "t": { - "type": "string", - "enum": [ - "Tuple" - ] + "minItems": 2, + "maxItems": 2 } }, "required": [ @@ -150,15 +150,15 @@ { "type": "object", "properties": { - "c": { - "type": "integer", - "format": "int32" - }, "t": { "type": "string", "enum": [ "WithInt" ] + }, + "c": { + "type": "integer", + "format": "int32" } }, "required": [ @@ -168,24 +168,24 @@ } ], "definitions": { + "UnitStruct": { + "type": "null" + }, "Struct": { "type": "object", "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" + }, + "bar": { + "type": "boolean" } }, "required": [ "foo", "bar" ] - }, - "UnitStruct": { - "type": "null" } } } \ 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 0351fa20..a4835354 100644 --- a/schemars/tests/expected/enum-external-duf.json +++ b/schemars/tests/expected/enum-external-duf.json @@ -19,10 +19,10 @@ } } }, - "additionalProperties": false, "required": [ "stringMap" - ] + ], + "additionalProperties": false }, { "type": "object", @@ -31,10 +31,10 @@ "$ref": "#/definitions/UnitStruct" } }, - "additionalProperties": false, "required": [ "unitStructNewType" - ] + ], + "additionalProperties": false }, { "type": "object", @@ -43,10 +43,10 @@ "$ref": "#/definitions/Struct" } }, - "additionalProperties": false, "required": [ "structNewType" - ] + ], + "additionalProperties": false }, { "type": "object", @@ -54,12 +54,12 @@ "struct": { "type": "object", "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" + }, + "bar": { + "type": "boolean" } }, "additionalProperties": false, @@ -69,10 +69,10 @@ ] } }, - "additionalProperties": false, "required": [ "struct" - ] + ], + "additionalProperties": false }, { "type": "object", @@ -88,14 +88,14 @@ "type": "boolean" } ], - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 } }, - "additionalProperties": false, "required": [ "tuple" - ] + ], + "additionalProperties": false }, { "type": "object", @@ -105,31 +105,31 @@ "format": "int32" } }, - "additionalProperties": false, "required": [ "withInt" - ] + ], + "additionalProperties": false } ], "definitions": { + "UnitStruct": { + "type": "null" + }, "Struct": { "type": "object", "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" + }, + "bar": { + "type": "boolean" } }, "required": [ "foo", "bar" ] - }, - "UnitStruct": { - "type": "null" } } } \ No newline at end of file diff --git a/schemars/tests/expected/enum-external.json b/schemars/tests/expected/enum-external.json index 4c1d5a6d..8a1de2b0 100644 --- a/schemars/tests/expected/enum-external.json +++ b/schemars/tests/expected/enum-external.json @@ -19,10 +19,10 @@ } } }, - "additionalProperties": false, "required": [ "stringMap" - ] + ], + "additionalProperties": false }, { "type": "object", @@ -31,10 +31,10 @@ "$ref": "#/definitions/UnitStruct" } }, - "additionalProperties": false, "required": [ "unitStructNewType" - ] + ], + "additionalProperties": false }, { "type": "object", @@ -43,10 +43,10 @@ "$ref": "#/definitions/Struct" } }, - "additionalProperties": false, "required": [ "structNewType" - ] + ], + "additionalProperties": false }, { "type": "object", @@ -54,12 +54,12 @@ "struct": { "type": "object", "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" + }, + "bar": { + "type": "boolean" } }, "required": [ @@ -68,10 +68,10 @@ ] } }, - "additionalProperties": false, "required": [ "struct" - ] + ], + "additionalProperties": false }, { "type": "object", @@ -87,14 +87,14 @@ "type": "boolean" } ], - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 } }, - "additionalProperties": false, "required": [ "tuple" - ] + ], + "additionalProperties": false }, { "type": "object", @@ -104,31 +104,31 @@ "format": "int32" } }, - "additionalProperties": false, "required": [ "withInt" - ] + ], + "additionalProperties": false } ], "definitions": { + "UnitStruct": { + "type": "null" + }, "Struct": { "type": "object", "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" + }, + "bar": { + "type": "boolean" } }, "required": [ "foo", "bar" ] - }, - "UnitStruct": { - "type": "null" } } } \ 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 9c2aa9cd..de51ba14 100644 --- a/schemars/tests/expected/enum-internal-duf.json +++ b/schemars/tests/expected/enum-internal-duf.json @@ -10,10 +10,10 @@ "const": "UnitOne" } }, - "additionalProperties": false, "required": [ "typeProperty" - ] + ], + "additionalProperties": false }, { "type": "object", @@ -38,21 +38,21 @@ "const": "UnitStructNewType" } }, - "additionalProperties": false, "required": [ "typeProperty" - ] + ], + "additionalProperties": false }, { "type": "object", "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" }, + "bar": { + "type": "boolean" + }, "typeProperty": { "type": "string", "const": "StructNewType" @@ -67,13 +67,13 @@ { "type": "object", "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" }, + "bar": { + "type": "boolean" + }, "typeProperty": { "type": "string", "const": "Struct" @@ -94,10 +94,10 @@ "const": "UnitTwo" } }, - "additionalProperties": false, "required": [ "typeProperty" - ] + ], + "additionalProperties": false }, { "type": "object", diff --git a/schemars/tests/expected/enum-internal.json b/schemars/tests/expected/enum-internal.json index 7fd931c0..a5dffe3d 100644 --- a/schemars/tests/expected/enum-internal.json +++ b/schemars/tests/expected/enum-internal.json @@ -44,13 +44,13 @@ { "type": "object", "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" }, + "bar": { + "type": "boolean" + }, "typeProperty": { "type": "string", "const": "StructNewType" @@ -65,13 +65,13 @@ { "type": "object", "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" }, + "bar": { + "type": "boolean" + }, "typeProperty": { "type": "string", "const": "Struct" diff --git a/schemars/tests/expected/enum-simple-internal-duf.json b/schemars/tests/expected/enum-simple-internal-duf.json index 4fdf04e3..7fc20c19 100644 --- a/schemars/tests/expected/enum-simple-internal-duf.json +++ b/schemars/tests/expected/enum-simple-internal-duf.json @@ -10,10 +10,10 @@ "const": "A" } }, - "additionalProperties": false, "required": [ "typeProperty" - ] + ], + "additionalProperties": false }, { "type": "object", @@ -23,10 +23,10 @@ "const": "B" } }, - "additionalProperties": false, "required": [ "typeProperty" - ] + ], + "additionalProperties": false }, { "type": "object", @@ -36,10 +36,10 @@ "const": "C" } }, - "additionalProperties": false, "required": [ "typeProperty" - ] + ], + "additionalProperties": false } ] } \ 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 e3f7c66d..13a7d0ad 100644 --- a/schemars/tests/expected/enum-untagged-duf.json +++ b/schemars/tests/expected/enum-untagged-duf.json @@ -20,12 +20,12 @@ { "type": "object", "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" + }, + "bar": { + "type": "boolean" } }, "additionalProperties": false, @@ -45,8 +45,8 @@ "type": "boolean" } ], - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 }, { "type": "integer", @@ -54,24 +54,24 @@ } ], "definitions": { + "UnitStruct": { + "type": "null" + }, "Struct": { "type": "object", "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" + }, + "bar": { + "type": "boolean" } }, "required": [ "foo", "bar" ] - }, - "UnitStruct": { - "type": "null" } } } \ No newline at end of file diff --git a/schemars/tests/expected/enum-untagged.json b/schemars/tests/expected/enum-untagged.json index 2b23ffb3..ed72b43a 100644 --- a/schemars/tests/expected/enum-untagged.json +++ b/schemars/tests/expected/enum-untagged.json @@ -20,12 +20,12 @@ { "type": "object", "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" + }, + "bar": { + "type": "boolean" } }, "required": [ @@ -44,8 +44,8 @@ "type": "boolean" } ], - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 }, { "type": "integer", @@ -53,24 +53,24 @@ } ], "definitions": { + "UnitStruct": { + "type": "null" + }, "Struct": { "type": "object", "properties": { - "bar": { - "type": "boolean" - }, "foo": { "type": "integer", "format": "int32" + }, + "bar": { + "type": "boolean" } }, "required": [ "foo", "bar" ] - }, - "UnitStruct": { - "type": "null" } } } \ No newline at end of file diff --git a/schemars/tests/expected/enumset.json b/schemars/tests/expected/enumset.json index b4dc26a6..0f9baa26 100644 --- a/schemars/tests/expected/enumset.json +++ b/schemars/tests/expected/enumset.json @@ -2,10 +2,10 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Set_of_Foo", "type": "array", + "uniqueItems": true, "items": { "$ref": "#/definitions/Foo" }, - "uniqueItems": true, "definitions": { "Foo": { "type": "string", diff --git a/schemars/tests/expected/examples.json b/schemars/tests/expected/examples.json index 3203c4a8..9f0a5053 100644 --- a/schemars/tests/expected/examples.json +++ b/schemars/tests/expected/examples.json @@ -3,6 +3,14 @@ "title": "Struct", "type": "object", "properties": { + "foo": { + "type": "integer", + "format": "int32", + "examples": [ + 8, + null + ] + }, "bar": { "type": "boolean" }, @@ -14,26 +22,18 @@ "examples": [ null ] - }, - "foo": { - "type": "integer", - "format": "int32", - "examples": [ - 8, - null - ] } }, + "required": [ + "foo", + "bar" + ], "examples": [ { + "foo": 0, "bar": false, - "baz": null, - "foo": 0 + "baz": null }, null - ], - "required": [ - "foo", - "bar" ] } \ No newline at end of file diff --git a/schemars/tests/expected/flatten.json b/schemars/tests/expected/flatten.json index 8f77dd2e..4ea70944 100644 --- a/schemars/tests/expected/flatten.json +++ b/schemars/tests/expected/flatten.json @@ -3,20 +3,20 @@ "title": "Flat", "type": "object", "properties": { - "b": { - "type": "boolean" - }, "f": { "type": "number", "format": "float" }, - "os": { - "type": "string", - "default": "" + "b": { + "type": "boolean" }, "s": { "type": "string" }, + "os": { + "type": "string", + "default": "" + }, "v": { "type": "array", "items": { diff --git a/schemars/tests/expected/from_json_value.json b/schemars/tests/expected/from_json_value.json index cd1c6349..6cc1ecc9 100644 --- a/schemars/tests/expected/from_json_value.json +++ b/schemars/tests/expected/from_json_value.json @@ -2,12 +2,21 @@ "$schema": "http://json-schema.org/draft-07/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", @@ -19,31 +28,22 @@ } } } - }, - "one": { - "type": "integer" - }, - "zero": { - "type": "integer" - }, - "zeroPointZero": { - "type": "number" } }, "examples": [ { - "bool": true, + "zero": 0, + "one": 1, "minusOne": -1, + "zeroPointZero": 0.0, + "bool": true, "null": null, "object": { "array": [ "foo", "bar" ] - }, - "one": 1, - "zero": 0, - "zeroPointZero": 0.0 + } } ] } \ 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 4c6adcfe..52c05243 100644 --- a/schemars/tests/expected/from_value_2019_09.json +++ b/schemars/tests/expected/from_value_2019_09.json @@ -3,12 +3,28 @@ "title": "MyStruct", "type": "object", "properties": { + "myInt": { + "type": "integer" + }, "myBool": { "type": "boolean" }, + "myNullableEnum": true, "myInnerStruct": { "type": "object", "properties": { + "my_map": { + "type": "object", + "additionalProperties": { + "type": "number" + } + }, + "my_vec": { + "type": "array", + "items": { + "type": "string" + } + }, "my_empty_map": { "type": "object", "additionalProperties": true @@ -17,19 +33,13 @@ "type": "array", "items": true }, - "my_map": { - "type": "object", - "additionalProperties": { - "type": "number" - } - }, "my_tuple": { "type": "array", "items": [ { "type": "string", - "maxLength": 1, - "minLength": 1 + "minLength": 1, + "maxLength": 1 }, { "type": "integer" @@ -37,40 +47,30 @@ ], "maxItems": 2, "minItems": 2 - }, - "my_vec": { - "type": "array", - "items": { - "type": "string" - } } } - }, - "myInt": { - "type": "integer" - }, - "myNullableEnum": true + } }, "examples": [ { + "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/from_value_draft07.json b/schemars/tests/expected/from_value_draft07.json index 5d87f028..de89fada 100644 --- a/schemars/tests/expected/from_value_draft07.json +++ b/schemars/tests/expected/from_value_draft07.json @@ -3,12 +3,28 @@ "title": "MyStruct", "type": "object", "properties": { + "myInt": { + "type": "integer" + }, "myBool": { "type": "boolean" }, + "myNullableEnum": true, "myInnerStruct": { "type": "object", "properties": { + "my_map": { + "type": "object", + "additionalProperties": { + "type": "number" + } + }, + "my_vec": { + "type": "array", + "items": { + "type": "string" + } + }, "my_empty_map": { "type": "object", "additionalProperties": true @@ -17,19 +33,13 @@ "type": "array", "items": true }, - "my_map": { - "type": "object", - "additionalProperties": { - "type": "number" - } - }, "my_tuple": { "type": "array", "items": [ { "type": "string", - "maxLength": 1, - "minLength": 1 + "minLength": 1, + "maxLength": 1 }, { "type": "integer" @@ -37,40 +47,30 @@ ], "maxItems": 2, "minItems": 2 - }, - "my_vec": { - "type": "array", - "items": { - "type": "string" - } } } - }, - "myInt": { - "type": "integer" - }, - "myNullableEnum": true + } }, "examples": [ { + "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/from_value_openapi3.json b/schemars/tests/expected/from_value_openapi3.json index 04059354..4e9dd2cc 100644 --- a/schemars/tests/expected/from_value_openapi3.json +++ b/schemars/tests/expected/from_value_openapi3.json @@ -3,12 +3,30 @@ "title": "MyStruct", "type": "object", "properties": { + "myInt": { + "type": "integer" + }, "myBool": { "type": "boolean" }, + "myNullableEnum": { + "nullable": true + }, "myInnerStruct": { "type": "object", "properties": { + "my_map": { + "type": "object", + "additionalProperties": { + "type": "number" + } + }, + "my_vec": { + "type": "array", + "items": { + "type": "string" + } + }, "my_empty_map": { "type": "object", "additionalProperties": true @@ -17,19 +35,13 @@ "type": "array", "items": {} }, - "my_map": { - "type": "object", - "additionalProperties": { - "type": "number" - } - }, "my_tuple": { "type": "array", "items": [ { "type": "string", - "maxLength": 1, - "minLength": 1 + "minLength": 1, + "maxLength": 1 }, { "type": "integer" @@ -37,40 +49,28 @@ ], "maxItems": 2, "minItems": 2 - }, - "my_vec": { - "type": "array", - "items": { - "type": "string" - } } } - }, - "myInt": { - "type": "integer" - }, - "myNullableEnum": { - "nullable": true } }, "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 9c209e62..318bf9bf 100644 --- a/schemars/tests/expected/indexmap.json +++ b/schemars/tests/expected/indexmap.json @@ -11,11 +11,11 @@ }, "set": { "type": "array", + "uniqueItems": true, "items": { "type": "integer", "format": "int" - }, - "uniqueItems": true + } } }, "required": [ diff --git a/schemars/tests/expected/macro_built_enum.json b/schemars/tests/expected/macro_built_enum.json index 51a9bb73..4fedbb2c 100644 --- a/schemars/tests/expected/macro_built_enum.json +++ b/schemars/tests/expected/macro_built_enum.json @@ -9,10 +9,10 @@ "$ref": "#/definitions/InnerStruct" } }, - "additionalProperties": false, "required": [ "InnerStruct" - ] + ], + "additionalProperties": false } ], "definitions": { diff --git a/schemars/tests/expected/macro_built_struct.json b/schemars/tests/expected/macro_built_struct.json index e958c8ef..74a279c8 100644 --- a/schemars/tests/expected/macro_built_struct.json +++ b/schemars/tests/expected/macro_built_struct.json @@ -3,14 +3,14 @@ "title": "A", "type": "object", "properties": { - "v": { - "type": "integer", - "format": "int32" - }, "x": { "type": "integer", "format": "uint8", "minimum": 0 + }, + "v": { + "type": "integer", + "format": "int32" } }, "required": [ diff --git a/schemars/tests/expected/nonzero_ints.json b/schemars/tests/expected/nonzero_ints.json index 6ee51057..97dee99d 100644 --- a/schemars/tests/expected/nonzero_ints.json +++ b/schemars/tests/expected/nonzero_ints.json @@ -3,12 +3,10 @@ "title": "MyStruct", "type": "object", "properties": { - "nonzero_signed": { + "unsigned": { "type": "integer", - "format": "int32", - "not": { - "const": 0 - } + "format": "uint32", + "minimum": 0 }, "nonzero_unsigned": { "type": "integer", @@ -19,10 +17,12 @@ "type": "integer", "format": "int32" }, - "unsigned": { + "nonzero_signed": { "type": "integer", - "format": "uint32", - "minimum": 0 + "format": "int32", + "not": { + "const": 0 + } } }, "required": [ diff --git a/schemars/tests/expected/os_strings.json b/schemars/tests/expected/os_strings.json index 927f3f93..b422026c 100644 --- a/schemars/tests/expected/os_strings.json +++ b/schemars/tests/expected/os_strings.json @@ -3,10 +3,10 @@ "title": "OsStrings", "type": "object", "properties": { - "borrowed": { + "owned": { "$ref": "#/definitions/OsString" }, - "owned": { + "borrowed": { "$ref": "#/definitions/OsString" } }, diff --git a/schemars/tests/expected/range.json b/schemars/tests/expected/range.json index 19e47806..25f7389c 100644 --- a/schemars/tests/expected/range.json +++ b/schemars/tests/expected/range.json @@ -3,14 +3,14 @@ "title": "MyStruct", "type": "object", "properties": { - "bound": { - "$ref": "#/definitions/Bound_of_string" + "range": { + "$ref": "#/definitions/Range_of_uint" }, "inclusive": { "$ref": "#/definitions/Range_of_double" }, - "range": { - "$ref": "#/definitions/Range_of_uint" + "bound": { + "$ref": "#/definitions/Bound_of_string" } }, "required": [ @@ -19,6 +19,42 @@ "bound" ], "definitions": { + "Range_of_uint": { + "type": "object", + "properties": { + "start": { + "type": "integer", + "format": "uint", + "minimum": 0 + }, + "end": { + "type": "integer", + "format": "uint", + "minimum": 0 + } + }, + "required": [ + "start", + "end" + ] + }, + "Range_of_double": { + "type": "object", + "properties": { + "start": { + "type": "number", + "format": "double" + }, + "end": { + "type": "number", + "format": "double" + } + }, + "required": [ + "start", + "end" + ] + }, "Bound_of_string": { "oneOf": [ { @@ -48,42 +84,6 @@ "const": "Unbounded" } ] - }, - "Range_of_double": { - "type": "object", - "properties": { - "end": { - "type": "number", - "format": "double" - }, - "start": { - "type": "number", - "format": "double" - } - }, - "required": [ - "start", - "end" - ] - }, - "Range_of_uint": { - "type": "object", - "properties": { - "end": { - "type": "integer", - "format": "uint", - "minimum": 0 - }, - "start": { - "type": "integer", - "format": "uint", - "minimum": 0 - } - }, - "required": [ - "start", - "end" - ] } } } \ No newline at end of file diff --git a/schemars/tests/expected/remote_derive.json b/schemars/tests/expected/remote_derive.json index 9477e0ff..0f27ad36 100644 --- a/schemars/tests/expected/remote_derive.json +++ b/schemars/tests/expected/remote_derive.json @@ -6,27 +6,27 @@ "command_line": { "type": "string" }, - "system_cpu_time": { + "wall_time": { + "$ref": "#/definitions/Duration" + }, + "user_cpu_time": { + "default": { + "secs": 0, + "nanos": 0 + }, "allOf": [ { "$ref": "#/definitions/Duration" } - ], - "default": "0.000000000s" + ] }, - "user_cpu_time": { + "system_cpu_time": { + "default": "0.000000000s", "allOf": [ { "$ref": "#/definitions/Duration" } - ], - "default": { - "nanos": 0, - "secs": 0 - } - }, - "wall_time": { - "$ref": "#/definitions/Duration" + ] } }, "required": [ @@ -37,13 +37,13 @@ "Duration": { "type": "object", "properties": { - "nanos": { - "type": "integer", - "format": "int32" - }, "secs": { "type": "integer", "format": "int64" + }, + "nanos": { + "type": "integer", + "format": "int32" } }, "required": [ diff --git a/schemars/tests/expected/remote_derive_generic.json b/schemars/tests/expected/remote_derive_generic.json index b533320b..f6ba3ef4 100644 --- a/schemars/tests/expected/remote_derive_generic.json +++ b/schemars/tests/expected/remote_derive_generic.json @@ -6,21 +6,21 @@ "byte_or_bool2": { "$ref": "#/definitions/Or_for_uint8_and_boolean" }, + "unit_or_t2": { + "$ref": "#/definitions/Or_for_null_and_int32" + }, + "s": { + "$ref": "#/definitions/Str" + }, "fake_map": { "type": "object", "additionalProperties": { "type": "array", + "uniqueItems": true, "items": { "type": "string" - }, - "uniqueItems": true + } } - }, - "s": { - "$ref": "#/definitions/Str" - }, - "unit_or_t2": { - "$ref": "#/definitions/Or_for_null_and_int32" } }, "required": [ @@ -30,26 +30,26 @@ "fake_map" ], "definitions": { - "Or_for_null_and_int32": { + "Or_for_uint8_and_boolean": { "anyOf": [ { - "type": "null" + "type": "integer", + "format": "uint8", + "minimum": 0 }, { - "type": "integer", - "format": "int32" + "type": "boolean" } ] }, - "Or_for_uint8_and_boolean": { + "Or_for_null_and_int32": { "anyOf": [ { - "type": "integer", - "format": "uint8", - "minimum": 0 + "type": "null" }, { - "type": "boolean" + "type": "integer", + "format": "int32" } ] }, diff --git a/schemars/tests/expected/result.json b/schemars/tests/expected/result.json index 44c079e4..8a25a0ea 100644 --- a/schemars/tests/expected/result.json +++ b/schemars/tests/expected/result.json @@ -15,18 +15,6 @@ "result2" ], "definitions": { - "MyStruct": { - "type": "object", - "properties": { - "foo": { - "type": "integer", - "format": "int32" - } - }, - "required": [ - "foo" - ] - }, "Result_of_MyStruct_or_Array_of_string": { "oneOf": [ { @@ -56,6 +44,18 @@ } ] }, + "MyStruct": { + "type": "object", + "properties": { + "foo": { + "type": "integer", + "format": "int32" + } + }, + "required": [ + "foo" + ] + }, "Result_of_boolean_or_null": { "oneOf": [ { diff --git a/schemars/tests/expected/schema-name-custom.json b/schemars/tests/expected/schema-name-custom.json index dc5336ba..447edbd9 100644 --- a/schemars/tests/expected/schema-name-custom.json +++ b/schemars/tests/expected/schema-name-custom.json @@ -3,9 +3,6 @@ "title": "a-new-name-Array_of_string-int32-int32", "type": "object", "properties": { - "inner": { - "$ref": "#/definitions/another-new-name" - }, "t": { "type": "integer", "format": "int32" @@ -21,6 +18,9 @@ "items": { "type": "string" } + }, + "inner": { + "$ref": "#/definitions/another-new-name" } }, "required": [ diff --git a/schemars/tests/expected/schema-name-default.json b/schemars/tests/expected/schema-name-default.json index 7524c633..bd2ab9a2 100644 --- a/schemars/tests/expected/schema-name-default.json +++ b/schemars/tests/expected/schema-name-default.json @@ -3,9 +3,6 @@ "title": "MyStruct_for_int32_and_null_and_boolean_and_Array_of_string", "type": "object", "properties": { - "inner": { - "$ref": "#/definitions/MySimpleStruct" - }, "t": { "type": "integer", "format": "int32" @@ -21,6 +18,9 @@ "items": { "type": "string" } + }, + "inner": { + "$ref": "#/definitions/MySimpleStruct" } }, "required": [ diff --git a/schemars/tests/expected/schema-name-mixed-generics.json b/schemars/tests/expected/schema-name-mixed-generics.json index fdadbab7..43424108 100644 --- a/schemars/tests/expected/schema-name-mixed-generics.json +++ b/schemars/tests/expected/schema-name-mixed-generics.json @@ -3,12 +3,12 @@ "title": "MixedGenericStruct_for_MyStruct_for_int32_and_null_and_boolean_and_Array_of_string_and_42_and_z", "type": "object", "properties": { + "generic": { + "$ref": "#/definitions/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" } }, "required": [ @@ -16,24 +16,9 @@ "foo" ], "definitions": { - "MySimpleStruct": { - "type": "object", - "properties": { - "foo": { - "type": "integer", - "format": "int32" - } - }, - "required": [ - "foo" - ] - }, "MyStruct_for_int32_and_null_and_boolean_and_Array_of_string": { "type": "object", "properties": { - "inner": { - "$ref": "#/definitions/MySimpleStruct" - }, "t": { "type": "integer", "format": "int32" @@ -49,6 +34,9 @@ "items": { "type": "string" } + }, + "inner": { + "$ref": "#/definitions/MySimpleStruct" } }, "required": [ @@ -58,6 +46,18 @@ "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_settings-2019_09.json b/schemars/tests/expected/schema_settings-2019_09.json index 947aa2e7..22f962f2 100644 --- a/schemars/tests/expected/schema_settings-2019_09.json +++ b/schemars/tests/expected/schema_settings-2019_09.json @@ -3,16 +3,6 @@ "title": "Outer", "type": "object", "properties": { - "inner": { - "anyOf": [ - { - "$ref": "#/definitions/Inner" - }, - { - "type": "null" - } - ] - }, "int": { "type": "integer", "format": "int32", @@ -21,10 +11,20 @@ null ] }, - "value": true, "values": { "type": "object", "additionalProperties": true + }, + "value": true, + "inner": { + "anyOf": [ + { + "$ref": "#/definitions/Inner" + }, + { + "type": "null" + } + ] } }, "required": [ @@ -52,10 +52,10 @@ "properties": { "ValueNewType": true }, - "additionalProperties": false, "required": [ "ValueNewType" - ] + ], + "additionalProperties": false } ] } diff --git a/schemars/tests/expected/schema_settings-openapi3.json b/schemars/tests/expected/schema_settings-openapi3.json index b6de3027..c4e51990 100644 --- a/schemars/tests/expected/schema_settings-openapi3.json +++ b/schemars/tests/expected/schema_settings-openapi3.json @@ -3,23 +3,23 @@ "title": "Outer", "type": "object", "properties": { - "inner": { - "allOf": [ - { - "$ref": "#/components/schemas/Inner" - } - ], - "nullable": true - }, "int": { "type": "integer", "format": "int32", "example": 8 }, - "value": {}, "values": { "type": "object", "additionalProperties": true + }, + "value": {}, + "inner": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/Inner" + } + ] } }, "required": [ @@ -49,10 +49,10 @@ "properties": { "ValueNewType": {} }, - "additionalProperties": false, "required": [ "ValueNewType" - ] + ], + "additionalProperties": false } ] } diff --git a/schemars/tests/expected/schema_settings.json b/schemars/tests/expected/schema_settings.json index 6a66ca0c..4a502621 100644 --- a/schemars/tests/expected/schema_settings.json +++ b/schemars/tests/expected/schema_settings.json @@ -3,16 +3,6 @@ "title": "Outer", "type": "object", "properties": { - "inner": { - "anyOf": [ - { - "$ref": "#/definitions/Inner" - }, - { - "type": "null" - } - ] - }, "int": { "type": "integer", "format": "int32", @@ -21,10 +11,20 @@ null ] }, - "value": true, "values": { "type": "object", "additionalProperties": true + }, + "value": true, + "inner": { + "anyOf": [ + { + "$ref": "#/definitions/Inner" + }, + { + "type": "null" + } + ] } }, "required": [ @@ -52,10 +52,10 @@ "properties": { "ValueNewType": true }, - "additionalProperties": false, "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 5b95671e..ab848e3f 100644 --- a/schemars/tests/expected/schema_with-enum-adjacent-tagged.json +++ b/schemars/tests/expected/schema_with-enum-adjacent-tagged.json @@ -5,6 +5,12 @@ { "type": "object", "properties": { + "t": { + "type": "string", + "enum": [ + "Struct" + ] + }, "c": { "type": "object", "properties": { @@ -15,12 +21,6 @@ "required": [ "foo" ] - }, - "t": { - "type": "string", - "enum": [ - "Struct" - ] } }, "required": [ @@ -31,14 +31,14 @@ { "type": "object", "properties": { - "c": { - "type": "boolean" - }, "t": { "type": "string", "enum": [ "NewType" ] + }, + "c": { + "type": "boolean" } }, "required": [ @@ -49,6 +49,12 @@ { "type": "object", "properties": { + "t": { + "type": "string", + "enum": [ + "Tuple" + ] + }, "c": { "type": "array", "items": [ @@ -60,14 +66,8 @@ "format": "int32" } ], - "maxItems": 2, - "minItems": 2 - }, - "t": { - "type": "string", - "enum": [ - "Tuple" - ] + "minItems": 2, + "maxItems": 2 } }, "required": [ @@ -78,14 +78,14 @@ { "type": "object", "properties": { - "c": { - "type": "boolean" - }, "t": { "type": "string", "enum": [ "Unit" ] + }, + "c": { + "type": "boolean" } }, "required": [ diff --git a/schemars/tests/expected/schema_with-enum-external.json b/schemars/tests/expected/schema_with-enum-external.json index 78b6475e..5f28d2f7 100644 --- a/schemars/tests/expected/schema_with-enum-external.json +++ b/schemars/tests/expected/schema_with-enum-external.json @@ -17,10 +17,10 @@ ] } }, - "additionalProperties": false, "required": [ "struct" - ] + ], + "additionalProperties": false }, { "type": "object", @@ -29,10 +29,10 @@ "type": "boolean" } }, - "additionalProperties": false, "required": [ "newType" - ] + ], + "additionalProperties": false }, { "type": "object", @@ -48,14 +48,14 @@ "format": "int32" } ], - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 } }, - "additionalProperties": false, "required": [ "tuple" - ] + ], + "additionalProperties": false }, { "type": "object", @@ -64,10 +64,10 @@ "type": "boolean" } }, - "additionalProperties": false, "required": [ "unit" - ] + ], + "additionalProperties": false } ] } \ 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 b912febe..834eb41e 100644 --- a/schemars/tests/expected/schema_with-enum-untagged.json +++ b/schemars/tests/expected/schema_with-enum-untagged.json @@ -27,8 +27,8 @@ "format": "int32" } ], - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 }, { "type": "boolean" diff --git a/schemars/tests/expected/schema_with-struct.json b/schemars/tests/expected/schema_with-struct.json index 7cc55bf6..31caebe0 100644 --- a/schemars/tests/expected/schema_with-struct.json +++ b/schemars/tests/expected/schema_with-struct.json @@ -3,15 +3,15 @@ "title": "Struct", "type": "object", "properties": { + "foo": { + "type": "boolean" + }, "bar": { "type": "integer", "format": "int32" }, "baz": { "type": "boolean" - }, - "foo": { - "type": "boolean" } }, "required": [ diff --git a/schemars/tests/expected/schema_with-tuple.json b/schemars/tests/expected/schema_with-tuple.json index 1a8f8ca0..4e2c0951 100644 --- a/schemars/tests/expected/schema_with-tuple.json +++ b/schemars/tests/expected/schema_with-tuple.json @@ -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/skip_enum_variants.json b/schemars/tests/expected/skip_enum_variants.json index b955e779..d2457340 100644 --- a/schemars/tests/expected/skip_enum_variants.json +++ b/schemars/tests/expected/skip_enum_variants.json @@ -16,10 +16,10 @@ "format": "float" } }, - "additionalProperties": false, "required": [ "Included1" - ] + ], + "additionalProperties": false } ] } \ No newline at end of file diff --git a/schemars/tests/expected/skip_struct_fields.json b/schemars/tests/expected/skip_struct_fields.json index 78902987..48549ced 100644 --- a/schemars/tests/expected/skip_struct_fields.json +++ b/schemars/tests/expected/skip_struct_fields.json @@ -3,18 +3,18 @@ "title": "MyStruct", "type": "object", "properties": { - "included": { - "type": "null" - }, "readable": { "type": "string", - "default": "", - "readOnly": true + "readOnly": true, + "default": "" }, "writable": { "type": "number", "format": "float", "writeOnly": true + }, + "included": { + "type": "null" } }, "required": [ diff --git a/schemars/tests/expected/skip_tuple_fields.json b/schemars/tests/expected/skip_tuple_fields.json index 9e5745b4..3cdce842 100644 --- a/schemars/tests/expected/skip_tuple_fields.json +++ b/schemars/tests/expected/skip_tuple_fields.json @@ -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/struct-normal-additional-properties.json b/schemars/tests/expected/struct-normal-additional-properties.json index 5ff105c7..529e2c4b 100644 --- a/schemars/tests/expected/struct-normal-additional-properties.json +++ b/schemars/tests/expected/struct-normal-additional-properties.json @@ -3,6 +3,10 @@ "title": "Struct", "type": "object", "properties": { + "foo": { + "type": "integer", + "format": "int32" + }, "bar": { "type": "boolean" }, @@ -11,10 +15,6 @@ "string", "null" ] - }, - "foo": { - "type": "integer", - "format": "int32" } }, "additionalProperties": false, diff --git a/schemars/tests/expected/struct-normal.json b/schemars/tests/expected/struct-normal.json index 0a5a0afb..3e73393b 100644 --- a/schemars/tests/expected/struct-normal.json +++ b/schemars/tests/expected/struct-normal.json @@ -3,6 +3,10 @@ "title": "Struct", "type": "object", "properties": { + "foo": { + "type": "integer", + "format": "int32" + }, "bar": { "type": "boolean" }, @@ -11,10 +15,6 @@ "string", "null" ] - }, - "foo": { - "type": "integer", - "format": "int32" } }, "required": [ diff --git a/schemars/tests/expected/struct-tuple.json b/schemars/tests/expected/struct-tuple.json index ced169ea..c89e4164 100644 --- a/schemars/tests/expected/struct-tuple.json +++ b/schemars/tests/expected/struct-tuple.json @@ -17,6 +17,6 @@ ] } ], - "maxItems": 3, - "minItems": 3 + "minItems": 3, + "maxItems": 3 } \ No newline at end of file diff --git a/schemars/tests/expected/transparent-struct.json b/schemars/tests/expected/transparent-struct.json index e83f905b..7fc1c32c 100644 --- a/schemars/tests/expected/transparent-struct.json +++ b/schemars/tests/expected/transparent-struct.json @@ -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/validate.json b/schemars/tests/expected/validate.json index 2d6e43c5..04131ce0 100644 --- a/schemars/tests/expected/validate.json +++ b/schemars/tests/expected/validate.json @@ -3,6 +3,30 @@ "title": "Struct", "type": "object", "properties": { + "min_max": { + "type": "number", + "format": "float", + "minimum": 0.01, + "maximum": 100 + }, + "min_max2": { + "type": "number", + "format": "float", + "minimum": 1, + "maximum": 1000 + }, + "regex_str1": { + "type": "string", + "pattern": "^[Hh]ello\\b" + }, + "regex_str2": { + "type": "string", + "pattern": "^[Hh]ello\\b" + }, + "regex_str3": { + "type": "string", + "pattern": "^\\d+$" + }, "contains_str1": { "type": "string", "pattern": "substring\\.\\.\\." @@ -15,40 +39,23 @@ "type": "string", "format": "email" }, + "tel": { + "type": "string", + "format": "phone" + }, "homepage": { "type": "string", "format": "uri" }, - "map_contains": { - "type": "object", - "additionalProperties": { - "type": "null" - }, - "required": [ - "map_key" - ] - }, - "min_max": { - "type": "number", - "format": "float", - "maximum": 100, - "minimum": 0.01 - }, - "min_max2": { - "type": "number", - "format": "float", - "maximum": 1000, - "minimum": 1 - }, "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", @@ -56,28 +63,21 @@ "type": "integer", "format": "int32" }, - "maxItems": 2, - "minItems": 2 - }, - "regex_str1": { - "type": "string", - "pattern": "^[Hh]ello\\b" - }, - "regex_str2": { - "type": "string", - "pattern": "^[Hh]ello\\b" + "minItems": 2, + "maxItems": 2 }, - "regex_str3": { - "type": "string", - "pattern": "^\\d+$" + "map_contains": { + "type": "object", + "additionalProperties": { + "type": "null" + }, + "required": [ + "map_key" + ] }, "required_option": { "type": "boolean" }, - "tel": { - "type": "string", - "format": "phone" - }, "x": { "type": "integer", "format": "int32" diff --git a/schemars/tests/expected/validate_inner.json b/schemars/tests/expected/validate_inner.json index f8a7eca1..ba4ee9ea 100644 --- a/schemars/tests/expected/validate_inner.json +++ b/schemars/tests/expected/validate_inner.json @@ -7,11 +7,11 @@ "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", @@ -20,45 +20,45 @@ "pattern": "substring\\.\\.\\." } }, - "vec_i32_range": { + "vec_str_regex": { "type": "array", "items": { - "type": "integer", - "format": "int32", - "maximum": 10, - "minimum": -10 + "type": "string", + "pattern": "^[Hh]ello\\b" } }, "vec_str_length": { "type": "array", "items": { "type": "string", - "maxLength": 100, - "minLength": 1 + "minLength": 1, + "maxLength": 100 } }, "vec_str_length2": { "type": "array", "items": { "type": "string", - "maxLength": 100, - "minLength": 1 + "minLength": 1, + "maxLength": 100 }, - "maxItems": 3, - "minItems": 1 + "minItems": 1, + "maxItems": 3 }, - "vec_str_regex": { + "vec_str_url": { "type": "array", "items": { "type": "string", - "pattern": "^[Hh]ello\\b" + "format": "uri" } }, - "vec_str_url": { + "vec_i32_range": { "type": "array", "items": { - "type": "string", - "format": "uri" + "type": "integer", + "format": "int32", + "minimum": -10, + "maximum": 10 } } }, diff --git a/schemars/tests/expected/validate_newtype.json b/schemars/tests/expected/validate_newtype.json index 4c1146e8..89bec96c 100644 --- a/schemars/tests/expected/validate_newtype.json +++ b/schemars/tests/expected/validate_newtype.json @@ -3,6 +3,6 @@ "title": "NewType", "type": "integer", "format": "uint8", - "maximum": 10, - "minimum": 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 fa2dbcda..4fdba53d 100644 --- a/schemars/tests/expected/validate_schemars_attrs.json +++ b/schemars/tests/expected/validate_schemars_attrs.json @@ -3,6 +3,30 @@ "title": "Struct2", "type": "object", "properties": { + "min_max": { + "type": "number", + "format": "float", + "minimum": 0.01, + "maximum": 100 + }, + "min_max2": { + "type": "number", + "format": "float", + "minimum": 1, + "maximum": 1000 + }, + "regex_str1": { + "type": "string", + "pattern": "^[Hh]ello\\b" + }, + "regex_str2": { + "type": "string", + "pattern": "^[Hh]ello\\b" + }, + "regex_str3": { + "type": "string", + "pattern": "^\\d+$" + }, "contains_str1": { "type": "string", "pattern": "substring\\.\\.\\." @@ -15,40 +39,23 @@ "type": "string", "format": "email" }, + "tel": { + "type": "string", + "format": "phone" + }, "homepage": { "type": "string", "format": "uri" }, - "map_contains": { - "type": "object", - "additionalProperties": { - "type": "null" - }, - "required": [ - "map_key" - ] - }, - "min_max": { - "type": "number", - "format": "float", - "maximum": 100, - "minimum": 0.01 - }, - "min_max2": { - "type": "number", - "format": "float", - "maximum": 1000, - "minimum": 1 - }, "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", @@ -56,28 +63,21 @@ "type": "integer", "format": "int32" }, - "maxItems": 2, - "minItems": 2 - }, - "regex_str1": { - "type": "string", - "pattern": "^[Hh]ello\\b" - }, - "regex_str2": { - "type": "string", - "pattern": "^[Hh]ello\\b" + "minItems": 2, + "maxItems": 2 }, - "regex_str3": { - "type": "string", - "pattern": "^\\d+$" + "map_contains": { + "type": "object", + "additionalProperties": { + "type": "null" + }, + "required": [ + "map_key" + ] }, "required_option": { "type": "boolean" }, - "tel": { - "type": "string", - "format": "phone" - }, "x": { "type": "integer", "format": "int32" diff --git a/schemars/tests/expected/validate_tuple.json b/schemars/tests/expected/validate_tuple.json index 619b8c4d..2a0e2660 100644 --- a/schemars/tests/expected/validate_tuple.json +++ b/schemars/tests/expected/validate_tuple.json @@ -6,13 +6,13 @@ { "type": "integer", "format": "uint8", - "maximum": 10, - "minimum": 0 + "minimum": 0, + "maximum": 10 }, { "type": "boolean" } ], - "maxItems": 2, - "minItems": 2 + "minItems": 2, + "maxItems": 2 } \ No newline at end of file From 22e89a5dd6c8f7ac57c1c5ac48fb9ab6af7e3ca1 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sat, 18 May 2024 23:00:24 +0100 Subject: [PATCH 11/40] Never add a field with the `default` attribute to a schema's `required` properties --- schemars/src/_private.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemars/src/_private.rs b/schemars/src/_private.rs index 129d92ea..4be6c271 100644 --- a/schemars/src/_private.rs +++ b/schemars/src/_private.rs @@ -129,7 +129,7 @@ pub fn insert_object_property( properties.insert(key.to_owned(), sub_schema.into()); } - if required || !(has_default || T::_schemars_private_is_option()) { + if !has_default && (required || !T::_schemars_private_is_option()) { if let Some(req) = obj .entry("required") .or_insert(Value::Array(Vec::new())) From 95475ad1b463eef2be1e6d664ee915cb50f55f77 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 19 May 2024 18:07:51 +0100 Subject: [PATCH 12/40] Use `$defs` instead of `definitions` in draft 2019-09 This also changes the strategy for running visitors on subschemas - it is now done eagerly, as soon as they're added to the definitions map. --- schemars/src/gen.rs | 106 +++++++++++------- schemars/src/visit.rs | 2 +- .../doc_comments_struct_ref_siblings.json | 4 +- .../expected/schema_settings-2019_09.json | 4 +- .../expected/schema_settings-openapi3.json | 56 ++++----- 5 files changed, 98 insertions(+), 74 deletions(-) diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index 678ee419..1c6fd88a 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -75,7 +75,7 @@ impl 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::new(), inline_subschemas: false, @@ -253,7 +253,8 @@ impl SchemaGenerator { // insert into definitions BEFORE calling json_schema to avoid infinite recursion self.definitions.insert(name.clone(), dummy); - let schema = self.json_schema_internal::(id); + let mut schema = self.json_schema_internal::(id); + Self::run_visitors(&mut schema, &mut self.settings.visitors); self.definitions.insert(name, schema.to_value()); } @@ -306,16 +307,12 @@ impl SchemaGenerator { object.insert("$schema".into(), meta_schema.into()); } - if !self.definitions.is_empty() { - object.insert( - "definitions".into(), - serde_json::Value::Object(self.definitions.clone()), - ); - } - - for visitor in &mut self.settings.visitors { - visitor.visit_schema(&mut schema); - } + Self::add_definitions( + object, + self.definitions.clone(), + &self.settings.definitions_path, + ); + Self::run_visitors(&mut schema, &mut self.settings.visitors); schema } @@ -337,16 +334,8 @@ impl SchemaGenerator { object.insert("$schema".into(), meta_schema.into()); } - if !self.definitions.is_empty() { - object.insert( - "definitions".into(), - serde_json::Value::Object(self.definitions), - ); - } - - for visitor in &mut self.settings.visitors { - visitor.visit_schema(&mut schema); - } + Self::add_definitions(object, self.definitions, &self.settings.definitions_path); + Self::run_visitors(&mut schema, &mut self.settings.visitors); schema } @@ -374,16 +363,12 @@ impl SchemaGenerator { object.insert("$schema".into(), meta_schema.into()); } - if !self.definitions.is_empty() { - object.insert( - "definitions".into(), - serde_json::Value::Object(self.definitions.clone()), - ); - } - - for visitor in &mut self.settings.visitors { - visitor.visit_schema(&mut schema); - } + Self::add_definitions( + object, + self.definitions.clone(), + &self.settings.definitions_path, + ); + Self::run_visitors(&mut schema, &mut self.settings.visitors); Ok(schema) } @@ -411,16 +396,8 @@ impl SchemaGenerator { object.insert("$schema".into(), meta_schema.into()); } - if !self.definitions.is_empty() { - object.insert( - "definitions".into(), - serde_json::Value::Object(self.definitions), - ); - } - - for visitor in &mut self.settings.visitors { - visitor.visit_schema(&mut schema); - } + Self::add_definitions(object, self.definitions, &self.settings.definitions_path); + Self::run_visitors(&mut schema, &mut self.settings.visitors); Ok(schema) } @@ -450,6 +427,51 @@ impl SchemaGenerator { let pss = PendingSchemaState::new(self, id); T::json_schema(pss.gen) } + + fn add_definitions( + schema_object: &mut Map, + mut definitions: Map, + path: &str, + ) { + if definitions.is_empty() { + return; + } + + let target = match Self::json_pointer(schema_object, path) { + Some(d) => d, + None => return, + }; + + target.append(&mut definitions); + } + + fn json_pointer<'a>( + mut object: &'a mut Map, + pointer: &str, + ) -> Option<&'a mut Map> { + let segments = pointer.strip_prefix("#/")?.strip_suffix('/')?.split('/'); + + for mut segment in segments { + let replaced: String; + if segment.contains('~') { + replaced = segment.replace("~1", "/").replace("~0", "~"); + segment = &replaced; + } + + object = object + .entry(segment) + .or_insert(Value::Object(Map::default())) + .as_object_mut()?; + } + + Some(object) + } + + fn run_visitors(schema: &mut Schema, visitors: &mut [Box]) { + for visitor in visitors { + visitor.visit_schema(schema); + } + } } /// A [Visitor](Visitor) which implements additional traits required to be included in a [SchemaSettings]. diff --git a/schemars/src/visit.rs b/schemars/src/visit.rs index 5fd42736..a9ca7871 100644 --- a/schemars/src/visit.rs +++ b/schemars/src/visit.rs @@ -78,7 +78,7 @@ pub fn visit_schema(v: &mut V, schema: &mut Schema) { v.visit_schema(subschema) } } - "properties" | "patternProperties" | "definitions" | "$defs" => { + "properties" | "patternProperties" => { if let Some(obj) = value.as_object_mut() { for value in obj.values_mut() { if let Ok(subschema) = value.try_into() { diff --git a/schemars/tests/expected/doc_comments_struct_ref_siblings.json b/schemars/tests/expected/doc_comments_struct_ref_siblings.json index a3dcbe02..6b6caebf 100644 --- a/schemars/tests/expected/doc_comments_struct_ref_siblings.json +++ b/schemars/tests/expected/doc_comments_struct_ref_siblings.json @@ -14,7 +14,7 @@ }, "my_unit": { "description": "A unit struct instance", - "$ref": "#/definitions/MyUnitStruct" + "$ref": "#/$defs/MyUnitStruct" } }, "required": [ @@ -22,7 +22,7 @@ "my_undocumented_bool", "my_unit" ], - "definitions": { + "$defs": { "MyUnitStruct": { "title": "A Unit", "type": "null" diff --git a/schemars/tests/expected/schema_settings-2019_09.json b/schemars/tests/expected/schema_settings-2019_09.json index 22f962f2..568ce852 100644 --- a/schemars/tests/expected/schema_settings-2019_09.json +++ b/schemars/tests/expected/schema_settings-2019_09.json @@ -19,7 +19,7 @@ "inner": { "anyOf": [ { - "$ref": "#/definitions/Inner" + "$ref": "#/$defs/Inner" }, { "type": "null" @@ -32,7 +32,7 @@ "values", "value" ], - "definitions": { + "$defs": { "Inner": { "oneOf": [ { diff --git a/schemars/tests/expected/schema_settings-openapi3.json b/schemars/tests/expected/schema_settings-openapi3.json index c4e51990..6ff26047 100644 --- a/schemars/tests/expected/schema_settings-openapi3.json +++ b/schemars/tests/expected/schema_settings-openapi3.json @@ -27,34 +27,36 @@ "values", "value" ], - "definitions": { - "Inner": { - "oneOf": [ - { - "type": "string", - "enum": [ - "UndocumentedUnit1", - "UndocumentedUnit2" - ] - }, - { - "description": "This is a documented unit variant", - "type": "string", - "enum": [ - "DocumentedUnit" - ] - }, - { - "type": "object", - "properties": { - "ValueNewType": {} + "components": { + "schemas": { + "Inner": { + "oneOf": [ + { + "type": "string", + "enum": [ + "UndocumentedUnit1", + "UndocumentedUnit2" + ] }, - "required": [ - "ValueNewType" - ], - "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 From 3aa0e7fa3cfd488ef1696abdb3ff22ff8528604c Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 19 May 2024 20:49:45 +0100 Subject: [PATCH 13/40] Support JSON Schema draft 2020-12 and use it by default (#294) --- schemars/src/gen.rs | 33 +++++--- schemars/src/json_schema_impls/tuple.rs | 2 +- schemars/src/ser.rs | 2 +- schemars/src/visit.rs | 43 ++++++---- schemars/tests/expected/arrayvec.json | 2 +- schemars/tests/expected/arrayvec_string.json | 2 +- schemars/tests/expected/bigdecimal04.json | 2 +- schemars/tests/expected/bound.json | 2 +- schemars/tests/expected/bytes.json | 4 +- schemars/tests/expected/chrono-types.json | 2 +- schemars/tests/expected/crate_alias.json | 2 +- schemars/tests/expected/default.json | 16 ++-- schemars/tests/expected/deprecated-enum.json | 2 +- .../tests/expected/deprecated-struct.json | 2 +- .../tests/expected/doc_comments_enum.json | 2 +- .../tests/expected/doc_comments_override.json | 2 +- .../tests/expected/doc_comments_struct.json | 10 +-- .../expected/duration_and_systemtime.json | 8 +- schemars/tests/expected/either.json | 2 +- .../expected/enum-adjacent-tagged-duf.json | 10 +-- .../tests/expected/enum-adjacent-tagged.json | 10 +-- .../tests/expected/enum-external-duf.json | 10 +-- schemars/tests/expected/enum-external.json | 10 +-- .../tests/expected/enum-internal-duf.json | 2 +- schemars/tests/expected/enum-internal.json | 2 +- .../tests/expected/enum-repr-with-attrs.json | 2 +- schemars/tests/expected/enum-repr.json | 2 +- .../expected/enum-simple-internal-duf.json | 2 +- .../tests/expected/enum-simple-internal.json | 2 +- schemars/tests/expected/enum-unit-doc.json | 2 +- .../tests/expected/enum-untagged-duf.json | 10 +-- schemars/tests/expected/enum-untagged.json | 10 +-- schemars/tests/expected/enumset.json | 6 +- schemars/tests/expected/examples.json | 2 +- schemars/tests/expected/flatten.json | 2 +- schemars/tests/expected/from_json_value.json | 2 +- schemars/tests/expected/indexmap.json | 2 +- .../expected/inline-subschemas-recursive.json | 12 +-- .../tests/expected/inline-subschemas.json | 2 +- schemars/tests/expected/macro_built_enum.json | 6 +- .../tests/expected/macro_built_struct.json | 2 +- schemars/tests/expected/no-variants.json | 2 +- schemars/tests/expected/nonzero_ints.json | 2 +- schemars/tests/expected/os_strings.json | 8 +- .../tests/expected/property-name-struct.json | 2 +- schemars/tests/expected/range.json | 10 +-- schemars/tests/expected/remote_derive.json | 22 ++--- .../tests/expected/remote_derive_generic.json | 10 +-- schemars/tests/expected/result.json | 10 +-- schemars/tests/expected/rust_decimal.json | 2 +- schemars/tests/expected/same_name.json | 8 +- .../expected/schema-name-const-generics.json | 2 +- .../tests/expected/schema-name-custom.json | 6 +- .../tests/expected/schema-name-default.json | 6 +- .../expected/schema-name-mixed-generics.json | 8 +- .../expected/schema_settings-2019_09.json | 22 ++++- .../expected/schema_settings-2020_12.json | 83 +++++++++++++++++++ .../expected/schema_settings-openapi3.json | 22 ++++- schemars/tests/expected/schema_settings.json | 22 ++++- .../schema_with-enum-adjacent-tagged.json | 4 +- .../expected/schema_with-enum-external.json | 4 +- .../expected/schema_with-enum-internal.json | 2 +- .../expected/schema_with-enum-untagged.json | 4 +- .../tests/expected/schema_with-newtype.json | 2 +- .../tests/expected/schema_with-struct.json | 2 +- .../schema_with-transparent-newtype.json | 2 +- .../tests/expected/schema_with-tuple.json | 4 +- schemars/tests/expected/semver.json | 2 +- .../tests/expected/skip_enum_variants.json | 2 +- .../tests/expected/skip_struct_fields.json | 2 +- .../tests/expected/skip_tuple_fields.json | 4 +- schemars/tests/expected/smallvec.json | 2 +- schemars/tests/expected/smol_str.json | 2 +- schemars/tests/expected/struct-newtype.json | 2 +- .../struct-normal-additional-properties.json | 2 +- schemars/tests/expected/struct-normal.json | 2 +- schemars/tests/expected/struct-tuple.json | 4 +- schemars/tests/expected/struct-unit.json | 2 +- .../tests/expected/transparent-struct.json | 8 +- schemars/tests/expected/url.json | 2 +- schemars/tests/expected/uuid.json | 2 +- schemars/tests/expected/validate.json | 2 +- schemars/tests/expected/validate_inner.json | 2 +- schemars/tests/expected/validate_newtype.json | 2 +- .../expected/validate_schemars_attrs.json | 2 +- schemars/tests/expected/validate_tuple.json | 4 +- schemars/tests/schema_settings.rs | 6 ++ schemars_derive/src/schema_exprs.rs | 2 +- 88 files changed, 369 insertions(+), 210 deletions(-) create mode 100644 schemars/tests/expected/schema_settings-2020_12.json diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index 1c6fd88a..6446a2c0 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -18,18 +18,18 @@ use std::{any::Any, collections::HashSet, fmt::Debug}; /// 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, @@ -39,9 +39,9 @@ pub struct SchemaSettings { 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. + /// A list of visitors that get applied to all generated schemas. pub visitors: Vec>, /// Inline all subschemas instead of using references. /// @@ -53,30 +53,42 @@ pub struct SchemaSettings { impl Default for SchemaSettings { 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(), meta_schema: Some("http://json-schema.org/draft-07/schema#".to_owned()), - visitors: vec![Box::new(RemoveRefSiblings)], + visitors: 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: "#/$defs/".to_owned(), meta_schema: Some("https://json-schema.org/draft/2019-09/schema".to_owned()), + visitors: vec![Box::new(ReplacePrefixItems)], + inline_subschemas: false, + } + } + + /// 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()), visitors: Vec::new(), inline_subschemas: false, } @@ -99,6 +111,7 @@ impl SchemaSettings { }), Box::new(SetSingleExample), Box::new(ReplaceConstValue), + Box::new(ReplacePrefixItems), ], inline_subschemas: false, } diff --git a/schemars/src/json_schema_impls/tuple.rs b/schemars/src/json_schema_impls/tuple.rs index ab9a36d2..204169e9 100644 --- a/schemars/src/json_schema_impls/tuple.rs +++ b/schemars/src/json_schema_impls/tuple.rs @@ -25,7 +25,7 @@ macro_rules! tuple_impls { fn json_schema(gen: &mut SchemaGenerator) -> Schema { json_schema!({ "type": "array", - "items": [ + "prefixItems": [ $(gen.subschema_for::<$name>()),+ ], "minItems": $len, diff --git a/schemars/src/ser.rs b/schemars/src/ser.rs index 8be7b343..75ae6cac 100644 --- a/schemars/src/ser.rs +++ b/schemars/src/ser.rs @@ -378,7 +378,7 @@ impl serde::ser::SerializeTuple for SerializeTuple<'_> { let len = self.items.len(); let mut schema = json_schema!({ "type": "array", - "items": self.items, + "prefixItems": self.items, "maxItems": len, "minItems": len, }); diff --git a/schemars/src/visit.rs b/schemars/src/visit.rs index a9ca7871..30bdf0d8 100644 --- a/schemars/src/visit.rs +++ b/schemars/src/visit.rs @@ -50,15 +50,15 @@ pub fn visit_schema(v: &mut V, schema: &mut Schema) { | "if" | "then" | "else" - | "additionalItems" | "contains" | "additionalProperties" - | "propertyNames" => { + | "propertyNames" + | "items" => { if let Ok(subschema) = value.try_into() { v.visit_schema(subschema) } } - "allOf" | "anyOf" | "oneOf" => { + "allOf" | "anyOf" | "oneOf" | "prefixItems" => { if let Some(array) = value.as_array_mut() { for value in array { if let Ok(subschema) = value.try_into() { @@ -67,17 +67,6 @@ pub fn visit_schema(v: &mut V, schema: &mut Schema) { } } } - "items" => { - if let Some(array) = value.as_array_mut() { - for value in array { - if let Ok(subschema) = value.try_into() { - v.visit_schema(subschema) - } - } - } else if let Ok(subschema) = value.try_into() { - v.visit_schema(subschema) - } - } "properties" | "patternProperties" => { if let Some(obj) = value.as_object_mut() { for value in obj.values_mut() { @@ -126,7 +115,7 @@ impl Visitor for ReplaceBoolSchemas { /// 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`. +/// 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; @@ -187,3 +176,27 @@ impl Visitor for ReplaceConstValue { } } } + +/// This visitor will rename the `prefixItems` schema property to `items`. +/// +/// 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 Visitor for ReplacePrefixItems { + fn visit_schema(&mut self, schema: &mut Schema) { + visit_schema(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/tests/expected/arrayvec.json b/schemars/tests/expected/arrayvec.json index 3de09a08..998d0872 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 ad174d81..0604332c 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#", + "$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/bigdecimal04.json b/schemars/tests/expected/bigdecimal04.json index 855db6f7..e94ca271 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 e4ceb13b..a5645c67 100644 --- a/schemars/tests/expected/bound.json +++ b/schemars/tests/expected/bound.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyContainer", "type": "object", "properties": { diff --git a/schemars/tests/expected/bytes.json b/schemars/tests/expected/bytes.json index 8618a7f9..80486c13 100644 --- a/schemars/tests/expected/bytes.json +++ b/schemars/tests/expected/bytes.json @@ -1,8 +1,8 @@ { - "$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": { diff --git a/schemars/tests/expected/chrono-types.json b/schemars/tests/expected/chrono-types.json index ce03621c..de261307 100644 --- a/schemars/tests/expected/chrono-types.json +++ b/schemars/tests/expected/chrono-types.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "ChronoTypes", "type": "object", "properties": { diff --git a/schemars/tests/expected/crate_alias.json b/schemars/tests/expected/crate_alias.json index e88a449b..c2f14305 100644 --- a/schemars/tests/expected/crate_alias.json +++ b/schemars/tests/expected/crate_alias.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Struct", "type": "object", "properties": { diff --git a/schemars/tests/expected/default.json b/schemars/tests/expected/default.json index 88ad12ad..65b6a66a 100644 --- a/schemars/tests/expected/default.json +++ b/schemars/tests/expected/default.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "properties": { @@ -13,21 +13,17 @@ "default": false }, "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": { diff --git a/schemars/tests/expected/deprecated-enum.json b/schemars/tests/expected/deprecated-enum.json index e8711606..a61c3223 100644 --- a/schemars/tests/expected/deprecated-enum.json +++ b/schemars/tests/expected/deprecated-enum.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "DeprecatedEnum", "oneOf": [ { diff --git a/schemars/tests/expected/deprecated-struct.json b/schemars/tests/expected/deprecated-struct.json index 2b25c343..b7396ab0 100644 --- a/schemars/tests/expected/deprecated-struct.json +++ b/schemars/tests/expected/deprecated-struct.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "DeprecatedStruct", "type": "object", "properties": { diff --git a/schemars/tests/expected/doc_comments_enum.json b/schemars/tests/expected/doc_comments_enum.json index 7d9321fd..b2e8f941 100644 --- a/schemars/tests/expected/doc_comments_enum.json +++ b/schemars/tests/expected/doc_comments_enum.json @@ -1,5 +1,5 @@ { - "$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.", "oneOf": [ diff --git a/schemars/tests/expected/doc_comments_override.json b/schemars/tests/expected/doc_comments_override.json index 0f904331..b184d2a2 100644 --- a/schemars/tests/expected/doc_comments_override.json +++ b/schemars/tests/expected/doc_comments_override.json @@ -1,5 +1,5 @@ { - "$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", diff --git a/schemars/tests/expected/doc_comments_struct.json b/schemars/tests/expected/doc_comments_struct.json index 103a71a1..655121af 100644 --- a/schemars/tests/expected/doc_comments_struct.json +++ b/schemars/tests/expected/doc_comments_struct.json @@ -1,5 +1,5 @@ { - "$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", @@ -14,11 +14,7 @@ }, "my_unit": { "description": "A unit struct instance", - "allOf": [ - { - "$ref": "#/definitions/MyUnitStruct" - } - ] + "$ref": "#/$defs/MyUnitStruct" } }, "required": [ @@ -26,7 +22,7 @@ "my_undocumented_bool", "my_unit" ], - "definitions": { + "$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 bb25dcd7..7e301e5b 100644 --- a/schemars/tests/expected/duration_and_systemtime.json +++ b/schemars/tests/expected/duration_and_systemtime.json @@ -1,20 +1,20 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "properties": { "duration": { - "$ref": "#/definitions/Duration" + "$ref": "#/$defs/Duration" }, "time": { - "$ref": "#/definitions/SystemTime" + "$ref": "#/$defs/SystemTime" } }, "required": [ "duration", "time" ], - "definitions": { + "$defs": { "Duration": { "type": "object", "properties": { diff --git a/schemars/tests/expected/either.json b/schemars/tests/expected/either.json index 807c9c92..18d8f517 100644 --- a/schemars/tests/expected/either.json +++ b/schemars/tests/expected/either.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Either_int32_or_Either_boolean_or_null", "anyOf": [ { diff --git a/schemars/tests/expected/enum-adjacent-tagged-duf.json b/schemars/tests/expected/enum-adjacent-tagged-duf.json index b9cb5faf..dfb3bb8b 100644 --- a/schemars/tests/expected/enum-adjacent-tagged-duf.json +++ b/schemars/tests/expected/enum-adjacent-tagged-duf.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Adjacent", "oneOf": [ { @@ -49,7 +49,7 @@ ] }, "c": { - "$ref": "#/definitions/UnitStruct" + "$ref": "#/$defs/UnitStruct" } }, "required": [ @@ -68,7 +68,7 @@ ] }, "c": { - "$ref": "#/definitions/Struct" + "$ref": "#/$defs/Struct" } }, "required": [ @@ -121,7 +121,7 @@ }, "c": { "type": "array", - "items": [ + "prefixItems": [ { "type": "integer", "format": "int32" @@ -176,7 +176,7 @@ "additionalProperties": false } ], - "definitions": { + "$defs": { "UnitStruct": { "type": "null" }, diff --git a/schemars/tests/expected/enum-adjacent-tagged.json b/schemars/tests/expected/enum-adjacent-tagged.json index de777139..c631ae54 100644 --- a/schemars/tests/expected/enum-adjacent-tagged.json +++ b/schemars/tests/expected/enum-adjacent-tagged.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Adjacent", "oneOf": [ { @@ -47,7 +47,7 @@ ] }, "c": { - "$ref": "#/definitions/UnitStruct" + "$ref": "#/$defs/UnitStruct" } }, "required": [ @@ -65,7 +65,7 @@ ] }, "c": { - "$ref": "#/definitions/Struct" + "$ref": "#/$defs/Struct" } }, "required": [ @@ -115,7 +115,7 @@ }, "c": { "type": "array", - "items": [ + "prefixItems": [ { "type": "integer", "format": "int32" @@ -167,7 +167,7 @@ ] } ], - "definitions": { + "$defs": { "UnitStruct": { "type": "null" }, diff --git a/schemars/tests/expected/enum-external-duf.json b/schemars/tests/expected/enum-external-duf.json index a4835354..76be5b3f 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": [ { @@ -28,7 +28,7 @@ "type": "object", "properties": { "unitStructNewType": { - "$ref": "#/definitions/UnitStruct" + "$ref": "#/$defs/UnitStruct" } }, "required": [ @@ -40,7 +40,7 @@ "type": "object", "properties": { "structNewType": { - "$ref": "#/definitions/Struct" + "$ref": "#/$defs/Struct" } }, "required": [ @@ -79,7 +79,7 @@ "properties": { "tuple": { "type": "array", - "items": [ + "prefixItems": [ { "type": "integer", "format": "int32" @@ -111,7 +111,7 @@ "additionalProperties": false } ], - "definitions": { + "$defs": { "UnitStruct": { "type": "null" }, diff --git a/schemars/tests/expected/enum-external.json b/schemars/tests/expected/enum-external.json index 8a1de2b0..3c660fb9 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": [ { @@ -28,7 +28,7 @@ "type": "object", "properties": { "unitStructNewType": { - "$ref": "#/definitions/UnitStruct" + "$ref": "#/$defs/UnitStruct" } }, "required": [ @@ -40,7 +40,7 @@ "type": "object", "properties": { "structNewType": { - "$ref": "#/definitions/Struct" + "$ref": "#/$defs/Struct" } }, "required": [ @@ -78,7 +78,7 @@ "properties": { "tuple": { "type": "array", - "items": [ + "prefixItems": [ { "type": "integer", "format": "int32" @@ -110,7 +110,7 @@ "additionalProperties": false } ], - "definitions": { + "$defs": { "UnitStruct": { "type": "null" }, diff --git a/schemars/tests/expected/enum-internal-duf.json b/schemars/tests/expected/enum-internal-duf.json index de51ba14..73e4743b 100644 --- a/schemars/tests/expected/enum-internal-duf.json +++ b/schemars/tests/expected/enum-internal-duf.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Internal", "oneOf": [ { diff --git a/schemars/tests/expected/enum-internal.json b/schemars/tests/expected/enum-internal.json index a5dffe3d..2fd9770f 100644 --- a/schemars/tests/expected/enum-internal.json +++ b/schemars/tests/expected/enum-internal.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Internal", "oneOf": [ { diff --git a/schemars/tests/expected/enum-repr-with-attrs.json b/schemars/tests/expected/enum-repr-with-attrs.json index 7070de82..89d941b7 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 92d6f3af..b3e6bb96 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 7fc20c19..aaf1eef8 100644 --- a/schemars/tests/expected/enum-simple-internal-duf.json +++ b/schemars/tests/expected/enum-simple-internal-duf.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "SimpleInternal", "oneOf": [ { diff --git a/schemars/tests/expected/enum-simple-internal.json b/schemars/tests/expected/enum-simple-internal.json index 050089c9..e955d2af 100644 --- a/schemars/tests/expected/enum-simple-internal.json +++ b/schemars/tests/expected/enum-simple-internal.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "SimpleInternal", "oneOf": [ { diff --git a/schemars/tests/expected/enum-unit-doc.json b/schemars/tests/expected/enum-unit-doc.json index 86160e8c..10004354 100644 --- a/schemars/tests/expected/enum-unit-doc.json +++ b/schemars/tests/expected/enum-unit-doc.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "SoundOfMusic", "oneOf": [ { diff --git a/schemars/tests/expected/enum-untagged-duf.json b/schemars/tests/expected/enum-untagged-duf.json index 13a7d0ad..58bdbe16 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,10 +12,10 @@ } }, { - "$ref": "#/definitions/UnitStruct" + "$ref": "#/$defs/UnitStruct" }, { - "$ref": "#/definitions/Struct" + "$ref": "#/$defs/Struct" }, { "type": "object", @@ -36,7 +36,7 @@ }, { "type": "array", - "items": [ + "prefixItems": [ { "type": "integer", "format": "int32" @@ -53,7 +53,7 @@ "format": "int32" } ], - "definitions": { + "$defs": { "UnitStruct": { "type": "null" }, diff --git a/schemars/tests/expected/enum-untagged.json b/schemars/tests/expected/enum-untagged.json index ed72b43a..643cd20c 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,10 +12,10 @@ } }, { - "$ref": "#/definitions/UnitStruct" + "$ref": "#/$defs/UnitStruct" }, { - "$ref": "#/definitions/Struct" + "$ref": "#/$defs/Struct" }, { "type": "object", @@ -35,7 +35,7 @@ }, { "type": "array", - "items": [ + "prefixItems": [ { "type": "integer", "format": "int32" @@ -52,7 +52,7 @@ "format": "int32" } ], - "definitions": { + "$defs": { "UnitStruct": { "type": "null" }, diff --git a/schemars/tests/expected/enumset.json b/schemars/tests/expected/enumset.json index 0f9baa26..72a39a19 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", "uniqueItems": true, "items": { - "$ref": "#/definitions/Foo" + "$ref": "#/$defs/Foo" }, - "definitions": { + "$defs": { "Foo": { "type": "string", "enum": [ diff --git a/schemars/tests/expected/examples.json b/schemars/tests/expected/examples.json index 9f0a5053..b891aa0b 100644 --- a/schemars/tests/expected/examples.json +++ b/schemars/tests/expected/examples.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Struct", "type": "object", "properties": { diff --git a/schemars/tests/expected/flatten.json b/schemars/tests/expected/flatten.json index 4ea70944..7dfd54e0 100644 --- a/schemars/tests/expected/flatten.json +++ b/schemars/tests/expected/flatten.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Flat", "type": "object", "properties": { diff --git a/schemars/tests/expected/from_json_value.json b/schemars/tests/expected/from_json_value.json index 6cc1ecc9..b217fadf 100644 --- a/schemars/tests/expected/from_json_value.json +++ b/schemars/tests/expected/from_json_value.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "zero": { diff --git a/schemars/tests/expected/indexmap.json b/schemars/tests/expected/indexmap.json index 318bf9bf..8ba90a85 100644 --- a/schemars/tests/expected/indexmap.json +++ b/schemars/tests/expected/indexmap.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "IndexMapTypes", "type": "object", "properties": { diff --git a/schemars/tests/expected/inline-subschemas-recursive.json b/schemars/tests/expected/inline-subschemas-recursive.json index 0c1eebee..21f7f315 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" @@ -20,7 +20,7 @@ ], "properties": { "recursive": { - "$ref": "#/definitions/RecursiveOuter" + "$ref": "#/$defs/RecursiveOuter" } }, "required": [ @@ -28,14 +28,14 @@ ] } }, - "definitions": { + "$defs": { "RecursiveOuter": { "type": "object", "properties": { "direct": { "anyOf": [ { - "$ref": "#/definitions/RecursiveOuter" + "$ref": "#/$defs/RecursiveOuter" }, { "type": "null" @@ -49,7 +49,7 @@ ], "properties": { "recursive": { - "$ref": "#/definitions/RecursiveOuter" + "$ref": "#/$defs/RecursiveOuter" } }, "required": [ diff --git a/schemars/tests/expected/inline-subschemas.json b/schemars/tests/expected/inline-subschemas.json index 7ba76d58..cbee457a 100644 --- a/schemars/tests/expected/inline-subschemas.json +++ b/schemars/tests/expected/inline-subschemas.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyJob", "type": "object", "properties": { diff --git a/schemars/tests/expected/macro_built_enum.json b/schemars/tests/expected/macro_built_enum.json index 4fedbb2c..b27df44a 100644 --- a/schemars/tests/expected/macro_built_enum.json +++ b/schemars/tests/expected/macro_built_enum.json @@ -1,12 +1,12 @@ { - "$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": "#/definitions/InnerStruct" + "$ref": "#/$defs/InnerStruct" } }, "required": [ @@ -15,7 +15,7 @@ "additionalProperties": false } ], - "definitions": { + "$defs": { "InnerStruct": { "type": "object", "properties": { diff --git a/schemars/tests/expected/macro_built_struct.json b/schemars/tests/expected/macro_built_struct.json index 74a279c8..1d0fd083 100644 --- a/schemars/tests/expected/macro_built_struct.json +++ b/schemars/tests/expected/macro_built_struct.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "A", "type": "object", "properties": { diff --git a/schemars/tests/expected/no-variants.json b/schemars/tests/expected/no-variants.json index efe28535..14de7f4e 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 97dee99d..432e3b04 100644 --- a/schemars/tests/expected/nonzero_ints.json +++ b/schemars/tests/expected/nonzero_ints.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "properties": { diff --git a/schemars/tests/expected/os_strings.json b/schemars/tests/expected/os_strings.json index b422026c..72e0be10 100644 --- a/schemars/tests/expected/os_strings.json +++ b/schemars/tests/expected/os_strings.json @@ -1,20 +1,20 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "OsStrings", "type": "object", "properties": { "owned": { - "$ref": "#/definitions/OsString" + "$ref": "#/$defs/OsString" }, "borrowed": { - "$ref": "#/definitions/OsString" + "$ref": "#/$defs/OsString" } }, "required": [ "owned", "borrowed" ], - "definitions": { + "$defs": { "OsString": { "oneOf": [ { diff --git a/schemars/tests/expected/property-name-struct.json b/schemars/tests/expected/property-name-struct.json index 1aa6e781..82ea9ed8 100644 --- a/schemars/tests/expected/property-name-struct.json +++ b/schemars/tests/expected/property-name-struct.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "properties": { diff --git a/schemars/tests/expected/range.json b/schemars/tests/expected/range.json index 25f7389c..64db4bdc 100644 --- a/schemars/tests/expected/range.json +++ b/schemars/tests/expected/range.json @@ -1,16 +1,16 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "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" } }, "required": [ @@ -18,7 +18,7 @@ "inclusive", "bound" ], - "definitions": { + "$defs": { "Range_of_uint": { "type": "object", "properties": { diff --git a/schemars/tests/expected/remote_derive.json b/schemars/tests/expected/remote_derive.json index 0f27ad36..760630d1 100644 --- a/schemars/tests/expected/remote_derive.json +++ b/schemars/tests/expected/remote_derive.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Process", "type": "object", "properties": { @@ -7,33 +7,25 @@ "type": "string" }, "wall_time": { - "$ref": "#/definitions/Duration" + "$ref": "#/$defs/Duration" }, "user_cpu_time": { + "$ref": "#/$defs/Duration", "default": { "secs": 0, "nanos": 0 - }, - "allOf": [ - { - "$ref": "#/definitions/Duration" - } - ] + } }, "system_cpu_time": { - "default": "0.000000000s", - "allOf": [ - { - "$ref": "#/definitions/Duration" - } - ] + "$ref": "#/$defs/Duration", + "default": "0.000000000s" } }, "required": [ "command_line", "wall_time" ], - "definitions": { + "$defs": { "Duration": { "type": "object", "properties": { diff --git a/schemars/tests/expected/remote_derive_generic.json b/schemars/tests/expected/remote_derive_generic.json index f6ba3ef4..bef4d6ac 100644 --- a/schemars/tests/expected/remote_derive_generic.json +++ b/schemars/tests/expected/remote_derive_generic.json @@ -1,16 +1,16 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct_for_int32", "type": "object", "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", @@ -29,7 +29,7 @@ "s", "fake_map" ], - "definitions": { + "$defs": { "Or_for_uint8_and_boolean": { "anyOf": [ { diff --git a/schemars/tests/expected/result.json b/schemars/tests/expected/result.json index 8a25a0ea..b468835c 100644 --- a/schemars/tests/expected/result.json +++ b/schemars/tests/expected/result.json @@ -1,27 +1,27 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Container", "type": "object", "properties": { "result1": { - "$ref": "#/definitions/Result_of_MyStruct_or_Array_of_string" + "$ref": "#/$defs/Result_of_MyStruct_or_Array_of_string" }, "result2": { - "$ref": "#/definitions/Result_of_boolean_or_null" + "$ref": "#/$defs/Result_of_boolean_or_null" } }, "required": [ "result1", "result2" ], - "definitions": { + "$defs": { "Result_of_MyStruct_or_Array_of_string": { "oneOf": [ { "type": "object", "properties": { "Ok": { - "$ref": "#/definitions/MyStruct" + "$ref": "#/$defs/MyStruct" } }, "required": [ diff --git a/schemars/tests/expected/rust_decimal.json b/schemars/tests/expected/rust_decimal.json index 855db6f7..e94ca271 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 ebea3fed..fb65854d 100644 --- a/schemars/tests/expected/same_name.json +++ b/schemars/tests/expected/same_name.json @@ -1,20 +1,20 @@ { - "$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": "#/definitions/Config" + "$ref": "#/$defs/Config" }, "b_cfg": { - "$ref": "#/definitions/Config2" + "$ref": "#/$defs/Config2" } }, "required": [ "a_cfg", "b_cfg" ], - "definitions": { + "$defs": { "Config": { "type": "object", "properties": { diff --git a/schemars/tests/expected/schema-name-const-generics.json b/schemars/tests/expected/schema-name-const-generics.json index 6bb3ab1b..3c2edccb 100644 --- a/schemars/tests/expected/schema-name-const-generics.json +++ b/schemars/tests/expected/schema-name-const-generics.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "const-generics-z-42", "type": "object", "properties": { diff --git a/schemars/tests/expected/schema-name-custom.json b/schemars/tests/expected/schema-name-custom.json index 447edbd9..70b4d983 100644 --- a/schemars/tests/expected/schema-name-custom.json +++ b/schemars/tests/expected/schema-name-custom.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "a-new-name-Array_of_string-int32-int32", "type": "object", "properties": { @@ -20,7 +20,7 @@ } }, "inner": { - "$ref": "#/definitions/another-new-name" + "$ref": "#/$defs/another-new-name" } }, "required": [ @@ -30,7 +30,7 @@ "w", "inner" ], - "definitions": { + "$defs": { "another-new-name": { "type": "object", "properties": { diff --git a/schemars/tests/expected/schema-name-default.json b/schemars/tests/expected/schema-name-default.json index bd2ab9a2..2243ddd7 100644 --- a/schemars/tests/expected/schema-name-default.json +++ b/schemars/tests/expected/schema-name-default.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct_for_int32_and_null_and_boolean_and_Array_of_string", "type": "object", "properties": { @@ -20,7 +20,7 @@ } }, "inner": { - "$ref": "#/definitions/MySimpleStruct" + "$ref": "#/$defs/MySimpleStruct" } }, "required": [ @@ -30,7 +30,7 @@ "w", "inner" ], - "definitions": { + "$defs": { "MySimpleStruct": { "type": "object", "properties": { diff --git a/schemars/tests/expected/schema-name-mixed-generics.json b/schemars/tests/expected/schema-name-mixed-generics.json index 43424108..85c6792e 100644 --- a/schemars/tests/expected/schema-name-mixed-generics.json +++ b/schemars/tests/expected/schema-name-mixed-generics.json @@ -1,10 +1,10 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$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", "properties": { "generic": { - "$ref": "#/definitions/MyStruct_for_int32_and_null_and_boolean_and_Array_of_string" + "$ref": "#/$defs/MyStruct_for_int32_and_null_and_boolean_and_Array_of_string" }, "foo": { "type": "integer", @@ -15,7 +15,7 @@ "generic", "foo" ], - "definitions": { + "$defs": { "MyStruct_for_int32_and_null_and_boolean_and_Array_of_string": { "type": "object", "properties": { @@ -36,7 +36,7 @@ } }, "inner": { - "$ref": "#/definitions/MySimpleStruct" + "$ref": "#/$defs/MySimpleStruct" } }, "required": [ diff --git a/schemars/tests/expected/schema_settings-2019_09.json b/schemars/tests/expected/schema_settings-2019_09.json index 568ce852..6b6dc614 100644 --- a/schemars/tests/expected/schema_settings-2019_09.json +++ b/schemars/tests/expected/schema_settings-2019_09.json @@ -25,12 +25,32 @@ "type": "null" } ] + }, + "tuples": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + { + "type": "integer", + "format": "int64" + } + ], + "minItems": 2, + "maxItems": 2 + } } }, "required": [ "int", "values", - "value" + "value", + "tuples" ], "$defs": { "Inner": { 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 00000000..79640586 --- /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 6ff26047..e5032f0d 100644 --- a/schemars/tests/expected/schema_settings-openapi3.json +++ b/schemars/tests/expected/schema_settings-openapi3.json @@ -20,12 +20,32 @@ "$ref": "#/components/schemas/Inner" } ] + }, + "tuples": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + { + "type": "integer", + "format": "int64" + } + ], + "minItems": 2, + "maxItems": 2 + } } }, "required": [ "int", "values", - "value" + "value", + "tuples" ], "components": { "schemas": { diff --git a/schemars/tests/expected/schema_settings.json b/schemars/tests/expected/schema_settings.json index 4a502621..a836cd68 100644 --- a/schemars/tests/expected/schema_settings.json +++ b/schemars/tests/expected/schema_settings.json @@ -25,12 +25,32 @@ "type": "null" } ] + }, + "tuples": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + { + "type": "integer", + "format": "int64" + } + ], + "minItems": 2, + "maxItems": 2 + } } }, "required": [ "int", "values", - "value" + "value", + "tuples" ], "definitions": { "Inner": { diff --git a/schemars/tests/expected/schema_with-enum-adjacent-tagged.json b/schemars/tests/expected/schema_with-enum-adjacent-tagged.json index ab848e3f..3e7173d5 100644 --- a/schemars/tests/expected/schema_with-enum-adjacent-tagged.json +++ b/schemars/tests/expected/schema_with-enum-adjacent-tagged.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Adjacent", "oneOf": [ { @@ -57,7 +57,7 @@ }, "c": { "type": "array", - "items": [ + "prefixItems": [ { "type": "boolean" }, diff --git a/schemars/tests/expected/schema_with-enum-external.json b/schemars/tests/expected/schema_with-enum-external.json index 5f28d2f7..5f5fe83e 100644 --- a/schemars/tests/expected/schema_with-enum-external.json +++ b/schemars/tests/expected/schema_with-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": [ { @@ -39,7 +39,7 @@ "properties": { "tuple": { "type": "array", - "items": [ + "prefixItems": [ { "type": "boolean" }, diff --git a/schemars/tests/expected/schema_with-enum-internal.json b/schemars/tests/expected/schema_with-enum-internal.json index 90871ad5..e6d707b9 100644 --- a/schemars/tests/expected/schema_with-enum-internal.json +++ b/schemars/tests/expected/schema_with-enum-internal.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Internal", "oneOf": [ { diff --git a/schemars/tests/expected/schema_with-enum-untagged.json b/schemars/tests/expected/schema_with-enum-untagged.json index 834eb41e..9057c87a 100644 --- a/schemars/tests/expected/schema_with-enum-untagged.json +++ b/schemars/tests/expected/schema_with-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": [ { @@ -18,7 +18,7 @@ }, { "type": "array", - "items": [ + "prefixItems": [ { "type": "boolean" }, diff --git a/schemars/tests/expected/schema_with-newtype.json b/schemars/tests/expected/schema_with-newtype.json index 8b709fb1..eb4a2f61 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 31caebe0..0674f1e7 100644 --- a/schemars/tests/expected/schema_with-struct.json +++ b/schemars/tests/expected/schema_with-struct.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Struct", "type": "object", "properties": { diff --git a/schemars/tests/expected/schema_with-transparent-newtype.json b/schemars/tests/expected/schema_with-transparent-newtype.json index 9f6afb36..2aeff686 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 4e2c0951..1296e33f 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" }, diff --git a/schemars/tests/expected/semver.json b/schemars/tests/expected/semver.json index e8c59b9b..9f5b1556 100644 --- a/schemars/tests/expected/semver.json +++ b/schemars/tests/expected/semver.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "SemverTypes", "type": "object", "properties": { diff --git a/schemars/tests/expected/skip_enum_variants.json b/schemars/tests/expected/skip_enum_variants.json index d2457340..bd3a893b 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": [ { diff --git a/schemars/tests/expected/skip_struct_fields.json b/schemars/tests/expected/skip_struct_fields.json index 48549ced..2a74c32d 100644 --- a/schemars/tests/expected/skip_struct_fields.json +++ b/schemars/tests/expected/skip_struct_fields.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "properties": { diff --git a/schemars/tests/expected/skip_tuple_fields.json b/schemars/tests/expected/skip_tuple_fields.json index 3cdce842..c3a4255e 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" diff --git a/schemars/tests/expected/smallvec.json b/schemars/tests/expected/smallvec.json index 7ce011d4..be50ed71 100644 --- a/schemars/tests/expected/smallvec.json +++ b/schemars/tests/expected/smallvec.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Array_of_string", "type": "array", "items": { diff --git a/schemars/tests/expected/smol_str.json b/schemars/tests/expected/smol_str.json index ad174d81..0604332c 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#", + "$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 284e47db..e7b1cba9 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 529e2c4b..2c55fb7d 100644 --- a/schemars/tests/expected/struct-normal-additional-properties.json +++ b/schemars/tests/expected/struct-normal-additional-properties.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Struct", "type": "object", "properties": { diff --git a/schemars/tests/expected/struct-normal.json b/schemars/tests/expected/struct-normal.json index 3e73393b..c49757c5 100644 --- a/schemars/tests/expected/struct-normal.json +++ b/schemars/tests/expected/struct-normal.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Struct", "type": "object", "properties": { diff --git a/schemars/tests/expected/struct-tuple.json b/schemars/tests/expected/struct-tuple.json index c89e4164..89600688 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" diff --git a/schemars/tests/expected/struct-unit.json b/schemars/tests/expected/struct-unit.json index 3252d20d..46f6fcf1 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/transparent-struct.json b/schemars/tests/expected/transparent-struct.json index 7fc1c32c..26920696 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" }, diff --git a/schemars/tests/expected/url.json b/schemars/tests/expected/url.json index 5722f9e8..425bf854 100644 --- a/schemars/tests/expected/url.json +++ b/schemars/tests/expected/url.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "UrlTypes", "type": "object", "properties": { diff --git a/schemars/tests/expected/uuid.json b/schemars/tests/expected/uuid.json index 8ba1a01b..98ae408a 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 04131ce0..57c7f521 100644 --- a/schemars/tests/expected/validate.json +++ b/schemars/tests/expected/validate.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Struct", "type": "object", "properties": { diff --git a/schemars/tests/expected/validate_inner.json b/schemars/tests/expected/validate_inner.json index ba4ee9ea..a77e0d75 100644 --- a/schemars/tests/expected/validate_inner.json +++ b/schemars/tests/expected/validate_inner.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Struct", "type": "object", "properties": { diff --git a/schemars/tests/expected/validate_newtype.json b/schemars/tests/expected/validate_newtype.json index 89bec96c..cd835f53 100644 --- a/schemars/tests/expected/validate_newtype.json +++ b/schemars/tests/expected/validate_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": "uint8", diff --git a/schemars/tests/expected/validate_schemars_attrs.json b/schemars/tests/expected/validate_schemars_attrs.json index 4fdba53d..5e7d31d7 100644 --- a/schemars/tests/expected/validate_schemars_attrs.json +++ b/schemars/tests/expected/validate_schemars_attrs.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Struct2", "type": "object", "properties": { diff --git a/schemars/tests/expected/validate_tuple.json b/schemars/tests/expected/validate_tuple.json index 2a0e2660..fa812242 100644 --- a/schemars/tests/expected/validate_tuple.json +++ b/schemars/tests/expected/validate_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": "uint8", diff --git a/schemars/tests/schema_settings.rs b/schemars/tests/schema_settings.rs index a82570db..3fe489ab 100644 --- a/schemars/tests/schema_settings.rs +++ b/schemars/tests/schema_settings.rs @@ -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)] @@ -39,6 +40,11 @@ fn schema_matches_2019_09() -> TestResult { test_generated_schema::("schema_settings-2019_09", SchemaSettings::draft2019_09()) } +#[test] +fn schema_matches_2020_12() -> TestResult { + test_generated_schema::("schema_settings-2020_12", SchemaSettings::draft2020_12()) +} + #[test] fn schema_matches_openapi3() -> TestResult { test_generated_schema::("schema_settings-openapi3", SchemaSettings::openapi3()) diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index 63571724..87e789e2 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -422,7 +422,7 @@ fn expr_for_tuple_struct(fields: &[Field]) -> TokenStream { quote! { schemars::json_schema!({ "type": "array", - "items": [#((#fields)),*], + "prefixItems": [#((#fields)),*], "minItems": #len, "maxItems": #len, }) From d32231c082db63835726d01cd31ebe4c1c2e8744 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Mon, 20 May 2024 15:25:16 +0100 Subject: [PATCH 14/40] Allow running visitors against pre-2020-12 schemas --- schemars/src/visit.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/schemars/src/visit.rs b/schemars/src/visit.rs index 30bdf0d8..3789cf59 100644 --- a/schemars/src/visit.rs +++ b/schemars/src/visit.rs @@ -45,6 +45,11 @@ pub trait Visitor { pub fn visit_schema(v: &mut V, 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 visitors 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" @@ -53,7 +58,7 @@ pub fn visit_schema(v: &mut V, schema: &mut Schema) { | "contains" | "additionalProperties" | "propertyNames" - | "items" => { + | "additionalItems" => { if let Ok(subschema) = value.try_into() { v.visit_schema(subschema) } @@ -67,6 +72,18 @@ pub fn visit_schema(v: &mut V, schema: &mut Schema) { } } } + // 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() { + v.visit_schema(subschema) + } + } + } else if let Ok(subschema) = value.try_into() { + v.visit_schema(subschema) + } + } "properties" | "patternProperties" => { if let Some(obj) = value.as_object_mut() { for value in obj.values_mut() { From 1247b8975ef8e742ab4b2f5f776038900dc69d12 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Thu, 23 May 2024 14:14:42 +0100 Subject: [PATCH 15/40] Revert to previous behaviour regarding visitors running on `$defs`/`definitions` lazily (only if/when root schema is created). Visitors will now always descend into `$defs`/`definitions`. If a generator is configured to use a different definitions path, then the visitor will also descend into that path (but a plain `Visitor` would NOT. --- schemars/src/gen.rs | 123 +++++++++++++++++++++++++----------------- schemars/src/visit.rs | 2 +- 2 files changed, 76 insertions(+), 49 deletions(-) diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index 6446a2c0..038228c6 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -35,7 +35,9 @@ pub struct SchemaSettings { 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. /// @@ -63,7 +65,7 @@ impl 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), Box::new(ReplacePrefixItems)], inline_subschemas: false, @@ -75,7 +77,7 @@ impl SchemaSettings { SchemaSettings { option_nullable: false, option_add_null_type: true, - definitions_path: "#/$defs/".to_owned(), + definitions_path: "/$defs".to_owned(), meta_schema: Some("https://json-schema.org/draft/2019-09/schema".to_owned()), visitors: vec![Box::new(ReplacePrefixItems)], inline_subschemas: false, @@ -87,7 +89,7 @@ impl SchemaSettings { SchemaSettings { option_nullable: false, option_add_null_type: true, - definitions_path: "#/$defs/".to_owned(), + definitions_path: "/$defs".to_owned(), meta_schema: Some("https://json-schema.org/draft/2020-12/schema".to_owned()), visitors: Vec::new(), inline_subschemas: false, @@ -99,7 +101,7 @@ impl 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/2021-09-28#/definitions/Schema" .to_owned(), @@ -247,7 +249,7 @@ impl SchemaGenerator { } }; - let reference = format!("{}{}", self.settings.definitions_path, name); + let reference = format!("#{}/{}", self.definitions_path_stripped(), name); if !self.definitions.contains_key(&name) { self.insert_new_subschema_for::(name, id); } @@ -266,8 +268,7 @@ impl SchemaGenerator { // insert into definitions BEFORE calling json_schema to avoid infinite recursion self.definitions.insert(name.clone(), dummy); - let mut schema = self.json_schema_internal::(id); - Self::run_visitors(&mut schema, &mut self.settings.visitors); + let schema = self.json_schema_internal::(id); self.definitions.insert(name, schema.to_value()); } @@ -320,12 +321,8 @@ impl SchemaGenerator { object.insert("$schema".into(), meta_schema.into()); } - Self::add_definitions( - object, - self.definitions.clone(), - &self.settings.definitions_path, - ); - Self::run_visitors(&mut schema, &mut self.settings.visitors); + self.add_definitions(object, self.definitions.clone()); + self.run_visitors(&mut schema); schema } @@ -343,12 +340,13 @@ impl SchemaGenerator { .entry("title") .or_insert_with(|| T::schema_name().into()); - if let Some(meta_schema) = self.settings.meta_schema { + if let Some(meta_schema) = std::mem::take(&mut self.settings.meta_schema) { object.insert("$schema".into(), meta_schema.into()); } - Self::add_definitions(object, self.definitions, &self.settings.definitions_path); - Self::run_visitors(&mut schema, &mut self.settings.visitors); + let definitions = self.take_definitions(); + self.add_definitions(object, definitions); + self.run_visitors(&mut schema); schema } @@ -376,12 +374,8 @@ impl SchemaGenerator { object.insert("$schema".into(), meta_schema.into()); } - Self::add_definitions( - object, - self.definitions.clone(), - &self.settings.definitions_path, - ); - Self::run_visitors(&mut schema, &mut self.settings.visitors); + self.add_definitions(object, self.definitions.clone()); + self.run_visitors(&mut schema); Ok(schema) } @@ -405,12 +399,13 @@ impl SchemaGenerator { object.insert("examples".into(), vec![example].into()); } - if let Some(meta_schema) = self.settings.meta_schema { + if let Some(meta_schema) = std::mem::take(&mut self.settings.meta_schema) { object.insert("$schema".into(), meta_schema.into()); } - Self::add_definitions(object, self.definitions, &self.settings.definitions_path); - Self::run_visitors(&mut schema, &mut self.settings.visitors); + let definitions = self.take_definitions(); + self.add_definitions(object, definitions); + self.run_visitors(&mut schema); Ok(schema) } @@ -442,15 +437,16 @@ impl SchemaGenerator { } fn add_definitions( + &mut self, schema_object: &mut Map, mut definitions: Map, - path: &str, ) { if definitions.is_empty() { return; } - let target = match Self::json_pointer(schema_object, path) { + let pointer = self.definitions_path_stripped(); + let target = match json_pointer_mut(schema_object, pointer, true) { Some(d) => d, None => return, }; @@ -458,33 +454,64 @@ impl SchemaGenerator { target.append(&mut definitions); } - fn json_pointer<'a>( - mut object: &'a mut Map, - pointer: &str, - ) -> Option<&'a mut Map> { - let segments = pointer.strip_prefix("#/")?.strip_suffix('/')?.split('/'); - - for mut segment in segments { - let replaced: String; - if segment.contains('~') { - replaced = segment.replace("~1", "/").replace("~0", "~"); - segment = &replaced; - } + fn run_visitors(&mut self, schema: &mut Schema) { + for visitor in self.visitors_mut() { + visitor.visit_schema(schema); + } - object = object - .entry(segment) - .or_insert(Value::Object(Map::default())) - .as_object_mut()?; + let pointer = self.definitions_path_stripped(); + // `$defs`` and `definitions` are both handled internally by `Visitor::visit_schema`. + // If the definitions are in any other location, explicitly visit them here to ensure + // they're run against any referenced subschemas. + if pointer != "/$defs" && pointer != "/definitions" { + if let Some(definitions) = schema + .as_object_mut() + .and_then(|so| json_pointer_mut(so, pointer, false)) + { + for subschema in definitions.values_mut().flat_map(<&mut Schema>::try_from) { + for visitor in self.visitors_mut() { + visitor.visit_schema(subschema); + } + } + } } + } - Some(object) + 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) } +} - fn run_visitors(schema: &mut Schema, visitors: &mut [Box]) { - for visitor in visitors { - visitor.visit_schema(schema); +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 [Visitor](Visitor) which implements additional traits required to be included in a [SchemaSettings]. diff --git a/schemars/src/visit.rs b/schemars/src/visit.rs index 3789cf59..850c1eb1 100644 --- a/schemars/src/visit.rs +++ b/schemars/src/visit.rs @@ -84,7 +84,7 @@ pub fn visit_schema(v: &mut V, schema: &mut Schema) { v.visit_schema(subschema) } } - "properties" | "patternProperties" => { + "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() { From fe05631f214416ceba756d8a464eebcb742a0c42 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Thu, 23 May 2024 17:44:50 +0100 Subject: [PATCH 16/40] impl JsonSchema for Schema --- schemars/src/schema.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/schemars/src/schema.rs b/schemars/src/schema.rs index 27cb0480..6eb55e8e 100644 --- a/schemars/src/schema.rs +++ b/schemars/src/schema.rs @@ -2,8 +2,7 @@ JSON Schema types. */ -use ref_cast::ref_cast_custom; -use ref_cast::RefCastCustom; +use ref_cast::{ref_cast_custom, RefCastCustom}; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; @@ -175,6 +174,22 @@ impl From for Schema { } } +impl crate::JsonSchema for Schema { + fn schema_name() -> String { + "Schema".to_owned() + } + + fn schema_id() -> std::borrow::Cow<'static, str> { + "schemars::Schema".into() + } + + fn json_schema(_: &mut crate::gen::SchemaGenerator) -> Schema { + crate::json_schema!({ + "type": ["object", "boolean"] + }) + } +} + mod ser { use serde::ser::{Serialize, SerializeMap, SerializeSeq}; use serde_json::Value; From 1aaa162e0bb6e9d6b8bd4f5d5c222eb65b3f62c6 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 26 May 2024 15:20:56 +0100 Subject: [PATCH 17/40] Make `schema_name()` return `Cow<'static, str>` instead of `String` --- docs/2-implementing.md | 13 +++---- schemars/src/gen.rs | 35 +++++++++---------- schemars/src/json_schema_impls/array.rs | 13 ++++--- schemars/src/json_schema_impls/arrayvec07.rs | 4 +-- schemars/src/json_schema_impls/chrono04.rs | 12 +++---- schemars/src/json_schema_impls/core.rs | 24 ++++++------- schemars/src/json_schema_impls/decimal.rs | 8 ++--- schemars/src/json_schema_impls/either1.rs | 10 ++---- schemars/src/json_schema_impls/ffi.rs | 6 ++-- schemars/src/json_schema_impls/maps.rs | 6 ++-- schemars/src/json_schema_impls/mod.rs | 2 +- .../src/json_schema_impls/nonzero_signed.rs | 6 ++-- .../src/json_schema_impls/nonzero_unsigned.rs | 8 ++--- schemars/src/json_schema_impls/primitives.rs | 30 +++++----------- schemars/src/json_schema_impls/semver1.rs | 6 ++-- schemars/src/json_schema_impls/sequences.rs | 14 ++++---- schemars/src/json_schema_impls/serdejson.rs | 16 +++------ schemars/src/json_schema_impls/time.rs | 12 +++---- schemars/src/json_schema_impls/tuple.rs | 6 ++-- schemars/src/json_schema_impls/url2.rs | 6 ++-- schemars/src/json_schema_impls/uuid1.rs | 6 ++-- schemars/src/lib.rs | 18 +++++----- schemars/src/schema.rs | 4 +-- schemars_derive/src/lib.rs | 14 +++++--- schemars_derive/src/schema_exprs.rs | 4 +-- 25 files changed, 126 insertions(+), 157 deletions(-) diff --git a/docs/2-implementing.md b/docs/2-implementing.md index c8dfb6ec..b3cea57a 100644 --- a/docs/2-implementing.md +++ b/docs/2-implementing.md @@ -12,13 +12,11 @@ permalink: /implementing/ ## schema_name ```rust -fn schema_name() -> String; +fn schema_name() -> Cow<'static, str>; ``` 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 @@ -29,8 +27,7 @@ This function returns a unique identifier of the type's schema - if two types re ```rust fn schema_id() -> Cow<'static, str> { - Cow::Owned( - format!("[{}]", T::schema_id())) + format!("[{}]", T::schema_id()).into() } ``` @@ -40,14 +37,14 @@ For a type with no generic type arguments, a reasonable implementation of this f ```rust 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 { diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index 038228c6..0bb1053c 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -12,10 +12,11 @@ use crate::{visit::*, JsonSchema}; use dyn_clone::DynClone; use serde::Serialize; use serde_json::{Map, Value}; -use std::borrow::Cow; use std::collections::HashMap; use std::{any::Any, collections::HashSet, fmt::Debug}; +type CowStr = std::borrow::Cow<'static, str>; + /// Settings to customize how Schemas are generated. /// /// 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. @@ -166,9 +167,9 @@ impl SchemaSettings { pub struct SchemaGenerator { settings: SchemaSettings, definitions: Map, - pending_schema_ids: HashSet>, - schema_id_to_name: HashMap, String>, - used_schema_names: HashSet, + pending_schema_ids: HashSet, + schema_id_to_name: HashMap, + used_schema_names: HashSet, } impl Clone for SchemaGenerator { @@ -230,11 +231,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; } @@ -250,7 +251,7 @@ impl SchemaGenerator { }; let reference = format!("#{}/{}", self.definitions_path_stripped(), name); - if !self.definitions.contains_key(&name) { + if !self.definitions.contains_key(name.as_ref()) { self.insert_new_subschema_for::(name, id); } Schema::new_ref(reference) @@ -259,18 +260,14 @@ impl SchemaGenerator { } } - fn insert_new_subschema_for( - &mut self, - name: String, - id: Cow<'static, str>, - ) { + 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.to_value()); + self.definitions.insert(name.into(), schema.to_value()); } /// Borrows the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated. @@ -410,15 +407,15 @@ impl SchemaGenerator { Ok(schema) } - 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 } } @@ -477,6 +474,8 @@ impl SchemaGenerator { } } + /// 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); diff --git a/schemars/src/json_schema_impls/array.rs b/schemars/src/json_schema_impls/array.rs index bacc079d..1da1abb6 100644 --- a/schemars/src/json_schema_impls/array.rs +++ b/schemars/src/json_schema_impls/array.rs @@ -6,12 +6,12 @@ use std::borrow::Cow; impl JsonSchema for [T; 0] { no_ref_schema!(); - 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 { @@ -28,13 +28,12 @@ macro_rules! array_impls { impl JsonSchema for [T; $len] { no_ref_schema!(); - 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 { diff --git a/schemars/src/json_schema_impls/arrayvec07.rs b/schemars/src/json_schema_impls/arrayvec07.rs index 3d4d0505..920f7c37 100644 --- a/schemars/src/json_schema_impls/arrayvec07.rs +++ b/schemars/src/json_schema_impls/arrayvec07.rs @@ -12,8 +12,8 @@ where { no_ref_schema!(); - 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 { diff --git a/schemars/src/json_schema_impls/chrono04.rs b/schemars/src/json_schema_impls/chrono04.rs index 5fd23e8b..e8afa2ac 100644 --- a/schemars/src/json_schema_impls/chrono04.rs +++ b/schemars/src/json_schema_impls/chrono04.rs @@ -6,12 +6,12 @@ use std::borrow::Cow; impl JsonSchema for Weekday { no_ref_schema!(); - fn schema_name() -> String { - "Weekday".to_owned() + fn schema_name() -> Cow<'static, str> { + "Weekday".into() } fn schema_id() -> Cow<'static, str> { - Cow::Borrowed("chrono::Weekday") + "chrono::Weekday".into() } fn json_schema(_: &mut SchemaGenerator) -> Schema { @@ -38,12 +38,12 @@ macro_rules! formatted_string_impl { impl $($desc)+ { no_ref_schema!(); - fn schema_name() -> String { - stringify!($ty).to_owned() + fn schema_name() -> Cow<'static, str> { + stringify!($ty).into() } fn schema_id() -> Cow<'static, str> { - Cow::Borrowed(stringify!(chrono::$ty)) + stringify!(chrono::$ty).into() } fn json_schema(_: &mut SchemaGenerator) -> Schema { diff --git a/schemars/src/json_schema_impls/core.rs b/schemars/src/json_schema_impls/core.rs index 16104f80..0ca4e10d 100644 --- a/schemars/src/json_schema_impls/core.rs +++ b/schemars/src/json_schema_impls/core.rs @@ -7,12 +7,12 @@ use std::ops::{Bound, Range, RangeInclusive}; impl JsonSchema for Option { no_ref_schema!(); - 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 { @@ -69,12 +69,12 @@ impl JsonSchema for Option { } 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 { @@ -100,12 +100,12 @@ impl JsonSchema for Result { } 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 { @@ -135,12 +135,12 @@ impl JsonSchema for Bound { } 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 { diff --git a/schemars/src/json_schema_impls/decimal.rs b/schemars/src/json_schema_impls/decimal.rs index 462e3d52..0ca98406 100644 --- a/schemars/src/json_schema_impls/decimal.rs +++ b/schemars/src/json_schema_impls/decimal.rs @@ -7,12 +7,8 @@ macro_rules! decimal_impl { impl JsonSchema for $type { no_ref_schema!(); - 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 { diff --git a/schemars/src/json_schema_impls/either1.rs b/schemars/src/json_schema_impls/either1.rs index 96ed8a88..334e9738 100644 --- a/schemars/src/json_schema_impls/either1.rs +++ b/schemars/src/json_schema_impls/either1.rs @@ -6,16 +6,12 @@ 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_name() -> Cow<'static, str> { + format!("Either_{}_or_{}", L::schema_name(), R::schema_name()).into() } fn schema_id() -> Cow<'static, str> { - Cow::Owned(format!( - "either::Either<{}, {}>", - L::schema_id(), - R::schema_id() - )) + format!("either::Either<{}, {}>", L::schema_id(), R::schema_id()).into() } fn json_schema(gen: &mut SchemaGenerator) -> Schema { diff --git a/schemars/src/json_schema_impls/ffi.rs b/schemars/src/json_schema_impls/ffi.rs index 4ad85c24..026d5ae0 100644 --- a/schemars/src/json_schema_impls/ffi.rs +++ b/schemars/src/json_schema_impls/ffi.rs @@ -4,12 +4,12 @@ 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 { diff --git a/schemars/src/json_schema_impls/maps.rs b/schemars/src/json_schema_impls/maps.rs index b934f8bb..9a630a78 100644 --- a/schemars/src/json_schema_impls/maps.rs +++ b/schemars/src/json_schema_impls/maps.rs @@ -10,12 +10,12 @@ macro_rules! map_impl { { no_ref_schema!(); - 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 { diff --git a/schemars/src/json_schema_impls/mod.rs b/schemars/src/json_schema_impls/mod.rs index ebcb61b2..e45e84c1 100644 --- a/schemars/src/json_schema_impls/mod.rs +++ b/schemars/src/json_schema_impls/mod.rs @@ -13,7 +13,7 @@ macro_rules! forward_impl { <$target>::is_referenceable() } - fn schema_name() -> String { + fn schema_name() -> std::borrow::Cow<'static, str> { <$target>::schema_name() } diff --git a/schemars/src/json_schema_impls/nonzero_signed.rs b/schemars/src/json_schema_impls/nonzero_signed.rs index d1b51429..634acc13 100644 --- a/schemars/src/json_schema_impls/nonzero_signed.rs +++ b/schemars/src/json_schema_impls/nonzero_signed.rs @@ -8,12 +8,12 @@ macro_rules! nonzero_unsigned_impl { impl JsonSchema for $type { no_ref_schema!(); - 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 { diff --git a/schemars/src/json_schema_impls/nonzero_unsigned.rs b/schemars/src/json_schema_impls/nonzero_unsigned.rs index e3c6d1d5..743af65f 100644 --- a/schemars/src/json_schema_impls/nonzero_unsigned.rs +++ b/schemars/src/json_schema_impls/nonzero_unsigned.rs @@ -1,6 +1,6 @@ use crate::gen::SchemaGenerator; -use crate::Schema; use crate::JsonSchema; +use crate::Schema; use std::borrow::Cow; use std::num::*; @@ -9,12 +9,12 @@ macro_rules! nonzero_unsigned_impl { impl JsonSchema for $type { no_ref_schema!(); - 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 { diff --git a/schemars/src/json_schema_impls/primitives.rs b/schemars/src/json_schema_impls/primitives.rs index 63121fe6..b65a93ae 100644 --- a/schemars/src/json_schema_impls/primitives.rs +++ b/schemars/src/json_schema_impls/primitives.rs @@ -9,12 +9,8 @@ macro_rules! simple_impl { impl JsonSchema for $type { no_ref_schema!(); - fn schema_name() -> String { - $instance_type.to_owned() - } - - fn schema_id() -> Cow<'static, str> { - Cow::Borrowed($instance_type) + fn schema_name() -> Cow<'static, str> { + $instance_type.into() } fn json_schema(_: &mut SchemaGenerator) -> Schema { @@ -28,12 +24,8 @@ macro_rules! simple_impl { impl JsonSchema for $type { no_ref_schema!(); - 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 { @@ -75,12 +67,8 @@ macro_rules! unsigned_impl { impl JsonSchema for $type { no_ref_schema!(); - 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 { @@ -104,12 +92,12 @@ unsigned_impl!(usize => "integer", "uint"); impl JsonSchema for char { no_ref_schema!(); - 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 { diff --git a/schemars/src/json_schema_impls/semver1.rs b/schemars/src/json_schema_impls/semver1.rs index fb47f788..dbcdf215 100644 --- a/schemars/src/json_schema_impls/semver1.rs +++ b/schemars/src/json_schema_impls/semver1.rs @@ -6,12 +6,12 @@ use std::borrow::Cow; impl JsonSchema for Version { no_ref_schema!(); - fn schema_name() -> String { - "Version".to_owned() + fn schema_name() -> Cow<'static, str> { + "Version".into() } fn schema_id() -> Cow<'static, str> { - Cow::Borrowed("semver::Version") + "semver::Version".into() } fn json_schema(_: &mut SchemaGenerator) -> Schema { diff --git a/schemars/src/json_schema_impls/sequences.rs b/schemars/src/json_schema_impls/sequences.rs index d14d4d33..dcc680f8 100644 --- a/schemars/src/json_schema_impls/sequences.rs +++ b/schemars/src/json_schema_impls/sequences.rs @@ -10,13 +10,12 @@ macro_rules! seq_impl { { no_ref_schema!(); - 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 { @@ -37,13 +36,12 @@ macro_rules! set_impl { { no_ref_schema!(); - 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 { diff --git a/schemars/src/json_schema_impls/serdejson.rs b/schemars/src/json_schema_impls/serdejson.rs index 5c6b58d4..fe767480 100644 --- a/schemars/src/json_schema_impls/serdejson.rs +++ b/schemars/src/json_schema_impls/serdejson.rs @@ -7,12 +7,8 @@ use std::collections::BTreeMap; impl JsonSchema for Value { no_ref_schema!(); - 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 { @@ -25,12 +21,8 @@ forward_impl!(Map => BTreeMap); impl JsonSchema for Number { no_ref_schema!(); - 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 { diff --git a/schemars/src/json_schema_impls/time.rs b/schemars/src/json_schema_impls/time.rs index 7317cfdc..0f1eb742 100644 --- a/schemars/src/json_schema_impls/time.rs +++ b/schemars/src/json_schema_impls/time.rs @@ -4,12 +4,12 @@ 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 { @@ -25,12 +25,12 @@ impl JsonSchema for Duration { } 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 { diff --git a/schemars/src/json_schema_impls/tuple.rs b/schemars/src/json_schema_impls/tuple.rs index 204169e9..2af816a1 100644 --- a/schemars/src/json_schema_impls/tuple.rs +++ b/schemars/src/json_schema_impls/tuple.rs @@ -8,10 +8,10 @@ macro_rules! tuple_impls { impl<$($name: JsonSchema),+> JsonSchema for ($($name,)+) { no_ref_schema!(); - 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> { @@ -19,7 +19,7 @@ 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 { diff --git a/schemars/src/json_schema_impls/url2.rs b/schemars/src/json_schema_impls/url2.rs index 2fcfd04d..04fa1e82 100644 --- a/schemars/src/json_schema_impls/url2.rs +++ b/schemars/src/json_schema_impls/url2.rs @@ -6,12 +6,12 @@ use url2::Url; impl JsonSchema for Url { no_ref_schema!(); - fn schema_name() -> String { - "Url".to_owned() + fn schema_name() -> Cow<'static, str> { + "Url".into() } fn schema_id() -> Cow<'static, str> { - Cow::Borrowed("url::Url") + "url::Url".into() } fn json_schema(_: &mut SchemaGenerator) -> Schema { diff --git a/schemars/src/json_schema_impls/uuid1.rs b/schemars/src/json_schema_impls/uuid1.rs index 825f7a24..297914ee 100644 --- a/schemars/src/json_schema_impls/uuid1.rs +++ b/schemars/src/json_schema_impls/uuid1.rs @@ -6,12 +6,12 @@ use uuid1::Uuid; impl JsonSchema for Uuid { no_ref_schema!(); - 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 { diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs index 32bb8c0b..b45fb21a 100644 --- a/schemars/src/lib.rs +++ b/schemars/src/lib.rs @@ -56,14 +56,14 @@ pub use schema::Schema; /// 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 { @@ -82,16 +82,16 @@ pub use schema::Schema; /// 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 { @@ -117,7 +117,7 @@ pub trait JsonSchema { /// 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. /// @@ -127,7 +127,7 @@ pub trait JsonSchema { /// /// The default implementation returns the same value as `schema_name()`. fn schema_id() -> Cow<'static, str> { - Cow::Owned(Self::schema_name()) + Self::schema_name() } /// Generates a JSON Schema for this type. diff --git a/schemars/src/schema.rs b/schemars/src/schema.rs index 6eb55e8e..91a35476 100644 --- a/schemars/src/schema.rs +++ b/schemars/src/schema.rs @@ -175,8 +175,8 @@ impl From for Schema { } impl crate::JsonSchema for Schema { - fn schema_name() -> String { - "Schema".to_owned() + fn schema_name() -> std::borrow::Cow<'static, str> { + "Schema".into() } fn schema_id() -> std::borrow::Cow<'static, str> { diff --git a/schemars_derive/src/lib.rs b/schemars_derive/src/lib.rs index c890f75c..a4c0742b 100644 --- a/schemars_derive/src/lib.rs +++ b/schemars_derive/src/lib.rs @@ -60,7 +60,7 @@ fn derive_json_schema(mut input: syn::DeriveInput, repr: bool) -> syn::Result::is_referenceable() } - fn schema_name() -> std::string::String { + fn schema_name() -> std::borrow::Cow<'static, str> { <#ty as schemars::JsonSchema>::schema_name() } @@ -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 } diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index 87e789e2..9fbc9ee5 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -114,8 +114,8 @@ fn type_for_schema(with_attr: &WithAttr) -> (syn::Type, Option) { false } - 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> { From f8b56cb455316af4dd0c4dabf53ef1eacb09cf49 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 26 May 2024 16:51:42 +0100 Subject: [PATCH 18/40] Replace `is_referenceable()` with `always_inline_schema()` --- docs/2-implementing.md | 10 +++++----- schemars/src/gen.rs | 16 ++++++++-------- schemars/src/json_schema_impls/array.rs | 4 ++-- schemars/src/json_schema_impls/arrayvec07.rs | 2 +- schemars/src/json_schema_impls/chrono04.rs | 4 ++-- schemars/src/json_schema_impls/core.rs | 2 +- schemars/src/json_schema_impls/decimal.rs | 2 +- schemars/src/json_schema_impls/either1.rs | 2 +- schemars/src/json_schema_impls/maps.rs | 2 +- schemars/src/json_schema_impls/mod.rs | 10 +++++----- schemars/src/json_schema_impls/nonzero_signed.rs | 2 +- .../src/json_schema_impls/nonzero_unsigned.rs | 2 +- schemars/src/json_schema_impls/primitives.rs | 8 ++++---- schemars/src/json_schema_impls/semver1.rs | 2 +- schemars/src/json_schema_impls/sequences.rs | 4 ++-- schemars/src/json_schema_impls/serdejson.rs | 4 ++-- schemars/src/json_schema_impls/tuple.rs | 2 +- schemars/src/json_schema_impls/url2.rs | 2 +- schemars/src/json_schema_impls/uuid1.rs | 2 +- schemars/src/lib.rs | 15 ++++++++------- schemars_derive/src/lib.rs | 4 ++-- schemars_derive/src/schema_exprs.rs | 4 ++-- 22 files changed, 53 insertions(+), 52 deletions(-) diff --git a/docs/2-implementing.md b/docs/2-implementing.md index b3cea57a..10375e4a 100644 --- a/docs/2-implementing.md +++ b/docs/2-implementing.md @@ -63,14 +63,14 @@ This function creates the JSON schema itself. The `gen` argument can be used to `json_schema` should not return a `$ref` schema. -## is_referenceable (optional) +## always_inline_schema (optional) ```rust -fn is_referenceable() -> bool; +fn always_inline_schema() -> 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. +If this function returns `false`, 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. +Generally, this should return `true` for types with simple schemas (such as primitives). For more complex types, it should return `false`. For recursive types, this **must** return `false` 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. +The default implementation of this function returns `false` to reduce the chance of someone inadvertently causing infinite cycles with recursive types. diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index 0bb1053c..3c9ac1a2 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -216,14 +216,14 @@ impl SchemaGenerator { /// 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 { @@ -270,7 +270,7 @@ impl SchemaGenerator { 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. @@ -278,7 +278,7 @@ impl SchemaGenerator { &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. @@ -286,7 +286,7 @@ impl SchemaGenerator { &mut self.definitions } - /// Returns the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated, + /// 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 @@ -302,7 +302,7 @@ impl SchemaGenerator { /// Generates a root JSON Schema for the type `T`. /// - /// 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 and include them in the returned `SchemaObject`'s /// [`definitions`](../schema/struct.Metadata.html#structfield.definitions) pub fn root_schema_for(&mut self) -> Schema { @@ -326,7 +326,7 @@ impl SchemaGenerator { /// Consumes `self` and generates a root JSON Schema for the type `T`. /// - /// 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 /// include them in the returned `SchemaObject`'s [`definitions`](../schema/struct.Metadata.html#structfield.definitions) pub fn into_root_schema_for(mut self) -> Schema { let mut schema = self.json_schema_internal::(T::schema_id()); diff --git a/schemars/src/json_schema_impls/array.rs b/schemars/src/json_schema_impls/array.rs index 1da1abb6..7dccbfb6 100644 --- a/schemars/src/json_schema_impls/array.rs +++ b/schemars/src/json_schema_impls/array.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; // Does not require T: JsonSchema. impl JsonSchema for [T; 0] { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { "EmptyArray".into() @@ -26,7 +26,7 @@ macro_rules! array_impls { ($($len:tt)+) => { $( impl JsonSchema for [T; $len] { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { format!("Array_size_{}_of_{}", $len, T::schema_name()).into() diff --git a/schemars/src/json_schema_impls/arrayvec07.rs b/schemars/src/json_schema_impls/arrayvec07.rs index 920f7c37..cd5f1544 100644 --- a/schemars/src/json_schema_impls/arrayvec07.rs +++ b/schemars/src/json_schema_impls/arrayvec07.rs @@ -10,7 +10,7 @@ impl JsonSchema for ArrayVec where T: JsonSchema, { - no_ref_schema!(); + always_inline!(); fn schema_name() -> std::borrow::Cow<'static, str> { format!("Array_up_to_size_{}_of_{}", CAP, T::schema_name()).into() diff --git a/schemars/src/json_schema_impls/chrono04.rs b/schemars/src/json_schema_impls/chrono04.rs index e8afa2ac..2635e568 100644 --- a/schemars/src/json_schema_impls/chrono04.rs +++ b/schemars/src/json_schema_impls/chrono04.rs @@ -4,7 +4,7 @@ use chrono04::prelude::*; use std::borrow::Cow; impl JsonSchema for Weekday { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { "Weekday".into() @@ -36,7 +36,7 @@ macro_rules! formatted_string_impl { }; ($ty:ident, $format:literal, $($desc:tt)+) => { impl $($desc)+ { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { stringify!($ty).into() diff --git a/schemars/src/json_schema_impls/core.rs b/schemars/src/json_schema_impls/core.rs index 0ca4e10d..d49f3921 100644 --- a/schemars/src/json_schema_impls/core.rs +++ b/schemars/src/json_schema_impls/core.rs @@ -5,7 +5,7 @@ use std::borrow::Cow; use std::ops::{Bound, Range, RangeInclusive}; impl JsonSchema for Option { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { format!("Nullable_{}", T::schema_name()).into() diff --git a/schemars/src/json_schema_impls/decimal.rs b/schemars/src/json_schema_impls/decimal.rs index 0ca98406..3266cccc 100644 --- a/schemars/src/json_schema_impls/decimal.rs +++ b/schemars/src/json_schema_impls/decimal.rs @@ -5,7 +5,7 @@ use std::borrow::Cow; macro_rules! decimal_impl { ($type:ty) => { impl JsonSchema for $type { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { "Decimal".into() diff --git a/schemars/src/json_schema_impls/either1.rs b/schemars/src/json_schema_impls/either1.rs index 334e9738..fcc3479e 100644 --- a/schemars/src/json_schema_impls/either1.rs +++ b/schemars/src/json_schema_impls/either1.rs @@ -4,7 +4,7 @@ use either1::Either; use std::borrow::Cow; impl JsonSchema for Either { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { format!("Either_{}_or_{}", L::schema_name(), R::schema_name()).into() diff --git a/schemars/src/json_schema_impls/maps.rs b/schemars/src/json_schema_impls/maps.rs index 9a630a78..19ae5cf5 100644 --- a/schemars/src/json_schema_impls/maps.rs +++ b/schemars/src/json_schema_impls/maps.rs @@ -8,7 +8,7 @@ macro_rules! map_impl { where V: JsonSchema, { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { format!("Map_of_{}", V::schema_name()).into() diff --git a/schemars/src/json_schema_impls/mod.rs b/schemars/src/json_schema_impls/mod.rs index e45e84c1..ca9ff289 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,8 +9,8 @@ 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() -> std::borrow::Cow<'static, str> { diff --git a/schemars/src/json_schema_impls/nonzero_signed.rs b/schemars/src/json_schema_impls/nonzero_signed.rs index 634acc13..c9d0919f 100644 --- a/schemars/src/json_schema_impls/nonzero_signed.rs +++ b/schemars/src/json_schema_impls/nonzero_signed.rs @@ -6,7 +6,7 @@ use std::num::*; macro_rules! nonzero_unsigned_impl { ($type:ty => $primitive:ty) => { impl JsonSchema for $type { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { stringify!($type).into() diff --git a/schemars/src/json_schema_impls/nonzero_unsigned.rs b/schemars/src/json_schema_impls/nonzero_unsigned.rs index 743af65f..afabc05d 100644 --- a/schemars/src/json_schema_impls/nonzero_unsigned.rs +++ b/schemars/src/json_schema_impls/nonzero_unsigned.rs @@ -7,7 +7,7 @@ use std::num::*; macro_rules! nonzero_unsigned_impl { ($type:ty => $primitive:ty) => { impl JsonSchema for $type { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { stringify!($type).into() diff --git a/schemars/src/json_schema_impls/primitives.rs b/schemars/src/json_schema_impls/primitives.rs index b65a93ae..a3bcce61 100644 --- a/schemars/src/json_schema_impls/primitives.rs +++ b/schemars/src/json_schema_impls/primitives.rs @@ -7,7 +7,7 @@ use std::path::{Path, PathBuf}; macro_rules! simple_impl { ($type:ty => $instance_type:literal) => { impl JsonSchema for $type { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { $instance_type.into() @@ -22,7 +22,7 @@ macro_rules! simple_impl { }; ($type:ty => $instance_type:literal, $format:literal) => { impl JsonSchema for $type { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { $format.into() @@ -65,7 +65,7 @@ simple_impl!(SocketAddrV6 => "string"); macro_rules! unsigned_impl { ($type:ty => $instance_type:literal, $format:literal) => { impl JsonSchema for $type { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { $format.into() @@ -90,7 +90,7 @@ unsigned_impl!(u128 => "integer", "uint128"); unsigned_impl!(usize => "integer", "uint"); impl JsonSchema for char { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { "Character".into() diff --git a/schemars/src/json_schema_impls/semver1.rs b/schemars/src/json_schema_impls/semver1.rs index dbcdf215..eb121fbc 100644 --- a/schemars/src/json_schema_impls/semver1.rs +++ b/schemars/src/json_schema_impls/semver1.rs @@ -4,7 +4,7 @@ use semver1::Version; use std::borrow::Cow; impl JsonSchema for Version { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { "Version".into() diff --git a/schemars/src/json_schema_impls/sequences.rs b/schemars/src/json_schema_impls/sequences.rs index dcc680f8..c1313390 100644 --- a/schemars/src/json_schema_impls/sequences.rs +++ b/schemars/src/json_schema_impls/sequences.rs @@ -8,7 +8,7 @@ macro_rules! seq_impl { where T: JsonSchema, { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { format!("Array_of_{}", T::schema_name()).into() @@ -34,7 +34,7 @@ macro_rules! set_impl { where T: JsonSchema, { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { format!("Set_of_{}", T::schema_name()).into() diff --git a/schemars/src/json_schema_impls/serdejson.rs b/schemars/src/json_schema_impls/serdejson.rs index fe767480..cd253260 100644 --- a/schemars/src/json_schema_impls/serdejson.rs +++ b/schemars/src/json_schema_impls/serdejson.rs @@ -5,7 +5,7 @@ use std::borrow::Cow; use std::collections::BTreeMap; impl JsonSchema for Value { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { "AnyValue".into() @@ -19,7 +19,7 @@ impl JsonSchema for Value { forward_impl!(Map => BTreeMap); impl JsonSchema for Number { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { "Number".into() diff --git a/schemars/src/json_schema_impls/tuple.rs b/schemars/src/json_schema_impls/tuple.rs index 2af816a1..c05a5f87 100644 --- a/schemars/src/json_schema_impls/tuple.rs +++ b/schemars/src/json_schema_impls/tuple.rs @@ -6,7 +6,7 @@ macro_rules! tuple_impls { ($($len:expr => ($($name:ident)+))+) => { $( impl<$($name: JsonSchema),+> JsonSchema for ($($name,)+) { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { let mut name = "Tuple_of_".to_owned(); diff --git a/schemars/src/json_schema_impls/url2.rs b/schemars/src/json_schema_impls/url2.rs index 04fa1e82..5a467d9d 100644 --- a/schemars/src/json_schema_impls/url2.rs +++ b/schemars/src/json_schema_impls/url2.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; use url2::Url; impl JsonSchema for Url { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { "Url".into() diff --git a/schemars/src/json_schema_impls/uuid1.rs b/schemars/src/json_schema_impls/uuid1.rs index 297914ee..a56c10cf 100644 --- a/schemars/src/json_schema_impls/uuid1.rs +++ b/schemars/src/json_schema_impls/uuid1.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; use uuid1::Uuid; impl JsonSchema for Uuid { - no_ref_schema!(); + always_inline!(); fn schema_name() -> Cow<'static, str> { "Uuid".into() diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs index b45fb21a..32fb1827 100644 --- a/schemars/src/lib.rs +++ b/schemars/src/lib.rs @@ -104,14 +104,15 @@ pub 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. @@ -132,7 +133,7 @@ pub trait JsonSchema { /// Generates a JSON Schema for this type. /// - /// If the returned schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will + /// If the returned schema depends on any [non-inlined](JsonSchema::always_inline_schema) schemas, then this method will /// add them to the [`SchemaGenerator`](gen::SchemaGenerator)'s schema definitions. /// /// This should not return a `$ref` schema. diff --git a/schemars_derive/src/lib.rs b/schemars_derive/src/lib.rs index a4c0742b..c4cfaa8c 100644 --- a/schemars_derive/src/lib.rs +++ b/schemars_derive/src/lib.rs @@ -56,8 +56,8 @@ 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::borrow::Cow<'static, str> { diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index 9fbc9ee5..f3f527d1 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -110,8 +110,8 @@ 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::borrow::Cow<'static, str> { From fb6e1a5c61f478ebb3724e072ef5c122ba20d6a2 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 26 May 2024 21:22:48 +0100 Subject: [PATCH 19/40] Regenerate example schemas --- docs/_includes/examples/custom_serialization.schema.json | 2 +- docs/_includes/examples/doc_comments.schema.json | 6 +++--- docs/_includes/examples/enum_repr.schema.json | 2 +- docs/_includes/examples/from_value.schema.json | 2 +- docs/_includes/examples/main.schema.json | 6 +++--- docs/_includes/examples/remote_derive.schema.json | 8 ++++---- docs/_includes/examples/schemars_attrs.schema.json | 6 +++--- docs/_includes/examples/serde_attrs.schema.json | 6 +++--- docs/_includes/examples/validate.schema.json | 2 +- schemars/examples/custom_serialization.schema.json | 2 +- schemars/examples/doc_comments.schema.json | 6 +++--- schemars/examples/enum_repr.schema.json | 2 +- schemars/examples/from_value.schema.json | 2 +- schemars/examples/main.schema.json | 6 +++--- schemars/examples/remote_derive.schema.json | 8 ++++---- schemars/examples/schemars_attrs.schema.json | 6 +++--- schemars/examples/serde_attrs.schema.json | 6 +++--- schemars/examples/validate.schema.json | 2 +- 18 files changed, 40 insertions(+), 40 deletions(-) diff --git a/docs/_includes/examples/custom_serialization.schema.json b/docs/_includes/examples/custom_serialization.schema.json index 1475c253..1aa09420 100644 --- a/docs/_includes/examples/custom_serialization.schema.json +++ b/docs/_includes/examples/custom_serialization.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "properties": { diff --git a/docs/_includes/examples/doc_comments.schema.json b/docs/_includes/examples/doc_comments.schema.json index 501b79c8..417d3bed 100644 --- a/docs/_includes/examples/doc_comments.schema.json +++ b/docs/_includes/examples/doc_comments.schema.json @@ -1,5 +1,5 @@ { - "$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.", "type": "object", @@ -18,7 +18,7 @@ "description": "This enum might be set, or it might not.", "anyOf": [ { - "$ref": "#/definitions/MyEnum" + "$ref": "#/$defs/MyEnum" }, { "type": "null" @@ -30,7 +30,7 @@ "my_int", "my_bool" ], - "definitions": { + "$defs": { "MyEnum": { "title": "My Amazing Enum", "oneOf": [ diff --git a/docs/_includes/examples/enum_repr.schema.json b/docs/_includes/examples/enum_repr.schema.json index 04841b7d..22d2233f 100644 --- a/docs/_includes/examples/enum_repr.schema.json +++ b/docs/_includes/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/docs/_includes/examples/from_value.schema.json b/docs/_includes/examples/from_value.schema.json index 237a90e7..64bad01c 100644 --- a/docs/_includes/examples/from_value.schema.json +++ b/docs/_includes/examples/from_value.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "properties": { diff --git a/docs/_includes/examples/main.schema.json b/docs/_includes/examples/main.schema.json index bcfe1372..5eab9093 100644 --- a/docs/_includes/examples/main.schema.json +++ b/docs/_includes/examples/main.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "properties": { @@ -13,7 +13,7 @@ "my_nullable_enum": { "anyOf": [ { - "$ref": "#/definitions/MyEnum" + "$ref": "#/$defs/MyEnum" }, { "type": "null" @@ -25,7 +25,7 @@ "my_int", "my_bool" ], - "definitions": { + "$defs": { "MyEnum": { "oneOf": [ { diff --git a/docs/_includes/examples/remote_derive.schema.json b/docs/_includes/examples/remote_derive.schema.json index b6315cbb..df697510 100644 --- a/docs/_includes/examples/remote_derive.schema.json +++ b/docs/_includes/examples/remote_derive.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Process", "type": "object", "properties": { @@ -9,11 +9,11 @@ "durations": { "type": "array", "items": { - "$ref": "#/definitions/Duration" + "$ref": "#/$defs/Duration" } }, "wall_time": { - "$ref": "#/definitions/Duration" + "$ref": "#/$defs/Duration" } }, "required": [ @@ -21,7 +21,7 @@ "wall_time", "durations" ], - "definitions": { + "$defs": { "Duration": { "type": "object", "properties": { diff --git a/docs/_includes/examples/schemars_attrs.schema.json b/docs/_includes/examples/schemars_attrs.schema.json index 9637e1b4..ba967677 100644 --- a/docs/_includes/examples/schemars_attrs.schema.json +++ b/docs/_includes/examples/schemars_attrs.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "properties": { @@ -9,7 +9,7 @@ "myNullableEnum": { "anyOf": [ { - "$ref": "#/definitions/MyEnum" + "$ref": "#/$defs/MyEnum" }, { "type": "null" @@ -37,7 +37,7 @@ "myBool", "myVecStr" ], - "definitions": { + "$defs": { "MyEnum": { "anyOf": [ { diff --git a/docs/_includes/examples/serde_attrs.schema.json b/docs/_includes/examples/serde_attrs.schema.json index 9bb2f82d..81798994 100644 --- a/docs/_includes/examples/serde_attrs.schema.json +++ b/docs/_includes/examples/serde_attrs.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "properties": { @@ -9,7 +9,7 @@ "myNullableEnum": { "anyOf": [ { - "$ref": "#/definitions/MyEnum" + "$ref": "#/$defs/MyEnum" }, { "type": "null" @@ -27,7 +27,7 @@ "myNumber", "myBool" ], - "definitions": { + "$defs": { "MyEnum": { "anyOf": [ { diff --git a/docs/_includes/examples/validate.schema.json b/docs/_includes/examples/validate.schema.json index 308f631e..e9f8a1d8 100644 --- a/docs/_includes/examples/validate.schema.json +++ b/docs/_includes/examples/validate.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "properties": { diff --git a/schemars/examples/custom_serialization.schema.json b/schemars/examples/custom_serialization.schema.json index 1475c253..1aa09420 100644 --- a/schemars/examples/custom_serialization.schema.json +++ b/schemars/examples/custom_serialization.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "properties": { diff --git a/schemars/examples/doc_comments.schema.json b/schemars/examples/doc_comments.schema.json index 501b79c8..417d3bed 100644 --- a/schemars/examples/doc_comments.schema.json +++ b/schemars/examples/doc_comments.schema.json @@ -1,5 +1,5 @@ { - "$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.", "type": "object", @@ -18,7 +18,7 @@ "description": "This enum might be set, or it might not.", "anyOf": [ { - "$ref": "#/definitions/MyEnum" + "$ref": "#/$defs/MyEnum" }, { "type": "null" @@ -30,7 +30,7 @@ "my_int", "my_bool" ], - "definitions": { + "$defs": { "MyEnum": { "title": "My Amazing Enum", "oneOf": [ diff --git a/schemars/examples/enum_repr.schema.json b/schemars/examples/enum_repr.schema.json index 04841b7d..22d2233f 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 237a90e7..64bad01c 100644 --- a/schemars/examples/from_value.schema.json +++ b/schemars/examples/from_value.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "properties": { diff --git a/schemars/examples/main.schema.json b/schemars/examples/main.schema.json index bcfe1372..5eab9093 100644 --- a/schemars/examples/main.schema.json +++ b/schemars/examples/main.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "properties": { @@ -13,7 +13,7 @@ "my_nullable_enum": { "anyOf": [ { - "$ref": "#/definitions/MyEnum" + "$ref": "#/$defs/MyEnum" }, { "type": "null" @@ -25,7 +25,7 @@ "my_int", "my_bool" ], - "definitions": { + "$defs": { "MyEnum": { "oneOf": [ { diff --git a/schemars/examples/remote_derive.schema.json b/schemars/examples/remote_derive.schema.json index b6315cbb..df697510 100644 --- a/schemars/examples/remote_derive.schema.json +++ b/schemars/examples/remote_derive.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Process", "type": "object", "properties": { @@ -9,11 +9,11 @@ "durations": { "type": "array", "items": { - "$ref": "#/definitions/Duration" + "$ref": "#/$defs/Duration" } }, "wall_time": { - "$ref": "#/definitions/Duration" + "$ref": "#/$defs/Duration" } }, "required": [ @@ -21,7 +21,7 @@ "wall_time", "durations" ], - "definitions": { + "$defs": { "Duration": { "type": "object", "properties": { diff --git a/schemars/examples/schemars_attrs.schema.json b/schemars/examples/schemars_attrs.schema.json index 9637e1b4..ba967677 100644 --- a/schemars/examples/schemars_attrs.schema.json +++ b/schemars/examples/schemars_attrs.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "properties": { @@ -9,7 +9,7 @@ "myNullableEnum": { "anyOf": [ { - "$ref": "#/definitions/MyEnum" + "$ref": "#/$defs/MyEnum" }, { "type": "null" @@ -37,7 +37,7 @@ "myBool", "myVecStr" ], - "definitions": { + "$defs": { "MyEnum": { "anyOf": [ { diff --git a/schemars/examples/serde_attrs.schema.json b/schemars/examples/serde_attrs.schema.json index 9bb2f82d..81798994 100644 --- a/schemars/examples/serde_attrs.schema.json +++ b/schemars/examples/serde_attrs.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "properties": { @@ -9,7 +9,7 @@ "myNullableEnum": { "anyOf": [ { - "$ref": "#/definitions/MyEnum" + "$ref": "#/$defs/MyEnum" }, { "type": "null" @@ -27,7 +27,7 @@ "myNumber", "myBool" ], - "definitions": { + "$defs": { "MyEnum": { "anyOf": [ { diff --git a/schemars/examples/validate.schema.json b/schemars/examples/validate.schema.json index 308f631e..e9f8a1d8 100644 --- a/schemars/examples/validate.schema.json +++ b/schemars/examples/validate.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyStruct", "type": "object", "properties": { From 760403e2f590bab1695f295fc0e146f12cf6e260 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Mon, 27 May 2024 11:09:15 +0100 Subject: [PATCH 20/40] Update doc comments and make `SchemaGenerator` available from crate root --- README.md | 38 +++++------ .../examples/custom_serialization.rs | 3 +- schemars/examples/custom_serialization.rs | 3 +- schemars/src/_private.rs | 9 +-- schemars/src/gen.rs | 35 +++++----- schemars/src/lib.rs | 23 ++++--- schemars/src/macros.rs | 33 +++++++--- schemars/src/schema.rs | 65 +++++++++++++++++-- schemars/src/ser.rs | 3 +- schemars/tests/schema_with_enum.rs | 2 +- schemars/tests/schema_with_struct.rs | 2 +- schemars_derive/src/lib.rs | 6 +- schemars_derive/src/schema_exprs.rs | 2 +- 13 files changed, 150 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 2ff1837d..b24b90db 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,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" @@ -51,7 +50,7 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); "my_nullable_enum": { "anyOf": [ { - "$ref": "#/definitions/MyEnum" + "$ref": "#/$defs/MyEnum" }, { "type": "null" @@ -59,26 +58,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", @@ -87,10 +85,12 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); "format": "float" } } - } + }, + "required": ["floats"] } }, - "additionalProperties": false + "additionalProperties": false, + "required": ["StructVariant"] } ] } @@ -134,24 +134,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", @@ -159,7 +158,8 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); } }, "additionalProperties": false, - "definitions": { + "required": ["myNumber", "myBool"], + "$defs": { "MyEnum": { "anyOf": [ { @@ -167,7 +167,6 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); }, { "type": "object", - "required": ["floats"], "properties": { "floats": { "type": "array", @@ -176,7 +175,8 @@ println!("{}", serde_json::to_string_pretty(&schema).unwrap()); "format": "float" } } - } + }, + "required": ["floats"] } ] } diff --git a/docs/_includes/examples/custom_serialization.rs b/docs/_includes/examples/custom_serialization.rs index c119ea5d..c32203f0 100644 --- a/docs/_includes/examples/custom_serialization.rs +++ b/docs/_includes/examples/custom_serialization.rs @@ -1,5 +1,4 @@ -use schemars::Schema; -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`. diff --git a/schemars/examples/custom_serialization.rs b/schemars/examples/custom_serialization.rs index c119ea5d..c32203f0 100644 --- a/schemars/examples/custom_serialization.rs +++ b/schemars/examples/custom_serialization.rs @@ -1,5 +1,4 @@ -use schemars::Schema; -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`. diff --git a/schemars/src/_private.rs b/schemars/src/_private.rs index 4be6c271..cdaec3a1 100644 --- a/schemars/src/_private.rs +++ b/schemars/src/_private.rs @@ -1,11 +1,6 @@ -use crate::gen::SchemaGenerator; -use crate::JsonSchema; -use crate::Schema; +use crate::{JsonSchema, Schema, SchemaGenerator}; use serde::Serialize; -use serde_json::json; -use serde_json::map::Entry; -use serde_json::Map; -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( diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index 3c9ac1a2..cf7f007a 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -12,8 +12,8 @@ use crate::{visit::*, JsonSchema}; use dyn_clone::DynClone; use serde::Serialize; use serde_json::{Map, Value}; -use std::collections::HashMap; -use std::{any::Any, collections::HashSet, fmt::Debug}; +use std::collections::{HashMap, HashSet}; +use std::{any::Any, fmt::Debug}; type CowStr = std::borrow::Cow<'static, str>; @@ -38,7 +38,7 @@ pub struct SchemaSettings { /// /// A single leading `#` and/or single trailing `/` are ignored. /// - /// Defaults to `/$defs`. + /// Defaults to `"/$defs"`. pub definitions_path: String, /// The URI of the meta-schema describing the structure of the generated schemas. /// @@ -55,6 +55,8 @@ 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::draft2020_12() } @@ -97,7 +99,7 @@ impl SchemaSettings { } } - /// 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 [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, @@ -153,7 +155,7 @@ impl SchemaSettings { /// /// # Example /// ``` -/// use schemars::{JsonSchema, gen::SchemaGenerator}; +/// use schemars::{JsonSchema, SchemaGenerator}; /// /// #[derive(JsonSchema)] /// struct MyStruct { @@ -203,7 +205,7 @@ impl SchemaGenerator { /// /// # Example /// ``` - /// use schemars::gen::SchemaGenerator; + /// use schemars::SchemaGenerator; /// /// let gen = SchemaGenerator::default(); /// let settings = gen.settings(); @@ -287,7 +289,7 @@ impl SchemaGenerator { } /// Returns the collection of all [non-inlined](JsonSchema::always_inline_schema) schemas that have been generated, - /// leaving an empty map in its place. + /// 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. @@ -300,11 +302,10 @@ impl SchemaGenerator { self.settings.visitors.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 [non-inlined](JsonSchema::always_inline_schema) 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) + /// 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()); @@ -324,10 +325,10 @@ impl SchemaGenerator { 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 [non-inlined](JsonSchema::always_inline_schema) schemas, then this method will - /// include them in the returned `SchemaObject`'s [`definitions`](../schema/struct.Metadata.html#structfield.definitions) + /// 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()); @@ -348,10 +349,12 @@ impl SchemaGenerator { 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, @@ -377,10 +380,12 @@ impl SchemaGenerator { 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, @@ -513,7 +518,7 @@ fn json_pointer_mut<'a>( Some(object) } -/// A [Visitor](Visitor) which implements additional traits required to be included in a [SchemaSettings]. +/// A [Visitor] 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`] diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs index 32fb1827..dbb307ab 100644 --- a/schemars/src/lib.rs +++ b/schemars/src/lib.rs @@ -11,7 +11,9 @@ 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; +/// Types for recursively modifying JSON schemas. pub mod visit; #[cfg(feature = "schemars_derive")] @@ -25,6 +27,7 @@ pub use schemars_derive::*; #[doc(hidden)] pub use serde_json as _serde_json; +pub use gen::SchemaGenerator; pub use schema::Schema; /// A type which can be described as a JSON Schema document. @@ -50,7 +53,7 @@ pub 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, JsonSchema}; +/// use schemars::{SchemaGenerator, Schema, JsonSchema, json_schema}; /// use std::borrow::Cow; /// /// struct NonGenericType; @@ -67,7 +70,9 @@ pub use schema::Schema; /// } /// /// fn json_schema(_gen: &mut SchemaGenerator) -> Schema { -/// todo!() +/// json_schema!({ +/// "foo": "bar" +/// }) /// } /// } /// @@ -76,7 +81,7 @@ pub 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, JsonSchema}; +/// use schemars::{SchemaGenerator, Schema, JsonSchema, json_schema}; /// use std::{borrow::Cow, marker::PhantomData}; /// /// struct GenericType(PhantomData); @@ -95,7 +100,9 @@ pub use schema::Schema; /// } /// /// fn json_schema(_gen: &mut SchemaGenerator) -> Schema { -/// todo!() +/// json_schema!({ +/// "foo": "bar" +/// }) /// } /// } /// @@ -126,7 +133,7 @@ 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> { Self::schema_name() } @@ -134,14 +141,14 @@ pub trait JsonSchema { /// Generates a JSON Schema for this type. /// /// If the returned schema depends on any [non-inlined](JsonSchema::always_inline_schema) schemas, then this method will - /// add them to the [`SchemaGenerator`](gen::SchemaGenerator)'s schema definitions. + /// 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) } diff --git a/schemars/src/macros.rs b/schemars/src/macros.rs index baa144d3..e3272847 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,13 +73,26 @@ 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() }; } -// TODO doc +/// 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 { ( diff --git a/schemars/src/schema.rs b/schemars/src/schema.rs index 91a35476..dd9e8664 100644 --- a/schemars/src/schema.rs +++ b/schemars/src/schema.rs @@ -7,6 +7,54 @@ use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; /// A JSON Schema. +/// +/// 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); @@ -32,28 +80,32 @@ impl Serialize for Schema { } impl Schema { - pub fn new() -> Self { - Self(Value::Object(Map::new())) - } - + /// 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 { let mut map = Map::new(); map.insert("$ref".to_owned(), Value::String(reference)); Self(Value::Object(map)) } + /// 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() } @@ -66,10 +118,15 @@ impl Schema { } } + /// 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. + /// + /// `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(); diff --git a/schemars/src/ser.rs b/schemars/src/ser.rs index 75ae6cac..f42c187a 100644 --- a/schemars/src/ser.rs +++ b/schemars/src/ser.rs @@ -1,5 +1,4 @@ -use crate::gen::SchemaGenerator; -use crate::{json_schema, JsonSchema, Schema}; +use crate::{json_schema, JsonSchema, Schema, SchemaGenerator}; use serde_json::{Error, Map, Value}; use std::fmt::Display; diff --git a/schemars/tests/schema_with_enum.rs b/schemars/tests/schema_with_enum.rs index 5a6c575b..cf01b37b 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 { +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 70ba24d6..5fc8c370 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 { +fn schema_fn(gen: &mut schemars::SchemaGenerator) -> schemars::Schema { ::json_schema(gen) } diff --git a/schemars_derive/src/lib.rs b/schemars_derive/src/lib.rs index c4cfaa8c..bd427bd5 100644 --- a/schemars_derive/src/lib.rs +++ b/schemars_derive/src/lib.rs @@ -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 { + 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 { + fn _schemars_private_non_optional_json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema { <#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(gen) } @@ -186,7 +186,7 @@ fn derive_json_schema(mut input: syn::DeriveInput, repr: bool) -> syn::Result schemars::Schema { + fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema { #schema_expr } }; diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index f3f527d1..48da5eff 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -127,7 +127,7 @@ fn type_for_schema(with_attr: &WithAttr) -> (syn::Type, Option) { )) } - fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Schema { + fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema { #fun(gen) } } From 3ee7c7f5e566ec50ba3b0a0a9a1acea32adfe2ad Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Mon, 27 May 2024 11:14:43 +0100 Subject: [PATCH 21/40] v1.0.0-alpha.1 --- Cargo.lock | 4 ++-- schemars/Cargo.toml | 4 ++-- schemars_derive/Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4653cb93..729d10c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -303,7 +303,7 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "schemars" -version = "0.8.21" +version = "1.0.0-alpha.1" dependencies = [ "arrayvec", "bigdecimal", @@ -329,7 +329,7 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.21" +version = "1.0.0-alpha.1" dependencies = [ "pretty_assertions", "proc-macro2", diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 2ddbc07c..238f42db 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.1" authors = ["Graham Esau "] edition = "2021" license = "MIT" @@ -14,7 +14,7 @@ build = "build.rs" rust-version = "1.60" [dependencies] -schemars_derive = { version = "=0.8.21", optional = true, path = "../schemars_derive" } +schemars_derive = { version = "=1.0.0-alpha.1", optional = true, path = "../schemars_derive" } serde = "1.0" serde_json = "1.0.25" dyn-clone = "1.0" diff --git a/schemars_derive/Cargo.toml b/schemars_derive/Cargo.toml index 94ef0fd6..6eff88f1 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.1" authors = ["Graham Esau "] edition = "2021" license = "MIT" From 97b70aa82cc4b6bd6ffddbf8d38a46b94af292b2 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Mon, 27 May 2024 14:25:45 +0100 Subject: [PATCH 22/40] Update readme for v1 --- README.md | 31 +++++++++++++------------------ schemars/Cargo.toml | 18 +++++++++--------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index b24b90db..756c8a7a 100644 --- a/README.md +++ b/README.md @@ -251,33 +251,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.1", features = ["chrono04"] } ``` diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 238f42db..320ddf9e 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -21,19 +21,19 @@ dyn-clone = "1.0" ref-cast = "1.0.22" # optional dependencies -chrono04 = { version = "0.4", default-features = false, optional = true, package = "chrono" } -indexmap2 = { version = "2.0", default-features = false, optional = true, package = "indexmap" } -either1 = { version = "1.3", default-features = false, optional = true, package = "either" } -uuid1 = { version = "1.0", default-features = false, optional = true, package = "uuid" } -smallvec1 = { version = "1.0", default-features = false, optional = true, package = "smallvec" } arrayvec07 = { version = "0.7", default-features = false, optional = true, package = "arrayvec" } -url2 = { version = "2.0", default-features = false, optional = true, package = "url" } -bytes1 = { version = "1.0", default-features = false, optional = true, package = "bytes" } -rust_decimal1 = { version = "1", default-features = false, optional = true, package = "rust_decimal"} bigdecimal04 = { version = "0.4", default-features = false, optional = true, package = "bigdecimal" } +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" } -smol_str02 = { version = "0.2.1", default-features = false, optional = true, package = "smol_str" } +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" From 840315b2ddcb670b50d0e91940c61452f95ab32f Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Wed, 5 Jun 2024 21:09:52 +0100 Subject: [PATCH 23/40] Add `#[schemars(extend("key" = value))]` attribute (#297) --- schemars/tests/expected/default.json | 4 +- .../tests/expected/extend_enum_adjacent.json | 101 ++++++++++++++++++ .../tests/expected/extend_enum_external.json | 73 +++++++++++++ .../tests/expected/extend_enum_internal.json | 55 ++++++++++ .../tests/expected/extend_enum_untagged.json | 46 ++++++++ schemars/tests/expected/extend_struct.json | 27 +++++ .../tests/expected/from_value_2019_09.json | 6 +- .../tests/expected/from_value_draft07.json | 6 +- .../tests/expected/from_value_openapi3.json | 6 +- .../expected/schema_settings-2019_09.json | 6 +- .../expected/schema_settings-openapi3.json | 6 +- schemars/tests/expected/schema_settings.json | 6 +- schemars/tests/extend.rs | 96 +++++++++++++++++ schemars/tests/ui/invalid_extend.rs | 11 ++ schemars/tests/ui/invalid_extend.stderr | 35 ++++++ schemars_derive/src/attr/mod.rs | 54 +++++++++- schemars_derive/src/metadata.rs | 7 ++ schemars_derive/src/schema_exprs.rs | 8 +- 18 files changed, 527 insertions(+), 26 deletions(-) create mode 100644 schemars/tests/expected/extend_enum_adjacent.json create mode 100644 schemars/tests/expected/extend_enum_external.json create mode 100644 schemars/tests/expected/extend_enum_internal.json create mode 100644 schemars/tests/expected/extend_enum_untagged.json create mode 100644 schemars/tests/expected/extend_struct.json create mode 100644 schemars/tests/extend.rs create mode 100644 schemars/tests/ui/invalid_extend.rs create mode 100644 schemars/tests/ui/invalid_extend.stderr diff --git a/schemars/tests/expected/default.json b/schemars/tests/expected/default.json index d70fb57f..72e580d4 100644 --- a/schemars/tests/expected/default.json +++ b/schemars/tests/expected/default.json @@ -13,11 +13,11 @@ "default": false }, "my_optional_string": { - "default": null, "type": [ "string", "null" - ] + ], + "default": null }, "my_struct2": { "$ref": "#/$defs/MyStruct2", diff --git a/schemars/tests/expected/extend_enum_adjacent.json b/schemars/tests/expected/extend_enum_adjacent.json new file mode 100644 index 00000000..6241e079 --- /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 00000000..c15d47f5 --- /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 00000000..0dee8174 --- /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 00000000..4f733fe9 --- /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 00000000..fc7dd50f --- /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/from_value_2019_09.json b/schemars/tests/expected/from_value_2019_09.json index 52c05243..939410da 100644 --- a/schemars/tests/expected/from_value_2019_09.json +++ b/schemars/tests/expected/from_value_2019_09.json @@ -35,6 +35,8 @@ }, "my_tuple": { "type": "array", + "minItems": 2, + "maxItems": 2, "items": [ { "type": "string", @@ -44,9 +46,7 @@ { "type": "integer" } - ], - "maxItems": 2, - "minItems": 2 + ] } } } diff --git a/schemars/tests/expected/from_value_draft07.json b/schemars/tests/expected/from_value_draft07.json index de89fada..721e8716 100644 --- a/schemars/tests/expected/from_value_draft07.json +++ b/schemars/tests/expected/from_value_draft07.json @@ -35,6 +35,8 @@ }, "my_tuple": { "type": "array", + "minItems": 2, + "maxItems": 2, "items": [ { "type": "string", @@ -44,9 +46,7 @@ { "type": "integer" } - ], - "maxItems": 2, - "minItems": 2 + ] } } } diff --git a/schemars/tests/expected/from_value_openapi3.json b/schemars/tests/expected/from_value_openapi3.json index 4e9dd2cc..88f08a79 100644 --- a/schemars/tests/expected/from_value_openapi3.json +++ b/schemars/tests/expected/from_value_openapi3.json @@ -37,6 +37,8 @@ }, "my_tuple": { "type": "array", + "minItems": 2, + "maxItems": 2, "items": [ { "type": "string", @@ -46,9 +48,7 @@ { "type": "integer" } - ], - "maxItems": 2, - "minItems": 2 + ] } } } diff --git a/schemars/tests/expected/schema_settings-2019_09.json b/schemars/tests/expected/schema_settings-2019_09.json index 6b6dc614..bc99f155 100644 --- a/schemars/tests/expected/schema_settings-2019_09.json +++ b/schemars/tests/expected/schema_settings-2019_09.json @@ -30,6 +30,8 @@ "type": "array", "items": { "type": "array", + "maxItems": 2, + "minItems": 2, "items": [ { "type": "integer", @@ -40,9 +42,7 @@ "type": "integer", "format": "int64" } - ], - "minItems": 2, - "maxItems": 2 + ] } } }, diff --git a/schemars/tests/expected/schema_settings-openapi3.json b/schemars/tests/expected/schema_settings-openapi3.json index e5032f0d..8365822f 100644 --- a/schemars/tests/expected/schema_settings-openapi3.json +++ b/schemars/tests/expected/schema_settings-openapi3.json @@ -25,6 +25,8 @@ "type": "array", "items": { "type": "array", + "maxItems": 2, + "minItems": 2, "items": [ { "type": "integer", @@ -35,9 +37,7 @@ "type": "integer", "format": "int64" } - ], - "minItems": 2, - "maxItems": 2 + ] } } }, diff --git a/schemars/tests/expected/schema_settings.json b/schemars/tests/expected/schema_settings.json index a836cd68..4c435ad7 100644 --- a/schemars/tests/expected/schema_settings.json +++ b/schemars/tests/expected/schema_settings.json @@ -30,6 +30,8 @@ "type": "array", "items": { "type": "array", + "maxItems": 2, + "minItems": 2, "items": [ { "type": "integer", @@ -40,9 +42,7 @@ "type": "integer", "format": "int64" } - ], - "minItems": 2, - "maxItems": 2 + ] } } }, diff --git a/schemars/tests/extend.rs b/schemars/tests/extend.rs new file mode 100644 index 00000000..08f42fa9 --- /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 doc_comments_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 doc_comments_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 doc_comments_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 doc_comments_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 doc_comments_enum_adjacent() -> TestResult { + test_default_generated_schema::("extend_enum_adjacent") +} diff --git a/schemars/tests/ui/invalid_extend.rs b/schemars/tests/ui/invalid_extend.rs new file mode 100644 index 00000000..b7295f73 --- /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 00000000..d7d21798 --- /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_derive/src/attr/mod.rs b/schemars_derive/src/attr/mod.rs index 86c93498..108cedc4 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,7 @@ pub struct Attrs { pub repr: Option, pub crate_name: Option, pub is_renamed: bool, + pub extensions: Vec<(String, TokenStream)>, } #[derive(Debug)] @@ -68,6 +69,7 @@ impl Attrs { description: self.description.as_ref().and_then(none_if_empty), deprecated: self.deprecated, examples: &self.examples, + extensions: &self.extensions, read_only: false, write_only: false, default: None, @@ -162,6 +164,29 @@ impl Attrs { } } + 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 +223,8 @@ impl Attrs { repr: None, crate_name: None, is_renamed: _, - } if examples.is_empty()) + extensions, + } if examples.is_empty() && extensions.is_empty()) } } @@ -322,3 +348,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/metadata.rs b/schemars_derive/src/metadata.rs index 6de68343..6a3808c3 100644 --- a/schemars_derive/src/metadata.rs +++ b/schemars_derive/src/metadata.rs @@ -9,6 +9,7 @@ pub struct SchemaMetadata<'a> { pub write_only: bool, pub examples: &'a [syn::Path], pub default: Option, + pub extensions: &'a [(String, TokenStream)], } impl<'a> SchemaMetadata<'a> { @@ -74,6 +75,12 @@ impl<'a> SchemaMetadata<'a> { }); } + 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 48da5eff..1f5df99c 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -232,13 +232,13 @@ fn expr_for_internal_tagged_enum<'a>( let name = variant.name(); let mut schema_expr = expr_for_internal_tagged_enum_variant(variant, deny_unknown_fields); - variant.attrs.as_metadata().apply_to_schema(&mut schema_expr); - - quote!({ + 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(); From 3150f98fc845959f866dccde0c01d23d265b8480 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Wed, 5 Jun 2024 21:15:16 +0100 Subject: [PATCH 24/40] v1.0.0-alpha.2 --- Cargo.lock | 4 ++-- README.md | 2 +- schemars/Cargo.toml | 4 ++-- schemars_derive/Cargo.toml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 729d10c4..2f87862b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -303,7 +303,7 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "schemars" -version = "1.0.0-alpha.1" +version = "1.0.0-alpha.2" dependencies = [ "arrayvec", "bigdecimal", @@ -329,7 +329,7 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "1.0.0-alpha.1" +version = "1.0.0-alpha.2" dependencies = [ "pretty_assertions", "proc-macro2", diff --git a/README.md b/README.md index 756c8a7a..118af038 100644 --- a/README.md +++ b/README.md @@ -274,5 +274,5 @@ For example, to implement `JsonSchema` on types from `chrono`, enable it as a fe ```toml [dependencies] -schemars = { version = "1.0.0-alpha.1", features = ["chrono04"] } +schemars = { version = "1.0.0-alpha.2", features = ["chrono04"] } ``` diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index 320ddf9e..c1b0a940 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 = "1.0.0-alpha.1" +version = "1.0.0-alpha.2" authors = ["Graham Esau "] edition = "2021" license = "MIT" @@ -14,7 +14,7 @@ build = "build.rs" rust-version = "1.60" [dependencies] -schemars_derive = { version = "=1.0.0-alpha.1", optional = true, path = "../schemars_derive" } +schemars_derive = { version = "=1.0.0-alpha.2", optional = true, path = "../schemars_derive" } serde = "1.0" serde_json = "1.0.25" dyn-clone = "1.0" diff --git a/schemars_derive/Cargo.toml b/schemars_derive/Cargo.toml index 6eff88f1..8f3a7753 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 = "1.0.0-alpha.1" +version = "1.0.0-alpha.2" authors = ["Graham Esau "] edition = "2021" license = "MIT" From d511d447f77aba3500ce37719aa98d546f8e9f27 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 9 Jun 2024 19:01:24 +0100 Subject: [PATCH 25/40] Add separate docs for v0.8/v1 --- docs/1-deriving.md | 5 +- docs/1.1-attributes.md | 71 ++-- docs/2-implementing.md | 1 - docs/3-generating.md | 7 +- docs/4-features.md | 1 - docs/5-examples.md | 3 +- docs/Dockerfile | 14 + docs/Gemfile | 2 +- docs/_config.yml | 41 ++- docs/_includes/example_v0.md | 14 + .../examples_v0/custom_serialization.rs | 60 ++++ .../custom_serialization.schema.json | 25 ++ docs/_includes/examples_v0/custom_settings.rs | 24 ++ .../examples_v0/custom_settings.schema.json | 68 ++++ docs/_includes/examples_v0/doc_comments.rs | 33 ++ .../examples_v0/doc_comments.schema.json | 79 +++++ docs/_includes/examples_v0/enum_repr.rs | 15 + .../examples_v0/enum_repr.schema.json | 11 + docs/_includes/examples_v0/from_value.rs | 24 ++ .../examples_v0/from_value.schema.json | 23 ++ docs/_includes/examples_v0/main.rs | 19 ++ docs/_includes/examples_v0/main.schema.json | 70 ++++ docs/_includes/examples_v0/remote_derive.rs | 42 +++ .../examples_v0/remote_derive.schema.json | 43 +++ docs/_includes/examples_v0/schemars_attrs.rs | 30 ++ .../examples_v0/schemars_attrs.schema.json | 67 ++++ docs/_includes/examples_v0/serde_attrs.rs | 24 ++ .../examples_v0/serde_attrs.schema.json | 54 +++ docs/_includes/examples_v0/validate.rs | 24 ++ .../examples_v0/validate.schema.json | 64 ++++ docs/_layouts/v0.md | 10 + docs/_layouts/v1.md | 10 + docs/_sass/color_schemes/default.scss | 2 +- docs/_sass/custom/custom.scss | 4 +- docs/_v0/1-deriving.md | 36 ++ docs/_v0/1.1-attributes.md | 323 ++++++++++++++++++ docs/_v0/2-implementing.md | 78 +++++ docs/_v0/3-generating.md | 35 ++ docs/_v0/4-features.md | 39 +++ docs/_v0/5-examples.md | 8 + docs/_v0/examples/1-derive_jsonschema.md | 12 + docs/_v0/examples/2-serde_attrs.md | 14 + docs/_v0/examples/3-schemars_attrs.md | 12 + docs/_v0/examples/4-custom_settings.md | 12 + docs/_v0/examples/5-remote_derive.md | 16 + docs/_v0/examples/6-doc_comments.md | 12 + docs/_v0/examples/7-custom_serialization.md | 19 ++ docs/_v0/examples/8-enum_repr.md | 13 + docs/_v0/examples/9-from_value.md | 15 + docs/_v0/index.md | 20 ++ docs/docker-compose.yml | 10 + docs/examples/1-derive_jsonschema.md | 1 - docs/examples/2-serde_attrs.md | 5 +- docs/examples/3-schemars_attrs.md | 3 +- docs/examples/4-custom_settings.md | 1 - docs/examples/5-remote_derive.md | 1 - docs/examples/6-doc_comments.md | 1 - docs/examples/7-custom_serialization.md | 3 +- docs/examples/8-enum_repr.md | 1 - docs/examples/9-from_value.md | 1 - docs/index.md | 3 +- 61 files changed, 1620 insertions(+), 58 deletions(-) create mode 100644 docs/Dockerfile create mode 100644 docs/_includes/example_v0.md create mode 100644 docs/_includes/examples_v0/custom_serialization.rs create mode 100644 docs/_includes/examples_v0/custom_serialization.schema.json create mode 100644 docs/_includes/examples_v0/custom_settings.rs create mode 100644 docs/_includes/examples_v0/custom_settings.schema.json create mode 100644 docs/_includes/examples_v0/doc_comments.rs create mode 100644 docs/_includes/examples_v0/doc_comments.schema.json create mode 100644 docs/_includes/examples_v0/enum_repr.rs create mode 100644 docs/_includes/examples_v0/enum_repr.schema.json create mode 100644 docs/_includes/examples_v0/from_value.rs create mode 100644 docs/_includes/examples_v0/from_value.schema.json create mode 100644 docs/_includes/examples_v0/main.rs create mode 100644 docs/_includes/examples_v0/main.schema.json create mode 100644 docs/_includes/examples_v0/remote_derive.rs create mode 100644 docs/_includes/examples_v0/remote_derive.schema.json create mode 100644 docs/_includes/examples_v0/schemars_attrs.rs create mode 100644 docs/_includes/examples_v0/schemars_attrs.schema.json create mode 100644 docs/_includes/examples_v0/serde_attrs.rs create mode 100644 docs/_includes/examples_v0/serde_attrs.schema.json create mode 100644 docs/_includes/examples_v0/validate.rs create mode 100644 docs/_includes/examples_v0/validate.schema.json create mode 100644 docs/_layouts/v0.md create mode 100644 docs/_layouts/v1.md create mode 100644 docs/_v0/1-deriving.md create mode 100644 docs/_v0/1.1-attributes.md create mode 100644 docs/_v0/2-implementing.md create mode 100644 docs/_v0/3-generating.md create mode 100644 docs/_v0/4-features.md create mode 100644 docs/_v0/5-examples.md create mode 100644 docs/_v0/examples/1-derive_jsonschema.md create mode 100644 docs/_v0/examples/2-serde_attrs.md create mode 100644 docs/_v0/examples/3-schemars_attrs.md create mode 100644 docs/_v0/examples/4-custom_settings.md create mode 100644 docs/_v0/examples/5-remote_derive.md create mode 100644 docs/_v0/examples/6-doc_comments.md create mode 100644 docs/_v0/examples/7-custom_serialization.md create mode 100644 docs/_v0/examples/8-enum_repr.md create mode 100644 docs/_v0/examples/9-from_value.md create mode 100644 docs/_v0/index.md create mode 100644 docs/docker-compose.yml diff --git a/docs/1-deriving.md b/docs/1-deriving.md index 6e37fc87..2c844ee4 100644 --- a/docs/1-deriving.md +++ b/docs/1-deriving.md @@ -1,5 +1,4 @@ --- -layout: default title: Deriving JsonSchema nav_order: 2 has_children: true @@ -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 00000000..b668d7f6 --- /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 00000000..32f4edda --- /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 00000000..ec563491 --- /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 00000000..4932fbed --- /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 00000000..0df7e3df --- /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 00000000..590b7990 --- /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 00000000..b6653194 --- /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 00000000..2a5de96f --- /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 00000000..e439f501 --- /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 00000000..ee0adb69 --- /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 00000000..24de317b --- /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 00000000..499e6701 --- /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 00000000..1cfcf819 --- /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 00000000..45198ff2 --- /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 00000000..3b10e0d2 --- /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 00000000..0afa5eb1 --- /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 3bf45489..efe245cd 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 0044a6ed..79536e2f 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 5956a252..d3a2c08f 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 e9482cc2..7b85f650 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 93e99c13..fd7f74ff 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 9a5cdf9c..66f4a491 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 8caa930f..b124ae77 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 0312c29b..9533cbaf 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 b711be0d..89229255 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 28683429..ad71e780 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 From 692958353d564f681b0813954c398f80566bb593 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 9 Jun 2024 19:26:40 +0100 Subject: [PATCH 26/40] Add `extend` attribute to docs --- docs/1.1-attributes.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/1.1-attributes.md b/docs/1.1-attributes.md index 66e57363..2e6aa66c 100644 --- a/docs/1.1-attributes.md +++ b/docs/1.1-attributes.md @@ -48,6 +48,7 @@ TABLE OF CONTENTS - [`example`](#example) - [`deprecated`](#deprecated) - [`crate`](#crate) + - [`extend`](#extend) - [Doc Comments (`doc`)](#doc) @@ -314,6 +315,21 @@ struct Struct { } ``` +

+ +`#[schemars(extend("key" = value))]` + +

+ +Set on a container, variant or field to add properties (or replace existing properties) in a generated schema. This can contain multiple key/value pairs and/or be specified multiple times, as long as each key is unique. + +The key must be a quoted string, and the value can be any expression that produces a type implementing `serde::Serialize`. The value can also be a JSON literal which can interpolate other values. + +```plaintext +#[schemars(extend("simple" = "string value", "complex" = {"array": [1, 2, 3]}))] +struct Struct; +``` +

Doc Comments (`#[doc = "..."]`) From 91ee3f915caac552c8759de968ef6c725dc4348c Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 9 Jun 2024 19:48:35 +0100 Subject: [PATCH 27/40] Update docs for v1 --- docs/1.1-attributes.md | 2 +- docs/2-implementing.md | 17 +++++++++++------ docs/3-generating.md | 9 ++++++++- docs/4-features.md | 31 +++++++++++++------------------ schemars/src/gen.rs | 2 +- 5 files changed, 34 insertions(+), 27 deletions(-) diff --git a/docs/1.1-attributes.md b/docs/1.1-attributes.md index 2e6aa66c..317c51b8 100644 --- a/docs/1.1-attributes.md +++ b/docs/1.1-attributes.md @@ -63,7 +63,7 @@ TABLE OF CONTENTS

-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. +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 `$defs` 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. diff --git a/docs/2-implementing.md b/docs/2-implementing.md index 6ee924e7..c106fa8a 100644 --- a/docs/2-implementing.md +++ b/docs/2-implementing.md @@ -6,7 +6,7 @@ permalink: /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: +[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, one which usually _should_ be implemented, and one which can optionally be implemented: ## schema_name @@ -14,9 +14,9 @@ permalink: /implementing/ fn schema_name() -> Cow<'static, str>; ``` -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. +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 `$defs` property for subschemas. -## schema_id +## schema_id (optional but recommended) ```rust fn schema_id() -> Cow<'static, str>; @@ -47,18 +47,23 @@ impl JsonSchema for NonGenericType { } fn json_schema(_gen: &mut SchemaGenerator) -> Schema { - todo!() + json_schema!({ + "type": "object", + "foo": "bar" + }) } } ``` +The default implementation of this function returns `Self::schema_name()`. + ## 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. +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 `$defs` so that it does not need to be duplicated when used more than once. `json_schema` should not return a `$ref` schema. @@ -68,7 +73,7 @@ This function creates the JSON schema itself. The `gen` argument can be used to fn always_inline_schema() -> bool; ``` -If this function returns `false`, 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. +If this function returns `false`, then Schemars can re-use the generate schema where possible by adding it to the root schema's `$defs` 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 `true` for types with simple schemas (such as primitives). For more complex types, it should return `false`. For recursive types, this **must** return `false` to prevent infinite cycles when generating schemas. diff --git a/docs/3-generating.md b/docs/3-generating.md index aebc7cca..eb9f8621 100644 --- a/docs/3-generating.md +++ b/docs/3-generating.md @@ -12,13 +12,20 @@ The easiest way to generate a schema for a type that implements is to use the [` 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. +This will create a schema that conforms 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 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. +For example, to generate a schema that conforms to [JSON Schema Draft 7](https://json-schema.org/specification-links.html#draft-7): + +```rust +let generator = SchemaSettings::draft07().into_generator(); +let my_schema = generator.into_root_schema_for::(); +``` + See the API documentation for more info on how to use those types for custom schema generation. ## Schema from Example Value diff --git a/docs/4-features.md b/docs/4-features.md index a6fa1013..d7b45773 100644 --- a/docs/4-features.md +++ b/docs/4-features.md @@ -7,33 +7,28 @@ permalink: /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` +- `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.2", features = ["chrono04"] } ``` diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index cf7f007a..f39cdf77 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -462,7 +462,7 @@ impl SchemaGenerator { } let pointer = self.definitions_path_stripped(); - // `$defs`` and `definitions` are both handled internally by `Visitor::visit_schema`. + // `$defs` and `definitions` are both handled internally by `Visitor::visit_schema`. // If the definitions are in any other location, explicitly visit them here to ensure // they're run against any referenced subschemas. if pointer != "/$defs" && pointer != "/definitions" { From ce153808636e6601abf9993f43a02a90d583c934 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 4 Aug 2024 16:43:22 +0100 Subject: [PATCH 28/40] Do not collapse newlines in doc comments (#310) --- .../examples/doc_comments.schema.json | 4 +- schemars/examples/doc_comments.schema.json | 4 +- schemars/tests/docs.rs | 10 ++--- .../tests/expected/doc_comments_enum.json | 4 +- schemars_derive/src/attr/doc.rs | 40 ++----------------- 5 files changed, 14 insertions(+), 48 deletions(-) diff --git a/docs/_includes/examples/doc_comments.schema.json b/docs/_includes/examples/doc_comments.schema.json index 417d3bed..4997ac52 100644 --- a/docs/_includes/examples/doc_comments.schema.json +++ b/docs/_includes/examples/doc_comments.schema.json @@ -1,7 +1,7 @@ { "$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\na custom title and description.", "type": "object", "properties": { "my_bool": { @@ -48,7 +48,7 @@ ] }, { - "description": "A struct-like enum variant which contains some floats", + "description": "A struct-like enum variant which contains\nsome floats", "type": "object", "properties": { "StructVariant": { diff --git a/schemars/examples/doc_comments.schema.json b/schemars/examples/doc_comments.schema.json index 417d3bed..4997ac52 100644 --- a/schemars/examples/doc_comments.schema.json +++ b/schemars/examples/doc_comments.schema.json @@ -1,7 +1,7 @@ { "$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\na custom title and description.", "type": "object", "properties": { "my_bool": { @@ -48,7 +48,7 @@ ] }, { - "description": "A struct-like enum variant which contains some floats", + "description": "A struct-like enum variant which contains\nsome floats", "type": "object", "properties": { "StructVariant": { diff --git a/schemars/tests/docs.rs b/schemars/tests/docs.rs index 788140d8..7d1d6d89 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/expected/doc_comments_enum.json b/schemars/tests/expected/doc_comments_enum.json index b2e8f941..0da3bed2 100644 --- a/schemars/tests/expected/doc_comments_enum.json +++ b/schemars/tests/expected/doc_comments_enum.json @@ -1,7 +1,7 @@ { "$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", @@ -25,7 +25,7 @@ "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" diff --git a/schemars_derive/src/attr/doc.rs b/schemars_derive/src/attr/doc.rs index aab15d21..df9daca4 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 { From ade95a54d5b6678550759c6a34dad9d9148582fa Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 4 Aug 2024 16:45:39 +0100 Subject: [PATCH 29/40] Remove default implementation of `Visitor::visit_schema()` Since it's now the only method, there's no good reason to implement the trait without implementing that method. --- schemars/src/visit.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/schemars/src/visit.rs b/schemars/src/visit.rs index 850c1eb1..35453e04 100644 --- a/schemars/src/visit.rs +++ b/schemars/src/visit.rs @@ -36,9 +36,7 @@ pub trait Visitor { /// 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) - } + fn visit_schema(&mut self, schema: &mut Schema); } /// Visits all subschemas of the [`Schema`]. From ef9c8dc56b483e787744915e21ce6eeff5ddc845 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 4 Aug 2024 16:59:10 +0100 Subject: [PATCH 30/40] Fix doctest --- schemars/src/gen.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index f39cdf77..1dc4bcfe 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -534,7 +534,11 @@ fn json_pointer_mut<'a>( /// #[derive(Debug, Clone)] /// struct MyVisitor; /// -/// impl Visitor for MyVisitor { } +/// impl Visitor for MyVisitor { +/// fn visit_schema(&mut self, schema: &mut schemars::Schema) { +/// todo!() +/// } +/// } /// /// let v: &dyn GenVisitor = &MyVisitor; /// assert!(v.as_any().is::()); From 71b45a8ba3ff5fcc16a0fd89fe76ab258ff322b5 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sun, 4 Aug 2024 17:26:08 +0100 Subject: [PATCH 31/40] Remove irrelevant comments I'm reasonably satisfied that the current behaviour of enum variants with `with`/`schema_with` attributes is correct --- schemars/tests/enum.rs | 4 ---- schemars/tests/enum_deny_unknown_fields.rs | 4 ---- schemars/tests/schema_with_enum.rs | 4 ---- 3 files changed, 12 deletions(-) diff --git a/schemars/tests/enum.rs b/schemars/tests/enum.rs index 70bd9d21..4c57f34a 100644 --- a/schemars/tests/enum.rs +++ b/schemars/tests/enum.rs @@ -31,7 +31,6 @@ enum External { }, UnitTwo, Tuple(i32, bool), - // FIXME this should probably only replace the "payload" of the enum #[schemars(with = "i32")] WithInt, } @@ -54,7 +53,6 @@ enum Internal { bar: bool, }, UnitTwo, - // FIXME this should probably only replace the "payload" of the enum #[schemars(with = "i32")] WithInt, } @@ -77,7 +75,6 @@ enum Untagged { bar: bool, }, Tuple(i32, bool), - // FIXME this should probably only replace the "payload" of the enum #[schemars(with = "i32")] WithInt, } @@ -101,7 +98,6 @@ enum Adjacent { }, Tuple(i32, bool), UnitTwo, - // FIXME this should probably only replace the "payload" of the enum #[schemars(with = "i32")] WithInt, } diff --git a/schemars/tests/enum_deny_unknown_fields.rs b/schemars/tests/enum_deny_unknown_fields.rs index 62c1a455..ef56d058 100644 --- a/schemars/tests/enum_deny_unknown_fields.rs +++ b/schemars/tests/enum_deny_unknown_fields.rs @@ -33,7 +33,6 @@ enum External { }, UnitTwo, Tuple(i32, bool), - // FIXME this should probably only replace the "payload" of the enum #[schemars(with = "i32")] WithInt, } @@ -57,7 +56,6 @@ enum Internal { bar: bool, }, UnitTwo, - // FIXME this should only replace the "payload" of the enum (which doesn't even make sense for unit enums!) #[schemars(with = "i32")] WithInt, } @@ -81,7 +79,6 @@ enum Untagged { bar: bool, }, Tuple(i32, bool), - // FIXME this should probably only replace the "payload" of the enum #[schemars(with = "i32")] WithInt, } @@ -106,7 +103,6 @@ enum Adjacent { }, Tuple(i32, bool), UnitTwo, - // FIXME this should probably only replace the "payload" of the enum #[schemars(with = "i32")] WithInt, } diff --git a/schemars/tests/schema_with_enum.rs b/schemars/tests/schema_with_enum.rs index cf01b37b..5cf419c4 100644 --- a/schemars/tests/schema_with_enum.rs +++ b/schemars/tests/schema_with_enum.rs @@ -21,7 +21,6 @@ pub enum External { #[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema, i32, ), - // FIXME this should probably only replace the "payload" of the enum #[schemars(schema_with = "schema_fn")] Unit, } @@ -39,7 +38,6 @@ pub enum Internal { foo: DoesntImplementJsonSchema, }, NewType(#[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema), - // FIXME this should probably only replace the "payload" of the enum #[schemars(schema_with = "schema_fn")] Unit, } @@ -61,7 +59,6 @@ pub enum Untagged { #[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema, i32, ), - // FIXME this should probably only replace the "payload" of the enum #[schemars(schema_with = "schema_fn")] Unit, } @@ -83,7 +80,6 @@ pub enum Adjacent { #[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema, i32, ), - // FIXME this should probably only replace the "payload" of the enum #[schemars(schema_with = "schema_fn")] Unit, } From 324be32de6bf0b4e409b8a4ca148c67fe8c2e4ec Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Wed, 7 Aug 2024 19:20:01 +0100 Subject: [PATCH 32/40] Replace `visit::Visitor` with `transform::Transform` --- schemars/src/gen.rs | 92 ++++---- schemars/src/lib.rs | 4 +- schemars/src/transform.rs | 371 ++++++++++++++++++++++++++++++ schemars/src/visit.rs | 217 ----------------- schemars/tests/schema_settings.rs | 21 +- 5 files changed, 432 insertions(+), 273 deletions(-) create mode 100644 schemars/src/transform.rs delete mode 100644 schemars/src/visit.rs diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index 1dc4bcfe..781a2e55 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -8,7 +8,7 @@ There are two main types in this module: */ use crate::Schema; -use crate::{visit::*, JsonSchema}; +use crate::{transform::*, JsonSchema}; use dyn_clone::DynClone; use serde::Serialize; use serde_json::{Map, Value}; @@ -44,8 +44,8 @@ pub struct SchemaSettings { /// /// Defaults to `"https://json-schema.org/draft/2020-12/schema"`. pub meta_schema: Option, - /// A list of visitors that get applied to all generated 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. @@ -70,7 +70,7 @@ impl SchemaSettings { option_add_null_type: true, definitions_path: "/definitions".to_owned(), meta_schema: Some("http://json-schema.org/draft-07/schema#".to_owned()), - visitors: vec![Box::new(RemoveRefSiblings), Box::new(ReplacePrefixItems)], + transforms: vec![Box::new(RemoveRefSiblings), Box::new(ReplacePrefixItems)], inline_subschemas: false, } } @@ -82,7 +82,7 @@ impl SchemaSettings { option_add_null_type: true, definitions_path: "/$defs".to_owned(), meta_schema: Some("https://json-schema.org/draft/2019-09/schema".to_owned()), - visitors: vec![Box::new(ReplacePrefixItems)], + transforms: vec![Box::new(ReplacePrefixItems)], inline_subschemas: false, } } @@ -94,7 +94,7 @@ impl SchemaSettings { option_add_null_type: true, definitions_path: "/$defs".to_owned(), meta_schema: Some("https://json-schema.org/draft/2020-12/schema".to_owned()), - visitors: Vec::new(), + transforms: Vec::new(), inline_subschemas: false, } } @@ -109,7 +109,7 @@ impl SchemaSettings { "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, @@ -139,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) -> Self { + self.transforms.push(Box::new(transform)); self } @@ -297,9 +297,9 @@ impl SchemaGenerator { 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 JSON Schema for the type `T`. @@ -320,7 +320,7 @@ impl SchemaGenerator { } self.add_definitions(object, self.definitions.clone()); - self.run_visitors(&mut schema); + self.apply_transforms(&mut schema); schema } @@ -344,7 +344,7 @@ impl SchemaGenerator { let definitions = self.take_definitions(); self.add_definitions(object, definitions); - self.run_visitors(&mut schema); + self.apply_transforms(&mut schema); schema } @@ -375,7 +375,7 @@ impl SchemaGenerator { } self.add_definitions(object, self.definitions.clone()); - self.run_visitors(&mut schema); + self.apply_transforms(&mut schema); Ok(schema) } @@ -407,7 +407,7 @@ impl SchemaGenerator { let definitions = self.take_definitions(); self.add_definitions(object, definitions); - self.run_visitors(&mut schema); + self.apply_transforms(&mut schema); Ok(schema) } @@ -456,26 +456,9 @@ impl SchemaGenerator { target.append(&mut definitions); } - fn run_visitors(&mut self, schema: &mut Schema) { - for visitor in self.visitors_mut() { - visitor.visit_schema(schema); - } - - let pointer = self.definitions_path_stripped(); - // `$defs` and `definitions` are both handled internally by `Visitor::visit_schema`. - // If the definitions are in any other location, explicitly visit them here to ensure - // they're run against any referenced subschemas. - if pointer != "/$defs" && pointer != "/definitions" { - if let Some(definitions) = schema - .as_object_mut() - .and_then(|so| json_pointer_mut(so, pointer, false)) - { - for subschema in definitions.values_mut().flat_map(<&mut Schema>::try_from) { - for visitor in self.visitors_mut() { - visitor.visit_schema(subschema); - } - } - } + fn apply_transforms(&mut self, schema: &mut Schema) { + for transform in self.transforms_mut() { + transform.transform(schema); } } @@ -518,43 +501,48 @@ fn json_pointer_mut<'a>( Some(object) } -/// A [Visitor] which implements additional traits required to be included in a [SchemaSettings]. +/// 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`] /// /// # 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 { -/// fn visit_schema(&mut self, schema: &mut schemars::Schema) { +/// 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 { + /// Upcasts this transform into an [`Any`], which can be used to inspect and manipulate it as its concrete type. fn as_any(&self) -> &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, { fn as_any(&self) -> &dyn Any { self } } + +impl Debug for Box { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self._debug_type_name(f) + } +} diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs index dbb307ab..afe49d12 100644 --- a/schemars/src/lib.rs +++ b/schemars/src/lib.rs @@ -13,8 +13,8 @@ mod macros; pub mod _private; /// Types for generating JSON schemas. pub mod gen; -/// Types for recursively modifying JSON schemas. -pub mod visit; +/// Types for defining modifications to JSON schemas. +pub mod transform; #[cfg(feature = "schemars_derive")] extern crate schemars_derive; diff --git a/schemars/src/transform.rs b/schemars/src/transform.rs new file mode 100644 index 00000000..7482b31b --- /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 35453e04..00000000 --- a/schemars/src/visit.rs +++ /dev/null @@ -1,217 +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 object schemas: -``` -use schemars::Schema; -use schemars::visit::{Visitor, visit_schema}; - -pub struct MyVisitor; - -impl Visitor for MyVisitor { - fn visit_schema(&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 delegate to default implementation to visit any subschemas - visit_schema(self, schema); - } -} -``` -*/ -use serde_json::{json, Value}; - -use crate::Schema; - -/// Trait used to recursively modify a constructed schema and its subschemas. -pub trait Visitor { - /// 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); -} - -/// Visits all subschemas of the [`Schema`]. -pub fn visit_schema(v: &mut V, 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 visitors 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() { - v.visit_schema(subschema) - } - } - "allOf" | "anyOf" | "oneOf" | "prefixItems" => { - if let Some(array) = value.as_array_mut() { - for value in array { - if let Ok(subschema) = value.try_into() { - v.visit_schema(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() { - v.visit_schema(subschema) - } - } - } else if let Ok(subschema) = value.try_into() { - v.visit_schema(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() { - v.visit_schema(subschema) - } - } - } - } - _ => {} - } - } - } -} - -/// 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) { - if let Some(obj) = schema.as_object_mut() { - if self.skip_additional_properties { - if let Some((ap_key, ap_value)) = obj.remove_entry("additionalProperties") { - visit_schema(self, schema); - - if let Some(obj) = schema.as_object_mut() { - obj.insert(ap_key, ap_value); - } - - return; - } - } - - visit_schema(self, schema); - } else { - schema.ensure_object(); - } - } -} - -/// This visitor will restructure JSON Schema objects so that the `$ref` property will never appear alongside any other properties. -/// -/// 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 Visitor for RemoveRefSiblings { - fn visit_schema(&mut self, schema: &mut Schema) { - visit_schema(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 - })); - } - } - } - } - } -} - -/// 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; - -impl Visitor for SetSingleExample { - fn visit_schema(&mut self, schema: &mut Schema) { - visit_schema(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); - } - } - } - } -} - -/// This visitor will replace the `const` schema property with a single-valued `enum` property. -/// -/// 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 Visitor for ReplaceConstValue { - fn visit_schema(&mut self, schema: &mut Schema) { - visit_schema(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])); - } - } - } -} - -/// This visitor will rename the `prefixItems` schema property to `items`. -/// -/// 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 Visitor for ReplacePrefixItems { - fn visit_schema(&mut self, schema: &mut Schema) { - visit_schema(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/tests/schema_settings.rs b/schemars/tests/schema_settings.rs index 3fe489ab..0c8741ce 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::*; @@ -47,5 +47,22 @@ fn schema_matches_2020_12() -> TestResult { #[test] fn schema_matches_openapi3() -> TestResult { - test_generated_schema::("schema_settings-openapi3", SchemaSettings::openapi3()) + 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) } From a1c3bcd5cfc5f6cf3a8a9d34ab0dd055714f36a2 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Thu, 8 Aug 2024 22:04:39 +0100 Subject: [PATCH 33/40] Add `Send` requirement to `GenTransform` This means `SchemaSettings` and `SchemaGenerator` are both now `Send` --- schemars/src/gen.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index 781a2e55..c8251724 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -140,7 +140,7 @@ impl SchemaSettings { } /// 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) -> Self { + pub fn with_transform(mut self, transform: impl Transform + Clone + 'static + Send) -> Self { self.transforms.push(Box::new(transform)); self } @@ -507,6 +507,7 @@ fn json_pointer_mut<'a>( /// - [`Transform`] /// - [`std::any::Any`] (implemented for all `'static` types) /// - [`std::clone::Clone`] +/// - [`std::marker::Send`] /// /// # Example /// ``` @@ -525,7 +526,7 @@ fn json_pointer_mut<'a>( /// let v: &dyn GenTransform = &MyTransform; /// assert!(v.as_any().is::()); /// ``` -pub trait GenTransform: Transform + DynClone + Any { +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. fn as_any(&self) -> &dyn Any; } @@ -534,7 +535,7 @@ dyn_clone::clone_trait_object!(GenTransform); impl GenTransform for T where - T: Transform + Clone + Any, + T: Transform + Clone + Any + Send, { fn as_any(&self) -> &dyn Any { self @@ -546,3 +547,10 @@ impl Debug for Box { self._debug_type_name(f) } } + +fn _assert_send() { + fn _assert() {} + + _assert::(); + _assert::(); +} From 29067a0331ec09997e116faa1f1a773e375284c7 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Fri, 9 Aug 2024 11:03:12 +0100 Subject: [PATCH 34/40] Add `GenTransform::as_any_mut` and add examples --- schemars/src/gen.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index c8251724..f6553eb3 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -528,7 +528,40 @@ fn json_pointer_mut<'a>( /// ``` 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!(GenTransform); @@ -540,6 +573,10 @@ where fn as_any(&self) -> &dyn Any { self } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } } impl Debug for Box { From 14b06e71ba906965c1ec6f6a2ef8d7d67fb8cb6b Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sat, 10 Aug 2024 09:56:52 +0100 Subject: [PATCH 35/40] Add `transform = ...` attribute (#312) This allows running arbitrary transforms on generated schemas when deriving `JsonSchema` --- docs/1.1-attributes.md | 22 ++++++++ docs/Gemfile | 4 -- .../expected/transform_enum_external.json | 25 +++++++++ schemars/tests/expected/transform_struct.json | 20 ++++++++ schemars/tests/extend.rs | 10 ++-- schemars/tests/transform.rs | 51 +++++++++++++++++++ schemars/tests/ui/transform_str.rs | 7 +++ schemars/tests/ui/transform_str.stderr | 6 +++ schemars_derive/src/attr/mod.rs | 24 ++++++++- schemars_derive/src/metadata.rs | 14 +++++ 10 files changed, 173 insertions(+), 10 deletions(-) create mode 100644 schemars/tests/expected/transform_enum_external.json create mode 100644 schemars/tests/expected/transform_struct.json create mode 100644 schemars/tests/transform.rs create mode 100644 schemars/tests/ui/transform_str.rs create mode 100644 schemars/tests/ui/transform_str.stderr diff --git a/docs/1.1-attributes.md b/docs/1.1-attributes.md index 317c51b8..52ddc8a2 100644 --- a/docs/1.1-attributes.md +++ b/docs/1.1-attributes.md @@ -49,6 +49,7 @@ TABLE OF CONTENTS - [`deprecated`](#deprecated) - [`crate`](#crate) - [`extend`](#extend) + - [`transform`](#transform) - [Doc Comments (`doc`)](#doc) @@ -326,10 +327,31 @@ Set on a container, variant or field to add properties (or replace existing prop The key must be a quoted string, and the value can be any expression that produces a type implementing `serde::Serialize`. The value can also be a JSON literal which can interpolate other values. ```plaintext +#[derive(JsonSchema)] #[schemars(extend("simple" = "string value", "complex" = {"array": [1, 2, 3]}))] struct Struct; ``` +

+ +`#[schemars(transform = some::transform)]` + +

+ +Set on a container, variant or field to run a `schemars::transform::Transform` against the generated schema. This can be specified multiple times to run multiple transforms. + +The `Transform` trait is implemented on functions with the signature `fn(&mut Schema) -> ()`, allowing you to do this: + +```rust +fn my_transform(schema: &mut Schema) { + todo!() +} + +#[derive(JsonSchema)] +#[schemars(transform = my_transform)] +struct Struct; +``` +

Doc Comments (`#[doc = "..."]`) diff --git a/docs/Gemfile b/docs/Gemfile index d3cf985c..0888f7a2 100644 --- a/docs/Gemfile +++ b/docs/Gemfile @@ -24,7 +24,3 @@ install_if -> { RUBY_PLATFORM =~ %r!mingw|mswin|java! } do gem "tzinfo", "~> 1.2" gem "tzinfo-data" end - -# Performance-booster for watching directories on Windows -gem "wdm", "~> 0.1.1", :install_if => Gem.win_platform? - diff --git a/schemars/tests/expected/transform_enum_external.json b/schemars/tests/expected/transform_enum_external.json new file mode 100644 index 00000000..af746e3c --- /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 00000000..6723414c --- /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/extend.rs b/schemars/tests/extend.rs index 08f42fa9..2b44f5e0 100644 --- a/schemars/tests/extend.rs +++ b/schemars/tests/extend.rs @@ -17,7 +17,7 @@ struct Struct { } #[test] -fn doc_comments_struct() -> TestResult { +fn extend_struct() -> TestResult { test_default_generated_schema::("extend_struct") } @@ -36,7 +36,7 @@ enum External { } #[test] -fn doc_comments_enum_external() -> TestResult { +fn extend_enum_external() -> TestResult { test_default_generated_schema::("extend_enum_external") } @@ -53,7 +53,7 @@ enum Internal { } #[test] -fn doc_comments_enum_internal() -> TestResult { +fn extend_enum_internal() -> TestResult { test_default_generated_schema::("extend_enum_internal") } @@ -72,7 +72,7 @@ enum Untagged { } #[test] -fn doc_comments_enum_untagged() -> TestResult { +fn extend_enum_untagged() -> TestResult { test_default_generated_schema::("extend_enum_untagged") } @@ -91,6 +91,6 @@ enum Adjacent { } #[test] -fn doc_comments_enum_adjacent() -> TestResult { +fn extend_enum_adjacent() -> TestResult { test_default_generated_schema::("extend_enum_adjacent") } diff --git a/schemars/tests/transform.rs b/schemars/tests/transform.rs new file mode 100644 index 00000000..13ac30b1 --- /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/transform_str.rs b/schemars/tests/ui/transform_str.rs new file mode 100644 index 00000000..6570accf --- /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 00000000..6ee36983 --- /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_derive/src/attr/mod.rs b/schemars_derive/src/attr/mod.rs index 108cedc4..6b6e6766 100644 --- a/schemars_derive/src/attr/mod.rs +++ b/schemars_derive/src/attr/mod.rs @@ -27,6 +27,7 @@ pub struct Attrs { pub crate_name: Option, pub is_renamed: bool, pub extensions: Vec<(String, TokenStream)>, + pub transforms: Vec, } #[derive(Debug)] @@ -70,6 +71,7 @@ impl Attrs { deprecated: self.deprecated, examples: &self.examples, extensions: &self.extensions, + transforms: &self.transforms, read_only: false, write_only: false, default: None, @@ -164,6 +166,25 @@ 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; @@ -224,7 +245,8 @@ impl Attrs { crate_name: None, is_renamed: _, extensions, - } if examples.is_empty() && extensions.is_empty()) + transforms + } if examples.is_empty() && extensions.is_empty() && transforms.is_empty()) } } diff --git a/schemars_derive/src/metadata.rs b/schemars_derive/src/metadata.rs index 6a3808c3..c6b5dfe7 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> { @@ -10,6 +11,7 @@ pub struct SchemaMetadata<'a> { pub examples: &'a [syn::Path], pub default: Option, pub extensions: &'a [(String, TokenStream)], + pub transforms: &'a [syn::Expr], } impl<'a> SchemaMetadata<'a> { @@ -23,6 +25,18 @@ impl<'a> SchemaMetadata<'a> { 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 { From 55b88b53dbf91981927b5a63120c230b9631f08c Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sat, 10 Aug 2024 13:01:36 +0100 Subject: [PATCH 36/40] Add migration guide --- docs/0-migrating.md | 175 ++++++++++++++++++++++++++++++++++ docs/1-deriving.md | 2 +- docs/2-implementing.md | 2 +- docs/3-generating.md | 2 +- docs/4-features.md | 2 +- docs/5-examples.md | 2 +- docs/_sass/custom/custom.scss | 2 +- 7 files changed, 181 insertions(+), 6 deletions(-) create mode 100644 docs/0-migrating.md diff --git a/docs/0-migrating.md b/docs/0-migrating.md new file mode 100644 index 00000000..4ee936ad --- /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 2c844ee4..0bb44209 100644 --- a/docs/1-deriving.md +++ b/docs/1-deriving.md @@ -1,6 +1,6 @@ --- title: Deriving JsonSchema -nav_order: 2 +nav_order: 3 has_children: true has_toc: false permalink: /deriving/ diff --git a/docs/2-implementing.md b/docs/2-implementing.md index c106fa8a..cb0f18c2 100644 --- a/docs/2-implementing.md +++ b/docs/2-implementing.md @@ -1,6 +1,6 @@ --- title: Implementing JsonSchema -nav_order: 3 +nav_order: 4 permalink: /implementing/ --- diff --git a/docs/3-generating.md b/docs/3-generating.md index eb9f8621..b8cd9705 100644 --- a/docs/3-generating.md +++ b/docs/3-generating.md @@ -1,6 +1,6 @@ --- title: Generating Schemas -nav_order: 4 +nav_order: 5 permalink: /generating/ --- diff --git a/docs/4-features.md b/docs/4-features.md index d7b45773..eb51bdfe 100644 --- a/docs/4-features.md +++ b/docs/4-features.md @@ -1,6 +1,6 @@ --- title: Feature Flags -nav_order: 5 +nav_order: 6 permalink: /features/ --- diff --git a/docs/5-examples.md b/docs/5-examples.md index 4219bb54..b7e38d77 100644 --- a/docs/5-examples.md +++ b/docs/5-examples.md @@ -1,6 +1,6 @@ --- title: Examples -nav_order: 6 +nav_order: 7 has_children: true permalink: /examples/ --- diff --git a/docs/_sass/custom/custom.scss b/docs/_sass/custom/custom.scss index fd11be79..75a0d5fa 100644 --- a/docs/_sass/custom/custom.scss +++ b/docs/_sass/custom/custom.scss @@ -12,7 +12,7 @@ pre.highlight, figure.highlight { line-height: 1.2em; } code { - font-size: 14px; + font-size: 0.85em; } // Always expand nav menu items From 56ebd54c6c3714c19b9f6bbd26ba6427f5988e62 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sat, 10 Aug 2024 13:38:48 +0100 Subject: [PATCH 37/40] Add v0/v1 note to readme --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 118af038..9634342f 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ # Schemars +> [!NOTE] +> 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 From 7bcd200a211136c94f05e4d485a3fb67ad167c70 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sat, 10 Aug 2024 13:40:05 +0100 Subject: [PATCH 38/40] v1.0.0-alpha.3 --- Cargo.lock | 4 ++-- README.md | 2 +- docs/4-features.md | 2 +- schemars/Cargo.toml | 4 ++-- schemars_derive/Cargo.toml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f87862b..e8be8039 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -303,7 +303,7 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "schemars" -version = "1.0.0-alpha.2" +version = "1.0.0-alpha.3" dependencies = [ "arrayvec", "bigdecimal", @@ -329,7 +329,7 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "1.0.0-alpha.2" +version = "1.0.0-alpha.3" dependencies = [ "pretty_assertions", "proc-macro2", diff --git a/README.md b/README.md index 9634342f..18c6d4b5 100644 --- a/README.md +++ b/README.md @@ -278,5 +278,5 @@ For example, to implement `JsonSchema` on types from `chrono`, enable it as a fe ```toml [dependencies] -schemars = { version = "1.0.0-alpha.2", features = ["chrono04"] } +schemars = { version = "1.0.0-alpha.3", features = ["chrono04"] } ``` diff --git a/docs/4-features.md b/docs/4-features.md index eb51bdfe..6c3e8717 100644 --- a/docs/4-features.md +++ b/docs/4-features.md @@ -30,5 +30,5 @@ For example, to implement `JsonSchema` on types from `chrono`, enable it as a fe ```toml [dependencies] -schemars = { version = "1.0.0-alpha.2", features = ["chrono04"] } +schemars = { version = "1.0.0-alpha.3", features = ["chrono04"] } ``` diff --git a/schemars/Cargo.toml b/schemars/Cargo.toml index c1b0a940..055c0102 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 = "1.0.0-alpha.2" +version = "1.0.0-alpha.3" authors = ["Graham Esau "] edition = "2021" license = "MIT" @@ -14,7 +14,7 @@ build = "build.rs" rust-version = "1.60" [dependencies] -schemars_derive = { version = "=1.0.0-alpha.2", optional = true, path = "../schemars_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" diff --git a/schemars_derive/Cargo.toml b/schemars_derive/Cargo.toml index 8f3a7753..2c1b3984 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 = "1.0.0-alpha.2" +version = "1.0.0-alpha.3" authors = ["Graham Esau "] edition = "2021" license = "MIT" From c61b26091eb3fa31a52e661e0e36fe9bf46aac3f Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sat, 10 Aug 2024 17:57:02 +0100 Subject: [PATCH 39/40] Update examples --- docs/_includes/examples/doc_comments.schema.json | 4 ++-- docs/_includes/examples/schemars_attrs.rs | 12 +++++++++--- docs/_includes/examples/schemars_attrs.schema.json | 2 +- schemars/examples/doc_comments.schema.json | 4 ++-- schemars/examples/schemars_attrs.rs | 12 +++++++++--- schemars/examples/schemars_attrs.schema.json | 2 +- 6 files changed, 24 insertions(+), 12 deletions(-) diff --git a/docs/_includes/examples/doc_comments.schema.json b/docs/_includes/examples/doc_comments.schema.json index 4997ac52..a3aa7670 100644 --- a/docs/_includes/examples/doc_comments.schema.json +++ b/docs/_includes/examples/doc_comments.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "My Amazing Struct", - "description": "This struct shows off generating a schema with\na custom title and description.", + "description": "This struct shows off generating a schema with\n a custom title and description.", "type": "object", "properties": { "my_bool": { @@ -48,7 +48,7 @@ ] }, { - "description": "A struct-like enum variant which contains\nsome floats", + "description": "A struct-like enum variant which contains\n some floats", "type": "object", "properties": { "StructVariant": { diff --git a/docs/_includes/examples/schemars_attrs.rs b/docs/_includes/examples/schemars_attrs.rs index 4ad2503f..dc9e4954 100644 --- a/docs/_includes/examples/schemars_attrs.rs +++ b/docs/_includes/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/docs/_includes/examples/schemars_attrs.schema.json b/docs/_includes/examples/schemars_attrs.schema.json index ba967677..85efffd0 100644 --- a/docs/_includes/examples/schemars_attrs.schema.json +++ b/docs/_includes/examples/schemars_attrs.schema.json @@ -19,7 +19,6 @@ }, "myNumber": { "type": "integer", - "format": "int32", "maximum": 10, "minimum": 1 }, @@ -37,6 +36,7 @@ "myBool", "myVecStr" ], + "x-customProperty": "example", "$defs": { "MyEnum": { "anyOf": [ diff --git a/schemars/examples/doc_comments.schema.json b/schemars/examples/doc_comments.schema.json index 4997ac52..a3aa7670 100644 --- a/schemars/examples/doc_comments.schema.json +++ b/schemars/examples/doc_comments.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "My Amazing Struct", - "description": "This struct shows off generating a schema with\na custom title and description.", + "description": "This struct shows off generating a schema with\n a custom title and description.", "type": "object", "properties": { "my_bool": { @@ -48,7 +48,7 @@ ] }, { - "description": "A struct-like enum variant which contains\nsome floats", + "description": "A struct-like enum variant which contains\n some floats", "type": "object", "properties": { "StructVariant": { diff --git a/schemars/examples/schemars_attrs.rs b/schemars/examples/schemars_attrs.rs index 4ad2503f..dc9e4954 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 ba967677..85efffd0 100644 --- a/schemars/examples/schemars_attrs.schema.json +++ b/schemars/examples/schemars_attrs.schema.json @@ -19,7 +19,6 @@ }, "myNumber": { "type": "integer", - "format": "int32", "maximum": 10, "minimum": 1 }, @@ -37,6 +36,7 @@ "myBool", "myVecStr" ], + "x-customProperty": "example", "$defs": { "MyEnum": { "anyOf": [ From 4609590e8e39bcc1dd1b30b7aa901a178f131012 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Sat, 10 Aug 2024 17:57:18 +0100 Subject: [PATCH 40/40] Update changelog for 1.0.0 alpha versions --- CHANGELOG.md | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59eb3ea0..b0f88f7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,89 @@ # 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 + +- `#[schemars(extend("key" = value))]` attribute which can be used to add properties (or replace existing properties) in a generated schema (https://github.com/GREsau/schemars/issues/50 / https://github.com/GREsau/schemars/pull/297) + - Can be set on a struct, enum, or enum variant + - Value can be any expression that results in a value implementing `Serialize` + - Value can also be a JSON literal following the rules of `serde_json::json!(value)` macro, i.e. it can interpolate other values that implement `Serialize` + +## [1.0.0-alpha.1] - 2024-05-27 + +### Added + +- `json_schema!` macro for creating a custom `Schema` +- Implement `JsonSchema` for [uuid](https://crates.io/crates/uuid) 1.x types, under the optional `uuid1` feature flag +- `SchemaSettings::draft2020_12()` to construct settings conforming to [JSON Schema draft 2020-12](https://json-schema.org/draft/2020-12/release-notes) + +### Changed (_⚠️ breaking changes ⚠️_) + +- The `Schema` type is now defined as a thin wrapper around a `serde_json::Value` +- The default `SchemaSettings` (used by the `schema_for!()`/`schema_for_value!()` macros and `SchemaGenerator::default()`) now conform to JSON Schema draft 2020-12 instead of draft 7. +- Schemas generated using `SchemaSettings::draft2019_09()` (and `draft2020_12()` and `default()`) now use `$defs` instead of `definitions`. While using `definitions` is allowed by the spec, `$defs` is the preferred property for storing reusable schemas. +- `JsonSchema::schema_name()` now returns `Cow<'static, str>` instead of `String` +- `JsonSchema::is_referenceable()` has been removed, and replaced with the more clearly-named `JsonSchema::always_inline()` (which should returns the **opposite** value to what `is_referenceable` returned!) +- The `SchemaGenerator.definitions` field is now a `serde_json::Map` +- Macros/functions that previously returned a `RootSchema` now return a `Schema` instead +- 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` + - `indexmap2`, `arrayvec07` and `bigdecimal04` are unchanged + +### Removed (_⚠️ breaking changes ⚠️_) + +- Removed deprecated `SchemaGenerator` methods `make_extensible`, `schema_for_any` and `schema_for_none` +- Removed the `schema` module + - The `Schema` type is now accessible from the crate root (i.e. `schemars::Schema` instead of `schemars::schema::Schema`) + - All other types that were in the module have been removed: + - `RootSchema` + - `SchemaObject` + - `Metadata` + - `SubschemaValidation` + - `NumberValidation` + - `StringValidation` + - `ArrayValidation` + - `ObjectValidation` + - `InstanceType` + - `SingleOrVec` +- Removed `schemars::Set` and `schemars::Map` type aliases +- Removed the `impl_json_schema` feature flag - `JsonSchema` is now always implemented on `Schema` +- Remove methods `visit_schema_object` and `visit_root_schema` from the `Visitor` trait (`visit_schema` is unchanged) + - Visitors that previously defined `visit_schema_object` should instead define `visit_schema` and use an `if let Some(obj) = schema.as_object_mut()` or similar construct +- Old versions of optional dependencies have been removed - all of these have newer versions (shown in brackets) which are supported by schemars + - `indexmap` (consider using `indexmap2`) + - `uuid08` (consider using `uuid1`) + - `arrayvec05` (consider using `arrayvec07`) + - `bigdecimal03` (consider using `bigdecimal04`) +- Remove the retain_examples field from SetSingleExample, which is now a unit struct + ## [0.8.21] - 2024-05-23 ### Fixed: