Define Schema as a newtype around serde_json::Value (#289)

This commit is contained in:
Graham Esau 2024-05-12 19:23:54 +01:00 committed by GitHub
parent 7f6a7b7e32
commit 342cd5fd09
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
79 changed files with 1410 additions and 2394 deletions

View file

@ -17,7 +17,7 @@ jobs:
- nightly - nightly
include: include:
- rust: 1.60.0 - rust: 1.60.0
test_features: "--features impl_json_schema" test_features: ""
allow_failure: false allow_failure: false
- rust: stable - rust: stable
test_features: "--all-features" test_features: "--all-features"

100
Cargo.lock generated
View file

@ -2,12 +2,6 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.7.4" version = "0.7.4"
@ -29,17 +23,6 @@ dependencies = [
"serde", "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]] [[package]]
name = "bigdecimal" name = "bigdecimal"
version = "0.4.2" version = "0.4.2"
@ -168,12 +151,6 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.14.2" version = "0.14.2"
@ -196,17 +173,6 @@ dependencies = [
"unicode-normalization", "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]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.0.2" version = "2.0.2"
@ -214,8 +180,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.14.2", "hashbrown",
"serde",
] ]
[[package]] [[package]]
@ -284,29 +249,49 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.69" version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.33" version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [ dependencies = [
"proc-macro2", "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]] [[package]]
name = "rust_decimal" name = "rust_decimal"
version = "1.32.0" version = "1.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c4216490d5a413bc6d10fa4742bd7d4955941d062c0ef873141d6b0e7b30fd" checksum = "a4c4216490d5a413bc6d10fa4742bd7d4955941d062c0ef873141d6b0e7b30fd"
dependencies = [ dependencies = [
"arrayvec 0.7.4", "arrayvec",
"num-traits", "num-traits",
] ]
@ -320,18 +305,16 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
name = "schemars" name = "schemars"
version = "0.8.19" version = "0.8.19"
dependencies = [ dependencies = [
"arrayvec 0.5.2", "arrayvec",
"arrayvec 0.7.4", "bigdecimal",
"bigdecimal 0.3.1",
"bigdecimal 0.4.2",
"bytes", "bytes",
"chrono", "chrono",
"dyn-clone", "dyn-clone",
"either", "either",
"enumset", "enumset",
"indexmap 1.9.3", "indexmap",
"indexmap 2.0.2",
"pretty_assertions", "pretty_assertions",
"ref-cast",
"rust_decimal", "rust_decimal",
"schemars_derive", "schemars_derive",
"semver", "semver",
@ -341,8 +324,7 @@ dependencies = [
"smol_str", "smol_str",
"trybuild", "trybuild",
"url", "url",
"uuid 0.8.2", "uuid",
"uuid 1.5.0",
] ]
[[package]] [[package]]
@ -361,9 +343,6 @@ name = "semver"
version = "1.0.20" version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde" name = "serde"
@ -415,18 +394,15 @@ checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
[[package]] [[package]]
name = "smol_str" name = "smol_str"
version = "0.1.24" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9" checksum = "e6845563ada680337a52d43bb0b29f396f2d911616f6573012645b9e3d048a49"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.38" version = "2.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -504,12 +480,6 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "uuid"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.5.0" version = "1.5.0"

View file

@ -15,117 +15,91 @@ rust-version = "1.60"
[dependencies] [dependencies]
schemars_derive = { version = "=0.8.19", optional = true, path = "../schemars_derive" } 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" serde_json = "1.0.25"
dyn-clone = "1.0" dyn-clone = "1.0"
ref-cast = "1.0.22"
chrono = { version = "0.4", default-features = false, optional = true } # optional dependencies
indexmap = { version = "1.2", features = ["serde-1"], optional = true } chrono04 = { version = "0.4", default-features = false, optional = true, package = "chrono" }
indexmap2 = { version = "2.0", features = ["serde"], optional = true, package = "indexmap" } indexmap2 = { version = "2.0", default-features = false, optional = true, package = "indexmap" }
either = { version = "1.3", default-features = false, optional = true } either1 = { version = "1.3", default-features = false, optional = true, package = "either" }
uuid08 = { version = "0.8", default-features = false, optional = true, package = "uuid" }
uuid1 = { version = "1.0", default-features = false, optional = true, package = "uuid" } uuid1 = { version = "1.0", default-features = false, optional = true, package = "uuid" }
smallvec = { version = "1.0", optional = true } smallvec1 = { version = "1.0", default-features = false, optional = true, package = "smallvec" }
arrayvec05 = { version = "0.5", default-features = false, optional = true, package = "arrayvec" }
arrayvec07 = { version = "0.7", default-features = false, optional = true, package = "arrayvec" } arrayvec07 = { version = "0.7", default-features = false, optional = true, package = "arrayvec" }
url = { version = "2.0", default-features = false, optional = true } url2 = { version = "2.0", default-features = false, optional = true, package = "url" }
bytes = { version = "1.0", optional = true } bytes1 = { version = "1.0", default-features = false, optional = true, package = "bytes" }
rust_decimal = { version = "1", default-features = false, optional = true } rust_decimal1 = { version = "1", default-features = false, optional = true, package = "rust_decimal"}
bigdecimal03 = { version = "0.3", default-features = false, optional = true, package = "bigdecimal" }
bigdecimal04 = { version = "0.4", default-features = false, optional = true, package = "bigdecimal" } bigdecimal04 = { version = "0.4", default-features = false, optional = true, package = "bigdecimal" }
enumset = { version = "1.0", optional = true } enumset1 = { version = "1.0", default-features = false, optional = true, package = "enumset" }
smol_str = { version = "0.1.17", optional = true } smol_str02 = { version = "0.2.1", default-features = false, optional = true, package = "smol_str" }
semver = { version = "1.0.9", features = ["serde"], optional = true } semver1 = { version = "1.0.9", default-features = false, optional = true, package = "semver" }
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.2.1" pretty_assertions = "1.2.1"
trybuild = "1.0" trybuild = "1.0"
serde = { version = "1.0", features = ["derive"] }
[features] [features]
default = ["derive"] default = ["derive"]
derive = ["schemars_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"] 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 = [] 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]] [[test]]
name = "ui" name = "ui"
required-features = ["ui_test"] 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]] [[test]]
name = "url" name = "url"
required-features = ["url"] required-features = ["url2"]
[[test]] [[test]]
name = "enumset" name = "enumset"
required-features = ["enumset"] required-features = ["enumset1"]
[[test]] [[test]]
name = "smol_str" name = "smol_str"
required-features = ["smol_str"] required-features = ["smol_str02"]
[[test]] [[test]]
name = "semver" name = "semver"
required-features = ["semver"] required-features = ["semver1"]
[[test]] [[test]]
name = "decimal" name = "decimal"
required-features = ["rust_decimal", "bigdecimal03", "bigdecimal04"] required-features = ["rust_decimal1", "bigdecimal04"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View file

@ -1,4 +1,4 @@
use schemars::schema::{Schema, SchemaObject}; use schemars::Schema;
use schemars::{gen::SchemaGenerator, schema_for, JsonSchema}; use schemars::{gen::SchemaGenerator, schema_for, JsonSchema};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -21,9 +21,11 @@ pub struct MyStruct {
} }
fn make_custom_schema(gen: &mut SchemaGenerator) -> Schema { fn make_custom_schema(gen: &mut SchemaGenerator) -> Schema {
let mut schema: SchemaObject = <String>::json_schema(gen).into(); let mut schema = String::json_schema(gen);
schema.format = Some("boolean".to_owned()); schema
schema.into() .ensure_object()
.insert("format".into(), "boolean".into());
schema
} }
fn eight() -> i32 { fn eight() -> i32 {

View file

@ -1,7 +1,8 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::{InstanceType, ObjectValidation, Schema, SchemaObject}; use crate::JsonSchema;
use crate::{JsonSchema, Map, Set}; use crate::Schema;
use serde::Serialize; use serde::Serialize;
use serde_json::Map;
use serde_json::Value; use serde_json::Value;
// Helper for generating schemas for flattened `Option` fields. // Helper for generating schemas for flattened `Option` fields.
@ -12,12 +13,8 @@ pub fn json_schema_for_flatten<T: ?Sized + JsonSchema>(
let mut schema = T::_schemars_private_non_optional_json_schema(gen); let mut schema = T::_schemars_private_non_optional_json_schema(gen);
if T::_schemars_private_is_option() && !required { if T::_schemars_private_is_option() && !required {
if let Schema::Object(SchemaObject { if let Some(object) = schema.as_object_mut() {
object: Some(ref mut object_validation), object.remove("required");
..
}) = schema
{
object_validation.required.clear();
} }
} }
@ -57,38 +54,22 @@ impl<T: Serialize> MaybeSerializeWrapper<T> {
/// Create a schema for a unit enum /// Create a schema for a unit enum
pub fn new_unit_enum(variant: &str) -> Schema { pub fn new_unit_enum(variant: &str) -> Schema {
Schema::Object(SchemaObject { // TODO switch from single-valued "enum" to "const"
instance_type: Some(InstanceType::String.into()), json_schema!({
enum_values: Some(vec![variant.into()]), "type": "string",
..SchemaObject::default() "enum": [variant],
}) })
} }
/// Create a schema for an externally tagged enum /// Create a schema for an externally tagged enum
pub fn new_externally_tagged_enum(variant: &str, sub_schema: Schema) -> Schema { pub fn new_externally_tagged_enum(variant: &str, sub_schema: Schema) -> Schema {
Schema::Object(SchemaObject { json_schema!({
instance_type: Some(InstanceType::Object.into()), "type": "object",
object: Some(Box::new(ObjectValidation { "properties": {
properties: { variant: sub_schema
let mut props = Map::new(); },
props.insert(variant.to_owned(), sub_schema); "required": [variant],
props "additionalProperties": false,
},
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()
}) })
} }
@ -98,74 +79,87 @@ pub fn new_internally_tagged_enum(
variant: &str, variant: &str,
deny_unknown_fields: bool, deny_unknown_fields: bool,
) -> Schema { ) -> Schema {
let tag_schema = Schema::Object(SchemaObject { // TODO switch from single-valued "enum" to "const"
instance_type: Some(InstanceType::String.into()), let mut schema = json_schema!({
enum_values: Some(vec![variant.into()]), "type": "object",
..Default::default() "properties": {
tag_name: {
"type": "string",
"enum": [variant],
}
},
"required": [tag_name],
}); });
Schema::Object(SchemaObject {
instance_type: Some(InstanceType::Object.into()), if deny_unknown_fields {
object: Some(Box::new(ObjectValidation { schema
properties: { .as_object_mut()
let mut props = Map::new(); .unwrap()
props.insert(tag_name.to_owned(), tag_schema); .insert("additionalProperties".into(), false.into());
props }
},
required: { schema
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()
})
} }
pub fn insert_object_property<T: ?Sized + JsonSchema>( pub fn insert_object_property<T: ?Sized + JsonSchema>(
obj: &mut ObjectValidation, schema: &mut Schema,
key: &str, key: &str,
has_default: bool, has_default: bool,
required: 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()) { 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 { pub fn insert_validation_property(
use crate::Schema; schema: &mut Schema,
use serde_json::Value; required_type: &str,
key: &str,
value: impl Into<Value>,
) {
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 { pub fn append_required(schema: &mut Schema, key: &str) {
($method:ident, $name:ident, $ty:ty) => { if schema.has_type("object") {
pub fn $method(schema: Schema, $name: impl Into<$ty>) -> Schema { if let Value::Array(array) = schema
let value = $name.into(); .ensure_object()
if value == <$ty>::default() { .entry("required")
schema .or_insert(Value::Array(Vec::new()))
} else { {
let mut schema_obj = schema.into_object(); let value = Value::from(key);
schema_obj.metadata().$name = value.into(); if !array.contains(&value) {
Schema::Object(schema_obj) array.push(value);
}
} }
}; }
} }
}
add_metadata_fn!(add_description, description, String);
add_metadata_fn!(add_id, id, String); pub fn apply_inner_validation(schema: &mut Schema, f: fn(&mut Schema) -> ()) {
add_metadata_fn!(add_title, title, String); if let Some(inner_schema) = schema
add_metadata_fn!(add_deprecated, deprecated, bool); .as_object_mut()
add_metadata_fn!(add_read_only, read_only, bool); .and_then(|o| o.get_mut("items"))
add_metadata_fn!(add_write_only, write_only, bool); .and_then(|i| <&mut Schema>::try_from(i).ok())
add_metadata_fn!(add_default, default, Value); {
f(inner_schema);
pub fn add_examples<I: IntoIterator<Item = Value>>(schema: Schema, examples: I) -> Schema {
let mut schema_obj = schema.into_object();
schema_obj.metadata().examples.extend(examples);
Schema::Object(schema_obj)
} }
} }

View file

@ -1,180 +1,111 @@
use crate::schema::*; use serde_json::map::Entry;
use crate::{Map, Set}; use serde_json::Value;
use crate::Schema;
impl Schema { impl Schema {
/// This function is only public for use by schemars_derive. /// This function is only public for use by schemars_derive.
/// ///
/// It should not be considered part of the public API. /// It should not be considered part of the public API.
#[doc(hidden)] #[doc(hidden)]
pub fn flatten(self, other: Self) -> Schema { pub fn flatten(mut self, other: Self) -> Schema {
if is_null_type(&self) { // This special null-type-schema handling is here for backward-compatibility, but needs reviewing.
return other; // I think it's only needed to make internally-tagged enum unit variants behave correctly, but that
} else if is_null_type(&other) { // 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; return self;
} }
let s1: SchemaObject = self.into();
let s2: SchemaObject = other.into();
Schema::Object(s1.merge(s2))
}
}
pub(crate) trait Merge: Sized { if let Value::Object(mut obj2) = other.to_value() {
fn merge(self, other: Self) -> Self; let obj1 = self.ensure_object();
}
macro_rules! impl_merge { let ap2 = obj2.remove("additionalProperties");
($ty:ident { merge: $($merge_field:ident)*, or: $($or_field:ident)*, }) => { if let Entry::Occupied(mut ap1) = obj1.entry("additionalProperties") {
impl Merge for $ty { match ap2 {
fn merge(self, other: Self) -> Self { Some(ap2) => {
$ty { flatten_additional_properties(ap1.get_mut(), ap2);
$($merge_field: self.$merge_field.merge(other.$merge_field),)* }
$($or_field: self.$or_field.or(other.$or_field),)* 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<Box<Schema>> {
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<T: Merge> Merge for Option<T> {
fn merge(self, other: Self) -> Self {
match (self, other) {
(Some(x), Some(y)) => Some(x.merge(y)),
(None, y) => y,
(x, None) => x,
}
}
}
impl<T: Merge> Merge for Box<T> {
fn merge(mut self, other: Self) -> Self {
*self = (*self).merge(*other);
self self
} }
} }
impl<T> Merge for Vec<T> { // TODO validate behaviour when flattening a normal struct into a struct with deny_unknown_fields
fn merge(mut self, other: Self) -> Self { fn flatten_additional_properties(v1: &mut Value, v2: Value) {
self.extend(other); match (v1, v2) {
self (v1, Value::Bool(true)) => {
} *v1 = Value::Bool(true);
}
impl<K, V> Merge for Map<K, V>
where
K: std::hash::Hash + Eq + Ord,
{
fn merge(mut self, other: Self) -> Self {
self.extend(other);
self
}
}
impl<T: Ord> Merge for Set<T> {
fn merge(mut self, other: Self) -> Self {
self.extend(other);
self
}
}
impl Merge for SingleOrVec<InstanceType> {
fn merge(self, other: Self) -> Self {
if self == other {
return self;
} }
let mut vec = match (self, other) { (v1 @ Value::Bool(false), v2) => {
(SingleOrVec::Vec(v1), SingleOrVec::Vec(v2)) => v1.merge(v2), *v1 = v2;
(SingleOrVec::Vec(mut v), SingleOrVec::Single(s)) }
| (SingleOrVec::Single(s), SingleOrVec::Vec(mut v)) => { (Value::Object(o1), Value::Object(o2)) => {
v.push(*s); o1.extend(o2);
v }
} _ => {}
(SingleOrVec::Single(s1), SingleOrVec::Single(s2)) => vec![*s1, *s2],
};
vec.sort();
vec.dedup();
SingleOrVec::Vec(vec)
} }
} }
fn is_null_type(schema: &Schema) -> bool {
let s = match schema {
Schema::Object(s) => s,
_ => return false,
};
let instance_type = match &s.instance_type {
Some(SingleOrVec::Single(t)) => t,
_ => return false,
};
**instance_type == InstanceType::Null
}

View file

@ -7,12 +7,12 @@ There are two main types in this module:
* [`SchemaGenerator`], which manages the generation of a schema document. * [`SchemaGenerator`], which manages the generation of a schema document.
*/ */
use crate::schema::*; use crate::Schema;
use crate::{visit::*, JsonSchema, Map}; use crate::{visit::*, JsonSchema};
use dyn_clone::DynClone; use dyn_clone::DynClone;
use serde::Serialize; use serde::Serialize;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashMap; use std::collections::{BTreeMap, HashMap};
use std::{any::Any, collections::HashSet, fmt::Debug}; use std::{any::Any, collections::HashSet, fmt::Debug};
/// Settings to customize how Schemas are generated. /// Settings to customize how Schemas are generated.
@ -76,7 +76,7 @@ impl SchemaSettings {
option_add_null_type: true, option_add_null_type: true,
definitions_path: "#/definitions/".to_owned(), definitions_path: "#/definitions/".to_owned(),
meta_schema: Some("https://json-schema.org/draft/2019-09/schema".to_owned()), meta_schema: Some("https://json-schema.org/draft/2019-09/schema".to_owned()),
visitors: Vec::default(), visitors: Vec::new(),
inline_subschemas: false, inline_subschemas: false,
} }
} }
@ -96,9 +96,7 @@ impl SchemaSettings {
Box::new(ReplaceBoolSchemas { Box::new(ReplaceBoolSchemas {
skip_additional_properties: true, skip_additional_properties: true,
}), }),
Box::new(SetSingleExample { Box::new(SetSingleExample),
retain_examples: false,
}),
], ],
inline_subschemas: false, inline_subschemas: false,
} }
@ -150,7 +148,7 @@ impl SchemaSettings {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct SchemaGenerator { pub struct SchemaGenerator {
settings: SchemaSettings, settings: SchemaSettings,
definitions: Map<String, Schema>, definitions: BTreeMap<String, Schema>,
pending_schema_ids: HashSet<Cow<'static, str>>, pending_schema_ids: HashSet<Cow<'static, str>>,
schema_id_to_name: HashMap<Cow<'static, str>, String>, schema_id_to_name: HashMap<Cow<'static, str>, String>,
used_schema_names: HashSet<String>, used_schema_names: HashSet<String>,
@ -198,19 +196,6 @@ impl SchemaGenerator {
&self.settings &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. /// 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 [referenceable](JsonSchema::is_referenceable), this will add `T`'s schema to this generator's definitions, and
@ -262,7 +247,7 @@ impl SchemaGenerator {
name: String, name: String,
id: Cow<'static, str>, id: Cow<'static, str>,
) { ) {
let dummy = Schema::Bool(false); let dummy = false.into();
// insert into definitions BEFORE calling json_schema to avoid infinite recursion // insert into definitions BEFORE calling json_schema to avoid infinite recursion
self.definitions.insert(name.clone(), dummy); 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. /// 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. /// themselves.
pub fn definitions(&self) -> &Map<String, Schema> { pub fn definitions(&self) -> &BTreeMap<String, Schema> {
&self.definitions &self.definitions
} }
/// Mutably borrows the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated. /// 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. /// themselves.
pub fn definitions_mut(&mut self) -> &mut Map<String, Schema> { pub fn definitions_mut(&mut self) -> &mut BTreeMap<String, Schema> {
&mut self.definitions &mut self.definitions
} }
/// Returns the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated, /// Returns the collection of all [referenceable](JsonSchema::is_referenceable) 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 /// The keys of the returned `BTreeMap` are the [schema names](JsonSchema::schema_name), and the values are the schemas
/// themselves. /// themselves.
pub fn take_definitions(&mut self) -> Map<String, Schema> { pub fn take_definitions(&mut self) -> BTreeMap<String, Schema> {
std::mem::take(&mut self.definitions) 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 /// 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 /// add them to the `SchemaGenerator`'s schema definitions and include them in the returned `SchemaObject`'s
/// [`definitions`](../schema/struct.Metadata.html#structfield.definitions) /// [`definitions`](../schema/struct.Metadata.html#structfield.definitions)
pub fn root_schema_for<T: ?Sized + JsonSchema>(&mut self) -> RootSchema { pub fn root_schema_for<T: ?Sized + JsonSchema>(&mut self) -> Schema {
let mut schema = self.json_schema_internal::<T>(T::schema_id()).into_object(); let mut schema = self.json_schema_internal::<T>(T::schema_id());
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,
};
for visitor in &mut self.settings.visitors { let object = schema.ensure_object();
visitor.visit_root_schema(&mut root)
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`. /// 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 [referenceable](JsonSchema::is_referenceable) schemas, then this method will
/// include them in the returned `SchemaObject`'s [`definitions`](../schema/struct.Metadata.html#structfield.definitions) /// include them in the returned `SchemaObject`'s [`definitions`](../schema/struct.Metadata.html#structfield.definitions)
pub fn into_root_schema_for<T: ?Sized + JsonSchema>(mut self) -> RootSchema { pub fn into_root_schema_for<T: ?Sized + JsonSchema>(mut self) -> Schema {
let mut schema = self.json_schema_internal::<T>(T::schema_id()).into_object(); let mut schema = self.json_schema_internal::<T>(T::schema_id());
schema.metadata().title.get_or_insert_with(T::schema_name);
let mut root = RootSchema {
meta_schema: self.settings.meta_schema,
definitions: self.definitions,
schema,
};
for visitor in &mut self.settings.visitors { let object = schema.ensure_object();
visitor.visit_root_schema(&mut root)
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. /// Generates a root JSON Schema for the given example value.
@ -349,29 +366,39 @@ impl SchemaGenerator {
pub fn root_schema_for_value<T: ?Sized + Serialize>( pub fn root_schema_for_value<T: ?Sized + Serialize>(
&mut self, &mut self,
value: &T, value: &T,
) -> Result<RootSchema, serde_json::Error> { ) -> Result<Schema, serde_json::Error> {
let mut schema = value let mut schema = value.serialize(crate::ser::Serializer {
.serialize(crate::ser::Serializer { gen: self,
gen: self, include_title: true,
include_title: true, })?;
})?
.into_object(); let object = schema.ensure_object();
if let Ok(example) = serde_json::to_value(value) { 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 { if let Some(meta_schema) = self.settings.meta_schema.as_deref() {
meta_schema: self.settings.meta_schema.clone(), object.insert("$schema".into(), meta_schema.into());
definitions: self.definitions.clone(), }
schema,
}; 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 { 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. /// 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<T: ?Sized + Serialize>( pub fn into_root_schema_for_value<T: ?Sized + Serialize>(
mut self, mut self,
value: &T, value: &T,
) -> Result<RootSchema, serde_json::Error> { ) -> Result<Schema, serde_json::Error> {
let mut schema = value let mut schema = value.serialize(crate::ser::Serializer {
.serialize(crate::ser::Serializer { gen: &mut self,
gen: &mut self, include_title: true,
include_title: true, })?;
})?
.into_object(); let object = schema.ensure_object();
if let Ok(example) = serde_json::to_value(value) { 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 { if let Some(meta_schema) = self.settings.meta_schema {
meta_schema: self.settings.meta_schema, object.insert("$schema".into(), meta_schema.into());
definitions: self.definitions, }
schema,
}; 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 { for visitor in &mut self.settings.visitors {
visitor.visit_root_schema(&mut root) visitor.visit_schema(&mut schema);
} }
Ok(root) Ok(schema)
}
/// Attemps to find the schema that the given `schema` is referencing.
///
/// If the given `schema` has a [`$ref`](../schema/struct.SchemaObject.html#structfield.reference) property which refers
/// to another schema in `self`'s schema definitions, the referenced schema will be returned. Otherwise, returns `None`.
///
/// # Example
/// ```
/// use schemars::{JsonSchema, gen::SchemaGenerator};
///
/// #[derive(JsonSchema)]
/// struct MyStruct {
/// foo: i32,
/// }
///
/// let mut gen = SchemaGenerator::default();
/// let ref_schema = gen.subschema_for::<MyStruct>();
///
/// assert!(ref_schema.is_ref());
///
/// let dereferenced = gen.dereference(&ref_schema);
///
/// assert!(dereferenced.is_some());
/// assert!(!dereferenced.unwrap().is_ref());
/// assert_eq!(dereferenced, gen.definitions().get("MyStruct"));
/// ```
pub fn dereference<'a>(&'a self, schema: &Schema) -> Option<&'a Schema> {
match schema {
Schema::Object(SchemaObject {
reference: Some(ref schema_ref),
..
}) => {
let definitions_path = &self.settings().definitions_path;
if schema_ref.starts_with(definitions_path) {
let name = &schema_ref[definitions_path.len()..];
self.definitions.get(name)
} else {
None
}
}
_ => None,
}
} }
fn json_schema_internal<T: ?Sized + JsonSchema>(&mut self, id: Cow<'static, str>) -> Schema { fn json_schema_internal<T: ?Sized + JsonSchema>(&mut self, id: Cow<'static, str>) -> Schema {

View file

@ -1,6 +1,5 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::{json_schema, JsonSchema, Schema};
use crate::JsonSchema;
use std::borrow::Cow; use std::borrow::Cow;
// Does not require T: JsonSchema. // Does not require T: JsonSchema.
@ -16,15 +15,10 @@ impl<T> JsonSchema for [T; 0] {
} }
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { json_schema!({
instance_type: Some(InstanceType::Array.into()), "type": "array",
array: Some(Box::new(ArrayValidation { "maxItems": 0,
max_items: Some(0), })
..Default::default()
})),
..Default::default()
}
.into()
} }
} }
@ -44,17 +38,12 @@ macro_rules! array_impls {
} }
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
SchemaObject { json_schema!({
instance_type: Some(InstanceType::Array.into()), "type": "array",
array: Some(Box::new(ArrayValidation { "items": serde_json::Value::from(gen.subschema_for::<T>()),
items: Some(gen.subschema_for::<T>().into()), "minItems": $len,
max_items: Some($len), "maxItems": $len,
min_items: Some($len), })
..Default::default()
})),
..Default::default()
}
.into()
} }
} }
)+ )+
@ -67,40 +56,3 @@ array_impls! {
21 22 23 24 25 26 27 28 29 30 21 22 23 24 25 26 27 28 29 30
31 32 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::<i32>()))
);
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));
}
}

View file

@ -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!((<A> JsonSchema for ArrayString<A> where A: Array<Item = u8> + Copy) => String);
impl<A: Array> JsonSchema for ArrayVec<A>
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::<A::Item>().into()),
max_items: A::CAPACITY.try_into().ok(),
..Default::default()
})),
..Default::default()
}
.into()
}
}

View file

@ -1,8 +1,6 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::{json_schema, JsonSchema, Schema};
use crate::JsonSchema;
use arrayvec07::{ArrayString, ArrayVec}; use arrayvec07::{ArrayString, ArrayVec};
use std::convert::TryInto;
// Do not set maxLength on the schema as that describes length in characters, but we only // Do not set maxLength on the schema as that describes length in characters, but we only
// know max length in bytes. // know max length in bytes.
@ -19,15 +17,10 @@ where
} }
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
SchemaObject { json_schema!({
instance_type: Some(InstanceType::Array.into()), "type": "array",
array: Some(Box::new(ArrayValidation { "items": gen.subschema_for::<T>(),
items: Some(gen.subschema_for::<T>().into()), "maxItems": CAP
max_items: CAP.try_into().ok(), })
..Default::default()
})),
..Default::default()
}
.into()
} }
} }

View file

@ -1,6 +1,3 @@
use crate::gen::SchemaGenerator;
use crate::schema::*;
use crate::JsonSchema;
use std::sync::atomic::*; use std::sync::atomic::*;
forward_impl!(AtomicBool => bool); forward_impl!(AtomicBool => bool);
@ -22,12 +19,12 @@ forward_impl!(AtomicUsize => usize);
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::tests::schema_object_for; use crate::schema_for;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test] #[test]
fn schema_for_atomics() { fn schema_for_atomics() {
let atomic_schema = schema_object_for::<( let atomic_schema = schema_for!((
AtomicBool, AtomicBool,
AtomicI8, AtomicI8,
AtomicI16, AtomicI16,
@ -39,9 +36,8 @@ mod tests {
AtomicU32, AtomicU32,
AtomicU64, AtomicU64,
AtomicUsize, AtomicUsize,
)>(); ));
let basic_schema = let basic_schema = schema_for!((bool, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize));
schema_object_for::<(bool, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize)>();
assert_eq!(atomic_schema, basic_schema); assert_eq!(atomic_schema, basic_schema);
} }
} }

View file

@ -1,7 +0,0 @@
use crate::gen::SchemaGenerator;
use crate::schema::*;
use crate::JsonSchema;
use bytes::{Bytes, BytesMut};
forward_impl!((JsonSchema for Bytes) => Vec<u8>);
forward_impl!((JsonSchema for BytesMut) => Vec<u8>);

View file

@ -1,8 +1,6 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::{json_schema, JsonSchema, Schema};
use crate::JsonSchema; use chrono04::prelude::*;
use chrono::prelude::*;
use serde_json::json;
use std::borrow::Cow; use std::borrow::Cow;
impl JsonSchema for Weekday { impl JsonSchema for Weekday {
@ -17,20 +15,18 @@ impl JsonSchema for Weekday {
} }
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { json_schema!({
instance_type: Some(InstanceType::String.into()), "type": "string",
enum_values: Some(vec![ "enum": [
json!("Mon"), "Mon",
json!("Tue"), "Tue",
json!("Wed"), "Wed",
json!("Thu"), "Thu",
json!("Fri"), "Fri",
json!("Sat"), "Sat",
json!("Sun"), "Sun",
]), ]
..Default::default() })
}
.into()
} }
} }
@ -51,12 +47,10 @@ macro_rules! formatted_string_impl {
} }
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { json_schema!({
instance_type: Some(InstanceType::String.into()), "type": "string",
format: Some($format.to_owned()), "format": $format
..Default::default() })
}
.into()
} }
} }
}; };

View file

@ -1,7 +1,6 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::{json_schema, JsonSchema, Schema};
use crate::JsonSchema; use serde_json::Value;
use serde_json::json;
use std::borrow::Cow; use std::borrow::Cow;
use std::ops::{Bound, Range, RangeInclusive}; use std::ops::{Bound, Range, RangeInclusive};
@ -18,35 +17,45 @@ impl<T: JsonSchema> JsonSchema for Option<T> {
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut schema = gen.subschema_for::<T>(); let mut schema = gen.subschema_for::<T>();
if gen.settings().option_add_null_type { if gen.settings().option_add_null_type {
schema = match schema { schema = match schema.try_to_object() {
Schema::Bool(true) => Schema::Bool(true), Ok(mut obj) => {
Schema::Bool(false) => <()>::json_schema(gen), let instance_type = obj.get_mut("type");
Schema::Object(SchemaObject { match instance_type {
instance_type: Some(ref mut instance_type), Some(Value::Array(array)) => {
.. let null = Value::from("null");
}) => { if !array.contains(&null) {
add_null_type(instance_type); array.push(null);
schema }
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 { Err(true) => true.into(),
// TODO technically the schema already accepts null, so this may be unnecessary Err(false) => <()>::json_schema(gen),
subschemas: Some(Box::new(SubschemaValidation {
any_of: Some(vec![schema, <()>::json_schema(gen)]),
..Default::default()
})),
..Default::default()
}
.into(),
} }
} }
if gen.settings().option_nullable { if gen.settings().option_nullable {
let mut schema_obj = schema.into_object(); schema
schema_obj .ensure_object()
.extensions .insert("nullable".into(), true.into());
.insert("nullable".to_owned(), json!(true));
schema = Schema::Object(schema_obj);
}; };
schema schema
} }
@ -59,16 +68,6 @@ impl<T: JsonSchema> JsonSchema for Option<T> {
} }
} }
fn add_null_type(instance_type: &mut SingleOrVec<InstanceType>) {
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<T: JsonSchema, E: JsonSchema> JsonSchema for Result<T, E> { impl<T: JsonSchema, E: JsonSchema> JsonSchema for Result<T, E> {
fn schema_name() -> String { fn schema_name() -> String {
format!("Result_of_{}_or_{}", T::schema_name(), E::schema_name()) format!("Result_of_{}_or_{}", T::schema_name(), E::schema_name())
@ -79,27 +78,24 @@ impl<T: JsonSchema, E: JsonSchema> JsonSchema for Result<T, E> {
} }
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut ok_schema = SchemaObject { json_schema!({
instance_type: Some(InstanceType::Object.into()), "oneOf": [
..Default::default() {
}; "type": "object",
let obj = ok_schema.object(); "properties": {
obj.required.insert("Ok".to_owned()); "Ok": gen.subschema_for::<T>()
obj.properties },
.insert("Ok".to_owned(), gen.subschema_for::<T>()); "required": ["Ok"]
},
let mut err_schema = SchemaObject { {
instance_type: Some(InstanceType::Object.into()), "type": "object",
..Default::default() "properties": {
}; "Err": gen.subschema_for::<E>()
let obj = err_schema.object(); },
obj.required.insert("Err".to_owned()); "required": ["Err"]
obj.properties }
.insert("Err".to_owned(), gen.subschema_for::<E>()); ]
})
let mut schema = SchemaObject::default();
schema.subschemas().one_of = Some(vec![ok_schema.into(), err_schema.into()]);
schema.into()
} }
} }
@ -113,37 +109,28 @@ impl<T: JsonSchema> JsonSchema for Bound<T> {
} }
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut included_schema = SchemaObject { json_schema!({
instance_type: Some(InstanceType::Object.into()), "oneOf": [
..Default::default() {
}; "type": "object",
let obj = included_schema.object(); "properties": {
obj.required.insert("Included".to_owned()); "Included": gen.subschema_for::<T>()
obj.properties },
.insert("Included".to_owned(), gen.subschema_for::<T>()); "required": ["Included"]
},
let mut excluded_schema = SchemaObject { {
instance_type: Some(InstanceType::Object.into()), "type": "object",
..Default::default() "properties": {
}; "Excluded": gen.subschema_for::<T>()
let obj = excluded_schema.object(); },
obj.required.insert("Excluded".to_owned()); "required": ["Excluded"]
obj.properties },
.insert("Excluded".to_owned(), gen.subschema_for::<T>()); {
"type": "string",
let unbounded_schema = SchemaObject { "const": "Unbounded"
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()
} }
} }
@ -157,18 +144,15 @@ impl<T: JsonSchema> JsonSchema for Range<T> {
} }
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut schema = SchemaObject { let subschema = gen.subschema_for::<T>();
instance_type: Some(InstanceType::Object.into()), json_schema!({
..Default::default() "type": "object",
}; "properties": {
let obj = schema.object(); "start": subschema,
obj.required.insert("start".to_owned()); "end": subschema
obj.required.insert("end".to_owned()); },
obj.properties "required": ["start", "end"]
.insert("start".to_owned(), gen.subschema_for::<T>()); })
obj.properties
.insert("end".to_owned(), gen.subschema_for::<T>());
schema.into()
} }
} }
@ -177,54 +161,3 @@ forward_impl!((<T: JsonSchema> JsonSchema for RangeInclusive<T>) => Range<T>);
forward_impl!((<T: ?Sized> JsonSchema for std::marker::PhantomData<T>) => ()); forward_impl!((<T: ?Sized> JsonSchema for std::marker::PhantomData<T>) => ());
forward_impl!((<'a> JsonSchema for std::fmt::Arguments<'a>) => String); 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::<Option<i32>>();
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::<Option<Foo>>();
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::<Result<bool, String>>();
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::<bool>());
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::<String>());
}
}

View file

@ -1,6 +1,5 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::{json_schema, JsonSchema, Schema};
use crate::JsonSchema;
use std::borrow::Cow; use std::borrow::Cow;
macro_rules! decimal_impl { macro_rules! decimal_impl {
@ -17,23 +16,16 @@ macro_rules! decimal_impl {
} }
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { json_schema!({
instance_type: Some(InstanceType::String.into()), "type": "string",
string: Some(Box::new(StringValidation { "pattern": r"^-?[0-9]+(\.[0-9]+)?$",
pattern: Some(r"^-?[0-9]+(\.[0-9]+)?$".to_owned()), })
..Default::default()
})),
..Default::default()
}
.into()
} }
} }
}; };
} }
#[cfg(feature = "rust_decimal")] #[cfg(feature = "rust_decimal1")]
decimal_impl!(rust_decimal::Decimal); decimal_impl!(rust_decimal1::Decimal);
#[cfg(feature = "bigdecimal03")]
decimal_impl!(bigdecimal03::BigDecimal);
#[cfg(feature = "bigdecimal04")] #[cfg(feature = "bigdecimal04")]
decimal_impl!(bigdecimal04::BigDecimal); decimal_impl!(bigdecimal04::BigDecimal);

View file

@ -1,7 +1,6 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::{json_schema, JsonSchema, Schema};
use crate::JsonSchema; use either1::Either;
use either::Either;
use std::borrow::Cow; use std::borrow::Cow;
impl<L: JsonSchema, R: JsonSchema> JsonSchema for Either<L, R> { impl<L: JsonSchema, R: JsonSchema> JsonSchema for Either<L, R> {
@ -20,8 +19,8 @@ impl<L: JsonSchema, R: JsonSchema> JsonSchema for Either<L, R> {
} }
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut schema = SchemaObject::default(); json_schema!({
schema.subschemas().any_of = Some(vec![gen.subschema_for::<L>(), gen.subschema_for::<R>()]); "anyOf": [gen.subschema_for::<L>(), gen.subschema_for::<R>()],
schema.into() })
} }
} }

View file

@ -1,6 +0,0 @@
use crate::gen::SchemaGenerator;
use crate::schema::*;
use crate::JsonSchema;
use enumset::{EnumSet, EnumSetType};
forward_impl!((<T> JsonSchema for EnumSet<T> where T: EnumSetType + JsonSchema) => std::collections::BTreeSet<T>);

View file

@ -1,6 +1,5 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::{json_schema, JsonSchema, Schema};
use crate::JsonSchema;
use std::borrow::Cow; use std::borrow::Cow;
use std::ffi::{CStr, CString, OsStr, OsString}; use std::ffi::{CStr, CString, OsStr, OsString};
@ -14,27 +13,24 @@ impl JsonSchema for OsString {
} }
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut unix_schema = SchemaObject { json_schema!({
instance_type: Some(InstanceType::Object.into()), "oneOf": [
..Default::default() {
}; "type": "object",
let obj = unix_schema.object(); "properties": {
obj.required.insert("Unix".to_owned()); "Unix": <Vec<u8>>::json_schema(gen)
obj.properties },
.insert("Unix".to_owned(), <Vec<u8>>::json_schema(gen)); "required": ["Unix"]
},
let mut win_schema = SchemaObject { {
instance_type: Some(InstanceType::Object.into()), "type": "object",
..Default::default() "properties": {
}; "Windows": <Vec<u16>>::json_schema(gen)
let obj = win_schema.object(); },
obj.required.insert("Windows".to_owned()); "required": ["Windows"]
obj.properties },
.insert("Windows".to_owned(), <Vec<u16>>::json_schema(gen)); ]
})
let mut schema = SchemaObject::default();
schema.subschemas().one_of = Some(vec![unix_schema.into(), win_schema.into()]);
schema.into()
} }
} }

View file

@ -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!((<K, V: JsonSchema, H> JsonSchema for IndexMap<K, V, H>) => HashMap<K, V, H>);
forward_impl!((<T: JsonSchema, H> JsonSchema for IndexSet<T, H>) => HashSet<T, H>);

View file

@ -1,5 +1,3 @@
use crate::gen::SchemaGenerator;
use crate::schema::*;
use crate::JsonSchema; use crate::JsonSchema;
use indexmap2::{IndexMap, IndexSet}; use indexmap2::{IndexMap, IndexSet};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};

View file

@ -1,6 +1,5 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::{json_schema, JsonSchema, Schema};
use crate::JsonSchema;
use std::borrow::Cow; use std::borrow::Cow;
macro_rules! map_impl { macro_rules! map_impl {
@ -20,16 +19,10 @@ macro_rules! map_impl {
} }
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let subschema = gen.subschema_for::<V>(); json_schema!({
SchemaObject { "type": "object",
instance_type: Some(InstanceType::Object.into()), "additionalProperties": gen.subschema_for::<V>(),
object: Some(Box::new(ObjectValidation { })
additional_properties: Some(Box::new(subschema)),
..Default::default()
})),
..Default::default()
}
.into()
} }
} }
}; };

View file

@ -21,11 +21,11 @@ macro_rules! forward_impl {
<$target>::schema_id() <$target>::schema_id()
} }
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut $crate::gen::SchemaGenerator) -> $crate::Schema {
<$target>::json_schema(gen) <$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) <$target>::_schemars_private_non_optional_json_schema(gen)
} }
@ -35,55 +35,61 @@ macro_rules! forward_impl {
} }
}; };
($ty:ty => $target:ty) => { ($ty:ty => $target:ty) => {
forward_impl!((JsonSchema for $ty) => $target); forward_impl!(($crate::JsonSchema for $ty) => $target);
}; };
} }
mod array; 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; 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; mod ffi;
#[cfg(feature = "indexmap")]
mod indexmap;
#[cfg(feature = "indexmap2")]
mod indexmap2;
mod maps; mod maps;
mod nonzero_signed; mod nonzero_signed;
mod nonzero_unsigned; mod nonzero_unsigned;
mod primitives; mod primitives;
#[cfg(feature = "semver")]
mod semver;
mod sequences; mod sequences;
mod serdejson; mod serdejson;
#[cfg(feature = "smallvec")]
mod smallvec;
#[cfg(feature = "smol_str")]
mod smol_str;
mod time; mod time;
mod tuple; mod tuple;
#[cfg(feature = "url")] mod wrapper;
mod url;
#[cfg(feature = "uuid08")] #[cfg(std_atomic)]
mod uuid08; mod atomic;
#[cfg(feature = "arrayvec07")]
mod arrayvec07;
#[cfg(feature = "bytes1")]
mod bytes1 {
forward_impl!(bytes1::Bytes => Vec<u8>);
forward_impl!(bytes1::BytesMut => Vec<u8>);
}
#[cfg(feature = "chrono04")]
mod chrono04;
#[cfg(any(feature = "rust_decimal1", feature = "bigdecimal04"))]
mod decimal;
#[cfg(feature = "either1")]
mod either1;
#[cfg(feature = "enumset1")]
forward_impl!((<T: enumset1::EnumSetType + crate::JsonSchema> crate::JsonSchema for enumset1::EnumSet<T>) => std::collections::BTreeSet<T>);
#[cfg(feature = "indexmap2")]
mod indexmap2;
#[cfg(feature = "semver1")]
mod semver1;
#[cfg(feature = "smallvec1")]
forward_impl!((<A: smallvec1::Array> crate::JsonSchema for smallvec1::SmallVec<A> where A::Item: crate::JsonSchema) => Vec<A::Item>);
#[cfg(feature = "smol_str02")]
forward_impl!(smol_str02::SmolStr => String);
#[cfg(feature = "url2")]
mod url2;
#[cfg(feature = "uuid1")] #[cfg(feature = "uuid1")]
mod uuid1; mod uuid1;
mod wrapper;

View file

@ -1,6 +1,5 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::{JsonSchema, Schema};
use crate::JsonSchema;
use std::borrow::Cow; use std::borrow::Cow;
use std::num::*; use std::num::*;
@ -18,14 +17,12 @@ macro_rules! nonzero_unsigned_impl {
} }
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let zero_schema: Schema = SchemaObject { let mut schema = <$primitive>::json_schema(gen);
const_value: Some(0.into()), let object = schema.ensure_object();
..Default::default() object.insert("not".to_owned(), serde_json::json!({
} "const": 0
.into(); }));
let mut schema: SchemaObject = <$primitive>::json_schema(gen).into(); schema
schema.subschemas().not = Some(Box::from(zero_schema));
schema.into()
} }
} }
}; };

View file

@ -1,5 +1,5 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::Schema;
use crate::JsonSchema; use crate::JsonSchema;
use std::borrow::Cow; use std::borrow::Cow;
use std::num::*; use std::num::*;
@ -18,9 +18,10 @@ macro_rules! nonzero_unsigned_impl {
} }
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut schema: SchemaObject = <$primitive>::json_schema(gen).into(); let mut schema = <$primitive>::json_schema(gen);
schema.number().minimum = Some(1.0); let object = schema.ensure_object();
schema.into() 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!(NonZeroU64 => u64);
nonzero_unsigned_impl!(NonZeroU128 => u128); nonzero_unsigned_impl!(NonZeroU128 => u128);
nonzero_unsigned_impl!(NonZeroUsize => usize); 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::<NonZeroU32>();
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()));
}
}

View file

@ -1,67 +1,30 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::{json_schema, JsonSchema, Schema};
use crate::JsonSchema;
use std::borrow::Cow; use std::borrow::Cow;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
macro_rules! simple_impl { macro_rules! simple_impl {
($type:ty => $instance_type:ident) => { ($type:ty => $instance_type:literal) => {
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) => {
impl JsonSchema for $type { impl JsonSchema for $type {
no_ref_schema!(); no_ref_schema!();
fn schema_name() -> String { fn schema_name() -> String {
$name.to_owned() $instance_type.to_owned()
} }
fn schema_id() -> Cow<'static, str> { fn schema_id() -> Cow<'static, str> {
Cow::Borrowed($name) Cow::Borrowed($instance_type)
} }
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { json_schema!({
instance_type: Some(InstanceType::$instance_type.into()), "type": $instance_type
format: $format, })
..Default::default()
}
.into()
} }
} }
}; };
} ($type:ty => $instance_type:literal, $format:literal) => {
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) => {
impl JsonSchema for $type { impl JsonSchema for $type {
no_ref_schema!(); no_ref_schema!();
@ -74,24 +37,69 @@ macro_rules! unsigned_impl {
} }
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
let mut schema = SchemaObject { json_schema!({
instance_type: Some(InstanceType::$instance_type.into()), "type": $instance_type,
format: Some($format.to_owned()), "format": $format
..Default::default() })
};
schema.number().minimum = Some(0.0);
schema.into()
} }
} }
}; };
} }
unsigned_impl!(u8 => Integer, "uint8"); simple_impl!(str => "string");
unsigned_impl!(u16 => Integer, "uint16"); simple_impl!(String => "string");
unsigned_impl!(u32 => Integer, "uint32"); simple_impl!(bool => "boolean");
unsigned_impl!(u64 => Integer, "uint64"); simple_impl!(f32 => "number", "float");
unsigned_impl!(u128 => Integer, "uint128"); simple_impl!(f64 => "number", "double");
unsigned_impl!(usize => Integer, "uint"); 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 { impl JsonSchema for char {
no_ref_schema!(); no_ref_schema!();
@ -105,15 +113,10 @@ impl JsonSchema for char {
} }
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { json_schema!({
instance_type: Some(InstanceType::String.into()), "type": "string",
string: Some(Box::new(StringValidation { "minLength": 1,
min_length: Some(1), "maxLength": 1,
max_length: Some(1), })
..Default::default()
})),
..Default::default()
}
.into()
} }
} }

View file

@ -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()
}
}

View file

@ -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-]+)*))?$"
})
}
}

View file

@ -1,6 +1,5 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::{json_schema, JsonSchema, Schema};
use crate::JsonSchema;
use std::borrow::Cow; use std::borrow::Cow;
macro_rules! seq_impl { macro_rules! seq_impl {
@ -21,15 +20,10 @@ macro_rules! seq_impl {
} }
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
SchemaObject { json_schema!({
instance_type: Some(InstanceType::Array.into()), "type": "array",
array: Some(Box::new(ArrayValidation { "items": gen.subschema_for::<T>(),
items: Some(gen.subschema_for::<T>().into()), })
..Default::default()
})),
..Default::default()
}
.into()
} }
} }
}; };
@ -53,16 +47,11 @@ macro_rules! set_impl {
} }
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
SchemaObject { json_schema!({
instance_type: Some(InstanceType::Array.into()), "type": "array",
array: Some(Box::new(ArrayValidation { "uniqueItems": true,
unique_items: Some(true), "items": gen.subschema_for::<T>(),
items: Some(gen.subschema_for::<T>().into()), })
..Default::default()
})),
..Default::default()
}
.into()
} }
} }
}; };

View file

@ -1,6 +1,5 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::{json_schema, JsonSchema, Schema};
use crate::JsonSchema;
use serde_json::{Map, Number, Value}; use serde_json::{Map, Number, Value};
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::BTreeMap; use std::collections::BTreeMap;
@ -17,7 +16,7 @@ impl JsonSchema for Value {
} }
fn json_schema(_: &mut SchemaGenerator) -> Schema { 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 { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { json_schema!({
instance_type: Some(InstanceType::Number.into()), "type": "number"
..Default::default() })
}
.into()
} }
} }

View file

@ -1,6 +0,0 @@
use crate::gen::SchemaGenerator;
use crate::schema::*;
use crate::JsonSchema;
use smallvec::{Array, SmallVec};
forward_impl!((<A: Array> JsonSchema for SmallVec<A> where A::Item: JsonSchema) => Vec<A::Item>);

View file

@ -1,6 +0,0 @@
use crate::gen::SchemaGenerator;
use crate::schema::*;
use crate::JsonSchema;
use smol_str::SmolStr;
forward_impl!(SmolStr => String);

View file

@ -1,6 +1,5 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::{json_schema, JsonSchema, Schema};
use crate::JsonSchema;
use std::borrow::Cow; use std::borrow::Cow;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
@ -14,18 +13,14 @@ impl JsonSchema for Duration {
} }
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut schema = SchemaObject { json_schema!({
instance_type: Some(InstanceType::Object.into()), "type": "object",
..Default::default() "required": ["secs", "nanos"],
}; "properties": {
let obj = schema.object(); "secs": u64::json_schema(gen),
obj.required.insert("secs".to_owned()); "nanos": u32::json_schema(gen),
obj.required.insert("nanos".to_owned()); }
obj.properties })
.insert("secs".to_owned(), <u64>::json_schema(gen));
obj.properties
.insert("nanos".to_owned(), <u32>::json_schema(gen));
schema.into()
} }
} }
@ -39,17 +34,13 @@ impl JsonSchema for SystemTime {
} }
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut schema = SchemaObject { json_schema!({
instance_type: Some(InstanceType::Object.into()), "type": "object",
..Default::default() "required": ["secs_since_epoch", "nanos_since_epoch"],
}; "properties": {
let obj = schema.object(); "secs_since_epoch": u64::json_schema(gen),
obj.required.insert("secs_since_epoch".to_owned()); "nanos_since_epoch": u32::json_schema(gen),
obj.required.insert("nanos_since_epoch".to_owned()); }
obj.properties })
.insert("secs_since_epoch".to_owned(), <u64>::json_schema(gen));
obj.properties
.insert("nanos_since_epoch".to_owned(), <u32>::json_schema(gen));
schema.into()
} }
} }

View file

@ -1,6 +1,5 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::{json_schema, JsonSchema, Schema};
use crate::JsonSchema;
use std::borrow::Cow; use std::borrow::Cow;
macro_rules! tuple_impls { macro_rules! tuple_impls {
@ -24,20 +23,14 @@ macro_rules! tuple_impls {
} }
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let items = vec![ json_schema!({
$(gen.subschema_for::<$name>()),+ "type": "array",
]; "items": [
SchemaObject { $(gen.subschema_for::<$name>()),+
instance_type: Some(InstanceType::Array.into()), ],
array: Some(Box::new(ArrayValidation { "minItems": $len,
items: Some(items.into()), "maxItems": $len,
max_items: Some($len), })
min_items: Some($len),
..Default::default()
})),
..Default::default()
}
.into()
} }
} }
)+ )+
@ -62,29 +55,3 @@ tuple_impls! {
15 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14) 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) 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::<i32>(),
schema_for::<bool>()
]))
);
assert_eq!(array_validation.max_items, Some(2));
assert_eq!(array_validation.min_items, Some(2));
}
}

View file

@ -1,8 +1,7 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::{json_schema, JsonSchema, Schema};
use crate::JsonSchema;
use std::borrow::Cow; use std::borrow::Cow;
use url::Url; use url2::Url;
impl JsonSchema for Url { impl JsonSchema for Url {
no_ref_schema!(); no_ref_schema!();
@ -16,11 +15,9 @@ impl JsonSchema for Url {
} }
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { json_schema!({
instance_type: Some(InstanceType::String.into()), "type": "string",
format: Some("uri".to_owned()), "format": "uri",
..Default::default() })
}
.into()
} }
} }

View file

@ -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()
}
}

View file

@ -1,6 +1,5 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::{json_schema, JsonSchema, Schema};
use crate::JsonSchema;
use std::borrow::Cow; use std::borrow::Cow;
use uuid1::Uuid; use uuid1::Uuid;
@ -16,11 +15,9 @@ impl JsonSchema for Uuid {
} }
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { json_schema!({
instance_type: Some(InstanceType::String.into()), "type": "string",
format: Some("uuid".to_string()), "format": "uuid",
..Default::default() })
}
.into()
} }
} }

View file

@ -1,5 +1,3 @@
use crate::gen::SchemaGenerator;
use crate::schema::Schema;
use crate::JsonSchema; use crate::JsonSchema;
macro_rules! wrapper_impl { macro_rules! wrapper_impl {

View file

@ -1,32 +1,9 @@
#![forbid(unsafe_code)] #![deny(unsafe_code)]
#![doc = include_str!("../README.md")] #![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<K, V> = std::collections::BTreeMap<K, V>;
#[cfg(feature = "preserve_order")]
pub type Map<K, V> = indexmap::IndexMap<K, V>;
/// 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<T> = std::collections::BTreeSet<T>;
/// 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 flatten;
mod json_schema_impls; mod json_schema_impls;
mod schema;
mod ser; mod ser;
#[macro_use] #[macro_use]
mod macros; mod macros;
@ -36,7 +13,6 @@ mod macros;
#[doc(hidden)] #[doc(hidden)]
pub mod _private; pub mod _private;
pub mod gen; pub mod gen;
pub mod schema;
pub mod visit; pub mod visit;
#[cfg(feature = "schemars_derive")] #[cfg(feature = "schemars_derive")]
@ -50,7 +26,7 @@ pub use schemars_derive::*;
#[doc(hidden)] #[doc(hidden)]
pub use serde_json as _serde_json; 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. /// 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. /// 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: /// 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; /// use std::borrow::Cow;
/// ///
/// struct NonGenericType; /// 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: /// 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}; /// use std::{borrow::Cow, marker::PhantomData};
/// ///
/// struct GenericType<T>(PhantomData<T>); /// struct GenericType<T>(PhantomData<T>);
@ -175,24 +151,3 @@ pub trait JsonSchema {
false false
} }
} }
#[cfg(test)]
pub mod tests {
use super::*;
pub fn schema_object_for<T: JsonSchema>() -> schema::SchemaObject {
schema_object(schema_for::<T>())
}
pub fn schema_for<T: JsonSchema>() -> 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),
}
}
}

View file

@ -76,3 +76,19 @@ macro_rules! schema_for_value {
.unwrap() .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)
};
}

View file

@ -2,550 +2,229 @@
JSON Schema types. JSON Schema types.
*/ */
#[cfg(feature = "impl_json_schema")] use ref_cast::ref_cast_custom;
use crate as schemars; use ref_cast::RefCastCustom;
#[cfg(feature = "impl_json_schema")]
use crate::JsonSchema;
use crate::{Map, Set};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::{Map, Value};
use std::ops::Deref;
/// A JSON Schema. /// A JSON Schema.
#[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, PartialEq, RefCastCustom)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[repr(transparent)]
#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] pub struct Schema(Value);
#[serde(untagged)]
pub enum Schema { impl<'de> Deserialize<'de> for Schema {
/// A trivial boolean JSON Schema. fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
/// where
/// The schema `true` matches everything (always passes validation), whereas the schema `false` D: serde::Deserializer<'de>,
/// matches nothing (always fails validation). {
Bool(bool), let value = Value::deserialize(deserializer)?;
/// A JSON Schema object. Schema::validate(&value)?;
Object(SchemaObject), Ok(Schema(value))
}
}
impl Serialize for Schema {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
ser::OrderedKeywordWrapper(&self.0).serialize(serializer)
}
} }
impl Schema { impl Schema {
/// Creates a new `$ref` schema. pub fn new() -> Self {
/// Self(Value::Object(Map::new()))
/// 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()
} }
/// Returns `true` if `self` is a `$ref` schema. pub fn new_ref(reference: String) -> Self {
/// let mut map = Map::new();
/// If `self` is a [`SchemaObject`] with `Some` [`reference`](struct.SchemaObject.html#structfield.reference) set, this returns `true`. map.insert("$ref".to_owned(), Value::String(reference));
/// Otherwise, returns `false`. Self(Value::Object(map))
pub fn is_ref(&self) -> bool { }
match self {
Schema::Object(o) => o.is_ref(), pub fn as_value(&self) -> &Value {
&self.0
}
pub fn as_bool(&self) -> Option<bool> {
self.0.as_bool()
}
pub fn as_object(&self) -> Option<&Map<String, Value>> {
self.0.as_object()
}
pub fn as_object_mut(&mut self) -> Option<&mut Map<String, Value>> {
self.0.as_object_mut()
}
pub(crate) fn try_to_object(self) -> Result<Map<String, Value>, 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<String, Value> {
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, _ => false,
} }
} }
/// Converts the given schema (if it is a boolean schema) into an equivalent schema object. fn validate<E: serde::de::Error>(value: &Value) -> Result<(), E> {
/// use serde::de::Unexpected;
/// If the given schema is already a schema object, this has no effect. let unexpected = match value {
/// Value::Bool(_) | Value::Object(_) => return Ok(()),
/// # Example Value::Null => Unexpected::Unit,
/// ``` Value::Number(n) => {
/// use schemars::schema::{Schema, SchemaObject}; if let Some(u) = n.as_u64() {
/// Unexpected::Unsigned(u)
/// let bool_schema = Schema::Bool(true); } else if let Some(i) = n.as_i64() {
/// Unexpected::Signed(i)
/// assert_eq!(bool_schema.into_object(), SchemaObject::default()); } else if let Some(f) = n.as_f64() {
/// ``` Unexpected::Float(f)
pub fn into_object(self) -> SchemaObject { } else {
match self { unreachable!()
Schema::Object(o) => o, }
Schema::Bool(true) => SchemaObject::default(), }
Schema::Bool(false) => SchemaObject { Value::String(s) => Unexpected::Str(s),
subschemas: Some(Box::new(SubschemaValidation { Value::Array(_) => Unexpected::Seq,
not: Some(Schema::Object(Default::default()).into()), };
..Default::default()
})), Err(E::invalid_type(unexpected, &"object or boolean"))
..Default::default() }
},
} #[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<Schema> for Value {
fn from(v: Schema) -> Value {
v.0
} }
} }
impl From<SchemaObject> for Schema { impl std::convert::TryFrom<Value> for Schema {
fn from(o: SchemaObject) -> Self { type Error = serde_json::Error;
Schema::Object(o)
fn try_from(value: Value) -> serde_json::Result<Schema> {
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<Map<String, Value>> for Schema {
fn from(o: Map<String, Value>) -> Self {
Schema(Value::Object(o))
} }
} }
impl From<bool> for Schema { impl From<bool> for Schema {
fn from(b: bool) -> Self { fn from(b: bool) -> Self {
Schema::Bool(b) Schema(Value::Bool(b))
} }
} }
/// The root object of a JSON Schema document. mod ser {
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] use serde::ser::{Serialize, SerializeMap, SerializeSeq};
#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] use serde_json::Value;
#[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<String>,
/// 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<String, Schema>,
}
/// A JSON Schema object. const ORDERED_KEYWORDS_START: [&str; 6] =
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] ["$id", "$schema", "title", "description", "type", "format"];
#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))] const ORDERED_KEYWORDS_END: [&str; 2] = ["$defs", "definitions"];
#[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<Box<Metadata>>,
/// 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<SingleOrVec<InstanceType>>,
/// 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<String>,
/// 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<Vec<Value>>,
/// 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<Value>,
/// Properties of the [`SchemaObject`] which define validation assertions in terms of other schemas.
#[serde(flatten, deserialize_with = "skip_if_default")]
pub subschemas: Option<Box<SubschemaValidation>>,
/// Properties of the [`SchemaObject`] which define validation assertions for numbers.
#[serde(flatten, deserialize_with = "skip_if_default")]
pub number: Option<Box<NumberValidation>>,
/// Properties of the [`SchemaObject`] which define validation assertions for strings.
#[serde(flatten, deserialize_with = "skip_if_default")]
pub string: Option<Box<StringValidation>>,
/// Properties of the [`SchemaObject`] which define validation assertions for arrays.
#[serde(flatten, deserialize_with = "skip_if_default")]
pub array: Option<Box<ArrayValidation>>,
/// Properties of the [`SchemaObject`] which define validation assertions for objects.
#[serde(flatten, deserialize_with = "skip_if_default")]
pub object: Option<Box<ObjectValidation>>,
/// 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<String>,
/// Arbitrary extra properties which are not part of the JSON Schema specification, or which `schemars` does not support.
#[serde(flatten)]
pub extensions: Map<String, Value>,
}
// Deserializing "null" to `Option<Value>` directly results in `None`, pub(super) struct OrderedKeywordWrapper<'a>(pub &'a Value);
// this function instead makes it deserialize to `Some(Value::Null)`.
fn allow_null<'de, D>(de: D) -> Result<Option<Value>, D::Error>
where
D: serde::Deserializer<'de>,
{
Value::deserialize(de).map(Option::Some)
}
fn skip_if_default<'de, D, T>(deserializer: D) -> Result<Option<Box<T>>, D::Error> impl Serialize for OrderedKeywordWrapper<'_> {
where fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
D: serde::Deserializer<'de>, where
T: Deserialize<'de> + Default + PartialEq, S: serde::Serializer,
{ {
let value = T::deserialize(deserializer)?; match self.0 {
if value == T::default() { Value::Array(array) => {
Ok(None) let mut seq = serializer.serialize_seq(Some(array.len()))?;
} else { for value in array {
Ok(Some(Box::new(value))) 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 { for key in ORDERED_KEYWORDS_START {
($name:ident, $ret:ty) => { if let Some(value) = object.get(key) {
get_or_insert_default_fn!( map.serialize_entry(key, &OrderedKeywordWrapper(value))?;
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)
}
};
}
impl SchemaObject { for (key, value) in object {
/// Creates a new `$ref` schema. if !ORDERED_KEYWORDS_START.contains(&key.as_str())
/// && !ORDERED_KEYWORDS_END.contains(&key.as_str())
/// 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). map.serialize_entry(key, &OrderedKeywordWrapper(value))?;
pub fn new_ref(reference: String) -> Self { }
SchemaObject { }
reference: Some(reference),
..Default::default()
}
}
/// Returns `true` if `self` is a `$ref` schema. for key in ORDERED_KEYWORDS_END {
/// if let Some(value) = object.get(key) {
/// If `self` has `Some` [`reference`](struct.SchemaObject.html#structfield.reference) set, this returns `true`. map.serialize_entry(key, &OrderedKeywordWrapper(value))?;
/// Otherwise, returns `false`. }
pub fn is_ref(&self) -> bool { }
self.reference.is_some()
}
/// Returns `true` if `self` accepts values of the given type, according to the [`instance_type`](struct.SchemaObject.html#structfield.instance_type) field. map.end()
/// }
/// This is a basic check that always returns `true` if no `instance_type` is specified on the schema, _ => self.0.serialize(serializer),
/// 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<Schema> 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<String>,
/// 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<String>,
/// 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<String>,
/// 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<Value>,
/// 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<Value>,
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn is_false(b: &bool) -> bool {
!b
}
/// Properties of a [`SchemaObject`] which define validation assertions in terms of other schemas.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", default)]
pub struct SubschemaValidation {
/// The `allOf` keyword.
///
/// See [JSON Schema 9.2.1.1. "allOf"](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-9.2.1.1).
#[serde(skip_serializing_if = "Option::is_none")]
pub all_of: Option<Vec<Schema>>,
/// 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<Vec<Schema>>,
/// 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<Vec<Schema>>,
/// 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<Box<Schema>>,
/// 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<Box<Schema>>,
/// 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<Box<Schema>>,
/// 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<Box<Schema>>,
}
/// 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<f64>,
/// 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<f64>,
/// 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<f64>,
/// 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<f64>,
/// 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<f64>,
}
/// 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<u32>,
/// 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<u32>,
/// 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<String>,
}
/// 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<SingleOrVec<Schema>>,
/// 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<Box<Schema>>,
/// 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<u32>,
/// 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<u32>,
/// 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<bool>,
/// 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<Box<Schema>>,
}
/// 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<u32>,
/// 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<u32>,
/// 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<String>,
/// 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<String, Schema>,
/// 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<String, Schema>,
/// 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<Box<Schema>>,
/// 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<Box<Schema>>,
}
/// 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<T> {
Single(Box<T>),
Vec(Vec<T>),
}
impl<T> From<T> for SingleOrVec<T> {
fn from(single: T) -> Self {
SingleOrVec::Single(Box::new(single))
}
}
impl<T> From<Vec<T>> for SingleOrVec<T> {
fn from(vec: Vec<T>) -> Self {
SingleOrVec::Vec(vec)
}
}
impl<T: PartialEq> SingleOrVec<T> {
/// 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),
} }
} }
} }

View file

@ -1,8 +1,7 @@
use crate::schema::*; use crate::gen::SchemaGenerator;
use crate::JsonSchema; use crate::{json_schema, JsonSchema, Schema};
use crate::{gen::SchemaGenerator, Map}; use serde_json::{Error, Map, Value};
use serde_json::{Error, Value}; use std::fmt::Display;
use std::{convert::TryInto, fmt::Display};
pub(crate) struct Serializer<'a> { pub(crate) struct Serializer<'a> {
pub(crate) gen: &'a mut SchemaGenerator, pub(crate) gen: &'a mut SchemaGenerator,
@ -22,7 +21,7 @@ pub(crate) struct SerializeTuple<'a> {
pub(crate) struct SerializeMap<'a> { pub(crate) struct SerializeMap<'a> {
gen: &'a mut SchemaGenerator, gen: &'a mut SchemaGenerator,
properties: Map<String, Schema>, properties: Map<String, Value>,
current_key: Option<String>, current_key: Option<String>,
title: &'static str, title: &'static str,
} }
@ -36,13 +35,11 @@ macro_rules! forward_to_subschema_for {
} }
macro_rules! return_instance_type { 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<Self::Ok, Self::Error> { fn $fn(self, _value: $ty) -> Result<Self::Ok, Self::Error> {
Ok(SchemaObject { Ok(json_schema!({
instance_type: Some(InstanceType::$instance_type.into()), "type": $instance_type
..Default::default() }))
}
.into())
} }
}; };
} }
@ -59,18 +56,18 @@ impl<'a> serde::Serializer for Serializer<'a> {
type SerializeStruct = SerializeMap<'a>; type SerializeStruct = SerializeMap<'a>;
type SerializeStructVariant = Self; type SerializeStructVariant = Self;
return_instance_type!(serialize_i8, i8, Integer); return_instance_type!(serialize_i8, i8, "integer");
return_instance_type!(serialize_i16, i16, Integer); return_instance_type!(serialize_i16, i16, "integer");
return_instance_type!(serialize_i32, i32, Integer); return_instance_type!(serialize_i32, i32, "integer");
return_instance_type!(serialize_i64, i64, Integer); return_instance_type!(serialize_i64, i64, "integer");
return_instance_type!(serialize_i128, i128, Integer); return_instance_type!(serialize_i128, i128, "integer");
return_instance_type!(serialize_u8, u8, Integer); return_instance_type!(serialize_u8, u8, "integer");
return_instance_type!(serialize_u16, u16, Integer); return_instance_type!(serialize_u16, u16, "integer");
return_instance_type!(serialize_u32, u32, Integer); return_instance_type!(serialize_u32, u32, "integer");
return_instance_type!(serialize_u64, u64, Integer); return_instance_type!(serialize_u64, u64, "integer");
return_instance_type!(serialize_u128, u128, Integer); return_instance_type!(serialize_u128, u128, "integer");
return_instance_type!(serialize_f32, f32, Number); return_instance_type!(serialize_f32, f32, "number");
return_instance_type!(serialize_f64, f64, Number); return_instance_type!(serialize_f64, f64, "number");
forward_to_subschema_for!(serialize_bool, bool); forward_to_subschema_for!(serialize_bool, bool);
forward_to_subschema_for!(serialize_char, char); forward_to_subschema_for!(serialize_char, char);
@ -93,7 +90,7 @@ impl<'a> serde::Serializer for Serializer<'a> {
let value_schema = iter let value_schema = iter
.into_iter() .into_iter()
.try_fold(None, |acc, (_, v)| { .try_fold(None, |acc, (_, v)| {
if acc == Some(Schema::Bool(true)) { if acc == Some(true.into()) {
return Ok(acc); return Ok(acc);
} }
@ -103,21 +100,16 @@ impl<'a> serde::Serializer for Serializer<'a> {
})?; })?;
Ok(match &acc { Ok(match &acc {
None => Some(schema), None => Some(schema),
Some(items) if items != &schema => Some(Schema::Bool(true)), Some(items) if items != &schema => Some(true.into()),
_ => acc, _ => acc,
}) })
})? })?
.unwrap_or(Schema::Bool(true)); .unwrap_or(true.into());
Ok(SchemaObject { Ok(json_schema!({
instance_type: Some(InstanceType::Object.into()), "type": "object",
object: Some(Box::new(ObjectValidation { "additionalProperties": value_schema,
additional_properties: Some(Box::new(value_schema)), }))
..ObjectValidation::default()
})),
..SchemaObject::default()
}
.into())
} }
fn serialize_none(self) -> Result<Self::Ok, Self::Error> { fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
@ -132,52 +124,47 @@ impl<'a> serde::Serializer for Serializer<'a> {
where where
T: serde::Serialize, T: serde::Serialize,
{ {
// FIXME nasty duplication of `impl JsonSchema for Option<T>`
fn add_null_type(instance_type: &mut SingleOrVec<InstanceType>) {
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 { let mut schema = value.serialize(Serializer {
gen: self.gen, gen: self.gen,
include_title: false, include_title: false,
})?; })?;
if self.gen.settings().option_add_null_type { if self.gen.settings().option_add_null_type {
schema = match schema { schema = match schema.try_to_object() {
Schema::Bool(true) => Schema::Bool(true), Ok(mut obj) => {
Schema::Bool(false) => <()>::json_schema(self.gen), let value = obj.get_mut("type");
Schema::Object(SchemaObject { match value {
instance_type: Some(ref mut instance_type), Some(Value::Array(array)) => {
.. let null = Value::from("null");
}) => { if !array.contains(&null) {
add_null_type(instance_type); array.push(null);
schema }
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 { Err(true) => true.into(),
subschemas: Some(Box::new(SubschemaValidation { Err(false) => <()>::json_schema(self.gen),
any_of: Some(vec![schema, <()>::json_schema(self.gen)]),
..Default::default()
})),
..Default::default()
}
.into(),
} }
} }
if self.gen.settings().option_nullable { if self.gen.settings().option_nullable {
let mut schema_obj = schema.into_object(); schema
schema_obj .ensure_object()
.extensions .insert("nullable".into(), true.into());
.insert("nullable".to_owned(), serde_json::json!(true));
schema = Schema::Object(schema_obj);
}; };
Ok(schema) Ok(schema)
@ -193,7 +180,7 @@ impl<'a> serde::Serializer for Serializer<'a> {
_variant_index: u32, _variant_index: u32,
_variant: &'static str, _variant: &'static str,
) -> Result<Self::Ok, Self::Error> { ) -> Result<Self::Ok, Self::Error> {
Ok(Schema::Bool(true)) Ok(true.into())
} }
fn serialize_newtype_struct<T: ?Sized>( fn serialize_newtype_struct<T: ?Sized>(
@ -205,15 +192,13 @@ impl<'a> serde::Serializer for Serializer<'a> {
T: serde::Serialize, T: serde::Serialize,
{ {
let include_title = self.include_title; let include_title = self.include_title;
let mut result = value.serialize(self); let mut schema = value.serialize(self)?;
if include_title { if include_title && !name.is_empty() {
if let Ok(Schema::Object(ref mut object)) = result { schema.ensure_object().insert("title".into(), name.into());
object.metadata().title = Some(name.to_string());
}
} }
result Ok(schema)
} }
fn serialize_newtype_variant<T: ?Sized>( fn serialize_newtype_variant<T: ?Sized>(
@ -226,7 +211,7 @@ impl<'a> serde::Serializer for Serializer<'a> {
where where
T: serde::Serialize, T: serde::Serialize,
{ {
Ok(Schema::Bool(true)) Ok(true.into())
} }
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> { fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
@ -313,7 +298,7 @@ impl serde::ser::SerializeTupleVariant for Serializer<'_> {
} }
fn end(self) -> Result<Self::Ok, Self::Error> { fn end(self) -> Result<Self::Ok, Self::Error> {
Ok(Schema::Bool(true)) Ok(true.into())
} }
} }
@ -333,7 +318,7 @@ impl serde::ser::SerializeStructVariant for Serializer<'_> {
} }
fn end(self) -> Result<Self::Ok, Self::Error> { fn end(self) -> Result<Self::Ok, Self::Error> {
Ok(Schema::Bool(true)) Ok(true.into())
} }
} }
@ -345,7 +330,7 @@ impl serde::ser::SerializeSeq for SerializeSeq<'_> {
where where
T: serde::Serialize, T: serde::Serialize,
{ {
if self.items != Some(Schema::Bool(true)) { if self.items != Some(true.into()) {
let schema = value.serialize(Serializer { let schema = value.serialize(Serializer {
gen: self.gen, gen: self.gen,
include_title: false, include_title: false,
@ -354,7 +339,7 @@ impl serde::ser::SerializeSeq for SerializeSeq<'_> {
None => self.items = Some(schema), None => self.items = Some(schema),
Some(items) => { Some(items) => {
if items != &schema { 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<Self::Ok, Self::Error> { fn end(self) -> Result<Self::Ok, Self::Error> {
let items = self.items.unwrap_or(Schema::Bool(true)); let items = self.items.unwrap_or(true.into());
Ok(SchemaObject {
instance_type: Some(InstanceType::Array.into()), Ok(json_schema!({
array: Some(Box::new(ArrayValidation { "type": "array",
items: Some(items.into()), "items": items
..ArrayValidation::default() }))
})),
..SchemaObject::default()
}
.into())
} }
} }
@ -394,23 +375,21 @@ impl serde::ser::SerializeTuple for SerializeTuple<'_> {
} }
fn end(self) -> Result<Self::Ok, Self::Error> { fn end(self) -> Result<Self::Ok, Self::Error> {
let len = self.items.len().try_into().ok(); let len = self.items.len();
let mut schema = SchemaObject { let mut schema = json_schema!({
instance_type: Some(InstanceType::Array.into()), "type": "array",
array: Some(Box::new(ArrayValidation { "items": self.items,
items: Some(SingleOrVec::Vec(self.items)), "maxItems": len,
max_items: len, "minItems": len,
min_items: len, });
..ArrayValidation::default()
})),
..SchemaObject::default()
};
if !self.title.is_empty() { 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, gen: self.gen,
include_title: false, include_title: false,
})?; })?;
self.properties.insert(key, schema); self.properties.insert(key, schema.into());
Ok(()) Ok(())
} }
fn end(self) -> Result<Self::Ok, Self::Error> { fn end(self) -> Result<Self::Ok, Self::Error> {
let mut schema = SchemaObject { let mut schema = json_schema!({
instance_type: Some(InstanceType::Object.into()), "type": "object",
object: Some(Box::new(ObjectValidation { "properties": self.properties,
properties: self.properties, });
..ObjectValidation::default()
})),
..SchemaObject::default()
};
if !self.title.is_empty() { 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, gen: self.gen,
include_title: false, include_title: false,
})?; })?;
self.properties.insert(key.to_string(), prop_schema); self.properties.insert(key.to_string(), prop_schema.into());
Ok(()) Ok(())
} }

View file

@ -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. When overriding one of these methods, you will *usually* want to still call this default implementation.
# Example # Example
To add a custom property to all schemas: To add a custom property to all object schemas:
``` ```
use schemars::schema::SchemaObject; use schemars::Schema;
use schemars::visit::{Visitor, visit_schema_object}; use schemars::visit::{Visitor, visit_schema};
pub struct MyVisitor; pub struct MyVisitor;
impl Visitor for 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 // 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 // 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. /// Trait used to recursively modify a constructed schema and its subschemas.
pub trait Visitor { 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. /// 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. /// 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) { fn visit_schema(&mut self, schema: &mut Schema) {
visit_schema(self, 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: Visitor + ?Sized>(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`]. /// Visits all subschemas of the [`Schema`].
pub fn visit_schema<V: Visitor + ?Sized>(v: &mut V, schema: &mut Schema) { pub fn visit_schema<V: Visitor + ?Sized>(v: &mut V, schema: &mut Schema) {
if let Schema::Object(schema) = schema { if let Some(obj) = schema.as_object_mut() {
v.visit_schema_object(schema) for (key, value) in obj {
} match key.as_str() {
} "not"
| "if"
/// Visits all subschemas of the [`SchemaObject`]. | "then"
pub fn visit_schema_object<V: Visitor + ?Sized>(v: &mut V, schema: &mut SchemaObject) { | "else"
if let Some(sub) = &mut schema.subschemas { | "additionalItems"
visit_vec(v, &mut sub.all_of); | "contains"
visit_vec(v, &mut sub.any_of); | "additionalProperties"
visit_vec(v, &mut sub.one_of); | "propertyNames" => {
visit_box(v, &mut sub.not); if let Ok(subschema) = value.try_into() {
visit_box(v, &mut sub.if_schema); v.visit_schema(subschema)
visit_box(v, &mut sub.then_schema); }
visit_box(v, &mut sub.else_schema); }
} "allOf" | "anyOf" | "oneOf" => {
if let Some(array) = value.as_array_mut() {
if let Some(arr) = &mut schema.array { for value in array {
visit_single_or_vec(v, &mut arr.items); if let Ok(subschema) = value.try_into() {
visit_box(v, &mut arr.additional_items); v.visit_schema(subschema)
visit_box(v, &mut arr.contains); }
} }
}
if let Some(obj) = &mut schema.object { }
visit_map_values(v, &mut obj.properties); "items" => {
visit_map_values(v, &mut obj.pattern_properties); if let Some(array) = value.as_array_mut() {
visit_box(v, &mut obj.additional_properties); for value in array {
visit_box(v, &mut obj.property_names); if let Ok(subschema) = value.try_into() {
} v.visit_schema(subschema)
} }
}
fn visit_box<V: Visitor + ?Sized>(v: &mut V, target: &mut Option<Box<Schema>>) { } else if let Ok(subschema) = value.try_into() {
if let Some(s) = target { v.visit_schema(subschema)
v.visit_schema(s) }
} }
} "properties" | "patternProperties" | "definitions" | "$defs" => {
if let Some(obj) = value.as_object_mut() {
fn visit_vec<V: Visitor + ?Sized>(v: &mut V, target: &mut Option<Vec<Schema>>) { for value in obj.values_mut() {
if let Some(vec) = target { if let Ok(subschema) = value.try_into() {
for s in vec { v.visit_schema(subschema)
v.visit_schema(s) }
} }
} }
} }
_ => {}
fn visit_map_values<V: Visitor + ?Sized>(v: &mut V, target: &mut crate::Map<String, Schema>) {
for s in target.values_mut() {
v.visit_schema(s)
}
}
fn visit_single_or_vec<V: Visitor + ?Sized>(v: &mut V, target: &mut Option<SingleOrVec<Schema>>) {
match target {
None => {}
Some(SingleOrVec::Single(s)) => v.visit_schema(s),
Some(SingleOrVec::Vec(vec)) => {
for s in vec {
v.visit_schema(s)
} }
} }
} }
@ -133,29 +104,23 @@ pub struct ReplaceBoolSchemas {
impl Visitor for ReplaceBoolSchemas { impl Visitor for ReplaceBoolSchemas {
fn visit_schema(&mut self, schema: &mut Schema) { 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 { if let Some(obj) = schema.as_object_mut() {
*schema = Schema::Bool(b).into_object().into() obj.insert(ap_key, ap_value);
}
}
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;
} }
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; pub struct RemoveRefSiblings;
impl Visitor for RemoveRefSiblings { impl Visitor for RemoveRefSiblings {
fn visit_schema_object(&mut self, schema: &mut SchemaObject) { fn visit_schema(&mut self, schema: &mut Schema) {
visit_schema_object(self, schema); visit_schema(self, schema);
if let Some(reference) = schema.reference.take() { if let Some(obj) = schema.as_object_mut() {
if schema == &SchemaObject::default() { if obj.len() > 1 {
schema.reference = Some(reference); if let Some(ref_value) = obj.remove("$ref") {
} else { if let Value::Array(all_of) =
let ref_schema = Schema::new_ref(reference); obj.entry("allOf").or_insert(Value::Array(Vec::new()))
let all_of = &mut schema.subschemas().all_of; {
match all_of { all_of.push(json!({
Some(vec) => vec.push(ref_schema), "$ref": ref_value
None => *all_of = Some(vec![ref_schema]), }));
}
} }
} }
} }
@ -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. /// This is useful for dialects of JSON Schema (e.g. OpenAPI 3.0) that do not support the `examples` property.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SetSingleExample { pub struct SetSingleExample;
/// When set to `true`, the `examples` property will not be removed, but its first value will still be copied to `example`.
pub retain_examples: bool,
}
impl Visitor for SetSingleExample { impl Visitor for SetSingleExample {
fn visit_schema_object(&mut self, schema: &mut SchemaObject) { fn visit_schema(&mut self, schema: &mut Schema) {
visit_schema_object(self, schema); visit_schema(self, schema);
let first_example = schema.metadata.as_mut().and_then(|m| { if let Some(obj) = schema.as_object_mut() {
if self.retain_examples { if let Some(Value::Array(examples)) = obj.remove("examples") {
m.examples.first().cloned() if let Some(first_example) = examples.into_iter().next() {
} else { obj.insert("example".into(), first_example);
m.examples.drain(..).next() }
} }
});
if let Some(example) = first_example {
schema.extensions.insert("example".to_owned(), example);
} }
} }
} }

View file

@ -1,16 +1,6 @@
mod util; mod util;
use util::*; use util::*;
#[test]
fn arrayvec05() -> TestResult {
test_default_generated_schema::<arrayvec05::ArrayVec<[i32; 16]>>("arrayvec")
}
#[test]
fn arrayvec05_string() -> TestResult {
test_default_generated_schema::<arrayvec05::ArrayString<[u8; 16]>>("arrayvec_string")
}
#[test] #[test]
fn arrayvec07() -> TestResult { fn arrayvec07() -> TestResult {
test_default_generated_schema::<arrayvec07::ArrayVec<i32, 16>>("arrayvec") test_default_generated_schema::<arrayvec07::ArrayVec<i32, 16>>("arrayvec")

View file

@ -1,5 +1,5 @@
mod util; mod util;
use bytes::{Bytes, BytesMut}; use bytes1::{Bytes, BytesMut};
use util::*; use util::*;
#[test] #[test]

View file

@ -1,5 +1,5 @@
mod util; mod util;
use chrono::prelude::*; use chrono04::prelude::*;
use schemars::JsonSchema; use schemars::JsonSchema;
use util::*; use util::*;

View file

@ -3,12 +3,7 @@ use util::*;
#[test] #[test]
fn rust_decimal() -> TestResult { fn rust_decimal() -> TestResult {
test_default_generated_schema::<rust_decimal::Decimal>("rust_decimal") test_default_generated_schema::<rust_decimal1::Decimal>("rust_decimal")
}
#[test]
fn bigdecimal03() -> TestResult {
test_default_generated_schema::<bigdecimal03::BigDecimal>("bigdecimal03")
} }
#[test] #[test]

View file

@ -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::<Struct>();
let struct_schema = gen.definitions().get(&<Struct>::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));
}

View file

@ -1,5 +1,5 @@
mod util; mod util;
use either::Either; use either1::Either;
use util::*; use util::*;
#[test] #[test]

View file

@ -1,5 +1,7 @@
mod util; mod util;
use schemars::{JsonSchema, Map}; use std::collections::BTreeMap;
use schemars::JsonSchema;
use util::*; use util::*;
// Ensure that schemars_derive uses the full path to std::string::String // Ensure that schemars_derive uses the full path to std::string::String
@ -20,7 +22,7 @@ struct Struct {
#[schemars(rename_all = "camelCase")] #[schemars(rename_all = "camelCase")]
enum External { enum External {
UnitOne, UnitOne,
StringMap(Map<&'static str, &'static str>), StringMap(BTreeMap<&'static str, &'static str>),
UnitStructNewType(UnitStruct), UnitStructNewType(UnitStruct),
StructNewType(Struct), StructNewType(Struct),
Struct { Struct {
@ -29,6 +31,7 @@ enum External {
}, },
UnitTwo, UnitTwo,
Tuple(i32, bool), Tuple(i32, bool),
// FIXME this should probably only replace the "payload" of the enum
#[schemars(with = "i32")] #[schemars(with = "i32")]
WithInt, WithInt,
} }
@ -43,7 +46,7 @@ fn enum_external_tag() -> TestResult {
#[schemars(tag = "typeProperty")] #[schemars(tag = "typeProperty")]
enum Internal { enum Internal {
UnitOne, UnitOne,
StringMap(Map<&'static str, &'static str>), StringMap(BTreeMap<&'static str, &'static str>),
UnitStructNewType(UnitStruct), UnitStructNewType(UnitStruct),
StructNewType(Struct), StructNewType(Struct),
Struct { Struct {
@ -51,6 +54,7 @@ enum Internal {
bar: bool, bar: bool,
}, },
UnitTwo, UnitTwo,
// FIXME this should probably only replace the "payload" of the enum
#[schemars(with = "i32")] #[schemars(with = "i32")]
WithInt, WithInt,
} }
@ -65,7 +69,7 @@ fn enum_internal_tag() -> TestResult {
#[schemars(untagged)] #[schemars(untagged)]
enum Untagged { enum Untagged {
UnitOne, UnitOne,
StringMap(Map<&'static str, &'static str>), StringMap(BTreeMap<&'static str, &'static str>),
UnitStructNewType(UnitStruct), UnitStructNewType(UnitStruct),
StructNewType(Struct), StructNewType(Struct),
Struct { Struct {
@ -73,6 +77,7 @@ enum Untagged {
bar: bool, bar: bool,
}, },
Tuple(i32, bool), Tuple(i32, bool),
// FIXME this should probably only replace the "payload" of the enum
#[schemars(with = "i32")] #[schemars(with = "i32")]
WithInt, WithInt,
} }
@ -87,7 +92,7 @@ fn enum_untagged() -> TestResult {
#[schemars(tag = "t", content = "c")] #[schemars(tag = "t", content = "c")]
enum Adjacent { enum Adjacent {
UnitOne, UnitOne,
StringMap(Map<&'static str, &'static str>), StringMap(BTreeMap<&'static str, &'static str>),
UnitStructNewType(UnitStruct), UnitStructNewType(UnitStruct),
StructNewType(Struct), StructNewType(Struct),
Struct { Struct {
@ -96,6 +101,7 @@ enum Adjacent {
}, },
Tuple(i32, bool), Tuple(i32, bool),
UnitTwo, UnitTwo,
// FIXME this should probably only replace the "payload" of the enum
#[schemars(with = "i32")] #[schemars(with = "i32")]
WithInt, WithInt,
} }

View file

@ -1,5 +1,7 @@
mod util; mod util;
use schemars::{JsonSchema, Map}; use std::collections::BTreeMap;
use schemars::JsonSchema;
use util::*; use util::*;
// Ensure that schemars_derive uses the full path to std::string::String // 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)] #[schemars(rename_all = "camelCase", deny_unknown_fields)]
enum External { enum External {
UnitOne, UnitOne,
StringMap(Map<&'static str, &'static str>), StringMap(BTreeMap<&'static str, &'static str>),
UnitStructNewType(UnitStruct), UnitStructNewType(UnitStruct),
StructNewType(Struct), StructNewType(Struct),
Struct { Struct {
@ -31,6 +33,7 @@ enum External {
}, },
UnitTwo, UnitTwo,
Tuple(i32, bool), Tuple(i32, bool),
// FIXME this should probably only replace the "payload" of the enum
#[schemars(with = "i32")] #[schemars(with = "i32")]
WithInt, WithInt,
} }
@ -46,7 +49,7 @@ fn enum_external_tag() -> TestResult {
#[schemars(tag = "typeProperty", deny_unknown_fields)] #[schemars(tag = "typeProperty", deny_unknown_fields)]
enum Internal { enum Internal {
UnitOne, UnitOne,
StringMap(Map<&'static str, &'static str>), StringMap(BTreeMap<&'static str, &'static str>),
UnitStructNewType(UnitStruct), UnitStructNewType(UnitStruct),
StructNewType(Struct), StructNewType(Struct),
Struct { Struct {
@ -54,6 +57,7 @@ enum Internal {
bar: bool, bar: bool,
}, },
UnitTwo, UnitTwo,
// FIXME this should only replace the "payload" of the enum (which doesn't even make sense for unit enums!)
#[schemars(with = "i32")] #[schemars(with = "i32")]
WithInt, WithInt,
} }
@ -69,7 +73,7 @@ fn enum_internal_tag() -> TestResult {
#[schemars(untagged, deny_unknown_fields)] #[schemars(untagged, deny_unknown_fields)]
enum Untagged { enum Untagged {
UnitOne, UnitOne,
StringMap(Map<&'static str, &'static str>), StringMap(BTreeMap<&'static str, &'static str>),
UnitStructNewType(UnitStruct), UnitStructNewType(UnitStruct),
StructNewType(Struct), StructNewType(Struct),
Struct { Struct {
@ -77,6 +81,7 @@ enum Untagged {
bar: bool, bar: bool,
}, },
Tuple(i32, bool), Tuple(i32, bool),
// FIXME this should probably only replace the "payload" of the enum
#[schemars(with = "i32")] #[schemars(with = "i32")]
WithInt, WithInt,
} }
@ -92,7 +97,7 @@ fn enum_untagged() -> TestResult {
#[schemars(tag = "t", content = "c", deny_unknown_fields)] #[schemars(tag = "t", content = "c", deny_unknown_fields)]
enum Adjacent { enum Adjacent {
UnitOne, UnitOne,
StringMap(Map<&'static str, &'static str>), StringMap(BTreeMap<&'static str, &'static str>),
UnitStructNewType(UnitStruct), UnitStructNewType(UnitStruct),
StructNewType(Struct), StructNewType(Struct),
Struct { Struct {
@ -101,6 +106,7 @@ enum Adjacent {
}, },
Tuple(i32, bool), Tuple(i32, bool),
UnitTwo, UnitTwo,
// FIXME this should probably only replace the "payload" of the enum
#[schemars(with = "i32")] #[schemars(with = "i32")]
WithInt, WithInt,
} }

View file

@ -1,8 +1,11 @@
mod util; mod util;
use enumset::{EnumSet, EnumSetType}; use enumset1::{EnumSet, EnumSetType};
use schemars::JsonSchema; use schemars::JsonSchema;
use util::*; use util::*;
// needed to derive EnumSetType when using a crate alias
extern crate enumset1 as enumset;
#[derive(EnumSetType, JsonSchema)] #[derive(EnumSetType, JsonSchema)]
enum Foo { enum Foo {
Bar, Bar,

View file

@ -1,5 +1,5 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"title": "String", "title": "string",
"type": "string" "type": "string"
} }

View file

@ -1,6 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Decimal",
"type": "string",
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
}

View file

@ -1,6 +1,6 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema#", "$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": [ "anyOf": [
{ {
"type": "integer", "type": "integer",

View file

@ -127,8 +127,7 @@
"WithInt" "WithInt"
] ]
} }
}, }
"additionalProperties": false
} }
] ]
} }

View file

@ -15,7 +15,7 @@
"$ref": "#/definitions/Range_of_double" "$ref": "#/definitions/Range_of_double"
}, },
"bound": { "bound": {
"$ref": "#/definitions/Bound_of_String" "$ref": "#/definitions/Bound_of_string"
} }
}, },
"definitions": { "definitions": {
@ -55,7 +55,7 @@
} }
} }
}, },
"Bound_of_String": { "Bound_of_string": {
"oneOf": [ "oneOf": [
{ {
"type": "object", "type": "object",

View file

@ -10,10 +10,10 @@
], ],
"properties": { "properties": {
"byte_or_bool2": { "byte_or_bool2": {
"$ref": "#/definitions/Or_for_uint8_and_Boolean" "$ref": "#/definitions/Or_for_uint8_and_boolean"
}, },
"unit_or_t2": { "unit_or_t2": {
"$ref": "#/definitions/Or_for_Null_and_int32" "$ref": "#/definitions/Or_for_null_and_int32"
}, },
"s": { "s": {
"$ref": "#/definitions/Str" "$ref": "#/definitions/Str"
@ -30,7 +30,7 @@
} }
}, },
"definitions": { "definitions": {
"Or_for_uint8_and_Boolean": { "Or_for_uint8_and_boolean": {
"anyOf": [ "anyOf": [
{ {
"type": "integer", "type": "integer",
@ -42,7 +42,7 @@
} }
] ]
}, },
"Or_for_Null_and_int32": { "Or_for_null_and_int32": {
"anyOf": [ "anyOf": [
{ {
"type": "null" "type": "null"

View file

@ -8,14 +8,14 @@
], ],
"properties": { "properties": {
"result1": { "result1": {
"$ref": "#/definitions/Result_of_MyStruct_or_Array_of_String" "$ref": "#/definitions/Result_of_MyStruct_or_Array_of_string"
}, },
"result2": { "result2": {
"$ref": "#/definitions/Result_of_Boolean_or_Null" "$ref": "#/definitions/Result_of_boolean_or_null"
} }
}, },
"definitions": { "definitions": {
"Result_of_MyStruct_or_Array_of_String": { "Result_of_MyStruct_or_Array_of_string": {
"oneOf": [ "oneOf": [
{ {
"type": "object", "type": "object",
@ -56,7 +56,7 @@
} }
} }
}, },
"Result_of_Boolean_or_Null": { "Result_of_boolean_or_null": {
"oneOf": [ "oneOf": [
{ {
"type": "object", "type": "object",

View file

@ -1,6 +1,6 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema#", "$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", "type": "object",
"required": [ "required": [
"inner", "inner",

View file

@ -1,15 +1,11 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema#", "$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", "type": "object",
"required": [
"inner",
"t",
"u",
"v",
"w"
],
"properties": { "properties": {
"inner": {
"$ref": "#/definitions/MySimpleStruct"
},
"t": { "t": {
"type": "integer", "type": "integer",
"format": "int32" "format": "int32"
@ -25,23 +21,27 @@
"items": { "items": {
"type": "string" "type": "string"
} }
},
"inner": {
"$ref": "#/definitions/MySimpleStruct"
} }
}, },
"required": [
"inner",
"t",
"u",
"v",
"w"
],
"definitions": { "definitions": {
"MySimpleStruct": { "MySimpleStruct": {
"type": "object", "type": "object",
"required": [
"foo"
],
"properties": { "properties": {
"foo": { "foo": {
"type": "integer", "type": "integer",
"format": "int32" "format": "int32"
} }
} },
"required": [
"foo"
]
} }
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema#", "$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", "type": "object",
"required": [ "required": [
"foo", "foo",
@ -12,7 +12,7 @@
"format": "int32" "format": "int32"
}, },
"generic": { "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": { "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", "type": "object",
"required": [ "required": [
"inner", "inner",

View file

@ -22,8 +22,8 @@
}, },
{ {
"type": [ "type": [
"boolean", "object",
"object" "boolean"
], ],
"required": [ "required": [
"typeProperty" "typeProperty"
@ -39,8 +39,8 @@
}, },
{ {
"type": [ "type": [
"boolean", "object",
"object" "boolean"
], ],
"required": [ "required": [
"typeProperty" "typeProperty"

View file

@ -1,6 +1,6 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"title": "Array_of_String", "title": "Array_of_string",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"

View file

@ -1,5 +1,5 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"title": "String", "title": "string",
"type": "string" "type": "string"
} }

View file

@ -1,13 +1,15 @@
mod util; mod util;
use indexmap::{IndexMap, IndexSet}; use std::hash::RandomState;
use indexmap2::{IndexMap, IndexSet};
use schemars::JsonSchema; use schemars::JsonSchema;
use util::*; use util::*;
#[allow(dead_code)] #[allow(dead_code)]
#[derive(JsonSchema)] #[derive(JsonSchema)]
struct IndexMapTypes { struct IndexMapTypes {
map: IndexMap<i32, bool>, map: IndexMap<i32, bool, RandomState>,
set: IndexSet<isize>, set: IndexSet<isize, RandomState>,
} }
#[test] #[test]

View file

@ -1,16 +0,0 @@
mod util;
use indexmap2::{IndexMap, IndexSet};
use schemars::JsonSchema;
use util::*;
#[allow(dead_code)]
#[derive(JsonSchema)]
struct IndexMapTypes {
map: IndexMap<i32, bool>,
set: IndexSet<isize>,
}
#[test]
fn indexmap_types() -> TestResult {
test_default_generated_schema::<IndexMapTypes>("indexmap")
}

View file

@ -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::<RootSchema>("schema", SchemaSettings::draft07())
}
#[test]
fn schema_matches_2019_09() -> TestResult {
test_generated_schema::<RootSchema>("schema-2019_09", SchemaSettings::draft2019_09())
}
#[test]
fn schema_matches_openapi3() -> TestResult {
test_generated_schema::<RootSchema>("schema-openapi3", SchemaSettings::openapi3())
}

View file

@ -2,7 +2,7 @@ mod util;
use schemars::JsonSchema; use schemars::JsonSchema;
use util::*; use util::*;
fn schema_fn(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { fn schema_fn(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Schema {
<bool>::json_schema(gen) <bool>::json_schema(gen)
} }
@ -21,6 +21,7 @@ pub enum External {
#[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema, #[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema,
i32, i32,
), ),
// FIXME this should probably only replace the "payload" of the enum
#[schemars(schema_with = "schema_fn")] #[schemars(schema_with = "schema_fn")]
Unit, Unit,
} }
@ -38,6 +39,7 @@ pub enum Internal {
foo: DoesntImplementJsonSchema, foo: DoesntImplementJsonSchema,
}, },
NewType(#[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema), NewType(#[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema),
// FIXME this should probably only replace the "payload" of the enum
#[schemars(schema_with = "schema_fn")] #[schemars(schema_with = "schema_fn")]
Unit, Unit,
} }
@ -59,6 +61,7 @@ pub enum Untagged {
#[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema, #[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema,
i32, i32,
), ),
// FIXME this should probably only replace the "payload" of the enum
#[schemars(schema_with = "schema_fn")] #[schemars(schema_with = "schema_fn")]
Unit, Unit,
} }
@ -80,6 +83,7 @@ pub enum Adjacent {
#[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema, #[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema,
i32, i32,
), ),
// FIXME this should probably only replace the "payload" of the enum
#[schemars(schema_with = "schema_fn")] #[schemars(schema_with = "schema_fn")]
Unit, Unit,
} }

View file

@ -2,7 +2,7 @@ mod util;
use schemars::JsonSchema; use schemars::JsonSchema;
use util::*; use util::*;
fn schema_fn(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { fn schema_fn(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Schema {
<bool>::json_schema(gen) <bool>::json_schema(gen)
} }

View file

@ -1,6 +1,6 @@
mod util; mod util;
use schemars::JsonSchema; use schemars::JsonSchema;
use semver::Version; use semver1::Version;
use util::*; use util::*;
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -1,5 +1,5 @@
mod util; mod util;
use smallvec::SmallVec; use smallvec1::SmallVec;
use util::*; use util::*;
#[test] #[test]

View file

@ -1,5 +1,5 @@
mod util; mod util;
use smol_str::SmolStr; use smol_str02::SmolStr;
use util::*; use util::*;
#[test] #[test]

View file

@ -1,6 +1,6 @@
mod util; mod util;
use schemars::JsonSchema; use schemars::JsonSchema;
use url::Url; use url2::Url;
use util::*; use util::*;
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -1,5 +1,6 @@
use pretty_assertions::assert_eq; 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::error::Error;
use std::fs; use std::fs;
@ -17,7 +18,16 @@ pub fn test_default_generated_schema<T: JsonSchema>(file: &str) -> TestResult {
test_schema(&actual, file) 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)) { let expected_json = match fs::read_to_string(format!("tests/expected/{}.json", file)) {
Ok(j) => j, Ok(j) => j,
Err(e) => { Err(e) => {
@ -35,8 +45,30 @@ pub fn test_schema(actual: &RootSchema, file: &str) -> TestResult {
Ok(()) 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)?; let actual_json = serde_json::to_string_pretty(&schema)?;
fs::write(format!("tests/actual/{}.json", file), actual_json)?; fs::write(format!("tests/actual/{}.json", file), actual_json)?;
Ok(()) 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();
}
}
}
}
}
}

View file

@ -1,11 +1,6 @@
mod util; mod util;
use util::*; use util::*;
#[test]
fn uuid08() -> TestResult {
test_default_generated_schema::<uuid08::Uuid>("uuid")
}
#[test] #[test]
fn uuid1() -> TestResult { fn uuid1() -> TestResult {
test_default_generated_schema::<uuid1::Uuid>("uuid") test_default_generated_schema::<uuid1::Uuid>("uuid")

View file

@ -325,119 +325,85 @@ impl ValidationAttrs {
} }
pub fn apply_to_schema(&self, schema_expr: &mut TokenStream) { pub fn apply_to_schema(&self, schema_expr: &mut TokenStream) {
if let Some(apply_expr) = self.apply_to_schema_expr() { let setters = self.make_setters(quote!(&mut schema));
*schema_expr = quote! { if !setters.is_empty() {
{ *schema_expr = quote!({
let mut schema = #schema_expr; let mut schema = #schema_expr;
#apply_expr #(#setters)*
schema schema
} });
}
} }
} }
fn apply_to_schema_expr(&self) -> Option<TokenStream> { fn make_setters(&self, mut_schema: impl ToTokens) -> Vec<TokenStream> {
let mut array_validation = Vec::new(); let mut result = Vec::new();
let mut number_validation = Vec::new();
let mut object_validation = Vec::new();
let mut string_validation = Vec::new();
if let Some(length_min) = self.length_min.as_ref().or(self.length_equal.as_ref()) { if let Some(length_min) = self.length_min.as_ref().or(self.length_equal.as_ref()) {
string_validation.push(quote! { result.push(quote! {
validation.min_length = Some(#length_min as u32); schemars::_private::insert_validation_property(#mut_schema, "string", "minLength", #length_min);
}); });
array_validation.push(quote! { result.push(quote! {
validation.min_items = Some(#length_min as u32); 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()) { if let Some(length_max) = self.length_max.as_ref().or(self.length_equal.as_ref()) {
string_validation.push(quote! { result.push(quote! {
validation.max_length = Some(#length_max as u32); schemars::_private::insert_validation_property(#mut_schema, "string", "maxLength", #length_max);
}); });
array_validation.push(quote! { result.push(quote! {
validation.max_items = Some(#length_max as u32); schemars::_private::insert_validation_property(#mut_schema, "array", "maxItems", #length_max);
}); });
} }
if let Some(range_min) = &self.range_min { if let Some(range_min) = &self.range_min {
number_validation.push(quote! { result.push(quote! {
validation.minimum = Some(#range_min as f64); schemars::_private::insert_validation_property(#mut_schema, "number", "minimum", #range_min);
}); });
} }
if let Some(range_max) = &self.range_max { if let Some(range_max) = &self.range_max {
number_validation.push(quote! { result.push(quote! {
validation.maximum = Some(#range_max as f64); schemars::_private::insert_validation_property(#mut_schema, "number", "maximum", #range_max);
}); });
} }
if let Some(regex) = &self.regex { if let Some(regex) = &self.regex {
string_validation.push(quote! { result.push(quote! {
validation.pattern = Some(#regex.to_string()); schemars::_private::insert_validation_property(#mut_schema, "string", "pattern", #regex);
}); });
} }
if let Some(contains) = &self.contains { if let Some(contains) = &self.contains {
object_validation.push(quote! { result.push(quote! {
validation.required.insert(#contains.to_string()); schemars::_private::append_required(#mut_schema, #contains);
}); });
if self.regex.is_none() { if self.regex.is_none() {
let pattern = crate::regex_syntax::escape(contains); let pattern = crate::regex_syntax::escape(contains);
string_validation.push(quote! { result.push(quote! {
validation.pattern = Some(#pattern.to_string()); schemars::_private::insert_validation_property(#mut_schema, "string", "pattern", #pattern);
}); });
} }
} }
let format = self.format.as_ref().map(|f| { if let Some(format) = &self.format {
let f = f.schema_str(); let f = format.schema_str();
quote! { result.push(quote! {
schema_object.format = Some(#f.to_string()); schema.ensure_object().insert("format".to_owned(), #f.into());
}
});
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
}
}) })
} 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<TokenStream>) -> Option<TokenStream> {
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<TokenStream>) -> Option<TokenStream> {
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<TokenStream>) -> Option<TokenStream> {
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<TokenStream>) -> Option<TokenStream> {
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<Expr> { fn str_or_num_to_expr(cx: &Ctxt, meta_item_name: &str, expr: Expr) -> Option<Expr> {
// this odd double-parsing is to make `-10` parsed as an Lit instead of an Expr::Unary // 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()) { let lit: Lit = match syn::parse2(expr.to_token_stream()) {

View file

@ -68,11 +68,11 @@ fn derive_json_schema(mut input: syn::DeriveInput, repr: bool) -> syn::Result<To
<#ty as schemars::JsonSchema>::schema_id() <#ty as schemars::JsonSchema>::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) <#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) <#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<To
#schema_id #schema_id
} }
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Schema {
#schema_expr #schema_expr
} }
}; };

View file

@ -13,32 +13,46 @@ pub struct SchemaMetadata<'a> {
impl<'a> SchemaMetadata<'a> { impl<'a> SchemaMetadata<'a> {
pub fn apply_to_schema(&self, schema_expr: &mut TokenStream) { 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<TokenStream> {
let mut setters = Vec::<TokenStream>::new();
if let Some(title) = &self.title { if let Some(title) = &self.title {
*schema_expr = quote! { setters.push(quote! {
schemars::_private::metadata::add_title(#schema_expr, #title) obj.insert("title".to_owned(), #title.into());
}; });
} }
if let Some(description) = &self.description { if let Some(description) = &self.description {
*schema_expr = quote! { setters.push(quote! {
schemars::_private::metadata::add_description(#schema_expr, #description) obj.insert("description".to_owned(), #description.into());
}; });
} }
if self.deprecated { if self.deprecated {
*schema_expr = quote! { setters.push(quote! {
schemars::_private::metadata::add_deprecated(#schema_expr, true) obj.insert("deprecated".to_owned(), true.into());
}; });
} }
if self.read_only { if self.read_only {
*schema_expr = quote! { setters.push(quote! {
schemars::_private::metadata::add_read_only(#schema_expr, true) obj.insert("readOnly".to_owned(), true.into());
}; });
} }
if self.write_only { if self.write_only {
*schema_expr = quote! { setters.push(quote! {
schemars::_private::metadata::add_write_only(#schema_expr, true) obj.insert("writeOnly".to_owned(), true.into());
}; });
} }
if !self.examples.is_empty() { if !self.examples.is_empty() {
@ -47,16 +61,19 @@ impl<'a> SchemaMetadata<'a> {
schemars::_serde_json::value::to_value(#eg()) schemars::_serde_json::value::to_value(#eg())
} }
}); });
setters.push(quote! {
*schema_expr = quote! { obj.insert("examples".to_owned(), schemars::_serde_json::Value::Array([#(#examples),*].into_iter().flatten().collect()));
schemars::_private::metadata::add_examples(#schema_expr, [#(#examples),*].into_iter().flatten()) });
};
} }
if let Some(default) = &self.default { if let Some(default) = &self.default {
*schema_expr = quote! { setters.push(quote! {
schemars::_private::metadata::add_default(#schema_expr, #default.and_then(|d| schemars::_schemars_maybe_to_value!(d))) if let Some(default) = #default.and_then(|d| schemars::_schemars_maybe_to_value!(d)) {
}; obj.insert("default".to_owned(), default);
}
});
} }
setters
} }
} }

View file

@ -49,9 +49,18 @@ pub fn expr_for_repr(cont: &Container) -> Result<TokenStream, syn::Error> {
let enum_ident = &cont.ident; let enum_ident = &cont.ident;
let variant_idents = variants.iter().map(|v| &v.ident); let variant_idents = variants.iter().map(|v| &v.ident);
let mut schema_expr = schema_object(quote! { let mut schema_expr = quote!({
instance_type: Some(schemars::schema::InstanceType::Integer.into()), let mut map = schemars::_serde_json::Map::new();
enum_values: Some(vec![#((#enum_ident::#variant_idents as #repr_type).into()),*]), 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); cont.attrs.as_metadata().apply_to_schema(&mut schema_expr);
@ -118,7 +127,7 @@ fn type_for_schema(with_attr: &WithAttr) -> (syn::Type, Option<TokenStream>) {
)) ))
} }
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Schema {
#fun(gen) #fun(gen)
} }
} }
@ -160,9 +169,18 @@ fn expr_for_external_tagged_enum<'a>(
}) })
.partition(|v| v.is_unit() && v.attrs.is_default()); .partition(|v| v.is_unit() && v.attrs.is_default());
let unit_names = unit_variants.iter().map(|v| v.name()); let unit_names = unit_variants.iter().map(|v| v.name());
let unit_schema = schema_object(quote! { let unit_schema = quote!({
instance_type: Some(schemars::schema::InstanceType::String.into()), let mut map = schemars::_serde_json::Map::new();
enum_values: Some(vec![#(#unit_names.into()),*]), 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() { 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 let (add_content_to_props, add_content_to_required) = content_schema
.map(|content_schema| { .map(|content_schema| {
( (
quote!(props.insert(#content_name.to_owned(), #content_schema);), quote!(#content_name: (#content_schema),),
quote!(required.insert(#content_name.to_owned());), quote!(#content_name,),
) )
}) })
.unwrap_or_default(); .unwrap_or_default();
let name = variant.name(); let name = variant.name();
let tag_schema = schema_object(quote! { let tag_schema = quote! {
instance_type: Some(schemars::schema::InstanceType::String.into()), schemars::json_schema!({
enum_values: Some(vec![#name.into()]), "type": "string",
}); "enum": [#name],
})
};
let set_additional_properties = if deny_unknown_fields { let set_additional_properties = if deny_unknown_fields {
quote! { quote! {
additional_properties: Some(Box::new(false.into())), "additionalProperties": false,
} }
} else { } else {
TokenStream::new() TokenStream::new()
}; };
let mut outer_schema = schema_object(quote! { let mut outer_schema = quote! {
instance_type: Some(schemars::schema::InstanceType::Object.into()), schemars::json_schema!({
object: Some(Box::new(schemars::schema::ObjectValidation { "type": "object",
properties: { "properties": {
let mut props = schemars::Map::new(); #tag_name: (#tag_schema),
props.insert(#tag_name.to_owned(), #tag_schema);
#add_content_to_props #add_content_to_props
props
}, },
required: { "required": [
let mut required = schemars::Set::new(); #tag_name,
required.insert(#tag_name.to_owned());
#add_content_to_required #add_content_to_required
required ],
},
// As we're creating a "wrapper" object, we can honor the // As we're creating a "wrapper" object, we can honor the
// disposition of deny_unknown_fields. // disposition of deny_unknown_fields.
#set_additional_properties #set_additional_properties
..Default::default() })
})), };
});
variant variant
.attrs .attrs
@ -333,21 +348,19 @@ fn expr_for_adjacent_tagged_enum<'a>(
/// Callers must determine if all subschemas are mutually exclusive. This can /// 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. /// be done for most tagging regimes by checking that all tag names are unique.
fn variant_subschemas(unique: bool, schemas: Vec<TokenStream>) -> TokenStream { fn variant_subschemas(unique: bool, schemas: Vec<TokenStream>) -> TokenStream {
if unique { let keyword = if unique { "oneOf" } else { "anyOf" };
schema_object(quote! { quote!({
subschemas: Some(Box::new(schemars::schema::SubschemaValidation { let mut map = schemars::_serde_json::Map::new();
one_of: Some(vec![#(#schemas),*]), map.insert(
..Default::default() #keyword.to_owned(),
})), schemars::_serde_json::Value::Array({
}) let mut enum_values = Vec::new();
} else { #(enum_values.push(#schemas.to_value());)*
schema_object(quote! { enum_values
subschemas: Some(Box::new(schemars::schema::SubschemaValidation { }),
any_of: Some(vec![#(#schemas),*]), );
..Default::default() schemars::Schema::from(map)
})), })
})
}
} }
fn expr_for_untagged_enum_variant(variant: &Variant, deny_unknown_fields: bool) -> TokenStream { 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; let len = fields.len() as u32;
quote! { quote! {
schemars::schema::Schema::Object( schemars::json_schema!({
schemars::schema::SchemaObject { "type": "array",
instance_type: Some(schemars::schema::InstanceType::Array.into()), "items": [#((#fields)),*],
array: Some(Box::new(schemars::schema::ArrayValidation { "minItems": #len,
items: Some(vec![#(#fields),*].into()), "maxItems": #len,
max_items: Some(#len),
min_items: Some(#len),
..Default::default()
})),
..Default::default()
}) })
} }
} }
@ -477,7 +485,7 @@ fn expr_for_struct(
quote! { quote! {
{ {
#type_def #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 { let set_additional_properties = if deny_unknown_fields {
quote! { quote! {
object_validation.additional_properties = Some(Box::new(false.into())); "additionalProperties": false,
} }
} else { } else {
TokenStream::new() TokenStream::new()
@ -510,15 +518,12 @@ fn expr_for_struct(
quote! { quote! {
{ {
#set_container_default #set_container_default
let mut schema_object = schemars::schema::SchemaObject { let mut schema = schemars::json_schema!({
instance_type: Some(schemars::schema::InstanceType::Object.into()), "type": "object",
..Default::default() #set_additional_properties
}; });
let object_validation = schema_object.object();
#set_additional_properties
#(#properties)* #(#properties)*
schemars::schema::Schema::Object(schema_object) schema #(.flatten(#flattens))*
#(.flatten(#flattens))*
} }
} }
} }
@ -578,16 +583,6 @@ fn field_default_expr(field: &Field, container_has_default: bool) -> Option<Toke
}) })
} }
fn schema_object(properties: TokenStream) -> TokenStream {
quote! {
schemars::schema::Schema::Object(
schemars::schema::SchemaObject {
#properties
..Default::default()
})
}
}
fn prepend_type_def(type_def: Option<TokenStream>, schema_expr: &mut TokenStream) { fn prepend_type_def(type_def: Option<TokenStream>, schema_expr: &mut TokenStream) {
if let Some(type_def) = type_def { if let Some(type_def) = type_def {
*schema_expr = quote! { *schema_expr = quote! {