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 | 120 ++- schemars/examples/custom_serialization.rs | 10 +- schemars/src/_private.rs | 174 +++-- schemars/src/flatten.rs | 249 +++--- schemars/src/gen.rs | 240 +++--- 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 | 139 ++-- 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 | 707 +++++------------- schemars/src/ser.rs | 213 +++--- schemars/src/visit.rs | 217 +++--- 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, 1410 insertions(+), 2394 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 d37634e..7c72306 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: - nightly include: - rust: 1.60.0 - test_features: "--features impl_json_schema" + test_features: "" allow_failure: false - rust: stable test_features: "--all-features" diff --git a/Cargo.lock b/Cargo.lock index 516f979..757013a 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 579e8c0..47dc643 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"] - -[[test]] -name = "indexmap" -required-features = ["indexmap"] - -[[test]] -name = "indexmap2" -required-features = ["indexmap2"] - -[[test]] -name = "either" -required-features = ["either"] - -[[test]] -name = "uuid" -required-features = ["uuid08", "uuid1"] - -[[test]] -name = "smallvec" -required-features = ["smallvec"] - -[[test]] -name = "bytes" -required-features = ["bytes"] - -[[test]] -name = "arrayvec" -required-features = ["arrayvec05", "arrayvec07"] - -[[test]] -name = "schema_for_schema" -required-features = ["impl_json_schema"] - [[test]] name = "ui" required-features = ["ui_test"] +[[test]] +name = "chrono" +required-features = ["chrono04"] + +[[test]] +name = "indexmap" +required-features = ["indexmap2"] + +[[test]] +name = "either" +required-features = ["either1"] + +[[test]] +name = "uuid" +required-features = ["uuid1"] + +[[test]] +name = "smallvec" +required-features = ["smallvec1"] + +[[test]] +name = "bytes" +required-features = ["bytes1"] + +[[test]] +name = "arrayvec" +required-features = ["arrayvec07"] + [[test]] name = "url" -required-features = ["url"] +required-features = ["url2"] [[test]] name = "enumset" -required-features = ["enumset"] +required-features = ["enumset1"] [[test]] name = "smol_str" -required-features = ["smol_str"] +required-features = ["smol_str02"] [[test]] name = "semver" -required-features = ["semver"] +required-features = ["semver1"] [[test]] name = "decimal" -required-features = ["rust_decimal", "bigdecimal03", "bigdecimal04"] +required-features = ["rust_decimal1", "bigdecimal04"] [package.metadata.docs.rs] all-features = true diff --git a/schemars/examples/custom_serialization.rs b/schemars/examples/custom_serialization.rs index 53c78fa..c119ea5 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 c61ffc1..d4ac4a7 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; +pub fn insert_validation_property( + schema: &mut Schema, + required_type: &str, + key: &str, + value: impl Into, +) { + if schema.has_type(required_type) || (required_type == "number" && schema.has_type("integer")) { + schema.ensure_object().insert(key.to_owned(), value.into()); + } +} - macro_rules! add_metadata_fn { - ($method:ident, $name:ident, $ty:ty) => { - pub fn $method(schema: Schema, $name: impl Into<$ty>) -> Schema { - let value = $name.into(); - if value == <$ty>::default() { - schema - } else { - let mut schema_obj = schema.into_object(); - schema_obj.metadata().$name = value.into(); - Schema::Object(schema_obj) - } +pub fn append_required(schema: &mut Schema, key: &str) { + if schema.has_type("object") { + if let Value::Array(array) = schema + .ensure_object() + .entry("required") + .or_insert(Value::Array(Vec::new())) + { + let value = Value::from(key); + if !array.contains(&value) { + array.push(value); } - }; - } - - 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 e55cb7f..fc66b74 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; -} + if let Value::Object(mut obj2) = other.to_value() { + let obj1 = self.ensure_object(); -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),)* + 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) => { + 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`) + } + }; + } } } } - }; - ($ty:ident { or: $($or_field:ident)*, }) => { - impl_merge!( $ty { merge: , or: $($or_field)*, }); - }; -} -// For ObjectValidation::additional_properties. -impl Merge for Option> { - fn merge(self, other: Self) -> Self { - match (self.map(|x| *x), other.map(|x| *x)) { - // Perfer permissive schemas. - (Some(Schema::Bool(true)), _) => Some(Box::new(true.into())), - (_, Some(Schema::Bool(true))) => Some(Box::new(true.into())), - (None, _) => None, - (_, None) => None, - - // Merge if we have two non-trivial schemas. - (Some(Schema::Object(s1)), Some(Schema::Object(s2))) => { - Some(Box::new(Schema::Object(s1.merge(s2)))) - } - - // Perfer the more permissive schema. - (Some(s1 @ Schema::Object(_)), Some(Schema::Bool(false))) => Some(Box::new(s1)), - (Some(Schema::Bool(false)), Some(s2 @ Schema::Object(_))) => Some(Box::new(s2)), - - // Default to the null schema. - (Some(Schema::Bool(false)), Some(Schema::Bool(false))) => Some(Box::new(false.into())), - } - } -} - -impl_merge!(SchemaObject { - merge: extensions instance_type enum_values - metadata subschemas number string array object, - or: format const_value reference, -}); - -impl Merge for Metadata { - fn merge(self, other: Self) -> Self { - Metadata { - id: self.id.or(other.id), - title: self.title.or(other.title), - description: self.description.or(other.description), - default: self.default.or(other.default), - deprecated: self.deprecated || other.deprecated, - read_only: self.read_only || other.read_only, - write_only: self.write_only || other.write_only, - examples: self.examples.merge(other.examples), - } - } -} - -impl_merge!(SubschemaValidation { - or: all_of any_of one_of not if_schema then_schema else_schema, -}); - -impl_merge!(NumberValidation { - or: multiple_of maximum exclusive_maximum minimum exclusive_minimum, -}); - -impl_merge!(StringValidation { - or: max_length min_length pattern, -}); - -impl_merge!(ArrayValidation { - or: items additional_items max_items min_items unique_items contains, -}); - -impl_merge!(ObjectValidation { - merge: required properties pattern_properties additional_properties, - or: max_properties min_properties property_names, -}); - -impl Merge for Option { - fn merge(self, other: Self) -> Self { - match (self, other) { - (Some(x), Some(y)) => Some(x.merge(y)), - (None, y) => y, - (x, None) => x, - } - } -} - -impl Merge for Box { - fn merge(mut self, other: Self) -> Self { - *self = (*self).merge(*other); self } } -impl Merge for Vec { - fn merge(mut self, other: Self) -> Self { - self.extend(other); - self - } -} - -impl Merge for Map -where - K: std::hash::Hash + Eq + Ord, -{ - fn merge(mut self, other: Self) -> Self { - self.extend(other); - self - } -} - -impl Merge for Set { - fn merge(mut self, other: Self) -> Self { - self.extend(other); - self - } -} - -impl Merge for SingleOrVec { - fn merge(self, other: Self) -> Self { - if self == other { - return self; +// 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 a31839a..d19f80b 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()); - for visitor in &mut self.settings.visitors { - visitor.visit_root_schema(&mut root) + let object = schema.ensure_object(); + + object + .entry("title") + .or_insert_with(|| T::schema_name().into()); + + if let Some(meta_schema) = self.settings.meta_schema.as_deref() { + object.insert("$schema".into(), meta_schema.into()); } - root + 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_schema(&mut schema); + } + + 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()); - for visitor in &mut self.settings.visitors { - visitor.visit_root_schema(&mut root) + let object = schema.ensure_object(); + + object + .entry("title") + .or_insert_with(|| T::schema_name().into()); + + if let Some(meta_schema) = self.settings.meta_schema { + object.insert("$schema".into(), meta_schema.into()); } - 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(), + ), + ); + } + + for visitor in &mut self.settings.visitors { + visitor.visit_schema(&mut schema); + } + + 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, - }; + 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); } - Ok(root) - } - - /// 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, - } + 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 036d042..bacc079 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 281bde7..0000000 --- a/schemars/src/json_schema_impls/arrayvec05.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use arrayvec05::{Array, ArrayString, ArrayVec}; -use std::convert::TryInto; - -// Do not set maxLength on the schema as that describes length in characters, but we only -// know max length in bytes. -forward_impl!(( JsonSchema for ArrayString where A: Array + Copy) => String); - -impl JsonSchema for ArrayVec -where - A::Item: JsonSchema, -{ - no_ref_schema!(); - - fn schema_name() -> String { - format!( - "Array_up_to_size_{}_of_{}", - A::CAPACITY, - A::Item::schema_name() - ) - } - - fn json_schema(gen: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::Array.into()), - array: Some(Box::new(ArrayValidation { - items: Some(gen.subschema_for::().into()), - max_items: A::CAPACITY.try_into().ok(), - ..Default::default() - })), - ..Default::default() - } - .into() - } -} diff --git a/schemars/src/json_schema_impls/arrayvec07.rs b/schemars/src/json_schema_impls/arrayvec07.rs index e2d92c5..3d4d050 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 5d8f6d9..53d55c6 100644 --- a/schemars/src/json_schema_impls/atomic.rs +++ b/schemars/src/json_schema_impls/atomic.rs @@ -1,6 +1,3 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; use std::sync::atomic::*; forward_impl!(AtomicBool => bool); @@ -22,12 +19,12 @@ forward_impl!(AtomicUsize => usize); #[cfg(test)] mod tests { use super::*; - use crate::tests::schema_object_for; + use crate::schema_for; use pretty_assertions::assert_eq; #[test] fn schema_for_atomics() { - let atomic_schema = schema_object_for::<( + let atomic_schema = schema_for!(( AtomicBool, AtomicI8, AtomicI16, @@ -39,9 +36,8 @@ mod tests { AtomicU32, AtomicU64, AtomicUsize, - )>(); - let basic_schema = - schema_object_for::<(bool, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize)>(); + )); + let basic_schema = schema_for!((bool, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize)); assert_eq!(atomic_schema, basic_schema); } } diff --git a/schemars/src/json_schema_impls/bytes.rs b/schemars/src/json_schema_impls/bytes.rs deleted file mode 100644 index f1a0f29..0000000 --- a/schemars/src/json_schema_impls/bytes.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use bytes::{Bytes, BytesMut}; - -forward_impl!((JsonSchema for Bytes) => Vec); -forward_impl!((JsonSchema for BytesMut) => Vec); diff --git a/schemars/src/json_schema_impls/chrono.rs b/schemars/src/json_schema_impls/chrono04.rs similarity index 61% rename from schemars/src/json_schema_impls/chrono.rs rename to schemars/src/json_schema_impls/chrono04.rs index 3a1731b..5fd23e8 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 955ead6..16104f8 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 = match schema.try_to_object() { + Ok(mut obj) => { + let instance_type = obj.get_mut("type"); + match instance_type { + Some(Value::Array(array)) => { + let null = Value::from("null"); + if !array.contains(&null) { + array.push(null); + } + obj.into() + } + Some(Value::String(string)) => { + if string != "null" { + *instance_type.unwrap() = + Value::Array(vec![std::mem::take(string).into(), "null".into()]) + } + obj.into() + } + _ => json_schema!({ + "anyOf": [ + obj, + <()>::json_schema(gen) + ] + }), + } } - schema => SchemaObject { - // TODO technically the schema already accepts null, so this may be unnecessary - subschemas: Some(Box::new(SubschemaValidation { - any_of: Some(vec![schema, <()>::json_schema(gen)]), - ..Default::default() - })), - ..Default::default() - } - .into(), + Err(true) => true.into(), + Err(false) => <()>::json_schema(gen), } } + if gen.settings().option_nullable { - let mut schema_obj = schema.into_object(); - schema_obj - .extensions - .insert("nullable".to_owned(), json!(true)); - schema = Schema::Object(schema_obj); + schema + .ensure_object() + .insert("nullable".into(), true.into()); }; + schema } @@ -59,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 643ca44..462e3d5 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 957fdd1..96ed8a8 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 22a3ebc..0000000 --- a/schemars/src/json_schema_impls/enumset.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use enumset::{EnumSet, EnumSetType}; - -forward_impl!(( JsonSchema for EnumSet where T: EnumSetType + JsonSchema) => std::collections::BTreeSet); diff --git a/schemars/src/json_schema_impls/ffi.rs b/schemars/src/json_schema_impls/ffi.rs index 55b5012..4ad85c2 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 3bd3e54..0000000 --- a/schemars/src/json_schema_impls/indexmap.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use indexmap::{IndexMap, IndexSet}; -use std::collections::{HashMap, HashSet}; - -forward_impl!(( JsonSchema for IndexMap) => HashMap); -forward_impl!(( JsonSchema for IndexSet) => HashSet); diff --git a/schemars/src/json_schema_impls/indexmap2.rs b/schemars/src/json_schema_impls/indexmap2.rs index 44eeedb..9473dea 100644 --- a/schemars/src/json_schema_impls/indexmap2.rs +++ b/schemars/src/json_schema_impls/indexmap2.rs @@ -1,5 +1,3 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; use crate::JsonSchema; use indexmap2::{IndexMap, IndexSet}; use std::collections::{HashMap, HashSet}; diff --git a/schemars/src/json_schema_impls/maps.rs b/schemars/src/json_schema_impls/maps.rs index 7c27808..b934f8b 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 0c548eb..ebcb61b 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 c2fba2b..d1b5142 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 1963d56..e3c6d1d 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 d182984..63121fe 100644 --- a/schemars/src/json_schema_impls/primitives.rs +++ b/schemars/src/json_schema_impls/primitives.rs @@ -1,67 +1,30 @@ use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; +use crate::{json_schema, JsonSchema, Schema}; use std::borrow::Cow; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use std::path::{Path, PathBuf}; macro_rules! simple_impl { - ($type:ty => $instance_type:ident) => { - simple_impl!($type => $instance_type, stringify!($instance_type), None); - }; - ($type:ty => $instance_type:ident, $format:literal) => { - simple_impl!($type => $instance_type, $format, Some($format.to_owned())); - }; - ($type:ty => $instance_type:ident, $name:expr, $format:expr) => { + ($type:ty => $instance_type:literal) => { impl JsonSchema for $type { no_ref_schema!(); fn schema_name() -> String { - $name.to_owned() + $instance_type.to_owned() } fn schema_id() -> Cow<'static, str> { - Cow::Borrowed($name) + Cow::Borrowed($instance_type) } fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::$instance_type.into()), - format: $format, - ..Default::default() - } - .into() + json_schema!({ + "type": $instance_type + }) } } }; -} - -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 +37,69 @@ 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 + }) } } }; } -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"); +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:literal, $format:literal) => { + impl JsonSchema for $type { + no_ref_schema!(); + + fn schema_name() -> String { + $format.to_owned() + } + + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed($format) + } + + fn json_schema(_: &mut SchemaGenerator) -> Schema { + 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"); 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 dd8ed8e..0000000 --- a/schemars/src/json_schema_impls/semver.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use semver::Version; -use std::borrow::Cow; - -impl JsonSchema for Version { - no_ref_schema!(); - - fn schema_name() -> String { - "Version".to_owned() - } - - fn schema_id() -> Cow<'static, str> { - Cow::Borrowed("semver::Version") - } - - fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::String.into()), - string: Some(Box::new(StringValidation { - // https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string - pattern: Some(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$".to_owned()), - ..Default::default() - })), - ..Default::default() - } - .into() - } -} diff --git a/schemars/src/json_schema_impls/semver1.rs b/schemars/src/json_schema_impls/semver1.rs new file mode 100644 index 0000000..fb47f78 --- /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 780307f..d14d4d3 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 41eafd5..5c6b58d 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 f7a75e3..0000000 --- a/schemars/src/json_schema_impls/smallvec.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use smallvec::{Array, SmallVec}; - -forward_impl!(( JsonSchema for SmallVec where A::Item: JsonSchema) => Vec); diff --git a/schemars/src/json_schema_impls/smol_str.rs b/schemars/src/json_schema_impls/smol_str.rs deleted file mode 100644 index cbca4a1..0000000 --- a/schemars/src/json_schema_impls/smol_str.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use smol_str::SmolStr; - -forward_impl!(SmolStr => String); diff --git a/schemars/src/json_schema_impls/time.rs b/schemars/src/json_schema_impls/time.rs index 767a1d2..7317cfd 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 f67f06a..ab9a36d 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 be18612..2fcfd04 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 b3b18f8..0000000 --- a/schemars/src/json_schema_impls/uuid08.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::gen::SchemaGenerator; -use crate::schema::*; -use crate::JsonSchema; -use std::borrow::Cow; -use uuid08::Uuid; - -impl JsonSchema for Uuid { - no_ref_schema!(); - - fn schema_name() -> String { - "Uuid".to_string() - } - - fn schema_id() -> Cow<'static, str> { - Cow::Borrowed("uuid::Uuid") - } - - fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::String.into()), - format: Some("uuid".to_string()), - ..Default::default() - } - .into() - } -} diff --git a/schemars/src/json_schema_impls/uuid1.rs b/schemars/src/json_schema_impls/uuid1.rs index 2e0c6e9..825f7a2 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 243a06b..e7e233c 100644 --- a/schemars/src/json_schema_impls/wrapper.rs +++ b/schemars/src/json_schema_impls/wrapper.rs @@ -1,5 +1,3 @@ -use crate::gen::SchemaGenerator; -use crate::schema::Schema; use crate::JsonSchema; macro_rules! wrapper_impl { diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs index f2332e5..fbe5f1d 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 18a6810..baa144d 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 4bef66e..6a88158 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 Serialize for Schema { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + ser::OrderedKeywordWrapper(&self.0).serialize(serializer) + } } impl Schema { - /// Creates a new `$ref` schema. - /// - /// 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() + pub fn new() -> Self { + Self(Value::Object(Map::new())) } - /// 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(), + pub fn new_ref(reference: String) -> Self { + let mut map = Map::new(); + map.insert("$ref".to_owned(), Value::String(reference)); + Self(Value::Object(map)) + } + + pub fn as_value(&self) -> &Value { + &self.0 + } + + pub fn as_bool(&self) -> Option { + self.0.as_bool() + } + + pub fn as_object(&self) -> Option<&Map> { + self.0.as_object() + } + + pub fn as_object_mut(&mut self) -> Option<&mut Map> { + self.0.as_object_mut() + } + + pub(crate) fn try_to_object(self) -> Result, bool> { + match self.0 { + Value::Object(m) => Ok(m), + Value::Bool(b) => Err(b), + _ => unreachable!(), + } + } + + pub fn to_value(self) -> Value { + self.0 + } + + pub fn ensure_object(&mut self) -> &mut Map { + if let Some(b) = self.as_bool() { + let mut map = Map::new(); + if !b { + map.insert("not".into(), Value::Object(Map::new())); + } + self.0 = Value::Object(map); + } + + self.as_object_mut() + .expect("Schema value should be of type Object.") + } + + pub(crate) fn has_type(&self, ty: &str) -> bool { + match self.0.get("type") { + Some(Value::Array(values)) => values.iter().any(|v| v.as_str() == Some(ty)), + Some(Value::String(s)) => s == ty, _ => false, } } - /// Converts the given schema (if it is a boolean schema) into an equivalent schema object. - /// - /// If the given schema is already a schema object, this has no effect. - /// - /// # Example - /// ``` - /// use schemars::schema::{Schema, SchemaObject}; - /// - /// let bool_schema = Schema::Bool(true); - /// - /// assert_eq!(bool_schema.into_object(), SchemaObject::default()); - /// ``` - pub fn into_object(self) -> SchemaObject { - match self { - Schema::Object(o) => o, - Schema::Bool(true) => SchemaObject::default(), - Schema::Bool(false) => SchemaObject { - subschemas: Some(Box::new(SubschemaValidation { - not: Some(Schema::Object(Default::default()).into()), - ..Default::default() - })), - ..Default::default() - }, - } + fn validate(value: &Value) -> Result<(), E> { + use serde::de::Unexpected; + let unexpected = match value { + Value::Bool(_) | Value::Object(_) => return Ok(()), + Value::Null => Unexpected::Unit, + Value::Number(n) => { + if let Some(u) = n.as_u64() { + Unexpected::Unsigned(u) + } else if let Some(i) = n.as_i64() { + Unexpected::Signed(i) + } else if let Some(f) = n.as_f64() { + Unexpected::Float(f) + } else { + unreachable!() + } + } + Value::String(s) => Unexpected::Str(s), + Value::Array(_) => Unexpected::Seq, + }; + + Err(E::invalid_type(unexpected, &"object or boolean")) + } + + #[allow(unsafe_code)] + #[ref_cast_custom] + fn ref_cast(value: &Value) -> &Self; + + #[allow(unsafe_code)] + #[ref_cast_custom] + fn ref_cast_mut(value: &mut Value) -> &mut Self; +} + +impl From for Value { + fn from(v: Schema) -> Value { + v.0 } } -impl From for Schema { - fn from(o: SchemaObject) -> Self { - Schema::Object(o) +impl std::convert::TryFrom for Schema { + type Error = serde_json::Error; + + fn try_from(value: Value) -> serde_json::Result { + Schema::validate(&value)?; + Ok(Schema(value)) + } +} + +impl<'a> std::convert::TryFrom<&'a Value> for &'a Schema { + type Error = serde_json::Error; + + fn try_from(value: &Value) -> serde_json::Result<&Schema> { + Schema::validate(value)?; + Ok(Schema::ref_cast(value)) + } +} + +impl<'a> std::convert::TryFrom<&'a mut Value> for &'a mut Schema { + type Error = serde_json::Error; + + fn try_from(value: &mut Value) -> serde_json::Result<&mut Schema> { + Schema::validate(value)?; + Ok(Schema::ref_cast_mut(value)) + } +} + +impl Default for Schema { + fn default() -> Self { + Self(Value::Object(Map::new())) + } +} + +impl From> for Schema { + fn from(o: Map) -> Self { + Schema(Value::Object(o)) } } impl From for Schema { fn from(b: bool) -> Self { - Schema::Bool(b) + Schema(Value::Bool(b)) } } -/// The root object of a JSON Schema document. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] -#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", default)] -pub struct RootSchema { - /// The `$schema` keyword. - /// - /// See [JSON Schema 8.1.1. The "$schema" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.1.1). - #[serde(rename = "$schema", skip_serializing_if = "Option::is_none")] - pub meta_schema: Option, - /// The root schema itself. - #[serde(flatten)] - pub schema: SchemaObject, - /// The `definitions` keyword. - /// - /// In JSON Schema draft 2019-09 this was replaced by $defs, but in Schemars this is still - /// serialized as `definitions` for backward-compatibility. - /// - /// See [JSON Schema 8.2.5. Schema Re-Use With "$defs"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.5), - /// and [JSON Schema (draft 07) 9. Schema Re-Use With "definitions"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-01#section-9). - #[serde(alias = "$defs", skip_serializing_if = "Map::is_empty")] - pub definitions: Map, -} +mod ser { + use serde::ser::{Serialize, SerializeMap, SerializeSeq}; + use serde_json::Value; -/// 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, -} + const ORDERED_KEYWORDS_START: [&str; 6] = + ["$id", "$schema", "title", "description", "type", "format"]; + const ORDERED_KEYWORDS_END: [&str; 2] = ["$defs", "definitions"]; -// 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(super) struct OrderedKeywordWrapper<'a>(pub &'a Value); -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))) - } -} + 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()))?; -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) - } - }; -} + for key in ORDERED_KEYWORDS_START { + if let Some(value) = object.get(key) { + map.serialize_entry(key, &OrderedKeywordWrapper(value))?; + } + } -impl SchemaObject { - /// Creates a new `$ref` schema. - /// - /// The given reference string should be a URI reference. This will usually be a JSON Pointer - /// in [URI Fragment representation](https://tools.ietf.org/html/rfc6901#section-6). - pub fn new_ref(reference: String) -> Self { - SchemaObject { - reference: Some(reference), - ..Default::default() - } - } + 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))?; + } + } - /// 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() - } + for key in ORDERED_KEYWORDS_END { + if let Some(value) = object.get(key) { + map.serialize_entry(key, &OrderedKeywordWrapper(value))?; + } + } - /// Returns `true` if `self` accepts values of the given type, according to the [`instance_type`](struct.SchemaObject.html#structfield.instance_type) field. - /// - /// This is a basic check that always returns `true` if no `instance_type` is specified on the schema, - /// and does not check any subschemas. Because of this, both `{}` and `{"not": {}}` accept any type according - /// to this method. - pub fn has_type(&self, ty: InstanceType) -> bool { - self.instance_type - .as_ref() - .map_or(true, |x| x.contains(&ty)) - } - - 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() - } -} - -/// 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(clippy::trivially_copy_pass_by_ref)] -fn is_false(b: &bool) -> bool { - !b -} - -/// Properties of a [`SchemaObject`] which define validation assertions in terms of other schemas. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] -#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", default)] -pub struct SubschemaValidation { - /// The `allOf` keyword. - /// - /// See [JSON Schema 9.2.1.1. "allOf"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.1). - #[serde(skip_serializing_if = "Option::is_none")] - pub all_of: Option>, - /// The `anyOf` keyword. - /// - /// See [JSON Schema 9.2.1.2. "anyOf"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.2). - #[serde(skip_serializing_if = "Option::is_none")] - pub any_of: Option>, - /// The `oneOf` keyword. - /// - /// See [JSON Schema 9.2.1.3. "oneOf"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.3). - #[serde(skip_serializing_if = "Option::is_none")] - pub one_of: Option>, - /// The `not` keyword. - /// - /// See [JSON Schema 9.2.1.4. "not"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.4). - #[serde(skip_serializing_if = "Option::is_none")] - pub not: Option>, - /// The `if` keyword. - /// - /// See [JSON Schema 9.2.2.1. "if"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.1). - #[serde(rename = "if", skip_serializing_if = "Option::is_none")] - pub if_schema: Option>, - /// The `then` keyword. - /// - /// See [JSON Schema 9.2.2.2. "then"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.2). - #[serde(rename = "then", skip_serializing_if = "Option::is_none")] - pub then_schema: Option>, - /// The `else` keyword. - /// - /// See [JSON Schema 9.2.2.3. "else"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.3). - #[serde(rename = "else", skip_serializing_if = "Option::is_none")] - pub else_schema: Option>, -} - -/// Properties of a [`SchemaObject`] which define validation assertions for numbers. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] -#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", default)] -pub struct NumberValidation { - /// The `multipleOf` keyword. - /// - /// See [JSON Schema Validation 6.2.1. "multipleOf"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.1). - #[serde(skip_serializing_if = "Option::is_none")] - pub multiple_of: Option, - /// The `maximum` keyword. - /// - /// See [JSON Schema Validation 6.2.2. "maximum"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.2). - #[serde(skip_serializing_if = "Option::is_none")] - pub maximum: Option, - /// The `exclusiveMaximum` keyword. - /// - /// See [JSON Schema Validation 6.2.3. "exclusiveMaximum"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.3). - #[serde(skip_serializing_if = "Option::is_none")] - pub exclusive_maximum: Option, - /// The `minimum` keyword. - /// - /// See [JSON Schema Validation 6.2.4. "minimum"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.4). - #[serde(skip_serializing_if = "Option::is_none")] - pub minimum: Option, - /// The `exclusiveMinimum` keyword. - /// - /// See [JSON Schema Validation 6.2.5. "exclusiveMinimum"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.5). - #[serde(skip_serializing_if = "Option::is_none")] - pub exclusive_minimum: Option, -} - -/// Properties of a [`SchemaObject`] which define validation assertions for strings. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] -#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", default)] -pub struct StringValidation { - /// The `maxLength` keyword. - /// - /// See [JSON Schema Validation 6.3.1. "maxLength"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.1). - #[serde(skip_serializing_if = "Option::is_none")] - pub max_length: Option, - /// The `minLength` keyword. - /// - /// See [JSON Schema Validation 6.3.2. "minLength"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.2). - #[serde(skip_serializing_if = "Option::is_none")] - pub min_length: Option, - /// The `pattern` keyword. - /// - /// See [JSON Schema Validation 6.3.3. "pattern"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.3). - #[serde(skip_serializing_if = "Option::is_none")] - pub pattern: Option, -} - -/// Properties of a [`SchemaObject`] which define validation assertions for arrays. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] -#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", default)] -pub struct ArrayValidation { - /// The `items` keyword. - /// - /// See [JSON Schema 9.3.1.1. "items"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.1). - #[serde(skip_serializing_if = "Option::is_none")] - pub items: Option>, - /// The `additionalItems` keyword. - /// - /// See [JSON Schema 9.3.1.2. "additionalItems"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.2). - #[serde(skip_serializing_if = "Option::is_none")] - pub additional_items: Option>, - /// The `maxItems` keyword. - /// - /// See [JSON Schema Validation 6.4.1. "maxItems"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.1). - #[serde(skip_serializing_if = "Option::is_none")] - pub max_items: Option, - /// The `minItems` keyword. - /// - /// See [JSON Schema Validation 6.4.2. "minItems"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.2). - #[serde(skip_serializing_if = "Option::is_none")] - pub min_items: Option, - /// The `uniqueItems` keyword. - /// - /// See [JSON Schema Validation 6.4.3. "uniqueItems"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.3). - #[serde(skip_serializing_if = "Option::is_none")] - pub unique_items: Option, - /// The `contains` keyword. - /// - /// See [JSON Schema 9.3.1.4. "contains"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.4). - #[serde(skip_serializing_if = "Option::is_none")] - pub contains: Option>, -} - -/// Properties of a [`SchemaObject`] which define validation assertions for objects. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] -#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase", default)] -pub struct ObjectValidation { - /// The `maxProperties` keyword. - /// - /// See [JSON Schema Validation 6.5.1. "maxProperties"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.1). - #[serde(skip_serializing_if = "Option::is_none")] - pub max_properties: Option, - /// The `minProperties` keyword. - /// - /// See [JSON Schema Validation 6.5.2. "minProperties"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.2). - #[serde(skip_serializing_if = "Option::is_none")] - pub min_properties: Option, - /// The `required` keyword. - /// - /// See [JSON Schema Validation 6.5.3. "required"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.3). - #[serde(skip_serializing_if = "Set::is_empty")] - pub required: Set, - /// The `properties` keyword. - /// - /// See [JSON Schema 9.3.2.1. "properties"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.1). - #[serde(skip_serializing_if = "Map::is_empty")] - pub properties: Map, - /// The `patternProperties` keyword. - /// - /// See [JSON Schema 9.3.2.2. "patternProperties"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.2). - #[serde(skip_serializing_if = "Map::is_empty")] - pub pattern_properties: Map, - /// The `additionalProperties` keyword. - /// - /// See [JSON Schema 9.3.2.3. "additionalProperties"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.3). - #[serde(skip_serializing_if = "Option::is_none")] - pub additional_properties: Option>, - /// The `propertyNames` keyword. - /// - /// See [JSON Schema 9.3.2.5. "propertyNames"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.5). - #[serde(skip_serializing_if = "Option::is_none")] - pub property_names: Option>, -} - -/// The possible types of values in JSON Schema documents. -/// -/// See [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1). -#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] -#[serde(rename_all = "camelCase")] -pub enum InstanceType { - Null, - Boolean, - Object, - Array, - Number, - String, - Integer, -} - -/// A type which can be serialized as a single item, or multiple items. -/// -/// In some contexts, a `Single` may be semantically distinct from a `Vec` containing only item. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] -#[serde(untagged)] -pub enum SingleOrVec { - Single(Box), - Vec(Vec), -} - -impl From for SingleOrVec { - fn from(single: T) -> Self { - SingleOrVec::Single(Box::new(single)) - } -} - -impl From> for SingleOrVec { - fn from(vec: Vec) -> Self { - SingleOrVec::Vec(vec) - } -} - -impl SingleOrVec { - /// Returns `true` if `self` is either a `Single` equal to `x`, or a `Vec` containing `x`. - /// - /// # Examples - /// - /// ``` - /// use schemars::schema::SingleOrVec; - /// - /// let s = SingleOrVec::from(10); - /// assert!(s.contains(&10)); - /// assert!(!s.contains(&20)); - /// - /// let v = SingleOrVec::from(vec![10, 20]); - /// assert!(v.contains(&10)); - /// assert!(v.contains(&20)); - /// assert!(!v.contains(&30)); - /// ``` - pub fn contains(&self, x: &T) -> bool { - match self { - SingleOrVec::Single(s) => s.deref() == x, - SingleOrVec::Vec(v) => v.contains(x), + map.end() + } + _ => self.0.serialize(serializer), + } } } } diff --git a/schemars/src/ser.rs b/schemars/src/ser.rs index 3f69bef..8be7b34 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)); + .unwrap_or(true.into()); - Ok(SchemaObject { - instance_type: Some(InstanceType::Object.into()), - object: Some(Box::new(ObjectValidation { - additional_properties: Some(Box::new(value_schema)), - ..ObjectValidation::default() - })), - ..SchemaObject::default() - } - .into()) + Ok(json_schema!({ + "type": "object", + "additionalProperties": value_schema, + })) } fn serialize_none(self) -> Result { @@ -132,52 +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 = match schema.try_to_object() { + Ok(mut obj) => { + let value = obj.get_mut("type"); + match value { + Some(Value::Array(array)) => { + let null = Value::from("null"); + if !array.contains(&null) { + array.push(null); + } + obj.into() + } + Some(Value::String(string)) => { + if string != "null" { + *value.unwrap() = + Value::Array(vec![std::mem::take(string).into(), "null".into()]) + } + obj.into() + } + _ => json_schema!({ + "anyOf": [ + obj, + <()>::json_schema(self.gen) + ] + }), + } } - schema => SchemaObject { - subschemas: Some(Box::new(SubschemaValidation { - any_of: Some(vec![schema, <()>::json_schema(self.gen)]), - ..Default::default() - })), - ..Default::default() - } - .into(), + Err(true) => true.into(), + Err(false) => <()>::json_schema(self.gen), } } if self.gen.settings().option_nullable { - let mut schema_obj = schema.into_object(); - schema_obj - .extensions - .insert("nullable".to_owned(), serde_json::json!(true)); - schema = Schema::Object(schema_obj); + schema + .ensure_object() + .insert("nullable".into(), true.into()); }; Ok(schema) @@ -193,7 +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 182b572..cfe11df 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 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 Schema::Bool(b) = *schema { - *schema = Schema::Bool(b).into_object().into() - } - } - - fn visit_schema_object(&mut self, schema: &mut SchemaObject) { - if self.skip_additional_properties { - if let Some(obj) = &mut schema.object { - if let Some(ap) = &obj.additional_properties { - if let Schema::Bool(_) = ap.as_ref() { - let additional_properties = obj.additional_properties.take(); - visit_schema_object(self, schema); - schema.object().additional_properties = additional_properties; - - return; + 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); + fn visit_schema(&mut self, schema: &mut Schema) { + visit_schema(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]), + 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); + fn visit_schema(&mut self, schema: &mut Schema) { + visit_schema(self, schema); - let first_example = schema.metadata.as_mut().and_then(|m| { - if self.retain_examples { - m.examples.first().cloned() - } else { - m.examples.drain(..).next() + if let Some(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); + } } - }); - - if let Some(example) = first_example { - schema.extensions.insert("example".to_owned(), example); } } } diff --git a/schemars/tests/arrayvec.rs b/schemars/tests/arrayvec.rs index b00b395..e32d1a7 100644 --- a/schemars/tests/arrayvec.rs +++ b/schemars/tests/arrayvec.rs @@ -1,16 +1,6 @@ mod util; use util::*; -#[test] -fn arrayvec05() -> TestResult { - test_default_generated_schema::>("arrayvec") -} - -#[test] -fn arrayvec05_string() -> TestResult { - test_default_generated_schema::>("arrayvec_string") -} - #[test] fn arrayvec07() -> TestResult { test_default_generated_schema::>("arrayvec") diff --git a/schemars/tests/bytes.rs b/schemars/tests/bytes.rs index 688ab21..5797411 100644 --- a/schemars/tests/bytes.rs +++ b/schemars/tests/bytes.rs @@ -1,5 +1,5 @@ mod util; -use bytes::{Bytes, BytesMut}; +use bytes1::{Bytes, BytesMut}; use util::*; #[test] diff --git a/schemars/tests/chrono.rs b/schemars/tests/chrono.rs index 7f518d7..da6f8d4 100644 --- a/schemars/tests/chrono.rs +++ b/schemars/tests/chrono.rs @@ -1,5 +1,5 @@ mod util; -use chrono::prelude::*; +use chrono04::prelude::*; use schemars::JsonSchema; use util::*; diff --git a/schemars/tests/decimal.rs b/schemars/tests/decimal.rs index d245583..246e813 100644 --- a/schemars/tests/decimal.rs +++ b/schemars/tests/decimal.rs @@ -3,12 +3,7 @@ use util::*; #[test] fn rust_decimal() -> TestResult { - test_default_generated_schema::("rust_decimal") -} - -#[test] -fn bigdecimal03() -> TestResult { - test_default_generated_schema::("bigdecimal03") + test_default_generated_schema::("rust_decimal") } #[test] diff --git a/schemars/tests/dereference.rs b/schemars/tests/dereference.rs deleted file mode 100644 index b1abab0..0000000 --- a/schemars/tests/dereference.rs +++ /dev/null @@ -1,23 +0,0 @@ -use schemars::{gen::SchemaGenerator, JsonSchema}; -use std::ptr; - -#[allow(dead_code)] -#[derive(JsonSchema)] -struct Struct { - foo: i32, - bar: bool, -} - -#[test] -fn dereference_struct() { - let mut gen = SchemaGenerator::default(); - let struct_ref_schema = gen.subschema_for::(); - let struct_schema = gen.definitions().get(&::schema_name()).unwrap(); - - assert!(struct_ref_schema.is_ref()); - assert!(!struct_schema.is_ref()); - - let dereferenced = gen.dereference(&struct_ref_schema); - assert!(dereferenced.is_some()); - assert!(ptr::eq(dereferenced.unwrap(), struct_schema)); -} diff --git a/schemars/tests/either.rs b/schemars/tests/either.rs index 16a2e59..5dcd079 100644 --- a/schemars/tests/either.rs +++ b/schemars/tests/either.rs @@ -1,5 +1,5 @@ mod util; -use either::Either; +use either1::Either; use util::*; #[test] diff --git a/schemars/tests/enum.rs b/schemars/tests/enum.rs index 1ed6046..70bd9d2 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 6201784..62c1a45 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 27f2030..c04ba0e 100644 --- a/schemars/tests/enumset.rs +++ b/schemars/tests/enumset.rs @@ -1,8 +1,11 @@ mod util; -use enumset::{EnumSet, EnumSetType}; +use enumset1::{EnumSet, EnumSetType}; use schemars::JsonSchema; use util::*; +// needed to derive EnumSetType when using a crate alias +extern crate enumset1 as enumset; + #[derive(EnumSetType, JsonSchema)] enum Foo { Bar, diff --git a/schemars/tests/expected/arrayvec_string.json b/schemars/tests/expected/arrayvec_string.json index 42f099a..ad174d8 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 855db6f..0000000 --- a/schemars/tests/expected/bigdecimal03.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Decimal", - "type": "string", - "pattern": "^-?[0-9]+(\\.[0-9]+)?$" -} \ No newline at end of file diff --git a/schemars/tests/expected/either.json b/schemars/tests/expected/either.json index b028057..807c9c9 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 fc36644..501f13b 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 a3b14af..0bb3aeb 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 2fea80a..5a65f3b 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 d8d6ec1..2a01573 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 866fb9d..844699a 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 31f7f26..39c39a9 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 32ac797..bddd083 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 7ede7e6..75b28dc 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 eab45c0..7ce011d 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 42f099a..ad174d8 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 9501b67..e243f35 100644 --- a/schemars/tests/indexmap.rs +++ b/schemars/tests/indexmap.rs @@ -1,13 +1,15 @@ mod util; -use indexmap::{IndexMap, IndexSet}; +use std::hash::RandomState; + +use indexmap2::{IndexMap, IndexSet}; use schemars::JsonSchema; use util::*; #[allow(dead_code)] #[derive(JsonSchema)] struct IndexMapTypes { - map: IndexMap, - set: IndexSet, + map: IndexMap, + set: IndexSet, } #[test] diff --git a/schemars/tests/indexmap2.rs b/schemars/tests/indexmap2.rs deleted file mode 100644 index efc77dd..0000000 --- a/schemars/tests/indexmap2.rs +++ /dev/null @@ -1,16 +0,0 @@ -mod util; -use indexmap2::{IndexMap, IndexSet}; -use schemars::JsonSchema; -use util::*; - -#[allow(dead_code)] -#[derive(JsonSchema)] -struct IndexMapTypes { - map: IndexMap, - set: IndexSet, -} - -#[test] -fn indexmap_types() -> TestResult { - test_default_generated_schema::("indexmap") -} diff --git a/schemars/tests/schema_for_schema.rs b/schemars/tests/schema_for_schema.rs deleted file mode 100644 index d175505..0000000 --- a/schemars/tests/schema_for_schema.rs +++ /dev/null @@ -1,19 +0,0 @@ -mod util; -use schemars::gen::SchemaSettings; -use schemars::schema::RootSchema; -use util::*; - -#[test] -fn schema_matches_draft07() -> TestResult { - test_generated_schema::("schema", SchemaSettings::draft07()) -} - -#[test] -fn schema_matches_2019_09() -> TestResult { - test_generated_schema::("schema-2019_09", SchemaSettings::draft2019_09()) -} - -#[test] -fn schema_matches_openapi3() -> TestResult { - test_generated_schema::("schema-openapi3", SchemaSettings::openapi3()) -} diff --git a/schemars/tests/schema_with_enum.rs b/schemars/tests/schema_with_enum.rs index a91aa4d..5a6c575 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 9c87abe..70ba24d 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 617e508..3c351c6 100644 --- a/schemars/tests/semver.rs +++ b/schemars/tests/semver.rs @@ -1,6 +1,6 @@ mod util; use schemars::JsonSchema; -use semver::Version; +use semver1::Version; use util::*; #[allow(dead_code)] diff --git a/schemars/tests/smallvec.rs b/schemars/tests/smallvec.rs index bc9c6ef..8412a6a 100644 --- a/schemars/tests/smallvec.rs +++ b/schemars/tests/smallvec.rs @@ -1,5 +1,5 @@ mod util; -use smallvec::SmallVec; +use smallvec1::SmallVec; use util::*; #[test] diff --git a/schemars/tests/smol_str.rs b/schemars/tests/smol_str.rs index 1e48196..43fad30 100644 --- a/schemars/tests/smol_str.rs +++ b/schemars/tests/smol_str.rs @@ -1,5 +1,5 @@ mod util; -use smol_str::SmolStr; +use smol_str02::SmolStr; use util::*; #[test] diff --git a/schemars/tests/url.rs b/schemars/tests/url.rs index 0e9499a..018a66b 100644 --- a/schemars/tests/url.rs +++ b/schemars/tests/url.rs @@ -1,6 +1,6 @@ mod util; use schemars::JsonSchema; -use url::Url; +use url2::Url; use util::*; #[allow(dead_code)] diff --git a/schemars/tests/util/mod.rs b/schemars/tests/util/mod.rs index 595d68b..99cf677 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 bd673b5..77e92c2 100644 --- a/schemars/tests/uuid.rs +++ b/schemars/tests/uuid.rs @@ -1,11 +1,6 @@ mod util; use util::*; -#[test] -fn uuid08() -> TestResult { - test_default_generated_schema::("uuid") -} - #[test] fn uuid1() -> TestResult { test_default_generated_schema::("uuid") diff --git a/schemars_derive/src/attr/validation.rs b/schemars_derive/src/attr/validation.rs index a511ab4..8d90302 100644 --- a/schemars_derive/src/attr/validation.rs +++ b/schemars_derive/src/attr/validation.rs @@ -325,119 +325,85 @@ impl ValidationAttrs { } pub fn apply_to_schema(&self, schema_expr: &mut TokenStream) { - if let Some(apply_expr) = self.apply_to_schema_expr() { - *schema_expr = quote! { - { - let mut schema = #schema_expr; - #apply_expr - schema - } - } + let setters = self.make_setters(quote!(&mut schema)); + if !setters.is_empty() { + *schema_expr = quote!({ + let mut schema = #schema_expr; + #(#setters)* + schema + }); } } - fn apply_to_schema_expr(&self) -> Option { - let mut array_validation = Vec::new(); - let mut number_validation = Vec::new(); - let mut object_validation = Vec::new(); - let mut string_validation = Vec::new(); + fn make_setters(&self, mut_schema: impl ToTokens) -> Vec { + let mut result = Vec::new(); if let Some(length_min) = self.length_min.as_ref().or(self.length_equal.as_ref()) { - string_validation.push(quote! { - validation.min_length = Some(#length_min as u32); + result.push(quote! { + schemars::_private::insert_validation_property(#mut_schema, "string", "minLength", #length_min); }); - array_validation.push(quote! { - validation.min_items = Some(#length_min as u32); + result.push(quote! { + schemars::_private::insert_validation_property(#mut_schema, "array", "minItems", #length_min); }); } if let Some(length_max) = self.length_max.as_ref().or(self.length_equal.as_ref()) { - string_validation.push(quote! { - validation.max_length = Some(#length_max as u32); + result.push(quote! { + schemars::_private::insert_validation_property(#mut_schema, "string", "maxLength", #length_max); }); - array_validation.push(quote! { - validation.max_items = Some(#length_max as u32); + result.push(quote! { + schemars::_private::insert_validation_property(#mut_schema, "array", "maxItems", #length_max); }); } if let Some(range_min) = &self.range_min { - number_validation.push(quote! { - validation.minimum = Some(#range_min as f64); + result.push(quote! { + schemars::_private::insert_validation_property(#mut_schema, "number", "minimum", #range_min); }); } if let Some(range_max) = &self.range_max { - number_validation.push(quote! { - validation.maximum = Some(#range_max as f64); + result.push(quote! { + schemars::_private::insert_validation_property(#mut_schema, "number", "maximum", #range_max); }); } if let Some(regex) = &self.regex { - string_validation.push(quote! { - validation.pattern = Some(#regex.to_string()); + result.push(quote! { + schemars::_private::insert_validation_property(#mut_schema, "string", "pattern", #regex); }); } if let Some(contains) = &self.contains { - object_validation.push(quote! { - validation.required.insert(#contains.to_string()); + result.push(quote! { + schemars::_private::append_required(#mut_schema, #contains); }); if self.regex.is_none() { let pattern = crate::regex_syntax::escape(contains); - string_validation.push(quote! { - validation.pattern = Some(#pattern.to_string()); + result.push(quote! { + schemars::_private::insert_validation_property(#mut_schema, "string", "pattern", #pattern); }); } } - let format = self.format.as_ref().map(|f| { - let f = f.schema_str(); - quote! { - schema_object.format = Some(#f.to_string()); - } - }); - - let inner_validation = self - .inner - .as_deref() - .and_then(|inner| inner.apply_to_schema_expr()) - .map(|apply_expr| { - quote! { - if schema_object.has_type(schemars::schema::InstanceType::Array) { - if let Some(schemars::schema::SingleOrVec::Single(inner_schema)) = &mut schema_object.array().items { - let mut schema = &mut **inner_schema; - #apply_expr - } - } - } - }); - - let array_validation = wrap_array_validation(array_validation); - let number_validation = wrap_number_validation(number_validation); - let object_validation = wrap_object_validation(object_validation); - let string_validation = wrap_string_validation(string_validation); - - if array_validation.is_some() - || number_validation.is_some() - || object_validation.is_some() - || string_validation.is_some() - || format.is_some() - || inner_validation.is_some() - { - Some(quote! { - if let schemars::schema::Schema::Object(schema_object) = &mut schema { - #array_validation - #number_validation - #object_validation - #string_validation - #format - #inner_validation - } + if let Some(format) = &self.format { + let f = format.schema_str(); + result.push(quote! { + schema.ensure_object().insert("format".to_owned(), #f.into()); }) - } else { - None + }; + + if let Some(inner) = &self.inner { + let inner_setters = inner.make_setters(quote!(schema)); + if !inner_setters.is_empty() { + result.push(quote! { + schemars::_private::apply_inner_validation(#mut_schema, |schema| { #(#inner_setters)* }); + }) + } } + + result } } @@ -456,59 +422,6 @@ fn parse_lit_into_expr_path( }) } -fn wrap_array_validation(v: Vec) -> Option { - if v.is_empty() { - None - } else { - Some(quote! { - if schema_object.has_type(schemars::schema::InstanceType::Array) { - let validation = schema_object.array(); - #(#v)* - } - }) - } -} - -fn wrap_number_validation(v: Vec) -> Option { - if v.is_empty() { - None - } else { - Some(quote! { - if schema_object.has_type(schemars::schema::InstanceType::Integer) - || schema_object.has_type(schemars::schema::InstanceType::Number) { - let validation = schema_object.number(); - #(#v)* - } - }) - } -} - -fn wrap_object_validation(v: Vec) -> Option { - if v.is_empty() { - None - } else { - Some(quote! { - if schema_object.has_type(schemars::schema::InstanceType::Object) { - let validation = schema_object.object(); - #(#v)* - } - }) - } -} - -fn wrap_string_validation(v: Vec) -> Option { - if v.is_empty() { - None - } else { - Some(quote! { - if schema_object.has_type(schemars::schema::InstanceType::String) { - let validation = schema_object.string(); - #(#v)* - } - }) - } -} - fn str_or_num_to_expr(cx: &Ctxt, meta_item_name: &str, expr: Expr) -> Option { // this odd double-parsing is to make `-10` parsed as an Lit instead of an Expr::Unary let lit: Lit = match syn::parse2(expr.to_token_stream()) { diff --git a/schemars_derive/src/lib.rs b/schemars_derive/src/lib.rs index ea6c0a6..c890f75 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 3dd77a0..6de6834 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 b625114..585184c 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 | 31 ++++++-------- 6 files changed, 61 insertions(+), 75 deletions(-) diff --git a/schemars/src/_private.rs b/schemars/src/_private.rs index d4ac4a7..3de9614 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 fc66b74..69b0361 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 501f13b..7a9bcd7 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 37739b0..115dbf3 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 75b28dc..c4a0cc1 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 585184c..f22831e 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) - }; + let mut schema_expr = expr_for_internal_tagged_enum_variant(variant, deny_unknown_fields); + variant.attrs.as_metadata().apply_to_schema(&mut schema_expr); - 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 + 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 3de9614..0ba27ce 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 cf11d35..0000000 --- a/schemars/tests/expected/schema-2019_09.json +++ /dev/null @@ -1,758 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2019-09/schema", - "title": "RootSchema", - "description": "The root object of a JSON Schema document.", - "type": "object", - "properties": { - "$schema": { - "description": "The `$schema` keyword.\n\nSee [JSON Schema 8.1.1. The \"$schema\" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.1.1).", - "type": [ - "string", - "null" - ] - }, - "definitions": { - "description": "The `definitions` keyword.\n\nIn JSON Schema draft 2019-09 this was replaced by $defs, but in Schemars this is still serialized as `definitions` for backward-compatibility.\n\nSee [JSON Schema 8.2.5. Schema Re-Use With \"$defs\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.5), and [JSON Schema (draft 07) 9. Schema Re-Use With \"definitions\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-01#section-9).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "type": { - "description": "The `type` keyword.\n\nSee [JSON Schema Validation 6.1.1. \"type\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.1) and [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1).", - "anyOf": [ - { - "$ref": "#/definitions/SingleOrVec_for_InstanceType" - }, - { - "type": "null" - } - ] - }, - "format": { - "description": "The `format` keyword.\n\nSee [JSON Schema Validation 7. A Vocabulary for Semantic Content With \"format\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-7).", - "type": [ - "string", - "null" - ] - }, - "enum": { - "description": "The `enum` keyword.\n\nSee [JSON Schema Validation 6.1.2. \"enum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.2)", - "type": [ - "array", - "null" - ], - "items": true - }, - "const": { - "description": "The `const` keyword.\n\nSee [JSON Schema Validation 6.1.3. \"const\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.3)" - }, - "$ref": { - "description": "The `$ref` keyword.\n\nSee [JSON Schema 8.2.4.1. Direct References with \"$ref\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.4.1).", - "type": [ - "string", - "null" - ] - }, - "$id": { - "description": "The `$id` keyword.\n\nSee [JSON Schema 8.2.2. The \"$id\" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.2).", - "type": [ - "string", - "null" - ] - }, - "title": { - "description": "The `title` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": [ - "string", - "null" - ] - }, - "description": { - "description": "The `description` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": [ - "string", - "null" - ] - }, - "default": { - "description": "The `default` keyword.\n\nSee [JSON Schema Validation 9.2. \"default\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.2)." - }, - "deprecated": { - "description": "The `deprecated` keyword.\n\nSee [JSON Schema Validation 9.3. \"deprecated\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.3).", - "type": "boolean" - }, - "readOnly": { - "description": "The `readOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "writeOnly": { - "description": "The `writeOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "examples": { - "description": "The `examples` keyword.\n\nSee [JSON Schema Validation 9.5. \"examples\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.5).", - "type": "array", - "items": true - }, - "allOf": { - "description": "The `allOf` keyword.\n\nSee [JSON Schema 9.2.1.1. \"allOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.1).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "anyOf": { - "description": "The `anyOf` keyword.\n\nSee [JSON Schema 9.2.1.2. \"anyOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.2).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "oneOf": { - "description": "The `oneOf` keyword.\n\nSee [JSON Schema 9.2.1.3. \"oneOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.3).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "not": { - "description": "The `not` keyword.\n\nSee [JSON Schema 9.2.1.4. \"not\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.4).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "if": { - "description": "The `if` keyword.\n\nSee [JSON Schema 9.2.2.1. \"if\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.1).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "then": { - "description": "The `then` keyword.\n\nSee [JSON Schema 9.2.2.2. \"then\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.2).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "else": { - "description": "The `else` keyword.\n\nSee [JSON Schema 9.2.2.3. \"else\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.3).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "multipleOf": { - "description": "The `multipleOf` keyword.\n\nSee [JSON Schema Validation 6.2.1. \"multipleOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.1).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "maximum": { - "description": "The `maximum` keyword.\n\nSee [JSON Schema Validation 6.2.2. \"maximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.2).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "exclusiveMaximum": { - "description": "The `exclusiveMaximum` keyword.\n\nSee [JSON Schema Validation 6.2.3. \"exclusiveMaximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.3).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "minimum": { - "description": "The `minimum` keyword.\n\nSee [JSON Schema Validation 6.2.4. \"minimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.4).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "exclusiveMinimum": { - "description": "The `exclusiveMinimum` keyword.\n\nSee [JSON Schema Validation 6.2.5. \"exclusiveMinimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.5).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "maxLength": { - "description": "The `maxLength` keyword.\n\nSee [JSON Schema Validation 6.3.1. \"maxLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minLength": { - "description": "The `minLength` keyword.\n\nSee [JSON Schema Validation 6.3.2. \"minLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "pattern": { - "description": "The `pattern` keyword.\n\nSee [JSON Schema Validation 6.3.3. \"pattern\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.3).", - "type": [ - "string", - "null" - ] - }, - "items": { - "description": "The `items` keyword.\n\nSee [JSON Schema 9.3.1.1. \"items\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.1).", - "anyOf": [ - { - "$ref": "#/definitions/SingleOrVec_for_Schema" - }, - { - "type": "null" - } - ] - }, - "additionalItems": { - "description": "The `additionalItems` keyword.\n\nSee [JSON Schema 9.3.1.2. \"additionalItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.2).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "maxItems": { - "description": "The `maxItems` keyword.\n\nSee [JSON Schema Validation 6.4.1. \"maxItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minItems": { - "description": "The `minItems` keyword.\n\nSee [JSON Schema Validation 6.4.2. \"minItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "uniqueItems": { - "description": "The `uniqueItems` keyword.\n\nSee [JSON Schema Validation 6.4.3. \"uniqueItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.3).", - "type": [ - "boolean", - "null" - ] - }, - "contains": { - "description": "The `contains` keyword.\n\nSee [JSON Schema 9.3.1.4. \"contains\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.4).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "maxProperties": { - "description": "The `maxProperties` keyword.\n\nSee [JSON Schema Validation 6.5.1. \"maxProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minProperties": { - "description": "The `minProperties` keyword.\n\nSee [JSON Schema Validation 6.5.2. \"minProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "required": { - "description": "The `required` keyword.\n\nSee [JSON Schema Validation 6.5.3. \"required\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.3).", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "properties": { - "description": "The `properties` keyword.\n\nSee [JSON Schema 9.3.2.1. \"properties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.1).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "patternProperties": { - "description": "The `patternProperties` keyword.\n\nSee [JSON Schema 9.3.2.2. \"patternProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.2).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "additionalProperties": { - "description": "The `additionalProperties` keyword.\n\nSee [JSON Schema 9.3.2.3. \"additionalProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.3).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "propertyNames": { - "description": "The `propertyNames` keyword.\n\nSee [JSON Schema 9.3.2.5. \"propertyNames\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.5).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": true, - "definitions": { - "Schema": { - "description": "A JSON Schema.", - "anyOf": [ - { - "description": "A trivial boolean JSON Schema.\n\nThe schema `true` matches everything (always passes validation), whereas the schema `false` matches nothing (always fails validation).", - "type": "boolean" - }, - { - "description": "A JSON Schema object.", - "$ref": "#/definitions/SchemaObject" - } - ] - }, - "SchemaObject": { - "description": "A JSON Schema object.", - "type": "object", - "properties": { - "type": { - "description": "The `type` keyword.\n\nSee [JSON Schema Validation 6.1.1. \"type\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.1) and [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1).", - "anyOf": [ - { - "$ref": "#/definitions/SingleOrVec_for_InstanceType" - }, - { - "type": "null" - } - ] - }, - "format": { - "description": "The `format` keyword.\n\nSee [JSON Schema Validation 7. A Vocabulary for Semantic Content With \"format\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-7).", - "type": [ - "string", - "null" - ] - }, - "enum": { - "description": "The `enum` keyword.\n\nSee [JSON Schema Validation 6.1.2. \"enum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.2)", - "type": [ - "array", - "null" - ], - "items": true - }, - "const": { - "description": "The `const` keyword.\n\nSee [JSON Schema Validation 6.1.3. \"const\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.3)" - }, - "$ref": { - "description": "The `$ref` keyword.\n\nSee [JSON Schema 8.2.4.1. Direct References with \"$ref\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.4.1).", - "type": [ - "string", - "null" - ] - }, - "$id": { - "description": "The `$id` keyword.\n\nSee [JSON Schema 8.2.2. The \"$id\" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.2).", - "type": [ - "string", - "null" - ] - }, - "title": { - "description": "The `title` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": [ - "string", - "null" - ] - }, - "description": { - "description": "The `description` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": [ - "string", - "null" - ] - }, - "default": { - "description": "The `default` keyword.\n\nSee [JSON Schema Validation 9.2. \"default\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.2)." - }, - "deprecated": { - "description": "The `deprecated` keyword.\n\nSee [JSON Schema Validation 9.3. \"deprecated\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.3).", - "type": "boolean" - }, - "readOnly": { - "description": "The `readOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "writeOnly": { - "description": "The `writeOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "examples": { - "description": "The `examples` keyword.\n\nSee [JSON Schema Validation 9.5. \"examples\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.5).", - "type": "array", - "items": true - }, - "allOf": { - "description": "The `allOf` keyword.\n\nSee [JSON Schema 9.2.1.1. \"allOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.1).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "anyOf": { - "description": "The `anyOf` keyword.\n\nSee [JSON Schema 9.2.1.2. \"anyOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.2).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "oneOf": { - "description": "The `oneOf` keyword.\n\nSee [JSON Schema 9.2.1.3. \"oneOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.3).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "not": { - "description": "The `not` keyword.\n\nSee [JSON Schema 9.2.1.4. \"not\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.4).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "if": { - "description": "The `if` keyword.\n\nSee [JSON Schema 9.2.2.1. \"if\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.1).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "then": { - "description": "The `then` keyword.\n\nSee [JSON Schema 9.2.2.2. \"then\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.2).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "else": { - "description": "The `else` keyword.\n\nSee [JSON Schema 9.2.2.3. \"else\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.3).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "multipleOf": { - "description": "The `multipleOf` keyword.\n\nSee [JSON Schema Validation 6.2.1. \"multipleOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.1).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "maximum": { - "description": "The `maximum` keyword.\n\nSee [JSON Schema Validation 6.2.2. \"maximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.2).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "exclusiveMaximum": { - "description": "The `exclusiveMaximum` keyword.\n\nSee [JSON Schema Validation 6.2.3. \"exclusiveMaximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.3).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "minimum": { - "description": "The `minimum` keyword.\n\nSee [JSON Schema Validation 6.2.4. \"minimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.4).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "exclusiveMinimum": { - "description": "The `exclusiveMinimum` keyword.\n\nSee [JSON Schema Validation 6.2.5. \"exclusiveMinimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.5).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "maxLength": { - "description": "The `maxLength` keyword.\n\nSee [JSON Schema Validation 6.3.1. \"maxLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minLength": { - "description": "The `minLength` keyword.\n\nSee [JSON Schema Validation 6.3.2. \"minLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "pattern": { - "description": "The `pattern` keyword.\n\nSee [JSON Schema Validation 6.3.3. \"pattern\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.3).", - "type": [ - "string", - "null" - ] - }, - "items": { - "description": "The `items` keyword.\n\nSee [JSON Schema 9.3.1.1. \"items\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.1).", - "anyOf": [ - { - "$ref": "#/definitions/SingleOrVec_for_Schema" - }, - { - "type": "null" - } - ] - }, - "additionalItems": { - "description": "The `additionalItems` keyword.\n\nSee [JSON Schema 9.3.1.2. \"additionalItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.2).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "maxItems": { - "description": "The `maxItems` keyword.\n\nSee [JSON Schema Validation 6.4.1. \"maxItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minItems": { - "description": "The `minItems` keyword.\n\nSee [JSON Schema Validation 6.4.2. \"minItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "uniqueItems": { - "description": "The `uniqueItems` keyword.\n\nSee [JSON Schema Validation 6.4.3. \"uniqueItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.3).", - "type": [ - "boolean", - "null" - ] - }, - "contains": { - "description": "The `contains` keyword.\n\nSee [JSON Schema 9.3.1.4. \"contains\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.4).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "maxProperties": { - "description": "The `maxProperties` keyword.\n\nSee [JSON Schema Validation 6.5.1. \"maxProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minProperties": { - "description": "The `minProperties` keyword.\n\nSee [JSON Schema Validation 6.5.2. \"minProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "required": { - "description": "The `required` keyword.\n\nSee [JSON Schema Validation 6.5.3. \"required\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.3).", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "properties": { - "description": "The `properties` keyword.\n\nSee [JSON Schema 9.3.2.1. \"properties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.1).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "patternProperties": { - "description": "The `patternProperties` keyword.\n\nSee [JSON Schema 9.3.2.2. \"patternProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.2).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "additionalProperties": { - "description": "The `additionalProperties` keyword.\n\nSee [JSON Schema 9.3.2.3. \"additionalProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.3).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "propertyNames": { - "description": "The `propertyNames` keyword.\n\nSee [JSON Schema 9.3.2.5. \"propertyNames\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.5).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": true - }, - "SingleOrVec_for_InstanceType": { - "description": "A type which can be serialized as a single item, or multiple items.\n\nIn some contexts, a `Single` may be semantically distinct from a `Vec` containing only item.", - "anyOf": [ - { - "$ref": "#/definitions/InstanceType" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/InstanceType" - } - } - ] - }, - "InstanceType": { - "description": "The possible types of values in JSON Schema documents.\n\nSee [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1).", - "type": "string", - "enum": [ - "null", - "boolean", - "object", - "array", - "number", - "string", - "integer" - ] - }, - "SingleOrVec_for_Schema": { - "description": "A type which can be serialized as a single item, or multiple items.\n\nIn some contexts, a `Single` may be semantically distinct from a `Vec` containing only item.", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/Schema" - } - } - ] - } - } -} \ No newline at end of file diff --git a/schemars/tests/expected/schema-openapi3.json b/schemars/tests/expected/schema-openapi3.json deleted file mode 100644 index 0548828..0000000 --- a/schemars/tests/expected/schema-openapi3.json +++ /dev/null @@ -1,636 +0,0 @@ -{ - "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", - "title": "RootSchema", - "description": "The root object of a JSON Schema document.", - "type": "object", - "properties": { - "$schema": { - "description": "The `$schema` keyword.\n\nSee [JSON Schema 8.1.1. The \"$schema\" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.1.1).", - "type": "string", - "nullable": true - }, - "definitions": { - "description": "The `definitions` keyword.\n\nIn JSON Schema draft 2019-09 this was replaced by $defs, but in Schemars this is still serialized as `definitions` for backward-compatibility.\n\nSee [JSON Schema 8.2.5. Schema Re-Use With \"$defs\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.5), and [JSON Schema (draft 07) 9. Schema Re-Use With \"definitions\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-01#section-9).", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/Schema" - } - }, - "type": { - "description": "The `type` keyword.\n\nSee [JSON Schema Validation 6.1.1. \"type\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.1) and [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1).", - "allOf": [ - { - "$ref": "#/components/schemas/SingleOrVec_for_InstanceType" - } - ], - "nullable": true - }, - "format": { - "description": "The `format` keyword.\n\nSee [JSON Schema Validation 7. A Vocabulary for Semantic Content With \"format\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-7).", - "type": "string", - "nullable": true - }, - "enum": { - "description": "The `enum` keyword.\n\nSee [JSON Schema Validation 6.1.2. \"enum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.2)", - "type": "array", - "items": {}, - "nullable": true - }, - "const": { - "description": "The `const` keyword.\n\nSee [JSON Schema Validation 6.1.3. \"const\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.3)", - "nullable": true - }, - "$ref": { - "description": "The `$ref` keyword.\n\nSee [JSON Schema 8.2.4.1. Direct References with \"$ref\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.4.1).", - "type": "string", - "nullable": true - }, - "$id": { - "description": "The `$id` keyword.\n\nSee [JSON Schema 8.2.2. The \"$id\" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.2).", - "type": "string", - "nullable": true - }, - "title": { - "description": "The `title` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": "string", - "nullable": true - }, - "description": { - "description": "The `description` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": "string", - "nullable": true - }, - "default": { - "description": "The `default` keyword.\n\nSee [JSON Schema Validation 9.2. \"default\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.2).", - "nullable": true - }, - "deprecated": { - "description": "The `deprecated` keyword.\n\nSee [JSON Schema Validation 9.3. \"deprecated\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.3).", - "type": "boolean" - }, - "readOnly": { - "description": "The `readOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "writeOnly": { - "description": "The `writeOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "examples": { - "description": "The `examples` keyword.\n\nSee [JSON Schema Validation 9.5. \"examples\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.5).", - "type": "array", - "items": {} - }, - "allOf": { - "description": "The `allOf` keyword.\n\nSee [JSON Schema 9.2.1.1. \"allOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.1).", - "type": "array", - "items": { - "$ref": "#/components/schemas/Schema" - }, - "nullable": true - }, - "anyOf": { - "description": "The `anyOf` keyword.\n\nSee [JSON Schema 9.2.1.2. \"anyOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.2).", - "type": "array", - "items": { - "$ref": "#/components/schemas/Schema" - }, - "nullable": true - }, - "oneOf": { - "description": "The `oneOf` keyword.\n\nSee [JSON Schema 9.2.1.3. \"oneOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.3).", - "type": "array", - "items": { - "$ref": "#/components/schemas/Schema" - }, - "nullable": true - }, - "not": { - "description": "The `not` keyword.\n\nSee [JSON Schema 9.2.1.4. \"not\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.4).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "if": { - "description": "The `if` keyword.\n\nSee [JSON Schema 9.2.2.1. \"if\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.1).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "then": { - "description": "The `then` keyword.\n\nSee [JSON Schema 9.2.2.2. \"then\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.2).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "else": { - "description": "The `else` keyword.\n\nSee [JSON Schema 9.2.2.3. \"else\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.3).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "multipleOf": { - "description": "The `multipleOf` keyword.\n\nSee [JSON Schema Validation 6.2.1. \"multipleOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.1).", - "type": "number", - "format": "double", - "nullable": true - }, - "maximum": { - "description": "The `maximum` keyword.\n\nSee [JSON Schema Validation 6.2.2. \"maximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.2).", - "type": "number", - "format": "double", - "nullable": true - }, - "exclusiveMaximum": { - "description": "The `exclusiveMaximum` keyword.\n\nSee [JSON Schema Validation 6.2.3. \"exclusiveMaximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.3).", - "type": "number", - "format": "double", - "nullable": true - }, - "minimum": { - "description": "The `minimum` keyword.\n\nSee [JSON Schema Validation 6.2.4. \"minimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.4).", - "type": "number", - "format": "double", - "nullable": true - }, - "exclusiveMinimum": { - "description": "The `exclusiveMinimum` keyword.\n\nSee [JSON Schema Validation 6.2.5. \"exclusiveMinimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.5).", - "type": "number", - "format": "double", - "nullable": true - }, - "maxLength": { - "description": "The `maxLength` keyword.\n\nSee [JSON Schema Validation 6.3.1. \"maxLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.1).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "minLength": { - "description": "The `minLength` keyword.\n\nSee [JSON Schema Validation 6.3.2. \"minLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.2).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "pattern": { - "description": "The `pattern` keyword.\n\nSee [JSON Schema Validation 6.3.3. \"pattern\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.3).", - "type": "string", - "nullable": true - }, - "items": { - "description": "The `items` keyword.\n\nSee [JSON Schema 9.3.1.1. \"items\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.1).", - "allOf": [ - { - "$ref": "#/components/schemas/SingleOrVec_for_Schema" - } - ], - "nullable": true - }, - "additionalItems": { - "description": "The `additionalItems` keyword.\n\nSee [JSON Schema 9.3.1.2. \"additionalItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.2).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "maxItems": { - "description": "The `maxItems` keyword.\n\nSee [JSON Schema Validation 6.4.1. \"maxItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.1).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "minItems": { - "description": "The `minItems` keyword.\n\nSee [JSON Schema Validation 6.4.2. \"minItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.2).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "uniqueItems": { - "description": "The `uniqueItems` keyword.\n\nSee [JSON Schema Validation 6.4.3. \"uniqueItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.3).", - "type": "boolean", - "nullable": true - }, - "contains": { - "description": "The `contains` keyword.\n\nSee [JSON Schema 9.3.1.4. \"contains\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.4).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "maxProperties": { - "description": "The `maxProperties` keyword.\n\nSee [JSON Schema Validation 6.5.1. \"maxProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.1).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "minProperties": { - "description": "The `minProperties` keyword.\n\nSee [JSON Schema Validation 6.5.2. \"minProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.2).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "required": { - "description": "The `required` keyword.\n\nSee [JSON Schema Validation 6.5.3. \"required\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.3).", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "properties": { - "description": "The `properties` keyword.\n\nSee [JSON Schema 9.3.2.1. \"properties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.1).", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/Schema" - } - }, - "patternProperties": { - "description": "The `patternProperties` keyword.\n\nSee [JSON Schema 9.3.2.2. \"patternProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.2).", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/Schema" - } - }, - "additionalProperties": { - "description": "The `additionalProperties` keyword.\n\nSee [JSON Schema 9.3.2.3. \"additionalProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.3).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "propertyNames": { - "description": "The `propertyNames` keyword.\n\nSee [JSON Schema 9.3.2.5. \"propertyNames\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.5).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - } - }, - "additionalProperties": true, - "definitions": { - "Schema": { - "description": "A JSON Schema.", - "anyOf": [ - { - "description": "A trivial boolean JSON Schema.\n\nThe schema `true` matches everything (always passes validation), whereas the schema `false` matches nothing (always fails validation).", - "type": "boolean" - }, - { - "description": "A JSON Schema object.", - "allOf": [ - { - "$ref": "#/components/schemas/SchemaObject" - } - ] - } - ] - }, - "SchemaObject": { - "description": "A JSON Schema object.", - "type": "object", - "properties": { - "type": { - "description": "The `type` keyword.\n\nSee [JSON Schema Validation 6.1.1. \"type\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.1) and [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1).", - "allOf": [ - { - "$ref": "#/components/schemas/SingleOrVec_for_InstanceType" - } - ], - "nullable": true - }, - "format": { - "description": "The `format` keyword.\n\nSee [JSON Schema Validation 7. A Vocabulary for Semantic Content With \"format\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-7).", - "type": "string", - "nullable": true - }, - "enum": { - "description": "The `enum` keyword.\n\nSee [JSON Schema Validation 6.1.2. \"enum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.2)", - "type": "array", - "items": {}, - "nullable": true - }, - "const": { - "description": "The `const` keyword.\n\nSee [JSON Schema Validation 6.1.3. \"const\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.3)", - "nullable": true - }, - "$ref": { - "description": "The `$ref` keyword.\n\nSee [JSON Schema 8.2.4.1. Direct References with \"$ref\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.4.1).", - "type": "string", - "nullable": true - }, - "$id": { - "description": "The `$id` keyword.\n\nSee [JSON Schema 8.2.2. The \"$id\" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.2).", - "type": "string", - "nullable": true - }, - "title": { - "description": "The `title` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": "string", - "nullable": true - }, - "description": { - "description": "The `description` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": "string", - "nullable": true - }, - "default": { - "description": "The `default` keyword.\n\nSee [JSON Schema Validation 9.2. \"default\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.2).", - "nullable": true - }, - "deprecated": { - "description": "The `deprecated` keyword.\n\nSee [JSON Schema Validation 9.3. \"deprecated\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.3).", - "type": "boolean" - }, - "readOnly": { - "description": "The `readOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "writeOnly": { - "description": "The `writeOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "examples": { - "description": "The `examples` keyword.\n\nSee [JSON Schema Validation 9.5. \"examples\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.5).", - "type": "array", - "items": {} - }, - "allOf": { - "description": "The `allOf` keyword.\n\nSee [JSON Schema 9.2.1.1. \"allOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.1).", - "type": "array", - "items": { - "$ref": "#/components/schemas/Schema" - }, - "nullable": true - }, - "anyOf": { - "description": "The `anyOf` keyword.\n\nSee [JSON Schema 9.2.1.2. \"anyOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.2).", - "type": "array", - "items": { - "$ref": "#/components/schemas/Schema" - }, - "nullable": true - }, - "oneOf": { - "description": "The `oneOf` keyword.\n\nSee [JSON Schema 9.2.1.3. \"oneOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.3).", - "type": "array", - "items": { - "$ref": "#/components/schemas/Schema" - }, - "nullable": true - }, - "not": { - "description": "The `not` keyword.\n\nSee [JSON Schema 9.2.1.4. \"not\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.4).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "if": { - "description": "The `if` keyword.\n\nSee [JSON Schema 9.2.2.1. \"if\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.1).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "then": { - "description": "The `then` keyword.\n\nSee [JSON Schema 9.2.2.2. \"then\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.2).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "else": { - "description": "The `else` keyword.\n\nSee [JSON Schema 9.2.2.3. \"else\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.3).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "multipleOf": { - "description": "The `multipleOf` keyword.\n\nSee [JSON Schema Validation 6.2.1. \"multipleOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.1).", - "type": "number", - "format": "double", - "nullable": true - }, - "maximum": { - "description": "The `maximum` keyword.\n\nSee [JSON Schema Validation 6.2.2. \"maximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.2).", - "type": "number", - "format": "double", - "nullable": true - }, - "exclusiveMaximum": { - "description": "The `exclusiveMaximum` keyword.\n\nSee [JSON Schema Validation 6.2.3. \"exclusiveMaximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.3).", - "type": "number", - "format": "double", - "nullable": true - }, - "minimum": { - "description": "The `minimum` keyword.\n\nSee [JSON Schema Validation 6.2.4. \"minimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.4).", - "type": "number", - "format": "double", - "nullable": true - }, - "exclusiveMinimum": { - "description": "The `exclusiveMinimum` keyword.\n\nSee [JSON Schema Validation 6.2.5. \"exclusiveMinimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.5).", - "type": "number", - "format": "double", - "nullable": true - }, - "maxLength": { - "description": "The `maxLength` keyword.\n\nSee [JSON Schema Validation 6.3.1. \"maxLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.1).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "minLength": { - "description": "The `minLength` keyword.\n\nSee [JSON Schema Validation 6.3.2. \"minLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.2).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "pattern": { - "description": "The `pattern` keyword.\n\nSee [JSON Schema Validation 6.3.3. \"pattern\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.3).", - "type": "string", - "nullable": true - }, - "items": { - "description": "The `items` keyword.\n\nSee [JSON Schema 9.3.1.1. \"items\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.1).", - "allOf": [ - { - "$ref": "#/components/schemas/SingleOrVec_for_Schema" - } - ], - "nullable": true - }, - "additionalItems": { - "description": "The `additionalItems` keyword.\n\nSee [JSON Schema 9.3.1.2. \"additionalItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.2).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "maxItems": { - "description": "The `maxItems` keyword.\n\nSee [JSON Schema Validation 6.4.1. \"maxItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.1).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "minItems": { - "description": "The `minItems` keyword.\n\nSee [JSON Schema Validation 6.4.2. \"minItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.2).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "uniqueItems": { - "description": "The `uniqueItems` keyword.\n\nSee [JSON Schema Validation 6.4.3. \"uniqueItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.3).", - "type": "boolean", - "nullable": true - }, - "contains": { - "description": "The `contains` keyword.\n\nSee [JSON Schema 9.3.1.4. \"contains\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.4).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "maxProperties": { - "description": "The `maxProperties` keyword.\n\nSee [JSON Schema Validation 6.5.1. \"maxProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.1).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "minProperties": { - "description": "The `minProperties` keyword.\n\nSee [JSON Schema Validation 6.5.2. \"minProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.2).", - "type": "integer", - "format": "uint32", - "minimum": 0.0, - "nullable": true - }, - "required": { - "description": "The `required` keyword.\n\nSee [JSON Schema Validation 6.5.3. \"required\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.3).", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "properties": { - "description": "The `properties` keyword.\n\nSee [JSON Schema 9.3.2.1. \"properties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.1).", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/Schema" - } - }, - "patternProperties": { - "description": "The `patternProperties` keyword.\n\nSee [JSON Schema 9.3.2.2. \"patternProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.2).", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/Schema" - } - }, - "additionalProperties": { - "description": "The `additionalProperties` keyword.\n\nSee [JSON Schema 9.3.2.3. \"additionalProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.3).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - }, - "propertyNames": { - "description": "The `propertyNames` keyword.\n\nSee [JSON Schema 9.3.2.5. \"propertyNames\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.5).", - "allOf": [ - { - "$ref": "#/components/schemas/Schema" - } - ], - "nullable": true - } - }, - "additionalProperties": true - }, - "SingleOrVec_for_InstanceType": { - "description": "A type which can be serialized as a single item, or multiple items.\n\nIn some contexts, a `Single` may be semantically distinct from a `Vec` containing only item.", - "anyOf": [ - { - "$ref": "#/components/schemas/InstanceType" - }, - { - "type": "array", - "items": { - "$ref": "#/components/schemas/InstanceType" - } - } - ] - }, - "InstanceType": { - "description": "The possible types of values in JSON Schema documents.\n\nSee [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1).", - "type": "string", - "enum": [ - "null", - "boolean", - "object", - "array", - "number", - "string", - "integer" - ] - }, - "SingleOrVec_for_Schema": { - "description": "A type which can be serialized as a single item, or multiple items.\n\nIn some contexts, a `Single` may be semantically distinct from a `Vec` containing only item.", - "anyOf": [ - { - "$ref": "#/components/schemas/Schema" - }, - { - "type": "array", - "items": { - "$ref": "#/components/schemas/Schema" - } - } - ] - } - } -} \ No newline at end of file diff --git a/schemars/tests/expected/schema.json b/schemars/tests/expected/schema.json deleted file mode 100644 index 6cf840b..0000000 --- a/schemars/tests/expected/schema.json +++ /dev/null @@ -1,762 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "RootSchema", - "description": "The root object of a JSON Schema document.", - "type": "object", - "properties": { - "$schema": { - "description": "The `$schema` keyword.\n\nSee [JSON Schema 8.1.1. The \"$schema\" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.1.1).", - "type": [ - "string", - "null" - ] - }, - "definitions": { - "description": "The `definitions` keyword.\n\nIn JSON Schema draft 2019-09 this was replaced by $defs, but in Schemars this is still serialized as `definitions` for backward-compatibility.\n\nSee [JSON Schema 8.2.5. Schema Re-Use With \"$defs\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.5), and [JSON Schema (draft 07) 9. Schema Re-Use With \"definitions\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-01#section-9).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "type": { - "description": "The `type` keyword.\n\nSee [JSON Schema Validation 6.1.1. \"type\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.1) and [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1).", - "anyOf": [ - { - "$ref": "#/definitions/SingleOrVec_for_InstanceType" - }, - { - "type": "null" - } - ] - }, - "format": { - "description": "The `format` keyword.\n\nSee [JSON Schema Validation 7. A Vocabulary for Semantic Content With \"format\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-7).", - "type": [ - "string", - "null" - ] - }, - "enum": { - "description": "The `enum` keyword.\n\nSee [JSON Schema Validation 6.1.2. \"enum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.2)", - "type": [ - "array", - "null" - ], - "items": true - }, - "const": { - "description": "The `const` keyword.\n\nSee [JSON Schema Validation 6.1.3. \"const\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.3)" - }, - "$ref": { - "description": "The `$ref` keyword.\n\nSee [JSON Schema 8.2.4.1. Direct References with \"$ref\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.4.1).", - "type": [ - "string", - "null" - ] - }, - "$id": { - "description": "The `$id` keyword.\n\nSee [JSON Schema 8.2.2. The \"$id\" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.2).", - "type": [ - "string", - "null" - ] - }, - "title": { - "description": "The `title` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": [ - "string", - "null" - ] - }, - "description": { - "description": "The `description` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": [ - "string", - "null" - ] - }, - "default": { - "description": "The `default` keyword.\n\nSee [JSON Schema Validation 9.2. \"default\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.2)." - }, - "deprecated": { - "description": "The `deprecated` keyword.\n\nSee [JSON Schema Validation 9.3. \"deprecated\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.3).", - "type": "boolean" - }, - "readOnly": { - "description": "The `readOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "writeOnly": { - "description": "The `writeOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "examples": { - "description": "The `examples` keyword.\n\nSee [JSON Schema Validation 9.5. \"examples\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.5).", - "type": "array", - "items": true - }, - "allOf": { - "description": "The `allOf` keyword.\n\nSee [JSON Schema 9.2.1.1. \"allOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.1).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "anyOf": { - "description": "The `anyOf` keyword.\n\nSee [JSON Schema 9.2.1.2. \"anyOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.2).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "oneOf": { - "description": "The `oneOf` keyword.\n\nSee [JSON Schema 9.2.1.3. \"oneOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.3).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "not": { - "description": "The `not` keyword.\n\nSee [JSON Schema 9.2.1.4. \"not\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.4).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "if": { - "description": "The `if` keyword.\n\nSee [JSON Schema 9.2.2.1. \"if\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.1).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "then": { - "description": "The `then` keyword.\n\nSee [JSON Schema 9.2.2.2. \"then\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.2).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "else": { - "description": "The `else` keyword.\n\nSee [JSON Schema 9.2.2.3. \"else\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.3).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "multipleOf": { - "description": "The `multipleOf` keyword.\n\nSee [JSON Schema Validation 6.2.1. \"multipleOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.1).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "maximum": { - "description": "The `maximum` keyword.\n\nSee [JSON Schema Validation 6.2.2. \"maximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.2).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "exclusiveMaximum": { - "description": "The `exclusiveMaximum` keyword.\n\nSee [JSON Schema Validation 6.2.3. \"exclusiveMaximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.3).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "minimum": { - "description": "The `minimum` keyword.\n\nSee [JSON Schema Validation 6.2.4. \"minimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.4).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "exclusiveMinimum": { - "description": "The `exclusiveMinimum` keyword.\n\nSee [JSON Schema Validation 6.2.5. \"exclusiveMinimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.5).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "maxLength": { - "description": "The `maxLength` keyword.\n\nSee [JSON Schema Validation 6.3.1. \"maxLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minLength": { - "description": "The `minLength` keyword.\n\nSee [JSON Schema Validation 6.3.2. \"minLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "pattern": { - "description": "The `pattern` keyword.\n\nSee [JSON Schema Validation 6.3.3. \"pattern\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.3).", - "type": [ - "string", - "null" - ] - }, - "items": { - "description": "The `items` keyword.\n\nSee [JSON Schema 9.3.1.1. \"items\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.1).", - "anyOf": [ - { - "$ref": "#/definitions/SingleOrVec_for_Schema" - }, - { - "type": "null" - } - ] - }, - "additionalItems": { - "description": "The `additionalItems` keyword.\n\nSee [JSON Schema 9.3.1.2. \"additionalItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.2).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "maxItems": { - "description": "The `maxItems` keyword.\n\nSee [JSON Schema Validation 6.4.1. \"maxItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minItems": { - "description": "The `minItems` keyword.\n\nSee [JSON Schema Validation 6.4.2. \"minItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "uniqueItems": { - "description": "The `uniqueItems` keyword.\n\nSee [JSON Schema Validation 6.4.3. \"uniqueItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.3).", - "type": [ - "boolean", - "null" - ] - }, - "contains": { - "description": "The `contains` keyword.\n\nSee [JSON Schema 9.3.1.4. \"contains\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.4).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "maxProperties": { - "description": "The `maxProperties` keyword.\n\nSee [JSON Schema Validation 6.5.1. \"maxProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minProperties": { - "description": "The `minProperties` keyword.\n\nSee [JSON Schema Validation 6.5.2. \"minProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "required": { - "description": "The `required` keyword.\n\nSee [JSON Schema Validation 6.5.3. \"required\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.3).", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "properties": { - "description": "The `properties` keyword.\n\nSee [JSON Schema 9.3.2.1. \"properties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.1).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "patternProperties": { - "description": "The `patternProperties` keyword.\n\nSee [JSON Schema 9.3.2.2. \"patternProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.2).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "additionalProperties": { - "description": "The `additionalProperties` keyword.\n\nSee [JSON Schema 9.3.2.3. \"additionalProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.3).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "propertyNames": { - "description": "The `propertyNames` keyword.\n\nSee [JSON Schema 9.3.2.5. \"propertyNames\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.5).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": true, - "definitions": { - "Schema": { - "description": "A JSON Schema.", - "anyOf": [ - { - "description": "A trivial boolean JSON Schema.\n\nThe schema `true` matches everything (always passes validation), whereas the schema `false` matches nothing (always fails validation).", - "type": "boolean" - }, - { - "description": "A JSON Schema object.", - "allOf": [ - { - "$ref": "#/definitions/SchemaObject" - } - ] - } - ] - }, - "SchemaObject": { - "description": "A JSON Schema object.", - "type": "object", - "properties": { - "type": { - "description": "The `type` keyword.\n\nSee [JSON Schema Validation 6.1.1. \"type\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.1) and [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1).", - "anyOf": [ - { - "$ref": "#/definitions/SingleOrVec_for_InstanceType" - }, - { - "type": "null" - } - ] - }, - "format": { - "description": "The `format` keyword.\n\nSee [JSON Schema Validation 7. A Vocabulary for Semantic Content With \"format\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-7).", - "type": [ - "string", - "null" - ] - }, - "enum": { - "description": "The `enum` keyword.\n\nSee [JSON Schema Validation 6.1.2. \"enum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.2)", - "type": [ - "array", - "null" - ], - "items": true - }, - "const": { - "description": "The `const` keyword.\n\nSee [JSON Schema Validation 6.1.3. \"const\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.1.3)" - }, - "$ref": { - "description": "The `$ref` keyword.\n\nSee [JSON Schema 8.2.4.1. Direct References with \"$ref\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.4.1).", - "type": [ - "string", - "null" - ] - }, - "$id": { - "description": "The `$id` keyword.\n\nSee [JSON Schema 8.2.2. The \"$id\" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.2.2).", - "type": [ - "string", - "null" - ] - }, - "title": { - "description": "The `title` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": [ - "string", - "null" - ] - }, - "description": { - "description": "The `description` keyword.\n\nSee [JSON Schema Validation 9.1. \"title\" and \"description\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.1).", - "type": [ - "string", - "null" - ] - }, - "default": { - "description": "The `default` keyword.\n\nSee [JSON Schema Validation 9.2. \"default\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.2)." - }, - "deprecated": { - "description": "The `deprecated` keyword.\n\nSee [JSON Schema Validation 9.3. \"deprecated\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.3).", - "type": "boolean" - }, - "readOnly": { - "description": "The `readOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "writeOnly": { - "description": "The `writeOnly` keyword.\n\nSee [JSON Schema Validation 9.4. \"readOnly\" and \"writeOnly\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.4).", - "type": "boolean" - }, - "examples": { - "description": "The `examples` keyword.\n\nSee [JSON Schema Validation 9.5. \"examples\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-9.5).", - "type": "array", - "items": true - }, - "allOf": { - "description": "The `allOf` keyword.\n\nSee [JSON Schema 9.2.1.1. \"allOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.1).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "anyOf": { - "description": "The `anyOf` keyword.\n\nSee [JSON Schema 9.2.1.2. \"anyOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.2).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "oneOf": { - "description": "The `oneOf` keyword.\n\nSee [JSON Schema 9.2.1.3. \"oneOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.3).", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Schema" - } - }, - "not": { - "description": "The `not` keyword.\n\nSee [JSON Schema 9.2.1.4. \"not\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.4).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "if": { - "description": "The `if` keyword.\n\nSee [JSON Schema 9.2.2.1. \"if\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.1).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "then": { - "description": "The `then` keyword.\n\nSee [JSON Schema 9.2.2.2. \"then\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.2).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "else": { - "description": "The `else` keyword.\n\nSee [JSON Schema 9.2.2.3. \"else\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.2.3).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "multipleOf": { - "description": "The `multipleOf` keyword.\n\nSee [JSON Schema Validation 6.2.1. \"multipleOf\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.1).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "maximum": { - "description": "The `maximum` keyword.\n\nSee [JSON Schema Validation 6.2.2. \"maximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.2).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "exclusiveMaximum": { - "description": "The `exclusiveMaximum` keyword.\n\nSee [JSON Schema Validation 6.2.3. \"exclusiveMaximum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.3).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "minimum": { - "description": "The `minimum` keyword.\n\nSee [JSON Schema Validation 6.2.4. \"minimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.4).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "exclusiveMinimum": { - "description": "The `exclusiveMinimum` keyword.\n\nSee [JSON Schema Validation 6.2.5. \"exclusiveMinimum\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.2.5).", - "type": [ - "number", - "null" - ], - "format": "double" - }, - "maxLength": { - "description": "The `maxLength` keyword.\n\nSee [JSON Schema Validation 6.3.1. \"maxLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minLength": { - "description": "The `minLength` keyword.\n\nSee [JSON Schema Validation 6.3.2. \"minLength\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "pattern": { - "description": "The `pattern` keyword.\n\nSee [JSON Schema Validation 6.3.3. \"pattern\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.3.3).", - "type": [ - "string", - "null" - ] - }, - "items": { - "description": "The `items` keyword.\n\nSee [JSON Schema 9.3.1.1. \"items\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.1).", - "anyOf": [ - { - "$ref": "#/definitions/SingleOrVec_for_Schema" - }, - { - "type": "null" - } - ] - }, - "additionalItems": { - "description": "The `additionalItems` keyword.\n\nSee [JSON Schema 9.3.1.2. \"additionalItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.2).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "maxItems": { - "description": "The `maxItems` keyword.\n\nSee [JSON Schema Validation 6.4.1. \"maxItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minItems": { - "description": "The `minItems` keyword.\n\nSee [JSON Schema Validation 6.4.2. \"minItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "uniqueItems": { - "description": "The `uniqueItems` keyword.\n\nSee [JSON Schema Validation 6.4.3. \"uniqueItems\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.4.3).", - "type": [ - "boolean", - "null" - ] - }, - "contains": { - "description": "The `contains` keyword.\n\nSee [JSON Schema 9.3.1.4. \"contains\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.1.4).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "maxProperties": { - "description": "The `maxProperties` keyword.\n\nSee [JSON Schema Validation 6.5.1. \"maxProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.1).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "minProperties": { - "description": "The `minProperties` keyword.\n\nSee [JSON Schema Validation 6.5.2. \"minProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.2).", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "required": { - "description": "The `required` keyword.\n\nSee [JSON Schema Validation 6.5.3. \"required\"](https://tools.ietf.org/html/draft-handrews-json-schema-validation-02#section-6.5.3).", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "properties": { - "description": "The `properties` keyword.\n\nSee [JSON Schema 9.3.2.1. \"properties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.1).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "patternProperties": { - "description": "The `patternProperties` keyword.\n\nSee [JSON Schema 9.3.2.2. \"patternProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.2).", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Schema" - } - }, - "additionalProperties": { - "description": "The `additionalProperties` keyword.\n\nSee [JSON Schema 9.3.2.3. \"additionalProperties\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.3).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - }, - "propertyNames": { - "description": "The `propertyNames` keyword.\n\nSee [JSON Schema 9.3.2.5. \"propertyNames\"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.3.2.5).", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": true - }, - "SingleOrVec_for_InstanceType": { - "description": "A type which can be serialized as a single item, or multiple items.\n\nIn some contexts, a `Single` may be semantically distinct from a `Vec` containing only item.", - "anyOf": [ - { - "$ref": "#/definitions/InstanceType" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/InstanceType" - } - } - ] - }, - "InstanceType": { - "description": "The possible types of values in JSON Schema documents.\n\nSee [JSON Schema 4.2.1. Instance Data Model](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-4.2.1).", - "type": "string", - "enum": [ - "null", - "boolean", - "object", - "array", - "number", - "string", - "integer" - ] - }, - "SingleOrVec_for_Schema": { - "description": "A type which can be serialized as a single item, or multiple items.\n\nIn some contexts, a `Single` may be semantically distinct from a `Vec` containing only item.", - "anyOf": [ - { - "$ref": "#/definitions/Schema" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/Schema" - } - } - ] - } - } -} \ No newline at end of file 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 | 104 +++++++++--------- .../expected/schema_settings-openapi3.json | 40 +++---- 3 files changed, 73 insertions(+), 73 deletions(-) diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index d19f80b..8336ecc 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 132a59a..858d286 100644 --- a/schemars/tests/expected/from_value_openapi3.json +++ b/schemars/tests/expected/from_value_openapi3.json @@ -1,58 +1,7 @@ { - "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", + "$schema": "https://spec.openapis.org/oas/3.0/schema/2021-09-28#/definitions/Schema", "title": "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 - }, - "my_empty_vec": { - "type": "array", - "items": {} - }, - "my_tuple": { - "type": "array", - "items": [ - { - "type": "string", - "maxLength": 1, - "minLength": 1 - }, - { - "type": "integer" - } - ], - "maxItems": 2, - "minItems": 2 - } - } - } - }, "example": { "myBool": true, "myInnerStruct": { @@ -72,5 +21,56 @@ }, "myInt": 123, "myNullableEnum": null + }, + "properties": { + "myBool": { + "type": "boolean" + }, + "myInnerStruct": { + "type": "object", + "properties": { + "my_empty_map": { + "type": "object", + "additionalProperties": true + }, + "my_empty_vec": { + "type": "array", + "items": {} + }, + "my_map": { + "type": "object", + "additionalProperties": { + "type": "number" + } + }, + "my_tuple": { + "type": "array", + "items": [ + { + "type": "string", + "maxLength": 1, + "minLength": 1 + }, + { + "type": "integer" + } + ], + "maxItems": 2, + "minItems": 2 + }, + "my_vec": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "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 17fe805..15b752d 100644 --- a/schemars/tests/expected/schema_settings-openapi3.json +++ b/schemars/tests/expected/schema_settings-openapi3.json @@ -1,23 +1,8 @@ { - "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", + "$schema": "https://spec.openapis.org/oas/3.0/schema/2021-09-28#/definitions/Schema", "title": "Outer", "type": "object", - "required": [ - "int", - "value", - "values" - ], "properties": { - "int": { - "type": "integer", - "format": "int32", - "example": 8 - }, - "values": { - "type": "object", - "additionalProperties": true - }, - "value": {}, "inner": { "allOf": [ { @@ -25,8 +10,23 @@ } ], "nullable": true + }, + "int": { + "type": "integer", + "format": "int32", + "example": 8 + }, + "value": {}, + "values": { + "type": "object", + "additionalProperties": 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 | 32 ++--- 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 | 52 ++++----- .../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 | 102 ++++++++-------- .../tests/expected/from_value_draft07.json | 102 ++++++++-------- .../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 | 32 ++--- .../tests/expected/property-name-struct.json | 12 +- schemars/tests/expected/range.json | 110 +++++++++--------- schemars/tests/expected/remote_derive.json | 56 ++++----- .../tests/expected/remote_derive_generic.json | 46 ++++---- 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 | 32 ++--- .../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, 1135 insertions(+), 1124 deletions(-) diff --git a/schemars/src/schema.rs b/schemars/src/schema.rs index 6a88158..82be821 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 3c022dc..e4ceb13 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 e3e788f..b96b00c 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 66bf749..d37d482 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 aefef83..36f16e1 100644 --- a/schemars/tests/expected/default.json +++ b/schemars/tests/expected/default.json @@ -3,22 +3,22 @@ "title": "MyStruct", "type": "object", "properties": { - "my_int": { - "default": 0, - "type": "integer", - "format": "int32" - }, "my_bool": { - "default": false, - "type": "boolean" + "type": "boolean", + "default": false + }, + "my_int": { + "type": "integer", + "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_int": { - "default": 6, - "type": "integer", - "format": "int32" - }, "my_bool": { - "default": true, - "type": "boolean" + "type": "boolean", + "default": true + }, + "my_int": { + "type": "integer", + "format": "int32", + "default": 6 } } }, diff --git a/schemars/tests/expected/deprecated-enum.json b/schemars/tests/expected/deprecated-enum.json index 825ae50..55a98ef 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 8f7ba71..b915eb1 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 53aaa86..c983c1d 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 83dce79..0f90433 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 9f1b4bf..103a71a 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 35bb648..a3dcbe0 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 d01f7d5..b212599 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": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, "nanos": { "type": "integer", "format": "uint32", "minimum": 0.0 - } - } - }, - "SystemTime": { - "type": "object", - "required": [ - "nanos_since_epoch", - "secs_since_epoch" - ], - "properties": { - "secs_since_epoch": { + }, + "secs": { "type": "integer", "format": "uint64", "minimum": 0.0 - }, + } + }, + "required": [ + "nanos", + "secs" + ] + }, + "SystemTime": { + "type": "object", + "properties": { "nanos_since_epoch": { "type": "integer", "format": "uint32", "minimum": 0.0 + }, + "secs_since_epoch": { + "type": "integer", + "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 c5b54c8..a89ab50 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 efe19dc..9380c5b 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 b6b7b99..d492808 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 cc721df..b09f107 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 7a9bcd7..6db4d31 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 115dbf3..2382f2d 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 833f7b7..9e6e5d4 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 50cd62c..a549c8c 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 24397bf..7ce7d3d 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 7eccbe2..add81e4 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 4950c93..b4dc26a 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 c02a139..9c5c5ab 100644 --- a/schemars/tests/expected/examples.json +++ b/schemars/tests/expected/examples.json @@ -1,6 +1,29 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Struct", + "type": "object", + "properties": { + "bar": { + "type": "boolean" + }, + "baz": { + "type": [ + "string", + "null" + ], + "examples": [ + null + ] + }, + "foo": { + "type": "integer", + "format": "int32", + "examples": [ + 8, + null + ] + } + }, "examples": [ { "bar": false, @@ -9,31 +32,8 @@ }, null ], - "type": "object", "required": [ "bar", "foo" - ], - "properties": { - "foo": { - "examples": [ - 8, - null - ], - "type": "integer", - "format": "int32" - }, - "bar": { - "type": "boolean" - }, - "baz": { - "examples": [ - null - ], - "type": [ - "string", - "null" - ] - } - } + ] } \ No newline at end of file diff --git a/schemars/tests/expected/flatten.json b/schemars/tests/expected/flatten.json index fedf571..ec0aff3 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 0d94b66..cd1c634 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 9cea709..4c6adcf 100644 --- a/schemars/tests/expected/from_value_2019_09.json +++ b/schemars/tests/expected/from_value_2019_09.json @@ -1,6 +1,56 @@ { "$schema": "https://json-schema.org/draft/2019-09/schema", "title": "MyStruct", + "type": "object", + "properties": { + "myBool": { + "type": "boolean" + }, + "myInnerStruct": { + "type": "object", + "properties": { + "my_empty_map": { + "type": "object", + "additionalProperties": true + }, + "my_empty_vec": { + "type": "array", + "items": true + }, + "my_map": { + "type": "object", + "additionalProperties": { + "type": "number" + } + }, + "my_tuple": { + "type": "array", + "items": [ + { + "type": "string", + "maxLength": 1, + "minLength": 1 + }, + { + "type": "integer" + } + ], + "maxItems": 2, + "minItems": 2 + }, + "my_vec": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "myInt": { + "type": "integer" + }, + "myNullableEnum": true + }, "examples": [ { "myBool": true, @@ -22,55 +72,5 @@ "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 - }, - "my_empty_vec": { - "type": "array", - "items": true - }, - "my_tuple": { - "type": "array", - "items": [ - { - "type": "string", - "maxLength": 1, - "minLength": 1 - }, - { - "type": "integer" - } - ], - "maxItems": 2, - "minItems": 2 - } - } - } - } + ] } \ No newline at end of file diff --git a/schemars/tests/expected/from_value_draft07.json b/schemars/tests/expected/from_value_draft07.json index dec3996..5d87f02 100644 --- a/schemars/tests/expected/from_value_draft07.json +++ b/schemars/tests/expected/from_value_draft07.json @@ -1,6 +1,56 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "MyStruct", + "type": "object", + "properties": { + "myBool": { + "type": "boolean" + }, + "myInnerStruct": { + "type": "object", + "properties": { + "my_empty_map": { + "type": "object", + "additionalProperties": true + }, + "my_empty_vec": { + "type": "array", + "items": true + }, + "my_map": { + "type": "object", + "additionalProperties": { + "type": "number" + } + }, + "my_tuple": { + "type": "array", + "items": [ + { + "type": "string", + "maxLength": 1, + "minLength": 1 + }, + { + "type": "integer" + } + ], + "maxItems": 2, + "minItems": 2 + }, + "my_vec": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "myInt": { + "type": "integer" + }, + "myNullableEnum": true + }, "examples": [ { "myBool": true, @@ -22,55 +72,5 @@ "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 - }, - "my_empty_vec": { - "type": "array", - "items": true - }, - "my_tuple": { - "type": "array", - "items": [ - { - "type": "string", - "maxLength": 1, - "minLength": 1 - }, - { - "type": "integer" - } - ], - "maxItems": 2, - "minItems": 2 - } - } - } - } + ] } \ 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 858d286..0405935 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 98065d9..9c209e6 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 e051e5d..0c1eebe 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 b731553..fe4e83e 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 d030787..51a9bb7 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 811eae3..0c5840c 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 cbfcfd9..3b83929 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 d333675..50448ed 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", + "properties": { + "borrowed": { + "$ref": "#/definitions/OsString" + }, + "owned": { + "$ref": "#/definitions/OsString" + } + }, "required": [ "borrowed", "owned" ], - "properties": { - "owned": { - "$ref": "#/definitions/OsString" - }, - "borrowed": { - "$ref": "#/definitions/OsString" - } - }, "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 aa708fd..1aa6e78 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 0bb3aeb..bb68e3a 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", + "properties": { + "bound": { + "$ref": "#/definitions/Bound_of_string" + }, + "inclusive": { + "$ref": "#/definitions/Range_of_double" + }, + "range": { + "$ref": "#/definitions/Range_of_uint" + } + }, "required": [ "bound", "inclusive", "range" ], - "properties": { - "range": { - "$ref": "#/definitions/Range_of_uint" - }, - "inclusive": { - "$ref": "#/definitions/Range_of_double" - }, - "bound": { - "$ref": "#/definitions/Bound_of_string" - } - }, "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 6f09eb2..76df9b6 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" + "system_cpu_time": { + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ], + "default": "0.000000000s" }, "user_cpu_time": { + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ], "default": { "nanos": 0, "secs": 0 - }, - "allOf": [ - { - "$ref": "#/definitions/Duration" - } - ] + } }, - "system_cpu_time": { - "default": "0.000000000s", - "allOf": [ - { - "$ref": "#/definitions/Duration" - } - ] + "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 5a65f3b..d06320f 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,9 +15,32 @@ }, "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_null_and_int32": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "integer", + "format": "int32" + } + ] + }, "Or_for_uint8_and_boolean": { "anyOf": [ { @@ -42,17 +53,6 @@ } ] }, - "Or_for_null_and_int32": { - "anyOf": [ - { - "type": "null" - }, - { - "type": "integer", - "format": "int32" - } - ] - }, "Str": { "type": "string" } diff --git a/schemars/tests/expected/result.json b/schemars/tests/expected/result.json index 2a01573..44c079e 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 9e4e6b3..ebea3fe 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 7505b28..6bb3ab1 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 844699a..4e6cc36 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 bddd083..0da8044 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 fb936a7..4e18426 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 15b752d..5318b01 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 c876d6f..8e70c7c 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 3ecba4c..c29c59e 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 dea0219..78b6475 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 c4a0cc1..8b6fbeb 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 55147de..b912feb 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 feb5b51..40b5c56 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 d87ad04..e8c59b9 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 ba1bf23..b955e77 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 d488561..21e01a6 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", + "properties": { + "included": { + "type": "null" + }, + "readable": { + "type": "string", + "default": "", + "readOnly": true + }, + "writable": { + "type": "number", + "format": "float", + "writeOnly": true + } + }, "required": [ "included", "writable" - ], - "properties": { - "readable": { - "default": "", - "readOnly": true, - "type": "string" - }, - "writable": { - "writeOnly": true, - "type": "number", - "format": "float" - }, - "included": { - "type": "null" - } - } + ] } \ 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 1a8d749..ed8602a 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 cee25b1..e6fe68b 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 ddb8c5a..5722f9e 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 d4a14e3..38aabcc 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 443a9fa..ffe6792 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 86658ac..438b4aa 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 53c78fa..c119ea5 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 42fda99..1475c25 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 12ac7d5..8da4348 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 121cdb4..501b79c 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 4ba7735..237a90e 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 ddbd9d3..bcfe137 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 e584177..b6315cb 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 9a6a22a..9637e1b 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 d044193..9bb2f82 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 1e45a96..308f631 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 42fda99..1475c25 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 12ac7d5..8da4348 100644 --- a/schemars/examples/custom_settings.schema.json +++ b/schemars/examples/custom_settings.schema.json @@ -2,10 +2,6 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "MyStruct", "type": "object", - "required": [ - "my_bool", - "my_int" - ], "properties": { "my_bool": { "type": "boolean" @@ -23,32 +19,30 @@ "nullable": true } }, + "required": [ + "my_int", + "my_bool" + ], "definitions": { "MyEnum": { "oneOf": [ { "type": "object", - "required": [ - "StringNewType" - ], "properties": { "StringNewType": { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "StringNewType" + ] }, { "type": "object", - "required": [ - "StructVariant" - ], "properties": { "StructVariant": { "type": "object", - "required": [ - "floats" - ], "properties": { "floats": { "type": "array", @@ -57,10 +51,16 @@ "format": "float" } } - } + }, + "required": [ + "floats" + ] } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ + "StructVariant" + ] } ] } diff --git a/schemars/examples/doc_comments.schema.json b/schemars/examples/doc_comments.schema.json index 121cdb4..501b79c 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 4ba7735..237a90e 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 ddbd9d3..bcfe137 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 e584177..b6315cb 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 9a6a22a..9637e1b 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 d044193..9bb2f82 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 1e45a96..308f631 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 0ba27ce..2510401 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 8336ecc..c06f85a 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 cfe11df..5fd4273 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 55a98ef..dab90ec 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 c983c1d..fc38db1 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 6db4d31..758bf9b 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 2382f2d..765248f 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 9e6e5d4..4fdf04e 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 a549c8c..050089c 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 11a5c2a..86160e8 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 4e18426..e51f397 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 8e70c7c..07cdb0e 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 8b6fbeb..45bc8e9 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 f22831e..7a1de81 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 | 24 ++-- schemars/tests/expected/validate_inner.json | 10 +- schemars/tests/expected/validate_newtype.json | 4 +- .../expected/validate_schemars_attrs.json | 24 ++-- schemars/tests/expected/validate_tuple.json | 4 +- schemars/tests/util/mod.rs | 32 ----- schemars_derive/src/schema_exprs.rs | 112 +++++++++--------- 45 files changed, 280 insertions(+), 330 deletions(-) delete mode 100644 schemars/src/flatten.rs diff --git a/schemars/src/_private.rs b/schemars/src/_private.rs index 2510401..129d92e 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 69b0361..0000000 --- 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 fbe5f1d..32bb8c0 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 5e1f9a5..06458c6 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 b96b00c..b9e333d 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 d37d482..b01fc0d 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 dab90ec..ee3de61 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 b915eb1..d2af941 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 b212599..71c17b7 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 a89ab50..f746216 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 9380c5b..b977be0 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 d492808..0351fa2 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 b09f107..4c1d5a6 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 758bf9b..9c2aa9c 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 765248f..7fd931c 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 7ce7d3d..e3f7c66 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 add81e4..2b23ffb 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 9c5c5ab..3203c4a 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 ec0aff3..8f77dd2 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 fe4e83e..7ba76d5 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 0c5840c..e958c8e 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 3b83929..6ee5105 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 50448ed..927f3f9 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 bb68e3a..19e4780 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 76df9b6..9477e0f 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 d06320f..b533320 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 4e6cc36..dc5336b 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 39c39a9..7524c63 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 0da8044..fdadbab 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 e51f397..947aa2e 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 5318b01..b6de302 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 07cdb0e..6a66ca0 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 c29c59e..5b95671 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 45bc8e9..90871ad 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 40b5c56..7cc55bf 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 21e01a6..7890298 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 ed8602a..5ff105c 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 e6fe68b..0a5a0af 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 38aabcc..2d6e43c 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": [ - "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", + "contains_str1", + "contains_str2", + "email_address", "tel", + "homepage", + "non_empty_str", + "non_empty_str2", + "pair", + "map_contains", + "required_option", "x" ] } \ No newline at end of file diff --git a/schemars/tests/expected/validate_inner.json b/schemars/tests/expected/validate_inner.json index ffe6792..f8a7eca 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 796aecd..4c1146e 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 438b4aa..fa2dbcd 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": [ - "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", + "contains_str1", + "contains_str2", + "email_address", "tel", + "homepage", + "non_empty_str", + "non_empty_str2", + "pair", + "map_contains", + "required_option", "x" ] } \ No newline at end of file diff --git a/schemars/tests/expected/validate_tuple.json b/schemars/tests/expected/validate_tuple.json index 8ab6eaa..619b8c4 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 99cf677..3376c02 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 7a1de81..6357172 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 | 20 ++-- .../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 | 32 ++--- 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 | 46 ++++---- .../tests/expected/remote_derive_generic.json | 44 +++---- 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 | 30 ++--- .../expected/schema_settings-openapi3.json | 22 ++-- schemars/tests/expected/schema_settings.json | 30 ++--- .../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 | 100 ++++++++-------- schemars/tests/expected/validate_inner.json | 62 +++++----- schemars/tests/expected/validate_newtype.json | 4 +- .../expected/validate_schemars_attrs.json | 100 ++++++++-------- schemars/tests/expected/validate_tuple.json | 8 +- 60 files changed, 757 insertions(+), 773 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 757013a..c415a5f 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 47dc643..ec9ff00 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 c06f85a..678ee41 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 82be821..27cb048 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 06458c6..8618a7f 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 b9e333d..ce03621 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 b01fc0d..e88a449 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 36f16e1..88ad12a 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 ee3de61..e871160 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 d2af941..2b25c34 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 fc38db1..7d9321f 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 71c17b7..bb25dcd 100644 --- a/schemars/tests/expected/duration_and_systemtime.json +++ b/schemars/tests/expected/duration_and_systemtime.json @@ -18,15 +18,15 @@ "Duration": { "type": "object", "properties": { - "nanos": { - "type": "integer", - "format": "uint32", - "minimum": 0 - }, "secs": { "type": "integer", "format": "uint64", "minimum": 0 + }, + "nanos": { + "type": "integer", + "format": "uint32", + "minimum": 0 } }, "required": [ @@ -37,15 +37,15 @@ "SystemTime": { "type": "object", "properties": { - "nanos_since_epoch": { - "type": "integer", - "format": "uint32", - "minimum": 0 - }, "secs_since_epoch": { "type": "integer", "format": "uint64", "minimum": 0 + }, + "nanos_since_epoch": { + "type": "integer", + "format": "uint32", + "minimum": 0 } }, "required": [ diff --git a/schemars/tests/expected/enum-adjacent-tagged-duf.json b/schemars/tests/expected/enum-adjacent-tagged-duf.json index f746216..b9cb5fa 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 b977be0..de77713 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 0351fa2..a483535 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 4c1d5a6..8a1de2b 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 9c2aa9c..de51ba1 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 7fd931c..a5dffe3 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 4fdf04e..7fc20c1 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 e3f7c66..13a7d0a 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 2b23ffb..ed72b43 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 b4dc26a..0f9baa2 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 3203c4a..9f0a505 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 - ] } }, - "examples": [ - { - "bar": false, - "baz": null, - "foo": 0 - }, - null - ], "required": [ "foo", "bar" + ], + "examples": [ + { + "foo": 0, + "bar": false, + "baz": null + }, + null ] } \ No newline at end of file diff --git a/schemars/tests/expected/flatten.json b/schemars/tests/expected/flatten.json index 8f77dd2..4ea7094 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 cd1c634..6cc1ecc 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 4c6adcf..52c0524 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 5d87f02..de89fad 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 0405935..4e9dd2c 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 9c209e6..318bf9b 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 51a9bb7..4fedbb2 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 e958c8e..74a279c 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 6ee5105..97dee99 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 927f3f9..b422026 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 19e4780..25f7389 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 9477e0f..0f27ad3 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": { - "allOf": [ - { - "$ref": "#/definitions/Duration" - } - ], - "default": "0.000000000s" - }, - "user_cpu_time": { - "allOf": [ - { - "$ref": "#/definitions/Duration" - } - ], - "default": { - "nanos": 0, - "secs": 0 - } - }, "wall_time": { "$ref": "#/definitions/Duration" + }, + "user_cpu_time": { + "default": { + "secs": 0, + "nanos": 0 + }, + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "system_cpu_time": { + "default": "0.000000000s", + "allOf": [ + { + "$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 b533320..f6ba3ef 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" }, - "fake_map": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - } + "unit_or_t2": { + "$ref": "#/definitions/Or_for_null_and_int32" }, "s": { "$ref": "#/definitions/Str" }, - "unit_or_t2": { - "$ref": "#/definitions/Or_for_null_and_int32" + "fake_map": { + "type": "object", + "additionalProperties": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string" + } + } } }, "required": [ @@ -30,17 +30,6 @@ "fake_map" ], "definitions": { - "Or_for_null_and_int32": { - "anyOf": [ - { - "type": "null" - }, - { - "type": "integer", - "format": "int32" - } - ] - }, "Or_for_uint8_and_boolean": { "anyOf": [ { @@ -53,6 +42,17 @@ } ] }, + "Or_for_null_and_int32": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "integer", + "format": "int32" + } + ] + }, "Str": { "type": "string" } diff --git a/schemars/tests/expected/result.json b/schemars/tests/expected/result.json index 44c079e..8a25a0e 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 dc5336b..447edbd 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 7524c63..bd2ab9a 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 fdadbab..4342410 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 947aa2e..22f962f 100644 --- a/schemars/tests/expected/schema_settings-2019_09.json +++ b/schemars/tests/expected/schema_settings-2019_09.json @@ -3,6 +3,19 @@ "title": "Outer", "type": "object", "properties": { + "int": { + "type": "integer", + "format": "int32", + "examples": [ + 8, + null + ] + }, + "values": { + "type": "object", + "additionalProperties": true + }, + "value": true, "inner": { "anyOf": [ { @@ -12,19 +25,6 @@ "type": "null" } ] - }, - "int": { - "type": "integer", - "format": "int32", - "examples": [ - 8, - null - ] - }, - "value": true, - "values": { - "type": "object", - "additionalProperties": true } }, "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 b6de302..c4e5199 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 6a66ca0..4a50262 100644 --- a/schemars/tests/expected/schema_settings.json +++ b/schemars/tests/expected/schema_settings.json @@ -3,6 +3,19 @@ "title": "Outer", "type": "object", "properties": { + "int": { + "type": "integer", + "format": "int32", + "examples": [ + 8, + null + ] + }, + "values": { + "type": "object", + "additionalProperties": true + }, + "value": true, "inner": { "anyOf": [ { @@ -12,19 +25,6 @@ "type": "null" } ] - }, - "int": { - "type": "integer", - "format": "int32", - "examples": [ - 8, - null - ] - }, - "value": true, - "values": { - "type": "object", - "additionalProperties": true } }, "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 5b95671..ab848e3 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 78b6475..5f28d2f 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 b912feb..834eb41 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 7cc55bf..31caebe 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 1a8f8ca..4e2c095 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 b955e77..d245734 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 7890298..48549ce 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 9e5745b..3cdce84 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 5ff105c..529e2c4 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 0a5a0af..3e73393 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 ced169e..c89e416 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 e83f905..7fc1c32 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 2d6e43c..04131ce 100644 --- a/schemars/tests/expected/validate.json +++ b/schemars/tests/expected/validate.json @@ -3,61 +3,17 @@ "title": "Struct", "type": "object", "properties": { - "contains_str1": { - "type": "string", - "pattern": "substring\\.\\.\\." - }, - "contains_str2": { - "type": "string", - "pattern": "substring\\.\\.\\." - }, - "email_address": { - "type": "string", - "format": "email" - }, - "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 + "minimum": 0.01, + "maximum": 100 }, "min_max2": { "type": "number", "format": "float", - "maximum": 1000, - "minimum": 1 - }, - "non_empty_str": { - "type": "string", - "maxLength": 100, - "minLength": 1 - }, - "non_empty_str2": { - "type": "string", - "maxLength": 1000, - "minLength": 1 - }, - "pair": { - "type": "array", - "items": { - "type": "integer", - "format": "int32" - }, - "maxItems": 2, - "minItems": 2 + "minimum": 1, + "maximum": 1000 }, "regex_str1": { "type": "string", @@ -71,13 +27,57 @@ "type": "string", "pattern": "^\\d+$" }, - "required_option": { - "type": "boolean" + "contains_str1": { + "type": "string", + "pattern": "substring\\.\\.\\." + }, + "contains_str2": { + "type": "string", + "pattern": "substring\\.\\.\\." + }, + "email_address": { + "type": "string", + "format": "email" }, "tel": { "type": "string", "format": "phone" }, + "homepage": { + "type": "string", + "format": "uri" + }, + "non_empty_str": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "non_empty_str2": { + "type": "string", + "minLength": 1, + "maxLength": 1000 + }, + "pair": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + }, + "minItems": 2, + "maxItems": 2 + }, + "map_contains": { + "type": "object", + "additionalProperties": { + "type": "null" + }, + "required": [ + "map_key" + ] + }, + "required_option": { + "type": "boolean" + }, "x": { "type": "integer", "format": "int32" diff --git a/schemars/tests/expected/validate_inner.json b/schemars/tests/expected/validate_inner.json index f8a7eca..ba4ee9e 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,33 +20,6 @@ "pattern": "substring\\.\\.\\." } }, - "vec_i32_range": { - "type": "array", - "items": { - "type": "integer", - "format": "int32", - "maximum": 10, - "minimum": -10 - } - }, - "vec_str_length": { - "type": "array", - "items": { - "type": "string", - "maxLength": 100, - "minLength": 1 - } - }, - "vec_str_length2": { - "type": "array", - "items": { - "type": "string", - "maxLength": 100, - "minLength": 1 - }, - "maxItems": 3, - "minItems": 1 - }, "vec_str_regex": { "type": "array", "items": { @@ -54,12 +27,39 @@ "pattern": "^[Hh]ello\\b" } }, + "vec_str_length": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 100 + } + }, + "vec_str_length2": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "minItems": 1, + "maxItems": 3 + }, "vec_str_url": { "type": "array", "items": { "type": "string", "format": "uri" } + }, + "vec_i32_range": { + "type": "array", + "items": { + "type": "integer", + "format": "int32", + "minimum": -10, + "maximum": 10 + } } }, "required": [ diff --git a/schemars/tests/expected/validate_newtype.json b/schemars/tests/expected/validate_newtype.json index 4c1146e..89bec96 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 fa2dbcd..4fdba53 100644 --- a/schemars/tests/expected/validate_schemars_attrs.json +++ b/schemars/tests/expected/validate_schemars_attrs.json @@ -3,61 +3,17 @@ "title": "Struct2", "type": "object", "properties": { - "contains_str1": { - "type": "string", - "pattern": "substring\\.\\.\\." - }, - "contains_str2": { - "type": "string", - "pattern": "substring\\.\\.\\." - }, - "email_address": { - "type": "string", - "format": "email" - }, - "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 + "minimum": 0.01, + "maximum": 100 }, "min_max2": { "type": "number", "format": "float", - "maximum": 1000, - "minimum": 1 - }, - "non_empty_str": { - "type": "string", - "maxLength": 100, - "minLength": 1 - }, - "non_empty_str2": { - "type": "string", - "maxLength": 1000, - "minLength": 1 - }, - "pair": { - "type": "array", - "items": { - "type": "integer", - "format": "int32" - }, - "maxItems": 2, - "minItems": 2 + "minimum": 1, + "maximum": 1000 }, "regex_str1": { "type": "string", @@ -71,13 +27,57 @@ "type": "string", "pattern": "^\\d+$" }, - "required_option": { - "type": "boolean" + "contains_str1": { + "type": "string", + "pattern": "substring\\.\\.\\." + }, + "contains_str2": { + "type": "string", + "pattern": "substring\\.\\.\\." + }, + "email_address": { + "type": "string", + "format": "email" }, "tel": { "type": "string", "format": "phone" }, + "homepage": { + "type": "string", + "format": "uri" + }, + "non_empty_str": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "non_empty_str2": { + "type": "string", + "minLength": 1, + "maxLength": 1000 + }, + "pair": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + }, + "minItems": 2, + "maxItems": 2 + }, + "map_contains": { + "type": "object", + "additionalProperties": { + "type": "null" + }, + "required": [ + "map_key" + ] + }, + "required_option": { + "type": "boolean" + }, "x": { "type": "integer", "format": "int32" diff --git a/schemars/tests/expected/validate_tuple.json b/schemars/tests/expected/validate_tuple.json index 619b8c4..2a0e266 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 129d92e..4be6c27 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 678ee41..1c6fd88 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 5fd4273..a9ca787 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 a3dcbe0..6b6caeb 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 22f962f..568ce85 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 c4e5199..6ff2604 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 1c6fd88..6446a2c 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 ab9a36d..204169e 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 8be7b34..75ae6ca 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 a9ca787..30bdf0d 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 3de09a0..998d087 100644 --- a/schemars/tests/expected/arrayvec.json +++ b/schemars/tests/expected/arrayvec.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Array_up_to_size_16_of_int32", "type": "array", "items": { diff --git a/schemars/tests/expected/arrayvec_string.json b/schemars/tests/expected/arrayvec_string.json index ad174d8..0604332 100644 --- a/schemars/tests/expected/arrayvec_string.json +++ b/schemars/tests/expected/arrayvec_string.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$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 855db6f..e94ca27 100644 --- a/schemars/tests/expected/bigdecimal04.json +++ b/schemars/tests/expected/bigdecimal04.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Decimal", "type": "string", "pattern": "^-?[0-9]+(\\.[0-9]+)?$" diff --git a/schemars/tests/expected/bound.json b/schemars/tests/expected/bound.json index e4ceb13..a5645c6 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 8618a7f..80486c1 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 ce03621..de26130 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 e88a449..c2f1430 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 88ad12a..65b6a66 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 e871160..a61c322 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 2b25c34..b7396ab 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 7d9321f..b2e8f94 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 0f90433..b184d2a 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 103a71a..655121a 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 bb25dcd..7e301e5 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 807c9c9..18d8f51 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 b9cb5fa..dfb3bb8 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 de77713..c631ae5 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 a483535..76be5b3 100644 --- a/schemars/tests/expected/enum-external-duf.json +++ b/schemars/tests/expected/enum-external-duf.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "External", "oneOf": [ { @@ -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 8a1de2b..3c660fb 100644 --- a/schemars/tests/expected/enum-external.json +++ b/schemars/tests/expected/enum-external.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "External", "oneOf": [ { @@ -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 de51ba1..73e4743 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 a5dffe3..2fd9770 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 7070de8..89d941b 100644 --- a/schemars/tests/expected/enum-repr-with-attrs.json +++ b/schemars/tests/expected/enum-repr-with-attrs.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Renamed", "description": "Description from comment", "type": "integer", diff --git a/schemars/tests/expected/enum-repr.json b/schemars/tests/expected/enum-repr.json index 92d6f3a..b3e6bb9 100644 --- a/schemars/tests/expected/enum-repr.json +++ b/schemars/tests/expected/enum-repr.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Enum", "type": "integer", "enum": [ diff --git a/schemars/tests/expected/enum-simple-internal-duf.json b/schemars/tests/expected/enum-simple-internal-duf.json index 7fc20c1..aaf1eef 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 050089c..e955d2a 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 86160e8..1000435 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 13a7d0a..58bdbe1 100644 --- a/schemars/tests/expected/enum-untagged-duf.json +++ b/schemars/tests/expected/enum-untagged-duf.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Untagged", "anyOf": [ { @@ -12,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 ed72b43..643cd20 100644 --- a/schemars/tests/expected/enum-untagged.json +++ b/schemars/tests/expected/enum-untagged.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Untagged", "anyOf": [ { @@ -12,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 0f9baa2..72a39a1 100644 --- a/schemars/tests/expected/enumset.json +++ b/schemars/tests/expected/enumset.json @@ -1,12 +1,12 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Set_of_Foo", "type": "array", "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 9f0a505..b891aa0 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 4ea7094..7dfd54e 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 6cc1ecc..b217fad 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 318bf9b..8ba90a8 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 0c1eebe..21f7f31 100644 --- a/schemars/tests/expected/inline-subschemas-recursive.json +++ b/schemars/tests/expected/inline-subschemas-recursive.json @@ -1,12 +1,12 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "RecursiveOuter", "type": "object", "properties": { "direct": { "anyOf": [ { - "$ref": "#/definitions/RecursiveOuter" + "$ref": "#/$defs/RecursiveOuter" }, { "type": "null" @@ -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 7ba76d5..cbee457 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 4fedbb2..b27df44 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 74a279c..1d0fd08 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 efe2853..14de7f4 100644 --- a/schemars/tests/expected/no-variants.json +++ b/schemars/tests/expected/no-variants.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "NoVariants", "type": "string", "enum": [] diff --git a/schemars/tests/expected/nonzero_ints.json b/schemars/tests/expected/nonzero_ints.json index 97dee99..432e3b0 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 b422026..72e0be1 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 1aa6e78..82ea9ed 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 25f7389..64db4bd 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 0f27ad3..760630d 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 f6ba3ef..bef4d6a 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 8a25a0e..b468835 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 855db6f..e94ca27 100644 --- a/schemars/tests/expected/rust_decimal.json +++ b/schemars/tests/expected/rust_decimal.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Decimal", "type": "string", "pattern": "^-?[0-9]+(\\.[0-9]+)?$" diff --git a/schemars/tests/expected/same_name.json b/schemars/tests/expected/same_name.json index ebea3fe..fb65854 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 6bb3ab1..3c2edcc 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 447edbd..70b4d98 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 bd2ab9a..2243ddd 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 4342410..85c6792 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 568ce85..6b6dc61 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 0000000..7964058 --- /dev/null +++ b/schemars/tests/expected/schema_settings-2020_12.json @@ -0,0 +1,83 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Outer", + "type": "object", + "properties": { + "int": { + "type": "integer", + "format": "int32", + "examples": [ + 8, + null + ] + }, + "values": { + "type": "object", + "additionalProperties": true + }, + "value": true, + "inner": { + "anyOf": [ + { + "$ref": "#/$defs/Inner" + }, + { + "type": "null" + } + ] + }, + "tuples": { + "type": "array", + "items": { + "type": "array", + "prefixItems": [ + { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + { + "type": "integer", + "format": "int64" + } + ], + "minItems": 2, + "maxItems": 2 + } + } + }, + "required": [ + "int", + "values", + "value", + "tuples" + ], + "$defs": { + "Inner": { + "oneOf": [ + { + "type": "string", + "enum": [ + "UndocumentedUnit1", + "UndocumentedUnit2" + ] + }, + { + "description": "This is a documented unit variant", + "type": "string", + "const": "DocumentedUnit" + }, + { + "type": "object", + "properties": { + "ValueNewType": true + }, + "required": [ + "ValueNewType" + ], + "additionalProperties": false + } + ] + } + } +} \ No newline at end of file diff --git a/schemars/tests/expected/schema_settings-openapi3.json b/schemars/tests/expected/schema_settings-openapi3.json index 6ff2604..e5032f0 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 4a50262..a836cd6 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 ab848e3..3e7173d 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 5f28d2f..5f5fe83 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 90871ad..e6d707b 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 834eb41..9057c87 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 8b709fb..eb4a2f6 100644 --- a/schemars/tests/expected/schema_with-newtype.json +++ b/schemars/tests/expected/schema_with-newtype.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Newtype", "type": "boolean" } \ No newline at end of file diff --git a/schemars/tests/expected/schema_with-struct.json b/schemars/tests/expected/schema_with-struct.json index 31caebe..0674f1e 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 9f6afb3..2aeff68 100644 --- a/schemars/tests/expected/schema_with-transparent-newtype.json +++ b/schemars/tests/expected/schema_with-transparent-newtype.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "schema_fn", "type": "boolean" } \ No newline at end of file diff --git a/schemars/tests/expected/schema_with-tuple.json b/schemars/tests/expected/schema_with-tuple.json index 4e2c095..1296e33 100644 --- a/schemars/tests/expected/schema_with-tuple.json +++ b/schemars/tests/expected/schema_with-tuple.json @@ -1,8 +1,8 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Tuple", "type": "array", - "items": [ + "prefixItems": [ { "type": "boolean" }, diff --git a/schemars/tests/expected/semver.json b/schemars/tests/expected/semver.json index e8c59b9..9f5b155 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 d245734..bd3a893 100644 --- a/schemars/tests/expected/skip_enum_variants.json +++ b/schemars/tests/expected/skip_enum_variants.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "MyEnum", "oneOf": [ { diff --git a/schemars/tests/expected/skip_struct_fields.json b/schemars/tests/expected/skip_struct_fields.json index 48549ce..2a74c32 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 3cdce84..c3a4255 100644 --- a/schemars/tests/expected/skip_tuple_fields.json +++ b/schemars/tests/expected/skip_tuple_fields.json @@ -1,8 +1,8 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "TupleStruct", "type": "array", - "items": [ + "prefixItems": [ { "type": "number", "format": "float" diff --git a/schemars/tests/expected/smallvec.json b/schemars/tests/expected/smallvec.json index 7ce011d..be50ed7 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 ad174d8..0604332 100644 --- a/schemars/tests/expected/smol_str.json +++ b/schemars/tests/expected/smol_str.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "string", "type": "string" } \ No newline at end of file diff --git a/schemars/tests/expected/struct-newtype.json b/schemars/tests/expected/struct-newtype.json index 284e47d..e7b1cba 100644 --- a/schemars/tests/expected/struct-newtype.json +++ b/schemars/tests/expected/struct-newtype.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Newtype", "type": "integer", "format": "int32" diff --git a/schemars/tests/expected/struct-normal-additional-properties.json b/schemars/tests/expected/struct-normal-additional-properties.json index 529e2c4..2c55fb7 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 3e73393..c49757c 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 c89e416..8960068 100644 --- a/schemars/tests/expected/struct-tuple.json +++ b/schemars/tests/expected/struct-tuple.json @@ -1,8 +1,8 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Tuple", "type": "array", - "items": [ + "prefixItems": [ { "type": "integer", "format": "int32" diff --git a/schemars/tests/expected/struct-unit.json b/schemars/tests/expected/struct-unit.json index 3252d20..46f6fcf 100644 --- a/schemars/tests/expected/struct-unit.json +++ b/schemars/tests/expected/struct-unit.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Unit", "type": "null" } \ No newline at end of file diff --git a/schemars/tests/expected/transparent-struct.json b/schemars/tests/expected/transparent-struct.json index 7fc1c32..2692069 100644 --- a/schemars/tests/expected/transparent-struct.json +++ b/schemars/tests/expected/transparent-struct.json @@ -1,12 +1,12 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "OuterStruct", "type": "object", "properties": { "inner": { "anyOf": [ { - "$ref": "#/definitions/InnerStruct" + "$ref": "#/$defs/InnerStruct" }, { "type": "null" @@ -14,10 +14,10 @@ ] } }, - "definitions": { + "$defs": { "InnerStruct": { "type": "array", - "items": [ + "prefixItems": [ { "type": "string" }, diff --git a/schemars/tests/expected/url.json b/schemars/tests/expected/url.json index 5722f9e..425bf85 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 8ba1a01..98ae408 100644 --- a/schemars/tests/expected/uuid.json +++ b/schemars/tests/expected/uuid.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Uuid", "type": "string", "format": "uuid" diff --git a/schemars/tests/expected/validate.json b/schemars/tests/expected/validate.json index 04131ce..57c7f52 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 ba4ee9e..a77e0d7 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 89bec96..cd835f5 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 4fdba53..5e7d31d 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 2a0e266..fa81224 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 a82570d..3fe489a 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 6357172..87e789e 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 30bdf0d..3789cf5 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 | 131 +++++++++++++++++++++++++----------------- schemars/src/visit.rs | 2 +- 2 files changed, 80 insertions(+), 53 deletions(-) diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index 6446a2c..038228c 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; - } - - 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 { + 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 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 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 3789cf5..850c1eb 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 27cb048..6eb55e8 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 c8dfb6e..b3cea57 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 038228c..0bb1053 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 bacc079..1da1abb 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 3d4d050..920f7c3 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 5fd23e8..e8afa2a 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 16104f8..0ca4e10 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 462e3d5..0ca9840 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 96ed8a8..334e973 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 4ad85c2..026d5ae 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 b934f8b..9a630a7 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 ebcb61b..e45e84c 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 d1b5142..634acc1 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 e3c6d1d..743af65 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 63121fe..b65a93a 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 fb47f78..dbcdf21 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 d14d4d3..dcc680f 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 5c6b58d..fe76748 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 7317cfd..0f1eb74 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 204169e..2af816a 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 2fcfd04..04fa1e8 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 825f7a2..297914e 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 32bb8c0..b45fb21 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 6eb55e8..91a3547 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 c890f75..a4c0742 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 87e789e..9fbc9ee 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 b3cea57..10375e4 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 0bb1053..3c9ac1a 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 1da1abb..7dccbfb 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 920f7c3..cd5f154 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 e8afa2a..2635e56 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 0ca4e10..d49f392 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 0ca9840..3266ccc 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 334e973..fcc3479 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 9a630a7..19ae5cf 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 e45e84c..ca9ff28 100644 --- a/schemars/src/json_schema_impls/mod.rs +++ b/schemars/src/json_schema_impls/mod.rs @@ -1,7 +1,7 @@ -macro_rules! no_ref_schema { +macro_rules! always_inline { () => { - fn is_referenceable() -> bool { - false + fn always_inline_schema() -> bool { + true } }; } @@ -9,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 634acc1..c9d0919 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 743af65..afabc05 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 b65a93a..a3bcce6 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 dbcdf21..eb121fb 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 dcc680f..c131339 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 fe76748..cd25326 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 2af816a..c05a5f8 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 04fa1e8..5a467d9 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 297914e..a56c10c 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 b45fb21..32fb182 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 a4c0742..c4cfaa8 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 9fbc9ee..f3f527d 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 1475c25..1aa0942 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 501b79c..417d3be 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 04841b7..22d2233 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 237a90e..64bad01 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 bcfe137..5eab909 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 b6315cb..df69751 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 9637e1b..ba96767 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 9bb2f82..8179899 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 308f631..e9f8a1d 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 1475c25..1aa0942 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 501b79c..417d3be 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 04841b7..22d2233 100644 --- a/schemars/examples/enum_repr.schema.json +++ b/schemars/examples/enum_repr.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "SmallPrime", "type": "integer", "enum": [ diff --git a/schemars/examples/from_value.schema.json b/schemars/examples/from_value.schema.json index 237a90e..64bad01 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 bcfe137..5eab909 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 b6315cb..df69751 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 9637e1b..ba96767 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 9bb2f82..8179899 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 308f631..e9f8a1d 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 2ff1837..b24b90d 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 c119ea5..c32203f 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 c119ea5..c32203f 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 4be6c27..cdaec3a 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 3c9ac1a..cf7f007 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 32fb182..dbb307a 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 baa144d..e327284 100644 --- a/schemars/src/macros.rs +++ b/schemars/src/macros.rs @@ -17,11 +17,12 @@ #[macro_export] macro_rules! schema_for { ($type:ty) => { - $crate::gen::SchemaGenerator::default().into_root_schema_for::<$type>() + $crate::SchemaGenerator::default().into_root_schema_for::<$type>() }; } -/// Generates a [`RootSchema`](crate::schema::RootSchema) for the given type using default settings. +/// Generates a [`Schema`](crate::Schema) for the given type using default settings. +/// The default settings currently conform to [JSON Schema 2020-12](https://json-schema.org/specification-links#2020-12), but this is liable to change in a future version of Schemars if support for other JSON Schema versions is added. /// /// The type must implement [`JsonSchema`](crate::JsonSchema). /// @@ -40,22 +41,23 @@ macro_rules! schema_for { #[macro_export] macro_rules! schema_for { ($type:ty) => { - $crate::gen::SchemaGenerator::default().into_root_schema_for::<$type>() + $crate::SchemaGenerator::default().into_root_schema_for::<$type>() }; ($_:expr) => { compile_error!("This argument to `schema_for!` is not a type - did you mean to use `schema_for_value!` instead?") }; } -/// Generates a [`RootSchema`](crate::schema::RootSchema) for the given example value using default settings. +/// Generates a [`Schema`](crate::Schema) for the given example value using default settings. +/// The default settings currently conform to [JSON Schema 2020-12](https://json-schema.org/specification-links#2020-12), but this is liable to change in a future version of Schemars if support for other JSON Schema versions is added. /// /// The value must implement [`Serialize`](serde::Serialize). If the value also implements [`JsonSchema`](crate::JsonSchema), -/// then prefer using the [`schema_for!`](schema_for) macro which will generally produce a more precise schema, +/// then prefer using the [`schema_for!(Type)`](schema_for) macro which will generally produce a more precise schema, /// particularly when the value contains any enums. /// /// If the `Serialize` implementation of the value decides to fail, this macro will panic. -/// For a non-panicking alternative, create a [`SchemaGenerator`](crate::gen::SchemaGenerator) and use -/// its [`into_root_schema_for_value`](crate::gen::SchemaGenerator::into_root_schema_for_value) method. +/// For a non-panicking alternative, create a [`SchemaGenerator`](crate::SchemaGenerator) and use +/// its [`into_root_schema_for_value`](crate::SchemaGenerator::into_root_schema_for_value) method. /// /// # Example /// ``` @@ -71,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 91a3547..dd9e866 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 75ae6ca..f42c187 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 5a6c575..cf01b37 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 70ba24d..5fc8c37 100644 --- a/schemars/tests/schema_with_struct.rs +++ b/schemars/tests/schema_with_struct.rs @@ -2,7 +2,7 @@ mod util; use schemars::JsonSchema; use util::*; -fn schema_fn(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Schema { +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 c4cfaa8..bd427bd 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 f3f527d..48da5ef 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 4653cb9..729d10c 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 2ddbc07..238f42d 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 94ef0fd..6eff88f 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 b24b90d..756c8a7 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 238f42d..320ddf9 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 d70fb57..72e580d 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 0000000..6241e07 --- /dev/null +++ b/schemars/tests/expected/extend_enum_adjacent.json @@ -0,0 +1,101 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Adjacent", + "oneOf": [ + { + "type": "object", + "properties": { + "t": { + "type": "string", + "enum": [ + "Unit" + ] + } + }, + "required": [ + "t" + ], + "foo": "bar" + }, + { + "type": "object", + "properties": { + "t": { + "type": "string", + "enum": [ + "NewType" + ] + }, + "c": true + }, + "required": [ + "t", + "c" + ], + "foo": "bar" + }, + { + "type": "object", + "properties": { + "t": { + "type": "string", + "enum": [ + "Tuple" + ] + }, + "c": { + "type": "array", + "prefixItems": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "boolean" + } + ], + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "t", + "c" + ], + "foo": "bar" + }, + { + "type": "object", + "properties": { + "t": { + "type": "string", + "enum": [ + "Struct" + ] + }, + "c": { + "type": "object", + "properties": { + "i": { + "type": "integer", + "format": "int32" + }, + "b": { + "type": "boolean" + } + }, + "required": [ + "i", + "b" + ] + } + }, + "required": [ + "t", + "c" + ], + "foo": "bar" + } + ], + "foo": "bar" +} \ No newline at end of file diff --git a/schemars/tests/expected/extend_enum_external.json b/schemars/tests/expected/extend_enum_external.json new file mode 100644 index 0000000..c15d47f --- /dev/null +++ b/schemars/tests/expected/extend_enum_external.json @@ -0,0 +1,73 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "External", + "oneOf": [ + { + "type": "string", + "const": "Unit", + "foo": "bar" + }, + { + "type": "object", + "properties": { + "NewType": true + }, + "required": [ + "NewType" + ], + "additionalProperties": false, + "foo": "bar" + }, + { + "type": "object", + "properties": { + "Tuple": { + "type": "array", + "prefixItems": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "boolean" + } + ], + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "Tuple" + ], + "additionalProperties": false, + "foo": "bar" + }, + { + "type": "object", + "properties": { + "Struct": { + "type": "object", + "properties": { + "i": { + "type": "integer", + "format": "int32" + }, + "b": { + "type": "boolean" + } + }, + "required": [ + "i", + "b" + ] + } + }, + "required": [ + "Struct" + ], + "additionalProperties": false, + "foo": "bar" + } + ], + "foo": "bar" +} \ No newline at end of file diff --git a/schemars/tests/expected/extend_enum_internal.json b/schemars/tests/expected/extend_enum_internal.json new file mode 100644 index 0000000..0dee817 --- /dev/null +++ b/schemars/tests/expected/extend_enum_internal.json @@ -0,0 +1,55 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Internal", + "oneOf": [ + { + "type": "object", + "properties": { + "typeProperty": { + "type": "string", + "const": "Unit" + } + }, + "required": [ + "typeProperty" + ], + "foo": "bar" + }, + { + "type": "object", + "properties": { + "typeProperty": { + "type": "string", + "const": "NewType" + } + }, + "required": [ + "typeProperty" + ], + "foo": "bar" + }, + { + "type": "object", + "properties": { + "i": { + "type": "integer", + "format": "int32" + }, + "b": { + "type": "boolean" + }, + "typeProperty": { + "type": "string", + "const": "Struct" + } + }, + "required": [ + "typeProperty", + "i", + "b" + ], + "foo": "bar" + } + ], + "foo": "bar" +} \ No newline at end of file diff --git a/schemars/tests/expected/extend_enum_untagged.json b/schemars/tests/expected/extend_enum_untagged.json new file mode 100644 index 0000000..4f733fe --- /dev/null +++ b/schemars/tests/expected/extend_enum_untagged.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Untagged", + "anyOf": [ + { + "type": "null", + "foo": "bar" + }, + { + "foo": "bar" + }, + { + "type": "array", + "prefixItems": [ + { + "type": "integer", + "format": "int32" + }, + { + "type": "boolean" + } + ], + "minItems": 2, + "maxItems": 2, + "foo": "bar" + }, + { + "type": "object", + "properties": { + "i": { + "type": "integer", + "format": "int32" + }, + "b": { + "type": "boolean" + } + }, + "required": [ + "i", + "b" + ], + "foo": "bar" + } + ], + "foo": "bar" +} \ No newline at end of file diff --git a/schemars/tests/expected/extend_struct.json b/schemars/tests/expected/extend_struct.json new file mode 100644 index 0000000..fc7dd50 --- /dev/null +++ b/schemars/tests/expected/extend_struct.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Struct", + "type": "object", + "properties": { + "value": { + "foo": "bar" + }, + "int": { + "type": "overridden", + "format": "int32" + } + }, + "required": [ + "value", + "int" + ], + "msg": "hello world", + "obj": { + "array": [ + null, + null + ] + }, + "3": 3.0, + "pi": 3.14 +} \ No newline at end of file diff --git a/schemars/tests/expected/from_value_2019_09.json b/schemars/tests/expected/from_value_2019_09.json index 52c0524..939410d 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 de89fad..721e871 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 4e9dd2c..88f08a7 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 6b6dc61..bc99f15 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 e5032f0..8365822 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 a836cd6..4c435ad 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 0000000..08f42fa --- /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 0000000..b7295f7 --- /dev/null +++ b/schemars/tests/ui/invalid_extend.rs @@ -0,0 +1,11 @@ +use schemars::JsonSchema; + +#[derive(JsonSchema)] +#[schemars(extend(x))] +#[schemars(extend("x"))] +#[schemars(extend("x" = ))] +#[schemars(extend("y" = "ok!", "y" = "duplicated!"), extend("y" = "duplicated!"))] +#[schemars(extend("y" = "duplicated!"))] +pub struct Struct; + +fn main() {} diff --git a/schemars/tests/ui/invalid_extend.stderr b/schemars/tests/ui/invalid_extend.stderr new file mode 100644 index 0000000..d7d2179 --- /dev/null +++ b/schemars/tests/ui/invalid_extend.stderr @@ -0,0 +1,35 @@ +error: expected string literal + --> tests/ui/invalid_extend.rs:4:19 + | +4 | #[schemars(extend(x))] + | ^ + +error: expected `=` + --> tests/ui/invalid_extend.rs:5:22 + | +5 | #[schemars(extend("x"))] + | ^ + +error: Expected extension value + --> tests/ui/invalid_extend.rs:6:25 + | +6 | #[schemars(extend("x" = ))] + | ^ + +error: Duplicate extension key 'y' + --> tests/ui/invalid_extend.rs:7:32 + | +7 | #[schemars(extend("y" = "ok!", "y" = "duplicated!"), extend("y" = "duplicated!"))] + | ^^^ + +error: Duplicate extension key 'y' + --> tests/ui/invalid_extend.rs:7:61 + | +7 | #[schemars(extend("y" = "ok!", "y" = "duplicated!"), extend("y" = "duplicated!"))] + | ^^^ + +error: Duplicate extension key 'y' + --> tests/ui/invalid_extend.rs:8:19 + | +8 | #[schemars(extend("y" = "duplicated!"))] + | ^^^ diff --git a/schemars_derive/src/attr/mod.rs b/schemars_derive/src/attr/mod.rs index 86c9349..108cedc 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 6de6834..6a3808c 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 48da5ef..1f5df99 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 729d10c..2f87862 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 756c8a7..118af03 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 320ddf9..c1b0a94 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 6eff88f..8f3a775 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 6e37fc8..2c844ee 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 0000000..b668d7f --- /dev/null +++ b/docs/_v0/1.1-attributes.md @@ -0,0 +1,323 @@ +--- +title: Attributes +parent: Deriving JsonSchema +nav_order: 1 +permalink: /v0/deriving/attributes/ +--- + + + +# Attributes + +You can add attributes to your types to customize Schemars's derived `JsonSchema` implementation. + +[Serde](https://serde.rs/) allows setting `#[serde(...)]` attributes which change how types are serialized, and Schemars will generally respect these attributes to ensure that generated schemas will match how the type is serialized by serde_json. `#[serde(...)]` attributes can be overriden using `#[schemars(...)]` attributes, which behave identically (e.g. `#[schemars(rename_all = "camelCase")]`). You may find this useful if you want to change the generated schema without affecting Serde's behaviour, or if you're just not using Serde. + +[Validator](https://github.com/Keats/validator) allows setting `#[validate(...)]` attributes to restrict valid values of particular fields, many of which will be used by Schemars to generate more accurate schemas. These can also be overridden by `#[schemars(...)]` attributes. + +
+ +TABLE OF CONTENTS + + +1. [Supported Serde Attributes](#supported-serde-attributes) + - [`rename`](#rename) + - [`rename_all`](#rename_all) + - [`tag` / `content` / `untagged`](#tag) + - [`default`](#default) + - [`skip`](#skip) + - [`skip_serializing`](#skip_serializing) + - [`skip_deserializing`](#skip_deserializing) + - [`flatten`](#flatten) + - [`with`](#with) + - [`bound`](#bound) +1. [Supported Validator Attributes](#supported-validator-attributes) + - [`email` / `phone` / `url`](#email-phone-url) + - [`length`](#length) + - [`range`](#range) + - [`regex`](#regex) + - [`contains`](#contains) + - [`required` / `required_nested`](#required) +1. [Other Attributes](#other-attributes) + - [`schema_with`](#schema_with) + - [`title` / `description`](#title-description) + - [`example`](#example) + - [`deprecated`](#deprecated) + - [`crate`](#crate) + - [Doc Comments (`doc`)](#doc) + +
+ +## Supported Serde Attributes + +
+ +

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ +`#[deprecated]` + +

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

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

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

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

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

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

+ +If a struct, variant or field has any [doc comments](https://doc.rust-lang.org/stable/rust-by-example/meta/doc.html#doc-comments) (or [`doc` attributes](https://doc.rust-lang.org/rustdoc/the-doc-attribute.html)), then these will be used as the generated schema's `description`. If the first line is an ATX-style markdown heading (i.e. it begins with a # character), then it will be used as the schema's `title`, and the remaining lines will be the `description`. diff --git a/docs/_v0/2-implementing.md b/docs/_v0/2-implementing.md new file mode 100644 index 0000000..32f4edd --- /dev/null +++ b/docs/_v0/2-implementing.md @@ -0,0 +1,78 @@ +--- +title: Implementing JsonSchema +nav_order: 3 +permalink: /v0/implementing/ +--- + +# Implementing JsonSchema + +[Deriving `JsonSchema`]({{ site.baseurl }}{% link 1-deriving.md %}) is usually the easiest way to enable JSON schema generation for your types. But if you need more customisation, you can also implement `JsonSchema` manually. This trait has two associated functions which must be implemented, and one which can optionally be implemented: + +## schema_name + +```rust +fn schema_name() -> String; +``` + +This function returns the human-readable friendly name of the type's schema, which frequently is just the name of the type itself. The schema name is used as the title for root schemas, and the key within the root's `definitions` property for subschemas. + +NB in a future version of schemars, it's likely that this function will be changed to return a `Cow<'static, str>`. + +## schema_id + +```rust +fn schema_id() -> Cow<'static, str>; +``` + +This function returns a unique identifier of the type's schema - if two types return the same `schema_id`, then Schemars will consider them identical types. Because of this, if a type takes any generic type parameters, then its ID should depend on the type arguments. For example, the implementation of this function for `Vec where T: JsonSchema` is: + +```rust +fn schema_id() -> Cow<'static, str> { + Cow::Owned( + format!("[{}]", T::schema_id())) +} +``` + +`&mut Vec<&T>`, `LinkedList`, `Mutex>>`, and similar collection types also use that implementation, since they produce identical JSON schemas so they can be considered the same type. + +For a type with no generic type arguments, a reasonable implementation of this function would be to return the type name including module path (in case there is a type with the same name in another module/crate), e.g.: + +```rust +impl JsonSchema for NonGenericType { + fn schema_name() -> String { + // Exclude the module path to make the name in generated schemas clearer. + "NonGenericType".to_owned() + } + + fn schema_id() -> Cow<'static, str> { + // Include the module, in case a type with the same name is in another module/crate + Cow::Borrowed(concat!(module_path!(), "::NonGenericType")) + } + + fn json_schema(_gen: &mut SchemaGenerator) -> Schema { + todo!() + } +} +``` + +## json_schema + +```rust +fn json_schema(gen: &mut gen::SchemaGenerator) -> Schema; +``` + +This function creates the JSON schema itself. The `gen` argument can be used to check the schema generation settings, or to get schemas for other types. If you do need schemas for other types, you should call the `gen.subschema_for::()` method instead of `::json_schema(gen)`, as `subschema_for` can add `T`'s schema to the root schema's `definitions` so that it does not need to be duplicated when used more than once. + +`json_schema` should not return a `$ref` schema. + +## is_referenceable (optional) + +```rust +fn is_referenceable() -> bool; +``` + +If this function returns `true`, then Schemars can re-use the generate schema where possible by adding it to the root schema's `definitions` and having other schemas reference it using the `$ref` keyword. This can greatly simplify schemas that include a particular type multiple times, especially if that type's schema is fairly complex. + +Generally, this should return `false` for types with simple schemas (such as primitives). For more complex types, it should return `true`. For recursive types, this **must** return `true` to prevent infinite cycles when generating schemas. + +The default implementation of this function returns `true` to reduce the chance of someone inadvertently causing infinite cycles with recursive types. diff --git a/docs/_v0/3-generating.md b/docs/_v0/3-generating.md new file mode 100644 index 0000000..ec56349 --- /dev/null +++ b/docs/_v0/3-generating.md @@ -0,0 +1,35 @@ +--- +title: Generating Schemas +nav_order: 4 +permalink: /v0/generating/ +--- + +# Generating Schemas + +The easiest way to generate a schema for a type that implements is to use the [`schema_for!` macro](https://docs.rs/schemars/latest/schemars/macro.schema_for.html), like so: + +```rust +let my_schema = schema_for!(MyStruct); +``` + +This will create a schema that conforms to [JSON Schema Draft 7](https://json-schema.org/specification-links.html#draft-7), but this is liable to change in a future version of Schemars if support for other JSON Schema versions is added. + +If you want more control over how the schema is generated, you can use the [`gen` module](https://docs.rs/schemars/latest/schemars/gen/). There are two main types in this module: + +- [`SchemaSettings`](https://docs.rs/schemars/latest/schemars/gen/struct.SchemaSettings.html), which defines what JSON Schema features should be used when generating schemas (for example, how `Option`s should be represented). +- [`SchemaGenerator`](https://docs.rs/schemars/latest/schemars/gen/struct.SchemaGenerator.html), which manages the generation of a schema document. + +See the API documentation for more info on how to use those types for custom schema generation. + +## Schema from Example Value + +If you want a schema for a type that can't/doesn't implement `JsonSchema`, but does implement `serde::Serialize`, then you can generate a JSON schema from a value of that type using the [`schema_for_value!` macro](https://docs.rs/schemars/latest/schemars/macro.schema_for_value.html). However, this schema will generally be less precise than if the type implemented `JsonSchema` - particularly when it involves enums, since schemars will not make any assumptions about the structure of an enum based on a single variant. + +```rust +let value = MyStruct { foo = 123 }; +let my_schema = schema_for_value!(value); +``` + + diff --git a/docs/_v0/4-features.md b/docs/_v0/4-features.md new file mode 100644 index 0000000..4932fbe --- /dev/null +++ b/docs/_v0/4-features.md @@ -0,0 +1,39 @@ +--- +title: Feature Flags +nav_order: 5 +permalink: /v0/features/ +--- + +# Feature Flags and Optional Dependencies + +- `derive` (enabled by default) - provides `#[derive(JsonSchema)]` macro +- `impl_json_schema` - implements `JsonSchema` for Schemars types themselves +- `preserve_order` - keep the order of struct fields in `Schema` and `SchemaObject` +- `raw_value` - implements `JsonSchema` for `serde_json::value::RawValue` (enables the serde_json `raw_value` feature) + +Schemars can implement `JsonSchema` on types from several popular crates, enabled via feature flags (dependency versions are shown in brackets): + +- `chrono` - [chrono](https://crates.io/crates/chrono) (^0.4) +- `indexmap1` - [indexmap](https://crates.io/crates/indexmap) (^1.2) +- `indexmap2` - [indexmap](https://crates.io/crates/indexmap) (^2.0) +- `either` - [either](https://crates.io/crates/either) (^1.3) +- `uuid08` - [uuid](https://crates.io/crates/uuid) (^0.8) +- `uuid1` - [uuid](https://crates.io/crates/uuid) (^1.0) +- `smallvec` - [smallvec](https://crates.io/crates/smallvec) (^1.0) +- `arrayvec05` - [arrayvec](https://crates.io/crates/arrayvec) (^0.5) +- `arrayvec07` - [arrayvec](https://crates.io/crates/arrayvec) (^0.7) +- `url` - [url](https://crates.io/crates/url) (^2.0) +- `bytes` - [bytes](https://crates.io/crates/bytes) (^1.0) +- `enumset` - [enumset](https://crates.io/crates/enumset) (^1.0) +- `rust_decimal` - [rust_decimal](https://crates.io/crates/rust_decimal) (^1.0) +- `bigdecimal03` - [bigdecimal](https://crates.io/crates/bigdecimal) (^0.3) +- `bigdecimal04` - [bigdecimal](https://crates.io/crates/bigdecimal) (^0.4) +- `smol_str` - [smol_str](https://crates.io/crates/smol_str) (^0.1.17) +- `semver` - [semver](https://crates.io/crates/semver) (^1.0.9) + +For example, to implement `JsonSchema` on types from `chrono`, enable it as a feature in the `schemars` dependency in your `Cargo.toml` like so: + +```toml +[dependencies] +schemars = { version = "0.8", features = ["chrono"] } +``` diff --git a/docs/_v0/5-examples.md b/docs/_v0/5-examples.md new file mode 100644 index 0000000..0df7e3d --- /dev/null +++ b/docs/_v0/5-examples.md @@ -0,0 +1,8 @@ +--- +title: Examples +nav_order: 6 +has_children: true +permalink: /v0/examples/ +--- + +# Examples diff --git a/docs/_v0/examples/1-derive_jsonschema.md b/docs/_v0/examples/1-derive_jsonschema.md new file mode 100644 index 0000000..590b799 --- /dev/null +++ b/docs/_v0/examples/1-derive_jsonschema.md @@ -0,0 +1,12 @@ +--- +title: Deriving JsonSchema +parent: Examples +nav_order: 1 +summary: Deriving JsonSchema on a struct and enum. +--- + +# Deriving JsonSchema + +This is the simplest usage of Schemars. Both types are made to derive `JsonSchema`, and the `schema_for!` macro is used to generate the schema itself. + +{% include example_v0.md name="main" %} diff --git a/docs/_v0/examples/2-serde_attrs.md b/docs/_v0/examples/2-serde_attrs.md new file mode 100644 index 0000000..b665319 --- /dev/null +++ b/docs/_v0/examples/2-serde_attrs.md @@ -0,0 +1,14 @@ +--- +title: Using Serde Attributes +parent: Examples +nav_order: 2 +summary: "Deriving JsonSchema on types that use #[serde] attributes to customise serialization behaviour." +--- + +# Using Serde Attributes + +One of the main aims of this library is compatibility with [Serde](https://github.com/serde-rs/serde). Any generated schema _should_ match how [serde_json](https://github.com/serde-rs/json) would serialize/deserialize to/from JSON. To support this, Schemars will check for any `#[serde(...)]` attributes on types that derive `JsonSchema`, and adjust the generated schema accordingly. + +The list of supported `#[serde]` attributes are [documented here]({{ site.baseurl }}{% link 1.1-attributes.md %}#supported-serde-attributes). + +{% include example_v0.md name="serde_attrs" %} diff --git a/docs/_v0/examples/3-schemars_attrs.md b/docs/_v0/examples/3-schemars_attrs.md new file mode 100644 index 0000000..2a5de96 --- /dev/null +++ b/docs/_v0/examples/3-schemars_attrs.md @@ -0,0 +1,12 @@ +--- +title: Using Schemars Attributes +parent: Examples +nav_order: 3 +summary: "Deriving JsonSchema on types that use #[schemars] attributes to customise serialization behaviour." +--- + +# Using Serde Attributes + +`#[serde(...)]` attributes can be overriden (or replaced) with `#[schemars(...)]` attributes, which behave identically. You may find this useful if you want to change the generated schema without affecting Serde's behaviour, or if you're just not using Serde. + +{% include example_v0.md name="schemars_attrs" %} diff --git a/docs/_v0/examples/4-custom_settings.md b/docs/_v0/examples/4-custom_settings.md new file mode 100644 index 0000000..e439f50 --- /dev/null +++ b/docs/_v0/examples/4-custom_settings.md @@ -0,0 +1,12 @@ +--- +title: Custom Schema Settings +parent: Examples +nav_order: 4 +summary: Generating a schema using custom settings which changes how Option is handled. +--- + +# Custom Schema Settings + +The `gen` module allows you to customise how schemas are generated. For example, the default behaviour for `Option` is to include `null` in the schema's `type`s, but we can instead add a `nullable` property to its schema: + +{% include example_v0.md name="custom_settings" %} diff --git a/docs/_v0/examples/5-remote_derive.md b/docs/_v0/examples/5-remote_derive.md new file mode 100644 index 0000000..ee0adb6 --- /dev/null +++ b/docs/_v0/examples/5-remote_derive.md @@ -0,0 +1,16 @@ +--- +title: Derive for Remote Crate +parent: Examples +nav_order: 5 +summary: Deriving JsonSchema implementations for a type in somebody else's crate. +--- + +# Deriving JsonSchema for a Type in a Different Crate + +Rust's [orphan rule](https://doc.rust-lang.org/book/traits.html#rules-for-implementing-traits) requires that either the trait or the type for which you are implementing the trait must be defined in the same crate as the impl, so it is not possible to implement `JsonSchema` for a type in a different crate directly. + +To work around this, Schemars provides a way of deriving `JsonSchema` implementations for types in other people's crates. The only catch is that you have to provide a definition of the type for Schemars's derive to process. + +This is the same way that Serde allows remote deriving, which is why this page reads so similarly to [Serde's documentation](https://serde.rs/remote-derive.html)! + +{% include example_v0.md name="remote_derive" %} diff --git a/docs/_v0/examples/6-doc_comments.md b/docs/_v0/examples/6-doc_comments.md new file mode 100644 index 0000000..24de317 --- /dev/null +++ b/docs/_v0/examples/6-doc_comments.md @@ -0,0 +1,12 @@ +--- +title: Doc Comments +parent: Examples +nav_order: 6 +summary: Giving schemas a custom title and/or description using doc comments. +--- + +# Setting a Custom Title and/or Description Using Doc Comments + +If a struct, variant or field has any [doc comments](https://doc.rust-lang.org/stable/rust-by-example/meta/doc.html#doc-comments) (or [`doc` attributes](https://doc.rust-lang.org/rustdoc/the-doc-attribute.html)), then these will be used as the generated schema's `description`. If the first line is an ATX-style markdown heading (i.e. it begins with a # character), then it will be used as the schema's `title`, and the remaining lines will be the `description`. + +{% include example_v0.md name="doc_comments" %} diff --git a/docs/_v0/examples/7-custom_serialization.md b/docs/_v0/examples/7-custom_serialization.md new file mode 100644 index 0000000..499e670 --- /dev/null +++ b/docs/_v0/examples/7-custom_serialization.md @@ -0,0 +1,19 @@ +--- +title: Custom Serialization +parent: Examples +nav_order: 7 +summary: >- + If a field has a #[serde(with = "path")] attribute where "path" is not a type that implements JsonSchema, + then in order to derive JsonSchema on the type, it must also have a #[schemars(with = "Type")] attribute, + where "Type" implements JsonSchema. +--- + +# Deriving JsonSchema with Fields Using Custom Serialization + +Serde allows you to change how a field is (de)serialized by setting a [`#[serde(with = "path")]`](https://serde.rs/field-attrs.html#with) attribute, where `$path::serialize` and `$path::deserialize` must be functions with the correct signature. Schemars supports the same attribute, but `path` must be a type implementing `JsonSchema`. + +In order to derive `JsonSchema` on a type which includes a `#[serde(with = "path")]` attribute where `path` is not a type implementing `JsonSchema`, you'll need to override it with a suitable `#[schemars(with = "Type")]` or `#[schemars(schema_with = "path")]` attribute. + +{% include example_v0.md name="custom_serialization" %} + +Note that the `default` values in the schema are serialized as strings where appropriate. diff --git a/docs/_v0/examples/8-enum_repr.md b/docs/_v0/examples/8-enum_repr.md new file mode 100644 index 0000000..1cfcf81 --- /dev/null +++ b/docs/_v0/examples/8-enum_repr.md @@ -0,0 +1,13 @@ +--- +title: Serialize Enum as Number (serde_repr) +parent: Examples +nav_order: 8 +summary: >- + Generating a schema for with a C-like enum compatible with serde_repr. +--- + +# Serialize Enum as Number (serde_repr Compatibility) + +If you use the `#[repr(...)]` attribute on an enum to give it a C-like representation, then you may also want to use the [serde_repr](https://github.com/dtolnay/serde-repr) crate to serialize the enum values as numbers. In this case, you should use the corresponding `JsonSchema_repr` derive to ensure the schema for your type reflects how serde formats your type. + +{% include example_v0.md name="enum_repr" %} diff --git a/docs/_v0/examples/9-from_value.md b/docs/_v0/examples/9-from_value.md new file mode 100644 index 0000000..45198ff --- /dev/null +++ b/docs/_v0/examples/9-from_value.md @@ -0,0 +1,15 @@ +--- +title: Generate Schema from Example Value +parent: Examples +nav_order: 9 +summary: >- + Generating a schema for a serializable value. +--- + +# Generate Schema from Example Value + +If you want a schema for a type that can't/doesn't implement `JsonSchema`, but does implement [`serde::Serialize`](https://docs.serde.rs/serde/trait.Serialize.html), then you can generate a JSON schema from a value of that type. However, this schema will generally be less precise than if the type implemented `JsonSchema` - particularly when it involves enums, since schemars will not make any assumptions about the structure of an enum based on a single variant. + +{% include example_v0.md name="from_value" %} + +Note that the schema for the enum is not very useful in this case, since schemars doesn't know anything about the second variant. diff --git a/docs/_v0/index.md b/docs/_v0/index.md new file mode 100644 index 0000000..3b10e0d --- /dev/null +++ b/docs/_v0/index.md @@ -0,0 +1,20 @@ +--- +title: Overview +has_children: true +nav_order: 1 +permalink: /v0/ +--- + +# Schemars + +Schemars is a library to generate JSON Schema documents from Rust data structures. + +This is built on Rust's trait system - any type which implements the [`JsonSchema`](https://docs.rs/schemars/latest/schemars/trait.JsonSchema.html) trait can have a JSON Schema generated describing that type. Schemars implements this on many standard library types, and provides a derive macro to automatically implement it on custom types. + +One of the main aims of this library is compatibility with [Serde](https://github.com/serde-rs/serde). Any generated schema _should_ match how [serde_json](https://github.com/serde-rs/json) would serialize/deserialize to/from JSON. To support this, Schemars will check for any `#[serde(...)]` attributes on types that derive `JsonSchema`, and adjust the generated schema accordingly. + +## Basic Usage + +If you don't really care about the specifics, the easiest way to generate a JSON schema for your types is to `#[derive(JsonSchema)]` and use the `schema_for!` macro. All fields of the type must also implement `JsonSchema` - Schemars implements this for many standard library types. + +{% include example.md name="main" %} diff --git a/docs/docker-compose.yml b/docs/docker-compose.yml new file mode 100644 index 0000000..0afa5eb --- /dev/null +++ b/docs/docker-compose.yml @@ -0,0 +1,10 @@ +--- +services: + jekyll: + build: + context: . + volumes: + - ".:/docs" + working_dir: "/docs" + ports: + - 4000:4000 \ No newline at end of file diff --git a/docs/examples/1-derive_jsonschema.md b/docs/examples/1-derive_jsonschema.md index 3bf4548..efe245c 100644 --- a/docs/examples/1-derive_jsonschema.md +++ b/docs/examples/1-derive_jsonschema.md @@ -1,5 +1,4 @@ --- -layout: default title: Deriving JsonSchema parent: Examples nav_order: 1 diff --git a/docs/examples/2-serde_attrs.md b/docs/examples/2-serde_attrs.md index 0044a6e..79536e2 100644 --- a/docs/examples/2-serde_attrs.md +++ b/docs/examples/2-serde_attrs.md @@ -1,14 +1,13 @@ --- -layout: default title: Using Serde Attributes parent: Examples nav_order: 2 -summary: 'Deriving JsonSchema on types that use #[serde] attributes to customise serialization behaviour.' +summary: "Deriving JsonSchema on types that use #[serde] attributes to customise serialization behaviour." --- # Using Serde Attributes -One of the main aims of this library is compatibility with [Serde](https://github.com/serde-rs/serde). Any generated schema *should* match how [serde_json](https://github.com/serde-rs/json) would serialize/deserialize to/from JSON. To support this, Schemars will check for any `#[serde(...)]` attributes on types that derive `JsonSchema`, and adjust the generated schema accordingly. +One of the main aims of this library is compatibility with [Serde](https://github.com/serde-rs/serde). Any generated schema _should_ match how [serde_json](https://github.com/serde-rs/json) would serialize/deserialize to/from JSON. To support this, Schemars will check for any `#[serde(...)]` attributes on types that derive `JsonSchema`, and adjust the generated schema accordingly. The list of supported `#[serde]` attributes are [documented here]({{ site.baseurl }}{% link 1.1-attributes.md %}#supported-serde-attributes). diff --git a/docs/examples/3-schemars_attrs.md b/docs/examples/3-schemars_attrs.md index 5956a25..d3a2c08 100644 --- a/docs/examples/3-schemars_attrs.md +++ b/docs/examples/3-schemars_attrs.md @@ -1,9 +1,8 @@ --- -layout: default title: Using Schemars Attributes parent: Examples nav_order: 3 -summary: 'Deriving JsonSchema on types that use #[schemars] attributes to customise serialization behaviour.' +summary: "Deriving JsonSchema on types that use #[schemars] attributes to customise serialization behaviour." --- # Using Serde Attributes diff --git a/docs/examples/4-custom_settings.md b/docs/examples/4-custom_settings.md index e9482cc..7b85f65 100644 --- a/docs/examples/4-custom_settings.md +++ b/docs/examples/4-custom_settings.md @@ -1,5 +1,4 @@ --- -layout: default title: Custom Schema Settings parent: Examples nav_order: 4 diff --git a/docs/examples/5-remote_derive.md b/docs/examples/5-remote_derive.md index 93e99c1..fd7f74f 100644 --- a/docs/examples/5-remote_derive.md +++ b/docs/examples/5-remote_derive.md @@ -1,5 +1,4 @@ --- -layout: default title: Derive for Remote Crate parent: Examples nav_order: 5 diff --git a/docs/examples/6-doc_comments.md b/docs/examples/6-doc_comments.md index 9a5cdf9..66f4a49 100644 --- a/docs/examples/6-doc_comments.md +++ b/docs/examples/6-doc_comments.md @@ -1,5 +1,4 @@ --- -layout: default title: Doc Comments parent: Examples nav_order: 6 diff --git a/docs/examples/7-custom_serialization.md b/docs/examples/7-custom_serialization.md index 8caa930..b124ae7 100644 --- a/docs/examples/7-custom_serialization.md +++ b/docs/examples/7-custom_serialization.md @@ -1,5 +1,4 @@ --- -layout: default title: Custom Serialization parent: Examples nav_order: 7 @@ -13,7 +12,7 @@ summary: >- Serde allows you to change how a field is (de)serialized by setting a [`#[serde(with = "path")]`](https://serde.rs/field-attrs.html#with) attribute, where `$path::serialize` and `$path::deserialize` must be functions with the correct signature. Schemars supports the same attribute, but `path` must be a type implementing `JsonSchema`. -In order to derive `JsonSchema` on a type which includes a `#[serde(with = "path")]` attribute where `path` is not a type implementing `JsonSchema`, you'll need to override it with a suitable `#[schemars(with = "Type")]` or `#[schemars(schema_with = "path")]` attribute. +In order to derive `JsonSchema` on a type which includes a `#[serde(with = "path")]` attribute where `path` is not a type implementing `JsonSchema`, you'll need to override it with a suitable `#[schemars(with = "Type")]` or `#[schemars(schema_with = "path")]` attribute. {% include example.md name="custom_serialization" %} diff --git a/docs/examples/8-enum_repr.md b/docs/examples/8-enum_repr.md index 0312c29..9533cba 100644 --- a/docs/examples/8-enum_repr.md +++ b/docs/examples/8-enum_repr.md @@ -1,5 +1,4 @@ --- -layout: default title: Serialize Enum as Number (serde_repr) parent: Examples nav_order: 8 diff --git a/docs/examples/9-from_value.md b/docs/examples/9-from_value.md index b711be0..8922925 100644 --- a/docs/examples/9-from_value.md +++ b/docs/examples/9-from_value.md @@ -1,5 +1,4 @@ --- -layout: default title: Generate Schema from Example Value parent: Examples nav_order: 9 diff --git a/docs/index.md b/docs/index.md index 2868342..ad71e78 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,4 @@ --- -layout: default title: Overview nav_order: 1 --- @@ -10,7 +9,7 @@ Schemars is a library to generate JSON Schema documents from Rust data structure This is built on Rust's trait system - any type which implements the [`JsonSchema`](https://docs.rs/schemars/latest/schemars/trait.JsonSchema.html) trait can have a JSON Schema generated describing that type. Schemars implements this on many standard library types, and provides a derive macro to automatically implement it on custom types. -One of the main aims of this library is compatibility with [Serde](https://github.com/serde-rs/serde). Any generated schema *should* match how [serde_json](https://github.com/serde-rs/json) would serialize/deserialize to/from JSON. To support this, Schemars will check for any `#[serde(...)]` attributes on types that derive `JsonSchema`, and adjust the generated schema accordingly. +One of the main aims of this library is compatibility with [Serde](https://github.com/serde-rs/serde). Any generated schema _should_ match how [serde_json](https://github.com/serde-rs/json) would serialize/deserialize to/from JSON. To support this, Schemars will check for any `#[serde(...)]` attributes on types that derive `JsonSchema`, and adjust the generated schema accordingly. ## Basic Usage 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 66e5736..2e6aa66 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 2e6aa66..317c51b 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 6ee924e..c106fa8 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 aebc7cc..eb9f862 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 a6fa101..d7b4577 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 cf7f007..f39cdf7 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 417d3be..4997ac5 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 417d3be..4997ac5 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 788140d..7d1d6d8 100644 --- a/schemars/tests/docs.rs +++ b/schemars/tests/docs.rs @@ -5,12 +5,10 @@ use util::*; #[allow(dead_code)] #[derive(JsonSchema)] /** - * - * # This is the struct's title - * - * This is the struct's description. - * - */ +# This is the struct's title + +This is the struct's description. +*/ struct MyStruct { /// # An integer my_int: i32, diff --git a/schemars/tests/expected/doc_comments_enum.json b/schemars/tests/expected/doc_comments_enum.json index b2e8f94..0da3bed 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 aab15d2..df9daca 100644 --- a/schemars_derive/src/attr/doc.rs +++ b/schemars_derive/src/attr/doc.rs @@ -14,25 +14,15 @@ pub fn get_title_and_desc_from_doc(attrs: &[Attribute]) -> (Option, Opti .trim_start_matches('#') .trim() .to_owned(); - let maybe_desc = split.next().and_then(merge_description_lines); + let maybe_desc = split.next().map(|s| s.trim().to_owned()); (none_if_empty(title), maybe_desc) } else { - (None, merge_description_lines(&doc)) + (None, Some(doc)) } } -fn merge_description_lines(doc: &str) -> Option { - let desc = doc - .trim() - .split("\n\n") - .filter_map(|line| none_if_empty(line.trim().replace('\n', " "))) - .collect::>() - .join("\n\n"); - none_if_empty(desc) -} - fn get_doc(attrs: &[Attribute]) -> Option { - let attrs = attrs + let lines = attrs .iter() .filter_map(|attr| { if !attr.path().is_ident("doc") { @@ -52,29 +42,7 @@ fn get_doc(attrs: &[Attribute]) -> Option { }) .collect::>(); - let mut lines = attrs - .iter() - .flat_map(|a| a.split('\n')) - .map(str::trim) - .skip_while(|s| s.is_empty()) - .collect::>(); - - if let Some(&"") = lines.last() { - lines.pop(); - } - - // Added for backward-compatibility, but perhaps we shouldn't do this - // https://github.com/rust-lang/rust/issues/32088 - if lines.iter().all(|l| l.starts_with('*')) { - for line in lines.iter_mut() { - *line = line[1..].trim() - } - while let Some(&"") = lines.first() { - lines.remove(0); - } - }; - - none_if_empty(lines.join("\n")) + none_if_empty(lines.join("\n").trim().to_owned()) } fn none_if_empty(s: String) -> Option { 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 850c1eb..35453e0 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 f39cdf7..1dc4bcf 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 70bd9d2..4c57f34 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 62c1a45..ef56d05 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 cf01b37..5cf419c 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 1dc4bcf..781a2e5 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 dbb307a..afe49d1 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 0000000..7482b31 --- /dev/null +++ b/schemars/src/transform.rs @@ -0,0 +1,371 @@ +/*! +Contains the [`Transform`] trait, used to modify a constructed schema and optionally its subschemas. +This trait is automatically implemented for functions of the form `fn(&mut Schema) -> ()`. + +# Recursive Transforms + +To make a transform recursive (i.e. apply it to subschemas), you have two options: +1. call the [`transform_subschemas`] function within the transform function +2. wrap the `Transform` in a [`RecursiveTransform`] + +# Examples + +To add a custom property to all object schemas: + +``` +# use schemars::{Schema, json_schema}; +use schemars::transform::{Transform, transform_subschemas}; + +pub struct MyTransform; + +impl Transform for MyTransform { + fn transform(&mut self, schema: &mut Schema) { + // First, make our change to this schema + if let Some(obj) = schema.as_object_mut() { + obj.insert("my_property".to_string(), serde_json::json!("hello world")); + } + + // Then apply the transform to any subschemas + transform_subschemas(self, schema); + } +} + +let mut schema = json_schema!({ + "type": "array", + "items": {} +}); + +MyTransform.transform(&mut schema); + +assert_eq!( + schema, + json_schema!({ + "type": "array", + "items": { + "my_property": "hello world" + }, + "my_property": "hello world" + }) +); +``` + +The same example with a `fn` transform`: +``` +# use schemars::{Schema, json_schema}; +use schemars::transform::transform_subschemas; + +fn add_property(schema: &mut Schema) { + if let Some(obj) = schema.as_object_mut() { + obj.insert("my_property".to_string(), serde_json::json!("hello world")); + } + + transform_subschemas(&mut add_property, schema) +} + +let mut schema = json_schema!({ + "type": "array", + "items": {} +}); + +add_property(&mut schema); + +assert_eq!( + schema, + json_schema!({ + "type": "array", + "items": { + "my_property": "hello world" + }, + "my_property": "hello world" + }) +); +``` + +And the same example using a closure wrapped in a `RecursiveTransform`: +``` +# use schemars::{Schema, json_schema}; +use schemars::transform::{Transform, RecursiveTransform}; + +let mut transform = RecursiveTransform(|schema: &mut Schema| { + if let Some(obj) = schema.as_object_mut() { + obj.insert("my_property".to_string(), serde_json::json!("hello world")); + } +}); + +let mut schema = json_schema!({ + "type": "array", + "items": {} +}); + +transform.transform(&mut schema); + +assert_eq!( + schema, + json_schema!({ + "type": "array", + "items": { + "my_property": "hello world" + }, + "my_property": "hello world" + }) +); +``` + +*/ +use serde_json::{json, Value}; + +use crate::Schema; + +/// Trait used to modify a constructed schema and optionally its subschemas. +/// +/// See the [module documentation](self) for more details on implementing this trait. +pub trait Transform { + /// Applies the transform to the given [`Schema`]. + /// + /// When overriding this method, you may want to call the [`transform_subschemas`] function to also transform any subschemas. + fn transform(&mut self, schema: &mut Schema); + + // Not public API + // Hack to enable implementing Debug on Box even though closures don't implement Debug + #[doc(hidden)] + fn _debug_type_name(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(std::any::type_name::()) + } +} + +impl Transform for F +where + F: FnMut(&mut Schema), +{ + fn transform(&mut self, schema: &mut Schema) { + self(schema) + } +} + +/// Applies the given [`Transform`] to all direct subschemas of the [`Schema`]. +pub fn transform_subschemas(t: &mut T, schema: &mut Schema) { + if let Some(obj) = schema.as_object_mut() { + for (key, value) in obj { + // This is intentionally written to work with multiple JSON Schema versions, so that + // users can add their own transforms on the end of e.g. `SchemaSettings::draft07()` and + // they will still apply to all subschemas "as expected". + // This is why this match statement contains both `additionalProperties` (which was + // dropped in draft 2020-12) and `prefixItems` (which was added in draft 2020-12). + match key.as_str() { + "not" + | "if" + | "then" + | "else" + | "contains" + | "additionalProperties" + | "propertyNames" + | "additionalItems" => { + if let Ok(subschema) = value.try_into() { + t.transform(subschema) + } + } + "allOf" | "anyOf" | "oneOf" | "prefixItems" => { + if let Some(array) = value.as_array_mut() { + for value in array { + if let Ok(subschema) = value.try_into() { + t.transform(subschema) + } + } + } + } + // Support `items` array even though this is not allowed in draft 2020-12 (see above comment) + "items" => { + if let Some(array) = value.as_array_mut() { + for value in array { + if let Ok(subschema) = value.try_into() { + t.transform(subschema) + } + } + } else if let Ok(subschema) = value.try_into() { + t.transform(subschema) + } + } + "properties" | "patternProperties" | "$defs" | "definitions" => { + if let Some(obj) = value.as_object_mut() { + for value in obj.values_mut() { + if let Ok(subschema) = value.try_into() { + t.transform(subschema) + } + } + } + } + _ => {} + } + } + } +} + +/// A helper struct that can wrap a non-recursive [`Transform`] (i.e. one that does not apply to subschemas) into a recursive one. +/// +/// Its implementation of `Transform` will first apply the inner transform to the "parent" schema, and then its subschemas (and their subschemas, and so on). +/// +/// # Example +/// ``` +/// # use schemars::{Schema, json_schema}; +/// use schemars::transform::{Transform, RecursiveTransform}; +/// +/// let mut transform = RecursiveTransform(|schema: &mut Schema| { +/// if let Some(obj) = schema.as_object_mut() { +/// obj.insert("my_property".to_string(), serde_json::json!("hello world")); +/// } +/// }); +/// +/// let mut schema = json_schema!({ +/// "type": "array", +/// "items": {} +/// }); +/// +/// transform.transform(&mut schema); +/// +/// assert_eq!( +/// schema, +/// json_schema!({ +/// "type": "array", +/// "items": { +/// "my_property": "hello world" +/// }, +/// "my_property": "hello world" +/// }) +/// ); +/// ``` +#[derive(Debug, Clone)] +pub struct RecursiveTransform(pub T); + +impl Transform for RecursiveTransform +where + T: Transform, +{ + fn transform(&mut self, schema: &mut Schema) { + self.0.transform(schema); + transform_subschemas(self, schema); + } +} + +/// Replaces boolean JSON Schemas with equivalent object schemas. +/// This also applies to subschemas. +/// +/// This is useful for dialects of JSON Schema (e.g. OpenAPI 3.0) that do not support booleans as schemas. +#[derive(Debug, Clone)] +pub struct ReplaceBoolSchemas { + /// When set to `true`, a schema's `additionalProperties` property will not be changed from a boolean. + pub skip_additional_properties: bool, +} + +impl Transform for ReplaceBoolSchemas { + fn transform(&mut self, schema: &mut Schema) { + if let Some(obj) = schema.as_object_mut() { + if self.skip_additional_properties { + if let Some((ap_key, ap_value)) = obj.remove_entry("additionalProperties") { + transform_subschemas(self, schema); + + if let Some(obj) = schema.as_object_mut() { + obj.insert(ap_key, ap_value); + } + + return; + } + } + + transform_subschemas(self, schema); + } else { + schema.ensure_object(); + } + } +} + +/// Restructures JSON Schema objects so that the `$ref` property will never appear alongside any other properties. +/// This also applies to subschemas. +/// +/// This is useful for versions of JSON Schema (e.g. Draft 7) that do not support other properties alongside `$ref`. +#[derive(Debug, Clone)] +pub struct RemoveRefSiblings; + +impl Transform for RemoveRefSiblings { + fn transform(&mut self, schema: &mut Schema) { + transform_subschemas(self, schema); + + if let Some(obj) = schema.as_object_mut() { + if obj.len() > 1 { + if let Some(ref_value) = obj.remove("$ref") { + if let Value::Array(all_of) = + obj.entry("allOf").or_insert(Value::Array(Vec::new())) + { + all_of.push(json!({ + "$ref": ref_value + })); + } + } + } + } + } +} + +/// Removes the `examples` schema property and (if present) set its first value as the `example` property. +/// This also applies to subschemas. +/// +/// This is useful for dialects of JSON Schema (e.g. OpenAPI 3.0) that do not support the `examples` property. +#[derive(Debug, Clone)] +pub struct SetSingleExample; + +impl Transform for SetSingleExample { + fn transform(&mut self, schema: &mut Schema) { + transform_subschemas(self, schema); + + if let Some(obj) = schema.as_object_mut() { + if let Some(Value::Array(examples)) = obj.remove("examples") { + if let Some(first_example) = examples.into_iter().next() { + obj.insert("example".into(), first_example); + } + } + } + } +} + +/// Replaces the `const` schema property with a single-valued `enum` property. +/// This also applies to subschemas. +/// +/// This is useful for dialects of JSON Schema (e.g. OpenAPI 3.0) that do not support the `const` property. +#[derive(Debug, Clone)] +pub struct ReplaceConstValue; + +impl Transform for ReplaceConstValue { + fn transform(&mut self, schema: &mut Schema) { + transform_subschemas(self, schema); + + if let Some(obj) = schema.as_object_mut() { + if let Some(value) = obj.remove("const") { + obj.insert("enum".into(), Value::Array(vec![value])); + } + } + } +} + +/// Rename the `prefixItems` schema property to `items`. +/// This also applies to subschemas. +/// +/// If the schema contains both `prefixItems` and `items`, then this additionally renames `items` to `additionalItems`. +/// +/// This is useful for versions of JSON Schema (e.g. Draft 7) that do not support the `prefixItems` property. +#[derive(Debug, Clone)] +pub struct ReplacePrefixItems; + +impl Transform for ReplacePrefixItems { + fn transform(&mut self, schema: &mut Schema) { + transform_subschemas(self, schema); + + if let Some(obj) = schema.as_object_mut() { + if let Some(prefix_items) = obj.remove("prefixItems") { + let previous_items = obj.insert("items".to_owned(), prefix_items); + + if let Some(previous_items) = previous_items { + obj.insert("additionalItems".to_owned(), previous_items); + } + } + } + } +} diff --git a/schemars/src/visit.rs b/schemars/src/visit.rs deleted file mode 100644 index 35453e0..0000000 --- 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 3fe489a..0c8741c 100644 --- a/schemars/tests/schema_settings.rs +++ b/schemars/tests/schema_settings.rs @@ -1,6 +1,6 @@ mod util; use schemars::gen::SchemaSettings; -use schemars::JsonSchema; +use schemars::{JsonSchema, Schema}; use serde_json::Value; use std::collections::BTreeMap; use util::*; @@ -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 781a2e5..c825172 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 c825172..f6553eb 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 317c51b..52ddc8a 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 d3cf985..0888f7a 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 0000000..af746e3 --- /dev/null +++ b/schemars/tests/expected/transform_enum_external.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "External", + "oneOf": [ + { + "type": "string", + "const": "Unit", + "propertyCount": 0, + "upperType": "STRING" + }, + { + "type": "object", + "properties": { + "NewType": true + }, + "required": [ + "NewType" + ], + "additionalProperties": false, + "propertyCount": 1, + "upperType": "OBJECT" + } + ], + "propertyCount": 0 +} \ No newline at end of file diff --git a/schemars/tests/expected/transform_struct.json b/schemars/tests/expected/transform_struct.json new file mode 100644 index 0000000..6723414 --- /dev/null +++ b/schemars/tests/expected/transform_struct.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Struct", + "type": "object", + "properties": { + "value": true, + "int": { + "type": "integer", + "format": "int32", + "propertyCount": 0, + "upperType": "INTEGER" + } + }, + "required": [ + "value", + "int" + ], + "upperType": "OBJECT", + "propertyCount": 2 +} \ No newline at end of file diff --git a/schemars/tests/extend.rs b/schemars/tests/extend.rs index 08f42fa..2b44f5e 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 0000000..13ac30b --- /dev/null +++ b/schemars/tests/transform.rs @@ -0,0 +1,51 @@ +mod util; +use schemars::{transform::RecursiveTransform, JsonSchema, Schema}; +use serde_json::Value; +use util::*; + +fn capitalize_type(schema: &mut Schema) { + if let Some(obj) = schema.as_object_mut() { + if let Some(Value::String(ty)) = obj.get("type") { + obj.insert("upperType".to_owned(), ty.to_uppercase().into()); + } + } +} + +fn insert_property_count(schema: &mut Schema) { + if let Some(obj) = schema.as_object_mut() { + let count = obj + .get("properties") + .and_then(|p| p.as_object()) + .map_or(0, |p| p.len()); + obj.insert("propertyCount".to_owned(), count.into()); + } +} + +#[allow(dead_code)] +#[derive(JsonSchema)] +#[schemars(transform = RecursiveTransform(capitalize_type), transform = insert_property_count)] +struct Struct { + value: Value, + #[schemars(transform = insert_property_count)] + int: i32, +} + +#[test] +fn transform_struct() -> TestResult { + test_default_generated_schema::("transform_struct") +} + +#[allow(dead_code)] +#[derive(JsonSchema)] +#[schemars(transform = RecursiveTransform(capitalize_type), transform = insert_property_count)] +enum External { + #[schemars(transform = insert_property_count)] + Unit, + #[schemars(transform = insert_property_count)] + NewType(Value), +} + +#[test] +fn transform_enum_external() -> TestResult { + test_default_generated_schema::("transform_enum_external") +} diff --git a/schemars/tests/ui/transform_str.rs b/schemars/tests/ui/transform_str.rs new file mode 100644 index 0000000..6570acc --- /dev/null +++ b/schemars/tests/ui/transform_str.rs @@ -0,0 +1,7 @@ +use schemars::JsonSchema; + +#[derive(JsonSchema)] +#[schemars(transform = "x")] +pub struct Struct; + +fn main() {} diff --git a/schemars/tests/ui/transform_str.stderr b/schemars/tests/ui/transform_str.stderr new file mode 100644 index 0000000..6ee3698 --- /dev/null +++ b/schemars/tests/ui/transform_str.stderr @@ -0,0 +1,6 @@ +error: Expected a `fn(&mut Schema)` or other value implementing `schemars::transform::Transform`, found `&str`. + Did you mean `[schemars(transform = x)]`? + --> tests/ui/transform_str.rs:4:24 + | +4 | #[schemars(transform = "x")] + | ^^^ diff --git a/schemars_derive/src/attr/mod.rs b/schemars_derive/src/attr/mod.rs index 108cedc..6b6e676 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 6a3808c..c6b5dfe 100644 --- a/schemars_derive/src/metadata.rs +++ b/schemars_derive/src/metadata.rs @@ -1,4 +1,5 @@ use proc_macro2::TokenStream; +use syn::spanned::Spanned; #[derive(Debug, Clone)] pub struct SchemaMetadata<'a> { @@ -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 0000000..4ee936a --- /dev/null +++ b/docs/0-migrating.md @@ -0,0 +1,175 @@ +--- +title: Migrating from 0.8 +nav_order: 2 +has_children: true +has_toc: false +permalink: /migrating/ +layout: default +--- + +# Migrating from 0.8 to 1.0 + +
+

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

+ +## Optional dependencies + +All optional dependencies are now suffixed by their version: + +- `chrono` is now `chrono04` +- `either` is now `either1` +- `smallvec` is now `smallvec1` +- `url` is now `url2` +- `bytes` is now `bytes1` +- `rust_decimal` is now `rust_decimal1` +- `enumset` is now `enumset1` +- `smol_str` is now `smol_str02` +- `semver` is now `semver1` +- `indexmap`, `uuid08`, `arrayvec05` and `bigdecimal03` have been removed +- `indexmap2`, `arrayvec07` and `bigdecimal04` are unchanged + +## `Schema` is now a wrapper around `serde_json::Value` + +`Schema` is now defined as a wrapper around a `serde_json::Value` (which must be a `Value::Bool` or `Value::Object`), rather than a struct with a field for each JSON schema keyword (with some intermediary types). `Schema` is now available as `schemars::Schema` instead of `schemars::schema::Schema`, and all other types that were in the `schemars::schema` module have now been removed. Functions that previously returned a `RootSchema` now just return a `Schema`. + +A new macro `json_schema!(...)` is available to easily create new instances of `Schema`, which functions similarly to the [`serde_json::json!(...)` macro](https://docs.rs/serde_json/latest/serde_json/macro.json.html). + +Here's how you might create and modify a `Schema` in schemars v0.8: + +```rust +use schemars::schema::{InstanceType, ObjectValidation, Schema, SchemaObject}; +use schemars::Map; + +// Create a Schema for an object with property `foo` +let schema_object = SchemaObject { + instance_type: Some(InstanceType::Object.into()), + object: Some(Box::new(ObjectValidation { + properties: Map::from_iter([("foo".to_owned(), true.into())]), + ..Default::default() + })), + ..Default::default() +}; +let schema: Schema = schema_object.into(); + +// Make the `foo` property required +let mut schema_object = schema.into_object(); +let obj = schema_object.object(); +obj.required.insert("foo".to_owned()); +``` + +And the same thing in v1.0: + +```rust +use schemars::{json_schema, Schema}; + +// Create a Schema for an object with property `foo` +let mut schema: Schema = json_schema!({ + "type": "object", + "properties": { + "foo": true + } +}); + +// Make the `foo` property required +schema + .ensure_object() + .entry("required") + .or_insert(serde_json::Value::Array(Vec::new())) + .as_array_mut() + .expect("`required` should be an array") + .push("foo".into()); +``` + +## `visit::Visitor` replaced with `transform::Transform` + +The `visit` module and `Visitor` trait have been replace with `transform` and `Transform` respectively. Accordingly, these items have been renamed: + +- `SchemaSettings::visitors` -> `SchemaSettings::transforms` +- `SchemaSettings::with_visitor` -> `SchemaSettings::with_transform` +- `SchemaGenerator::visitors_mut` -> `SchemaGenerator::transforms_mut` +- `GenVisitor` -> `GenTransform` +- `Visitor::visit_schema` -> `Transform::transform` + - `visit_schema_object` and `visit_root_schema` methods have been removed +- `visit::visit_schema` -> `transform::transform_subschemas` + - `visit_schema_object` and `visit_root_schema` functions have been removed + +So if you had defined this `Visitor` in schemars 0.8: + +```rust +use schemars::schema::SchemaObject; +use schemars::visit::{visit_schema_object, Visitor}; + +pub struct MyVisitor; + +impl Visitor for MyVisitor { + fn visit_schema_object(&mut self, schema: &mut SchemaObject) { + // First, make our change to this schema + schema + .extensions + .insert("my_property".to_string(), serde_json::json!("hello world")); + + // Then delegate to default implementation to visit any subschemas + visit_schema_object(self, schema); + } +} + +let mut schema = schemars::schema_for!(str); +MyVisitor.visit_root_schema(&mut schema); +``` + +Then the equivalent `Transform` in schemars 1.0 would be: + +```rust +use schemars::transform::{transform_subschemas, Transform}; +use schemars::Schema; + +pub struct MyTransform; + +impl Transform for MyTransform { + fn transform(&mut self, schema: &mut Schema) { + // First, make our change to this schema + if let Some(obj) = schema.as_object_mut() { + obj.insert("my_property".to_string(), serde_json::json!("hello world")); + } + + // Then apply the transform to any subschemas + transform_subschemas(self, schema); + } +} + +let mut schema = schemars::schema_for!(str); +MyTransform.transform(&mut schema); +``` + +Also, since `Transform` is now implemented for functions that take a single `&mut Schema` argument, you could also define it as a function instead of a struct: + +```rust +fn my_transform(schema: &mut Schema) { + // First, make our change to this schema + if let Some(obj) = schema.as_object_mut() { + obj.insert("my_property".to_string(), serde_json::json!("hello world")); + } + + // Then apply the transform to any subschemas + transform_subschemas(&mut my_transform, schema); +} + +let mut schema = schemars::schema_for!(str); +my_transform(&mut schema); +// Or equivalently: +// my_transform.transform(&mut schema); +``` + +Finally, you can also use the `RecursiveTransform` newtype to convert a non-recursive `Transform` (i.e. one that does not transform subschemas) into a recursive one, like so: + +```rust +fn my_transform2(schema: &mut Schema) { + if let Some(obj) = schema.as_object_mut() { + obj.insert("my_property".to_string(), serde_json::json!("hello world")); + } +} + +let mut schema = schemars::schema_for!(str); +RecursiveTransform(my_transform2).transform(&mut schema); +``` diff --git a/docs/1-deriving.md b/docs/1-deriving.md index 2c844ee..0bb4420 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 c106fa8..cb0f18c 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 eb9f862..b8cd970 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 d7b4577..eb51bdf 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 4219bb5..b7e38d7 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 fd11be7..75a0d5f 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 118af03..9634342 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 2f87862..e8be803 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 9634342..18c6d4b 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 eb51bdf..6c3e871 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 c1b0a94..055c010 100644 --- a/schemars/Cargo.toml +++ b/schemars/Cargo.toml @@ -3,7 +3,7 @@ name = "schemars" description = "Generate JSON Schemas from Rust code" homepage = "https://graham.cool/schemars/" repository = "https://github.com/GREsau/schemars" -version = "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 8f3a775..2c1b398 100644 --- a/schemars_derive/Cargo.toml +++ b/schemars_derive/Cargo.toml @@ -3,7 +3,7 @@ name = "schemars_derive" description = "Macros for #[derive(JsonSchema)], for use with schemars" homepage = "https://graham.cool/schemars/" repository = "https://github.com/GREsau/schemars" -version = "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 4997ac5..a3aa767 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 4ad2503..dc9e495 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 ba96767..85efffd 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 4997ac5..a3aa767 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 4ad2503..dc9e495 100644 --- a/schemars/examples/schemars_attrs.rs +++ b/schemars/examples/schemars_attrs.rs @@ -1,11 +1,11 @@ -use schemars::{schema_for, JsonSchema}; +use schemars::{schema_for, JsonSchema, Schema}; use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, JsonSchema)] -#[schemars(rename_all = "camelCase", deny_unknown_fields)] +#[schemars(rename_all = "camelCase", deny_unknown_fields, extend("x-customProperty" = "example"))] pub struct MyStruct { #[serde(rename = "thisIsOverridden")] - #[schemars(rename = "myNumber", range(min = 1, max = 10))] + #[schemars(rename = "myNumber", range(min = 1, max = 10), transform = remove_format)] pub my_int: i32, pub my_bool: bool, #[schemars(default)] @@ -24,6 +24,12 @@ pub enum MyEnum { }, } +fn remove_format(schema: &mut Schema) { + if let Some(obj) = schema.as_object_mut() { + obj.remove("format"); + } +} + fn main() { let schema = schema_for!(MyStruct); println!("{}", serde_json::to_string_pretty(&schema).unwrap()); diff --git a/schemars/examples/schemars_attrs.schema.json b/schemars/examples/schemars_attrs.schema.json index ba96767..85efffd 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 59eb3ea..b0f88f7 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: