Define Schema
as a newtype around serde_json::Value
(#289)
This commit is contained in:
parent
7f6a7b7e32
commit
342cd5fd09
79 changed files with 1410 additions and 2394 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
|||
- nightly
|
||||
include:
|
||||
- rust: 1.60.0
|
||||
test_features: "--features impl_json_schema"
|
||||
test_features: ""
|
||||
allow_failure: false
|
||||
- rust: stable
|
||||
test_features: "--all-features"
|
||||
|
|
100
Cargo.lock
generated
100
Cargo.lock
generated
|
@ -2,12 +2,6 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.4"
|
||||
|
@ -29,17 +23,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bigdecimal"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bigdecimal"
|
||||
version = "0.4.2"
|
||||
|
@ -168,12 +151,6 @@ version = "0.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.2"
|
||||
|
@ -196,17 +173,6 @@ dependencies = [
|
|||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.2"
|
||||
|
@ -214,8 +180,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.2",
|
||||
"serde",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -284,29 +249,49 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.69"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ref-cast"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4846d4c50d1721b1a3bef8af76924eef20d5e723647333798c1b519b3a9473f"
|
||||
dependencies = [
|
||||
"ref-cast-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ref-cast-impl"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust_decimal"
|
||||
version = "1.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4c4216490d5a413bc6d10fa4742bd7d4955941d062c0ef873141d6b0e7b30fd"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.4",
|
||||
"arrayvec",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
|
@ -320,18 +305,16 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
|||
name = "schemars"
|
||||
version = "0.8.19"
|
||||
dependencies = [
|
||||
"arrayvec 0.5.2",
|
||||
"arrayvec 0.7.4",
|
||||
"bigdecimal 0.3.1",
|
||||
"bigdecimal 0.4.2",
|
||||
"arrayvec",
|
||||
"bigdecimal",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"dyn-clone",
|
||||
"either",
|
||||
"enumset",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.0.2",
|
||||
"indexmap",
|
||||
"pretty_assertions",
|
||||
"ref-cast",
|
||||
"rust_decimal",
|
||||
"schemars_derive",
|
||||
"semver",
|
||||
|
@ -341,8 +324,7 @@ dependencies = [
|
|||
"smol_str",
|
||||
"trybuild",
|
||||
"url",
|
||||
"uuid 0.8.2",
|
||||
"uuid 1.5.0",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -361,9 +343,6 @@ name = "semver"
|
|||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
|
@ -415,18 +394,15 @@ checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
|
|||
|
||||
[[package]]
|
||||
name = "smol_str"
|
||||
version = "0.1.24"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
checksum = "e6845563ada680337a52d43bb0b29f396f2d911616f6573012645b9e3d048a49"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.38"
|
||||
version = "2.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
|
||||
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -504,12 +480,6 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.5.0"
|
||||
|
|
|
@ -15,117 +15,91 @@ rust-version = "1.60"
|
|||
|
||||
[dependencies]
|
||||
schemars_derive = { version = "=0.8.19", optional = true, path = "../schemars_derive" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde = "1.0"
|
||||
serde_json = "1.0.25"
|
||||
dyn-clone = "1.0"
|
||||
ref-cast = "1.0.22"
|
||||
|
||||
chrono = { version = "0.4", default-features = false, optional = true }
|
||||
indexmap = { version = "1.2", features = ["serde-1"], optional = true }
|
||||
indexmap2 = { version = "2.0", features = ["serde"], optional = true, package = "indexmap" }
|
||||
either = { version = "1.3", default-features = false, optional = true }
|
||||
uuid08 = { version = "0.8", default-features = false, optional = true, package = "uuid" }
|
||||
# optional dependencies
|
||||
chrono04 = { version = "0.4", default-features = false, optional = true, package = "chrono" }
|
||||
indexmap2 = { version = "2.0", default-features = false, optional = true, package = "indexmap" }
|
||||
either1 = { version = "1.3", default-features = false, optional = true, package = "either" }
|
||||
uuid1 = { version = "1.0", default-features = false, optional = true, package = "uuid" }
|
||||
smallvec = { version = "1.0", optional = true }
|
||||
arrayvec05 = { version = "0.5", default-features = false, optional = true, package = "arrayvec" }
|
||||
smallvec1 = { version = "1.0", default-features = false, optional = true, package = "smallvec" }
|
||||
arrayvec07 = { version = "0.7", default-features = false, optional = true, package = "arrayvec" }
|
||||
url = { version = "2.0", default-features = false, optional = true }
|
||||
bytes = { version = "1.0", optional = true }
|
||||
rust_decimal = { version = "1", default-features = false, optional = true }
|
||||
bigdecimal03 = { version = "0.3", default-features = false, optional = true, package = "bigdecimal" }
|
||||
url2 = { version = "2.0", default-features = false, optional = true, package = "url" }
|
||||
bytes1 = { version = "1.0", default-features = false, optional = true, package = "bytes" }
|
||||
rust_decimal1 = { version = "1", default-features = false, optional = true, package = "rust_decimal"}
|
||||
bigdecimal04 = { version = "0.4", default-features = false, optional = true, package = "bigdecimal" }
|
||||
enumset = { version = "1.0", optional = true }
|
||||
smol_str = { version = "0.1.17", optional = true }
|
||||
semver = { version = "1.0.9", features = ["serde"], optional = true }
|
||||
enumset1 = { version = "1.0", default-features = false, optional = true, package = "enumset" }
|
||||
smol_str02 = { version = "0.2.1", default-features = false, optional = true, package = "smol_str" }
|
||||
semver1 = { version = "1.0.9", default-features = false, optional = true, package = "semver" }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.2.1"
|
||||
trybuild = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
[features]
|
||||
default = ["derive"]
|
||||
|
||||
derive = ["schemars_derive"]
|
||||
|
||||
# Use a different representation for the map type of Schemars.
|
||||
# This allows data to be read into a Value and written back to a JSON string
|
||||
# while preserving the order of map keys in the input.
|
||||
preserve_order = ["indexmap"]
|
||||
|
||||
impl_json_schema = ["derive"]
|
||||
# derive_json_schema will be removed in a later version
|
||||
derive_json_schema = ["impl_json_schema"]
|
||||
|
||||
# `uuid` feature contains `uuid08` only for back-compat - will be changed to include uuid 1.0 instead in a later version
|
||||
uuid = ["uuid08"]
|
||||
# `arrayvec` feature without version suffix is included only for back-compat - will be removed in a later version
|
||||
arrayvec = ["arrayvec05"]
|
||||
indexmap1 = ["indexmap"]
|
||||
|
||||
raw_value = ["serde_json/raw_value"]
|
||||
# `bigdecimal` feature without version suffix is included only for back-compat - will be removed in a later version
|
||||
bigdecimal = ["bigdecimal03"]
|
||||
|
||||
ui_test = []
|
||||
|
||||
[[test]]
|
||||
name = "chrono"
|
||||
required-features = ["chrono"]
|
||||
|
||||
[[test]]
|
||||
name = "indexmap"
|
||||
required-features = ["indexmap"]
|
||||
|
||||
[[test]]
|
||||
name = "indexmap2"
|
||||
required-features = ["indexmap2"]
|
||||
|
||||
[[test]]
|
||||
name = "either"
|
||||
required-features = ["either"]
|
||||
|
||||
[[test]]
|
||||
name = "uuid"
|
||||
required-features = ["uuid08", "uuid1"]
|
||||
|
||||
[[test]]
|
||||
name = "smallvec"
|
||||
required-features = ["smallvec"]
|
||||
|
||||
[[test]]
|
||||
name = "bytes"
|
||||
required-features = ["bytes"]
|
||||
|
||||
[[test]]
|
||||
name = "arrayvec"
|
||||
required-features = ["arrayvec05", "arrayvec07"]
|
||||
|
||||
[[test]]
|
||||
name = "schema_for_schema"
|
||||
required-features = ["impl_json_schema"]
|
||||
|
||||
[[test]]
|
||||
name = "ui"
|
||||
required-features = ["ui_test"]
|
||||
|
||||
[[test]]
|
||||
name = "chrono"
|
||||
required-features = ["chrono04"]
|
||||
|
||||
[[test]]
|
||||
name = "indexmap"
|
||||
required-features = ["indexmap2"]
|
||||
|
||||
[[test]]
|
||||
name = "either"
|
||||
required-features = ["either1"]
|
||||
|
||||
[[test]]
|
||||
name = "uuid"
|
||||
required-features = ["uuid1"]
|
||||
|
||||
[[test]]
|
||||
name = "smallvec"
|
||||
required-features = ["smallvec1"]
|
||||
|
||||
[[test]]
|
||||
name = "bytes"
|
||||
required-features = ["bytes1"]
|
||||
|
||||
[[test]]
|
||||
name = "arrayvec"
|
||||
required-features = ["arrayvec07"]
|
||||
|
||||
[[test]]
|
||||
name = "url"
|
||||
required-features = ["url"]
|
||||
required-features = ["url2"]
|
||||
|
||||
[[test]]
|
||||
name = "enumset"
|
||||
required-features = ["enumset"]
|
||||
required-features = ["enumset1"]
|
||||
|
||||
[[test]]
|
||||
name = "smol_str"
|
||||
required-features = ["smol_str"]
|
||||
required-features = ["smol_str02"]
|
||||
|
||||
[[test]]
|
||||
name = "semver"
|
||||
required-features = ["semver"]
|
||||
required-features = ["semver1"]
|
||||
|
||||
[[test]]
|
||||
name = "decimal"
|
||||
required-features = ["rust_decimal", "bigdecimal03", "bigdecimal04"]
|
||||
required-features = ["rust_decimal1", "bigdecimal04"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use schemars::schema::{Schema, SchemaObject};
|
||||
use schemars::Schema;
|
||||
use schemars::{gen::SchemaGenerator, schema_for, JsonSchema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -21,9 +21,11 @@ pub struct MyStruct {
|
|||
}
|
||||
|
||||
fn make_custom_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
let mut schema: SchemaObject = <String>::json_schema(gen).into();
|
||||
schema.format = Some("boolean".to_owned());
|
||||
schema.into()
|
||||
let mut schema = String::json_schema(gen);
|
||||
schema
|
||||
.ensure_object()
|
||||
.insert("format".into(), "boolean".into());
|
||||
schema
|
||||
}
|
||||
|
||||
fn eight() -> i32 {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::{InstanceType, ObjectValidation, Schema, SchemaObject};
|
||||
use crate::{JsonSchema, Map, Set};
|
||||
use crate::JsonSchema;
|
||||
use crate::Schema;
|
||||
use serde::Serialize;
|
||||
use serde_json::Map;
|
||||
use serde_json::Value;
|
||||
|
||||
// Helper for generating schemas for flattened `Option` fields.
|
||||
|
@ -12,12 +13,8 @@ pub fn json_schema_for_flatten<T: ?Sized + JsonSchema>(
|
|||
let mut schema = T::_schemars_private_non_optional_json_schema(gen);
|
||||
|
||||
if T::_schemars_private_is_option() && !required {
|
||||
if let Schema::Object(SchemaObject {
|
||||
object: Some(ref mut object_validation),
|
||||
..
|
||||
}) = schema
|
||||
{
|
||||
object_validation.required.clear();
|
||||
if let Some(object) = schema.as_object_mut() {
|
||||
object.remove("required");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,38 +54,22 @@ impl<T: Serialize> MaybeSerializeWrapper<T> {
|
|||
|
||||
/// Create a schema for a unit enum
|
||||
pub fn new_unit_enum(variant: &str) -> Schema {
|
||||
Schema::Object(SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
enum_values: Some(vec![variant.into()]),
|
||||
..SchemaObject::default()
|
||||
// TODO switch from single-valued "enum" to "const"
|
||||
json_schema!({
|
||||
"type": "string",
|
||||
"enum": [variant],
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a schema for an externally tagged enum
|
||||
pub fn new_externally_tagged_enum(variant: &str, sub_schema: Schema) -> Schema {
|
||||
Schema::Object(SchemaObject {
|
||||
instance_type: Some(InstanceType::Object.into()),
|
||||
object: Some(Box::new(ObjectValidation {
|
||||
properties: {
|
||||
let mut props = Map::new();
|
||||
props.insert(variant.to_owned(), sub_schema);
|
||||
props
|
||||
json_schema!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
variant: sub_schema
|
||||
},
|
||||
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()
|
||||
"required": [variant],
|
||||
"additionalProperties": false,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -98,74 +79,87 @@ pub fn new_internally_tagged_enum(
|
|||
variant: &str,
|
||||
deny_unknown_fields: bool,
|
||||
) -> Schema {
|
||||
let tag_schema = Schema::Object(SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
enum_values: Some(vec![variant.into()]),
|
||||
..Default::default()
|
||||
// TODO switch from single-valued "enum" to "const"
|
||||
let mut schema = json_schema!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
tag_name: {
|
||||
"type": "string",
|
||||
"enum": [variant],
|
||||
}
|
||||
},
|
||||
"required": [tag_name],
|
||||
});
|
||||
Schema::Object(SchemaObject {
|
||||
instance_type: Some(InstanceType::Object.into()),
|
||||
object: Some(Box::new(ObjectValidation {
|
||||
properties: {
|
||||
let mut props = Map::new();
|
||||
props.insert(tag_name.to_owned(), tag_schema);
|
||||
props
|
||||
},
|
||||
required: {
|
||||
let mut required = Set::new();
|
||||
required.insert(tag_name.to_owned());
|
||||
required
|
||||
},
|
||||
additional_properties: deny_unknown_fields.then(|| Box::new(false.into())),
|
||||
..Default::default()
|
||||
})),
|
||||
..SchemaObject::default()
|
||||
})
|
||||
|
||||
if deny_unknown_fields {
|
||||
schema
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.insert("additionalProperties".into(), false.into());
|
||||
}
|
||||
|
||||
schema
|
||||
}
|
||||
|
||||
pub fn insert_object_property<T: ?Sized + JsonSchema>(
|
||||
obj: &mut ObjectValidation,
|
||||
schema: &mut Schema,
|
||||
key: &str,
|
||||
has_default: bool,
|
||||
required: bool,
|
||||
schema: Schema,
|
||||
sub_schema: Schema,
|
||||
) {
|
||||
obj.properties.insert(key.to_owned(), schema);
|
||||
let obj = schema.ensure_object();
|
||||
if let Some(properties) = obj
|
||||
.entry("properties")
|
||||
.or_insert(Value::Object(Map::new()))
|
||||
.as_object_mut()
|
||||
{
|
||||
properties.insert(key.to_owned(), sub_schema.into());
|
||||
}
|
||||
|
||||
if required || !(has_default || T::_schemars_private_is_option()) {
|
||||
obj.required.insert(key.to_owned());
|
||||
if let Some(req) = obj
|
||||
.entry("required")
|
||||
.or_insert(Value::Array(Vec::new()))
|
||||
.as_array_mut()
|
||||
{
|
||||
req.push(key.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod metadata {
|
||||
use crate::Schema;
|
||||
use serde_json::Value;
|
||||
|
||||
macro_rules! add_metadata_fn {
|
||||
($method:ident, $name:ident, $ty:ty) => {
|
||||
pub fn $method(schema: Schema, $name: impl Into<$ty>) -> Schema {
|
||||
let value = $name.into();
|
||||
if value == <$ty>::default() {
|
||||
schema
|
||||
} else {
|
||||
let mut schema_obj = schema.into_object();
|
||||
schema_obj.metadata().$name = value.into();
|
||||
Schema::Object(schema_obj)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
add_metadata_fn!(add_description, description, String);
|
||||
add_metadata_fn!(add_id, id, String);
|
||||
add_metadata_fn!(add_title, title, String);
|
||||
add_metadata_fn!(add_deprecated, deprecated, bool);
|
||||
add_metadata_fn!(add_read_only, read_only, bool);
|
||||
add_metadata_fn!(add_write_only, write_only, bool);
|
||||
add_metadata_fn!(add_default, default, Value);
|
||||
|
||||
pub fn add_examples<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)
|
||||
pub fn insert_validation_property(
|
||||
schema: &mut Schema,
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append_required(schema: &mut Schema, key: &str) {
|
||||
if schema.has_type("object") {
|
||||
if let Value::Array(array) = schema
|
||||
.ensure_object()
|
||||
.entry("required")
|
||||
.or_insert(Value::Array(Vec::new()))
|
||||
{
|
||||
let value = Value::from(key);
|
||||
if !array.contains(&value) {
|
||||
array.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_inner_validation(schema: &mut Schema, f: fn(&mut Schema) -> ()) {
|
||||
if let Some(inner_schema) = schema
|
||||
.as_object_mut()
|
||||
.and_then(|o| o.get_mut("items"))
|
||||
.and_then(|i| <&mut Schema>::try_from(i).ok())
|
||||
{
|
||||
f(inner_schema);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,180 +1,111 @@
|
|||
use crate::schema::*;
|
||||
use crate::{Map, Set};
|
||||
use serde_json::map::Entry;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::Schema;
|
||||
|
||||
impl Schema {
|
||||
/// This function is only public for use by schemars_derive.
|
||||
///
|
||||
/// It should not be considered part of the public API.
|
||||
#[doc(hidden)]
|
||||
pub fn flatten(self, other: Self) -> Schema {
|
||||
if is_null_type(&self) {
|
||||
return other;
|
||||
} else if is_null_type(&other) {
|
||||
pub fn flatten(mut self, other: Self) -> Schema {
|
||||
// This special null-type-schema handling is here for backward-compatibility, but needs reviewing.
|
||||
// I think it's only needed to make internally-tagged enum unit variants behave correctly, but that
|
||||
// should be handled entirely within schemars_derive.
|
||||
if other
|
||||
.as_object()
|
||||
.and_then(|o| o.get("type"))
|
||||
.and_then(|t| t.as_str())
|
||||
== Some("null")
|
||||
{
|
||||
return self;
|
||||
}
|
||||
let s1: SchemaObject = self.into();
|
||||
let s2: SchemaObject = other.into();
|
||||
Schema::Object(s1.merge(s2))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait Merge: Sized {
|
||||
fn merge(self, other: Self) -> Self;
|
||||
}
|
||||
if let Value::Object(mut obj2) = other.to_value() {
|
||||
let obj1 = self.ensure_object();
|
||||
|
||||
macro_rules! impl_merge {
|
||||
($ty:ident { merge: $($merge_field:ident)*, or: $($or_field:ident)*, }) => {
|
||||
impl Merge for $ty {
|
||||
fn merge(self, other: Self) -> Self {
|
||||
$ty {
|
||||
$($merge_field: self.$merge_field.merge(other.$merge_field),)*
|
||||
$($or_field: self.$or_field.or(other.$or_field),)*
|
||||
let ap2 = obj2.remove("additionalProperties");
|
||||
if let Entry::Occupied(mut ap1) = obj1.entry("additionalProperties") {
|
||||
match ap2 {
|
||||
Some(ap2) => {
|
||||
flatten_additional_properties(ap1.get_mut(), ap2);
|
||||
}
|
||||
None => {
|
||||
ap1.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (key, value2) in obj2 {
|
||||
match obj1.entry(key) {
|
||||
Entry::Vacant(vacant) => {
|
||||
vacant.insert(value2);
|
||||
}
|
||||
Entry::Occupied(mut occupied) => {
|
||||
match occupied.key().as_str() {
|
||||
// This special "type" handling can probably be removed once the enum variant `with`/`schema_with` behaviour is fixed
|
||||
"type" => match (occupied.get_mut(), value2) {
|
||||
(Value::Array(a1), Value::Array(mut a2)) => {
|
||||
a2.retain(|v2| !a1.contains(v2));
|
||||
a1.extend(a2);
|
||||
}
|
||||
(v1, Value::Array(mut a2)) => {
|
||||
if !a2.contains(v1) {
|
||||
a2.push(std::mem::take(v1));
|
||||
*occupied.get_mut() = Value::Array(a2);
|
||||
}
|
||||
}
|
||||
(Value::Array(a1), v2) => {
|
||||
if !a1.contains(&v2) {
|
||||
a1.push(v2);
|
||||
}
|
||||
}
|
||||
(v1, v2) => {
|
||||
if v1 != &v2 {
|
||||
*occupied.get_mut() =
|
||||
Value::Array(vec![std::mem::take(v1), v2]);
|
||||
}
|
||||
}
|
||||
},
|
||||
"required" => {
|
||||
if let Value::Array(a1) = occupied.into_mut() {
|
||||
if let Value::Array(a2) = value2 {
|
||||
a1.extend(a2);
|
||||
}
|
||||
}
|
||||
}
|
||||
"properties" | "patternProperties" => {
|
||||
if let Value::Object(o1) = occupied.into_mut() {
|
||||
if let Value::Object(o2) = value2 {
|
||||
o1.extend(o2);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// leave the original value as it is (don't modify `self`)
|
||||
}
|
||||
};
|
||||
($ty:ident { or: $($or_field:ident)*, }) => {
|
||||
impl_merge!( $ty { merge: , or: $($or_field)*, });
|
||||
};
|
||||
}
|
||||
|
||||
// For ObjectValidation::additional_properties.
|
||||
impl Merge for Option<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
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Merge for Vec<T> {
|
||||
fn merge(mut self, other: Self) -> Self {
|
||||
self.extend(other);
|
||||
self
|
||||
// TODO validate behaviour when flattening a normal struct into a struct with deny_unknown_fields
|
||||
fn flatten_additional_properties(v1: &mut Value, v2: Value) {
|
||||
match (v1, v2) {
|
||||
(v1, Value::Bool(true)) => {
|
||||
*v1 = Value::Bool(true);
|
||||
}
|
||||
(v1 @ Value::Bool(false), v2) => {
|
||||
*v1 = v2;
|
||||
}
|
||||
(Value::Object(o1), Value::Object(o2)) => {
|
||||
o1.extend(o2);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
(SingleOrVec::Vec(v1), SingleOrVec::Vec(v2)) => v1.merge(v2),
|
||||
(SingleOrVec::Vec(mut v), SingleOrVec::Single(s))
|
||||
| (SingleOrVec::Single(s), SingleOrVec::Vec(mut v)) => {
|
||||
v.push(*s);
|
||||
v
|
||||
}
|
||||
(SingleOrVec::Single(s1), SingleOrVec::Single(s2)) => vec![*s1, *s2],
|
||||
};
|
||||
vec.sort();
|
||||
vec.dedup();
|
||||
SingleOrVec::Vec(vec)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_null_type(schema: &Schema) -> bool {
|
||||
let s = match schema {
|
||||
Schema::Object(s) => s,
|
||||
_ => return false,
|
||||
};
|
||||
let instance_type = match &s.instance_type {
|
||||
Some(SingleOrVec::Single(t)) => t,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
**instance_type == InstanceType::Null
|
||||
}
|
||||
|
|
|
@ -7,12 +7,12 @@ There are two main types in this module:
|
|||
* [`SchemaGenerator`], which manages the generation of a schema document.
|
||||
*/
|
||||
|
||||
use crate::schema::*;
|
||||
use crate::{visit::*, JsonSchema, Map};
|
||||
use crate::Schema;
|
||||
use crate::{visit::*, JsonSchema};
|
||||
use dyn_clone::DynClone;
|
||||
use serde::Serialize;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::{any::Any, collections::HashSet, fmt::Debug};
|
||||
|
||||
/// Settings to customize how Schemas are generated.
|
||||
|
@ -76,7 +76,7 @@ impl SchemaSettings {
|
|||
option_add_null_type: true,
|
||||
definitions_path: "#/definitions/".to_owned(),
|
||||
meta_schema: Some("https://json-schema.org/draft/2019-09/schema".to_owned()),
|
||||
visitors: Vec::default(),
|
||||
visitors: Vec::new(),
|
||||
inline_subschemas: false,
|
||||
}
|
||||
}
|
||||
|
@ -96,9 +96,7 @@ impl SchemaSettings {
|
|||
Box::new(ReplaceBoolSchemas {
|
||||
skip_additional_properties: true,
|
||||
}),
|
||||
Box::new(SetSingleExample {
|
||||
retain_examples: false,
|
||||
}),
|
||||
Box::new(SetSingleExample),
|
||||
],
|
||||
inline_subschemas: false,
|
||||
}
|
||||
|
@ -150,7 +148,7 @@ impl SchemaSettings {
|
|||
#[derive(Debug, Default)]
|
||||
pub struct SchemaGenerator {
|
||||
settings: SchemaSettings,
|
||||
definitions: Map<String, Schema>,
|
||||
definitions: BTreeMap<String, Schema>,
|
||||
pending_schema_ids: HashSet<Cow<'static, str>>,
|
||||
schema_id_to_name: HashMap<Cow<'static, str>, String>,
|
||||
used_schema_names: HashSet<String>,
|
||||
|
@ -198,19 +196,6 @@ impl SchemaGenerator {
|
|||
&self.settings
|
||||
}
|
||||
|
||||
#[deprecated = "This method no longer has any effect."]
|
||||
pub fn make_extensible(&self, _schema: &mut SchemaObject) {}
|
||||
|
||||
#[deprecated = "Use `Schema::Bool(true)` instead"]
|
||||
pub fn schema_for_any(&self) -> Schema {
|
||||
Schema::Bool(true)
|
||||
}
|
||||
|
||||
#[deprecated = "Use `Schema::Bool(false)` instead"]
|
||||
pub fn schema_for_none(&self) -> Schema {
|
||||
Schema::Bool(false)
|
||||
}
|
||||
|
||||
/// Generates a JSON Schema for the type `T`, and returns either the schema itself or a `$ref` schema referencing `T`'s schema.
|
||||
///
|
||||
/// If `T` is [referenceable](JsonSchema::is_referenceable), this will add `T`'s schema to this generator's definitions, and
|
||||
|
@ -262,7 +247,7 @@ impl SchemaGenerator {
|
|||
name: String,
|
||||
id: Cow<'static, str>,
|
||||
) {
|
||||
let dummy = Schema::Bool(false);
|
||||
let dummy = false.into();
|
||||
// insert into definitions BEFORE calling json_schema to avoid infinite recursion
|
||||
self.definitions.insert(name.clone(), dummy);
|
||||
|
||||
|
@ -273,26 +258,26 @@ impl SchemaGenerator {
|
|||
|
||||
/// Borrows the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated.
|
||||
///
|
||||
/// The keys of the returned `Map` are the [schema names](JsonSchema::schema_name), and the values are the schemas
|
||||
/// The keys of the returned `BTreeMap` are the [schema names](JsonSchema::schema_name), and the values are the schemas
|
||||
/// themselves.
|
||||
pub fn definitions(&self) -> &Map<String, Schema> {
|
||||
pub fn definitions(&self) -> &BTreeMap<String, Schema> {
|
||||
&self.definitions
|
||||
}
|
||||
|
||||
/// Mutably borrows the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated.
|
||||
///
|
||||
/// The keys of the returned `Map` are the [schema names](JsonSchema::schema_name), and the values are the schemas
|
||||
/// The keys of the returned `BTreeMap` are the [schema names](JsonSchema::schema_name), and the values are the schemas
|
||||
/// themselves.
|
||||
pub fn definitions_mut(&mut self) -> &mut Map<String, Schema> {
|
||||
pub fn definitions_mut(&mut self) -> &mut BTreeMap<String, Schema> {
|
||||
&mut self.definitions
|
||||
}
|
||||
|
||||
/// Returns the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated,
|
||||
/// leaving an empty map in its place.
|
||||
///
|
||||
/// The keys of the returned `Map` are the [schema names](JsonSchema::schema_name), and the values are the schemas
|
||||
/// The keys of the returned `BTreeMap` are the [schema names](JsonSchema::schema_name), and the values are the schemas
|
||||
/// themselves.
|
||||
pub fn take_definitions(&mut self) -> Map<String, Schema> {
|
||||
pub fn take_definitions(&mut self) -> BTreeMap<String, Schema> {
|
||||
std::mem::take(&mut self.definitions)
|
||||
}
|
||||
|
||||
|
@ -306,40 +291,72 @@ impl SchemaGenerator {
|
|||
/// If `T`'s schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will
|
||||
/// add them to the `SchemaGenerator`'s schema definitions and include them in the returned `SchemaObject`'s
|
||||
/// [`definitions`](../schema/struct.Metadata.html#structfield.definitions)
|
||||
pub fn root_schema_for<T: ?Sized + JsonSchema>(&mut self) -> RootSchema {
|
||||
let mut schema = self.json_schema_internal::<T>(T::schema_id()).into_object();
|
||||
schema.metadata().title.get_or_insert_with(T::schema_name);
|
||||
let mut root = RootSchema {
|
||||
meta_schema: self.settings.meta_schema.clone(),
|
||||
definitions: self.definitions.clone(),
|
||||
schema,
|
||||
};
|
||||
pub fn root_schema_for<T: ?Sized + JsonSchema>(&mut self) -> Schema {
|
||||
let mut schema = self.json_schema_internal::<T>(T::schema_id());
|
||||
|
||||
for visitor in &mut self.settings.visitors {
|
||||
visitor.visit_root_schema(&mut root)
|
||||
let object = schema.ensure_object();
|
||||
|
||||
object
|
||||
.entry("title")
|
||||
.or_insert_with(|| T::schema_name().into());
|
||||
|
||||
if let Some(meta_schema) = self.settings.meta_schema.as_deref() {
|
||||
object.insert("$schema".into(), meta_schema.into());
|
||||
}
|
||||
|
||||
root
|
||||
if !self.definitions.is_empty() {
|
||||
object.insert(
|
||||
"definitions".into(),
|
||||
serde_json::Value::Object(
|
||||
self.definitions
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone().into()))
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
for visitor in &mut self.settings.visitors {
|
||||
visitor.visit_schema(&mut schema);
|
||||
}
|
||||
|
||||
schema
|
||||
}
|
||||
|
||||
/// Consumes `self` and generates a root JSON Schema for the type `T`.
|
||||
///
|
||||
/// If `T`'s schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will
|
||||
/// include them in the returned `SchemaObject`'s [`definitions`](../schema/struct.Metadata.html#structfield.definitions)
|
||||
pub fn into_root_schema_for<T: ?Sized + JsonSchema>(mut self) -> RootSchema {
|
||||
let mut schema = self.json_schema_internal::<T>(T::schema_id()).into_object();
|
||||
schema.metadata().title.get_or_insert_with(T::schema_name);
|
||||
let mut root = RootSchema {
|
||||
meta_schema: self.settings.meta_schema,
|
||||
definitions: self.definitions,
|
||||
schema,
|
||||
};
|
||||
pub fn into_root_schema_for<T: ?Sized + JsonSchema>(mut self) -> Schema {
|
||||
let mut schema = self.json_schema_internal::<T>(T::schema_id());
|
||||
|
||||
for visitor in &mut self.settings.visitors {
|
||||
visitor.visit_root_schema(&mut root)
|
||||
let object = schema.ensure_object();
|
||||
|
||||
object
|
||||
.entry("title")
|
||||
.or_insert_with(|| T::schema_name().into());
|
||||
|
||||
if let Some(meta_schema) = self.settings.meta_schema {
|
||||
object.insert("$schema".into(), meta_schema.into());
|
||||
}
|
||||
|
||||
root
|
||||
if !self.definitions.is_empty() {
|
||||
object.insert(
|
||||
"definitions".into(),
|
||||
serde_json::Value::Object(
|
||||
self.definitions
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, v.into()))
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
for visitor in &mut self.settings.visitors {
|
||||
visitor.visit_schema(&mut schema);
|
||||
}
|
||||
|
||||
schema
|
||||
}
|
||||
|
||||
/// Generates a root JSON Schema for the given example value.
|
||||
|
@ -349,29 +366,39 @@ impl SchemaGenerator {
|
|||
pub fn root_schema_for_value<T: ?Sized + Serialize>(
|
||||
&mut self,
|
||||
value: &T,
|
||||
) -> Result<RootSchema, serde_json::Error> {
|
||||
let mut schema = value
|
||||
.serialize(crate::ser::Serializer {
|
||||
) -> Result<Schema, serde_json::Error> {
|
||||
let mut schema = value.serialize(crate::ser::Serializer {
|
||||
gen: self,
|
||||
include_title: true,
|
||||
})?
|
||||
.into_object();
|
||||
})?;
|
||||
|
||||
let object = schema.ensure_object();
|
||||
|
||||
if let Ok(example) = serde_json::to_value(value) {
|
||||
schema.metadata().examples.push(example);
|
||||
object.insert("examples".into(), vec![example].into());
|
||||
}
|
||||
|
||||
let mut root = RootSchema {
|
||||
meta_schema: self.settings.meta_schema.clone(),
|
||||
definitions: self.definitions.clone(),
|
||||
schema,
|
||||
};
|
||||
if let Some(meta_schema) = self.settings.meta_schema.as_deref() {
|
||||
object.insert("$schema".into(), meta_schema.into());
|
||||
}
|
||||
|
||||
if !self.definitions.is_empty() {
|
||||
object.insert(
|
||||
"definitions".into(),
|
||||
serde_json::Value::Object(
|
||||
self.definitions
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone().into()))
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
for visitor in &mut self.settings.visitors {
|
||||
visitor.visit_root_schema(&mut root)
|
||||
visitor.visit_schema(&mut schema);
|
||||
}
|
||||
|
||||
Ok(root)
|
||||
Ok(schema)
|
||||
}
|
||||
|
||||
/// Consumes `self` and generates a root JSON Schema for the given example value.
|
||||
|
@ -381,72 +408,39 @@ impl SchemaGenerator {
|
|||
pub fn into_root_schema_for_value<T: ?Sized + Serialize>(
|
||||
mut self,
|
||||
value: &T,
|
||||
) -> Result<RootSchema, serde_json::Error> {
|
||||
let mut schema = value
|
||||
.serialize(crate::ser::Serializer {
|
||||
) -> Result<Schema, serde_json::Error> {
|
||||
let mut schema = value.serialize(crate::ser::Serializer {
|
||||
gen: &mut self,
|
||||
include_title: true,
|
||||
})?
|
||||
.into_object();
|
||||
})?;
|
||||
|
||||
let object = schema.ensure_object();
|
||||
|
||||
if let Ok(example) = serde_json::to_value(value) {
|
||||
schema.metadata().examples.push(example);
|
||||
object.insert("examples".into(), vec![example].into());
|
||||
}
|
||||
|
||||
let mut root = RootSchema {
|
||||
meta_schema: self.settings.meta_schema,
|
||||
definitions: self.definitions,
|
||||
schema,
|
||||
};
|
||||
if let Some(meta_schema) = self.settings.meta_schema {
|
||||
object.insert("$schema".into(), meta_schema.into());
|
||||
}
|
||||
|
||||
if !self.definitions.is_empty() {
|
||||
object.insert(
|
||||
"definitions".into(),
|
||||
serde_json::Value::Object(
|
||||
self.definitions
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, v.into()))
|
||||
.collect(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
for visitor in &mut self.settings.visitors {
|
||||
visitor.visit_root_schema(&mut root)
|
||||
visitor.visit_schema(&mut schema);
|
||||
}
|
||||
|
||||
Ok(root)
|
||||
}
|
||||
|
||||
/// Attemps to find the schema that the given `schema` is referencing.
|
||||
///
|
||||
/// If the given `schema` has a [`$ref`](../schema/struct.SchemaObject.html#structfield.reference) property which refers
|
||||
/// to another schema in `self`'s schema definitions, the referenced schema will be returned. Otherwise, returns `None`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use schemars::{JsonSchema, gen::SchemaGenerator};
|
||||
///
|
||||
/// #[derive(JsonSchema)]
|
||||
/// struct MyStruct {
|
||||
/// foo: i32,
|
||||
/// }
|
||||
///
|
||||
/// let mut gen = SchemaGenerator::default();
|
||||
/// let ref_schema = gen.subschema_for::<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,
|
||||
}
|
||||
Ok(schema)
|
||||
}
|
||||
|
||||
fn json_schema_internal<T: ?Sized + JsonSchema>(&mut self, id: Cow<'static, str>) -> Schema {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use crate::{json_schema, JsonSchema, Schema};
|
||||
use std::borrow::Cow;
|
||||
|
||||
// Does not require T: JsonSchema.
|
||||
|
@ -16,15 +15,10 @@ impl<T> JsonSchema for [T; 0] {
|
|||
}
|
||||
|
||||
fn json_schema(_: &mut SchemaGenerator) -> Schema {
|
||||
SchemaObject {
|
||||
instance_type: Some(InstanceType::Array.into()),
|
||||
array: Some(Box::new(ArrayValidation {
|
||||
max_items: Some(0),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
json_schema!({
|
||||
"type": "array",
|
||||
"maxItems": 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,17 +38,12 @@ macro_rules! array_impls {
|
|||
}
|
||||
|
||||
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
SchemaObject {
|
||||
instance_type: Some(InstanceType::Array.into()),
|
||||
array: Some(Box::new(ArrayValidation {
|
||||
items: Some(gen.subschema_for::<T>().into()),
|
||||
max_items: Some($len),
|
||||
min_items: Some($len),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
json_schema!({
|
||||
"type": "array",
|
||||
"items": serde_json::Value::from(gen.subschema_for::<T>()),
|
||||
"minItems": $len,
|
||||
"maxItems": $len,
|
||||
})
|
||||
}
|
||||
}
|
||||
)+
|
||||
|
@ -67,40 +56,3 @@ array_impls! {
|
|||
21 22 23 24 25 26 27 28 29 30
|
||||
31 32
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::{schema_for, schema_object_for};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn schema_for_array() {
|
||||
let schema = schema_object_for::<[i32; 8]>();
|
||||
assert_eq!(
|
||||
schema.instance_type,
|
||||
Some(SingleOrVec::from(InstanceType::Array))
|
||||
);
|
||||
let array_validation = schema.array.unwrap();
|
||||
assert_eq!(
|
||||
array_validation.items,
|
||||
Some(SingleOrVec::from(schema_for::<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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use crate::{json_schema, JsonSchema, Schema};
|
||||
use arrayvec07::{ArrayString, ArrayVec};
|
||||
use std::convert::TryInto;
|
||||
|
||||
// Do not set maxLength on the schema as that describes length in characters, but we only
|
||||
// know max length in bytes.
|
||||
|
@ -19,15 +17,10 @@ where
|
|||
}
|
||||
|
||||
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
SchemaObject {
|
||||
instance_type: Some(InstanceType::Array.into()),
|
||||
array: Some(Box::new(ArrayValidation {
|
||||
items: Some(gen.subschema_for::<T>().into()),
|
||||
max_items: CAP.try_into().ok(),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
json_schema!({
|
||||
"type": "array",
|
||||
"items": gen.subschema_for::<T>(),
|
||||
"maxItems": CAP
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use std::sync::atomic::*;
|
||||
|
||||
forward_impl!(AtomicBool => bool);
|
||||
|
@ -22,12 +19,12 @@ forward_impl!(AtomicUsize => usize);
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::schema_object_for;
|
||||
use crate::schema_for;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn schema_for_atomics() {
|
||||
let atomic_schema = schema_object_for::<(
|
||||
let atomic_schema = schema_for!((
|
||||
AtomicBool,
|
||||
AtomicI8,
|
||||
AtomicI16,
|
||||
|
@ -39,9 +36,8 @@ mod tests {
|
|||
AtomicU32,
|
||||
AtomicU64,
|
||||
AtomicUsize,
|
||||
)>();
|
||||
let basic_schema =
|
||||
schema_object_for::<(bool, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize)>();
|
||||
));
|
||||
let basic_schema = schema_for!((bool, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize));
|
||||
assert_eq!(atomic_schema, basic_schema);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>);
|
|
@ -1,8 +1,6 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use chrono::prelude::*;
|
||||
use serde_json::json;
|
||||
use crate::{json_schema, JsonSchema, Schema};
|
||||
use chrono04::prelude::*;
|
||||
use std::borrow::Cow;
|
||||
|
||||
impl JsonSchema for Weekday {
|
||||
|
@ -17,20 +15,18 @@ impl JsonSchema for Weekday {
|
|||
}
|
||||
|
||||
fn json_schema(_: &mut SchemaGenerator) -> Schema {
|
||||
SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
enum_values: Some(vec![
|
||||
json!("Mon"),
|
||||
json!("Tue"),
|
||||
json!("Wed"),
|
||||
json!("Thu"),
|
||||
json!("Fri"),
|
||||
json!("Sat"),
|
||||
json!("Sun"),
|
||||
]),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
json_schema!({
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Mon",
|
||||
"Tue",
|
||||
"Wed",
|
||||
"Thu",
|
||||
"Fri",
|
||||
"Sat",
|
||||
"Sun",
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,12 +47,10 @@ macro_rules! formatted_string_impl {
|
|||
}
|
||||
|
||||
fn json_schema(_: &mut SchemaGenerator) -> Schema {
|
||||
SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
format: Some($format.to_owned()),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
json_schema!({
|
||||
"type": "string",
|
||||
"format": $format
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,7 +1,6 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use serde_json::json;
|
||||
use crate::{json_schema, JsonSchema, Schema};
|
||||
use serde_json::Value;
|
||||
use std::borrow::Cow;
|
||||
use std::ops::{Bound, Range, RangeInclusive};
|
||||
|
||||
|
@ -18,35 +17,45 @@ impl<T: JsonSchema> JsonSchema for Option<T> {
|
|||
|
||||
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
let mut schema = gen.subschema_for::<T>();
|
||||
|
||||
if gen.settings().option_add_null_type {
|
||||
schema = match schema {
|
||||
Schema::Bool(true) => Schema::Bool(true),
|
||||
Schema::Bool(false) => <()>::json_schema(gen),
|
||||
Schema::Object(SchemaObject {
|
||||
instance_type: Some(ref mut instance_type),
|
||||
..
|
||||
}) => {
|
||||
add_null_type(instance_type);
|
||||
schema
|
||||
schema = match schema.try_to_object() {
|
||||
Ok(mut obj) => {
|
||||
let instance_type = obj.get_mut("type");
|
||||
match instance_type {
|
||||
Some(Value::Array(array)) => {
|
||||
let null = Value::from("null");
|
||||
if !array.contains(&null) {
|
||||
array.push(null);
|
||||
}
|
||||
schema => SchemaObject {
|
||||
// TODO technically the schema already accepts null, so this may be unnecessary
|
||||
subschemas: Some(Box::new(SubschemaValidation {
|
||||
any_of: Some(vec![schema, <()>::json_schema(gen)]),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
obj.into()
|
||||
}
|
||||
.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)
|
||||
]
|
||||
}),
|
||||
}
|
||||
}
|
||||
Err(true) => true.into(),
|
||||
Err(false) => <()>::json_schema(gen),
|
||||
}
|
||||
}
|
||||
|
||||
if gen.settings().option_nullable {
|
||||
let mut schema_obj = schema.into_object();
|
||||
schema_obj
|
||||
.extensions
|
||||
.insert("nullable".to_owned(), json!(true));
|
||||
schema = Schema::Object(schema_obj);
|
||||
schema
|
||||
.ensure_object()
|
||||
.insert("nullable".into(), true.into());
|
||||
};
|
||||
|
||||
schema
|
||||
}
|
||||
|
||||
|
@ -59,16 +68,6 @@ impl<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> {
|
||||
fn schema_name() -> String {
|
||||
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 {
|
||||
let mut ok_schema = SchemaObject {
|
||||
instance_type: Some(InstanceType::Object.into()),
|
||||
..Default::default()
|
||||
};
|
||||
let obj = ok_schema.object();
|
||||
obj.required.insert("Ok".to_owned());
|
||||
obj.properties
|
||||
.insert("Ok".to_owned(), gen.subschema_for::<T>());
|
||||
|
||||
let mut err_schema = SchemaObject {
|
||||
instance_type: Some(InstanceType::Object.into()),
|
||||
..Default::default()
|
||||
};
|
||||
let obj = err_schema.object();
|
||||
obj.required.insert("Err".to_owned());
|
||||
obj.properties
|
||||
.insert("Err".to_owned(), gen.subschema_for::<E>());
|
||||
|
||||
let mut schema = SchemaObject::default();
|
||||
schema.subschemas().one_of = Some(vec![ok_schema.into(), err_schema.into()]);
|
||||
schema.into()
|
||||
json_schema!({
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Ok": gen.subschema_for::<T>()
|
||||
},
|
||||
"required": ["Ok"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Err": gen.subschema_for::<E>()
|
||||
},
|
||||
"required": ["Err"]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,37 +109,28 @@ impl<T: JsonSchema> JsonSchema for Bound<T> {
|
|||
}
|
||||
|
||||
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
let mut included_schema = SchemaObject {
|
||||
instance_type: Some(InstanceType::Object.into()),
|
||||
..Default::default()
|
||||
};
|
||||
let obj = included_schema.object();
|
||||
obj.required.insert("Included".to_owned());
|
||||
obj.properties
|
||||
.insert("Included".to_owned(), gen.subschema_for::<T>());
|
||||
|
||||
let mut excluded_schema = SchemaObject {
|
||||
instance_type: Some(InstanceType::Object.into()),
|
||||
..Default::default()
|
||||
};
|
||||
let obj = excluded_schema.object();
|
||||
obj.required.insert("Excluded".to_owned());
|
||||
obj.properties
|
||||
.insert("Excluded".to_owned(), gen.subschema_for::<T>());
|
||||
|
||||
let unbounded_schema = SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
const_value: Some(json!("Unbounded")),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut schema = SchemaObject::default();
|
||||
schema.subschemas().one_of = Some(vec![
|
||||
included_schema.into(),
|
||||
excluded_schema.into(),
|
||||
unbounded_schema.into(),
|
||||
]);
|
||||
schema.into()
|
||||
json_schema!({
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Included": gen.subschema_for::<T>()
|
||||
},
|
||||
"required": ["Included"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Excluded": gen.subschema_for::<T>()
|
||||
},
|
||||
"required": ["Excluded"]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "Unbounded"
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,18 +144,15 @@ impl<T: JsonSchema> JsonSchema for Range<T> {
|
|||
}
|
||||
|
||||
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
let mut schema = SchemaObject {
|
||||
instance_type: Some(InstanceType::Object.into()),
|
||||
..Default::default()
|
||||
};
|
||||
let obj = schema.object();
|
||||
obj.required.insert("start".to_owned());
|
||||
obj.required.insert("end".to_owned());
|
||||
obj.properties
|
||||
.insert("start".to_owned(), gen.subschema_for::<T>());
|
||||
obj.properties
|
||||
.insert("end".to_owned(), gen.subschema_for::<T>());
|
||||
schema.into()
|
||||
let subschema = gen.subschema_for::<T>();
|
||||
json_schema!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"start": subschema,
|
||||
"end": subschema
|
||||
},
|
||||
"required": ["start", "end"]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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!((<'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>());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use crate::{json_schema, JsonSchema, Schema};
|
||||
use std::borrow::Cow;
|
||||
|
||||
macro_rules! decimal_impl {
|
||||
|
@ -17,23 +16,16 @@ macro_rules! decimal_impl {
|
|||
}
|
||||
|
||||
fn json_schema(_: &mut SchemaGenerator) -> Schema {
|
||||
SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
string: Some(Box::new(StringValidation {
|
||||
pattern: Some(r"^-?[0-9]+(\.[0-9]+)?$".to_owned()),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
json_schema!({
|
||||
"type": "string",
|
||||
"pattern": r"^-?[0-9]+(\.[0-9]+)?$",
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "rust_decimal")]
|
||||
decimal_impl!(rust_decimal::Decimal);
|
||||
#[cfg(feature = "bigdecimal03")]
|
||||
decimal_impl!(bigdecimal03::BigDecimal);
|
||||
#[cfg(feature = "rust_decimal1")]
|
||||
decimal_impl!(rust_decimal1::Decimal);
|
||||
#[cfg(feature = "bigdecimal04")]
|
||||
decimal_impl!(bigdecimal04::BigDecimal);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use either::Either;
|
||||
use crate::{json_schema, JsonSchema, Schema};
|
||||
use either1::Either;
|
||||
use std::borrow::Cow;
|
||||
|
||||
impl<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 {
|
||||
let mut schema = SchemaObject::default();
|
||||
schema.subschemas().any_of = Some(vec![gen.subschema_for::<L>(), gen.subschema_for::<R>()]);
|
||||
schema.into()
|
||||
json_schema!({
|
||||
"anyOf": [gen.subschema_for::<L>(), gen.subschema_for::<R>()],
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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>);
|
|
@ -1,6 +1,5 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use crate::{json_schema, JsonSchema, Schema};
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::{CStr, CString, OsStr, OsString};
|
||||
|
||||
|
@ -14,27 +13,24 @@ impl JsonSchema for OsString {
|
|||
}
|
||||
|
||||
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
let mut unix_schema = SchemaObject {
|
||||
instance_type: Some(InstanceType::Object.into()),
|
||||
..Default::default()
|
||||
};
|
||||
let obj = unix_schema.object();
|
||||
obj.required.insert("Unix".to_owned());
|
||||
obj.properties
|
||||
.insert("Unix".to_owned(), <Vec<u8>>::json_schema(gen));
|
||||
|
||||
let mut win_schema = SchemaObject {
|
||||
instance_type: Some(InstanceType::Object.into()),
|
||||
..Default::default()
|
||||
};
|
||||
let obj = win_schema.object();
|
||||
obj.required.insert("Windows".to_owned());
|
||||
obj.properties
|
||||
.insert("Windows".to_owned(), <Vec<u16>>::json_schema(gen));
|
||||
|
||||
let mut schema = SchemaObject::default();
|
||||
schema.subschemas().one_of = Some(vec![unix_schema.into(), win_schema.into()]);
|
||||
schema.into()
|
||||
json_schema!({
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Unix": <Vec<u8>>::json_schema(gen)
|
||||
},
|
||||
"required": ["Unix"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Windows": <Vec<u16>>::json_schema(gen)
|
||||
},
|
||||
"required": ["Windows"]
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>);
|
|
@ -1,5 +1,3 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use indexmap2::{IndexMap, IndexSet};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use crate::{json_schema, JsonSchema, Schema};
|
||||
use std::borrow::Cow;
|
||||
|
||||
macro_rules! map_impl {
|
||||
|
@ -20,16 +19,10 @@ macro_rules! map_impl {
|
|||
}
|
||||
|
||||
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
let subschema = gen.subschema_for::<V>();
|
||||
SchemaObject {
|
||||
instance_type: Some(InstanceType::Object.into()),
|
||||
object: Some(Box::new(ObjectValidation {
|
||||
additional_properties: Some(Box::new(subschema)),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
json_schema!({
|
||||
"type": "object",
|
||||
"additionalProperties": gen.subschema_for::<V>(),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -21,11 +21,11 @@ macro_rules! forward_impl {
|
|||
<$target>::schema_id()
|
||||
}
|
||||
|
||||
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
fn json_schema(gen: &mut $crate::gen::SchemaGenerator) -> $crate::Schema {
|
||||
<$target>::json_schema(gen)
|
||||
}
|
||||
|
||||
fn _schemars_private_non_optional_json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
fn _schemars_private_non_optional_json_schema(gen: &mut $crate::gen::SchemaGenerator) -> $crate::Schema {
|
||||
<$target>::_schemars_private_non_optional_json_schema(gen)
|
||||
}
|
||||
|
||||
|
@ -35,55 +35,61 @@ macro_rules! forward_impl {
|
|||
}
|
||||
};
|
||||
($ty:ty => $target:ty) => {
|
||||
forward_impl!((JsonSchema for $ty) => $target);
|
||||
forward_impl!(($crate::JsonSchema for $ty) => $target);
|
||||
};
|
||||
}
|
||||
|
||||
mod array;
|
||||
#[cfg(feature = "arrayvec05")]
|
||||
mod arrayvec05;
|
||||
#[cfg(feature = "arrayvec07")]
|
||||
mod arrayvec07;
|
||||
#[cfg(std_atomic)]
|
||||
mod atomic;
|
||||
#[cfg(feature = "bytes")]
|
||||
mod bytes;
|
||||
#[cfg(feature = "chrono")]
|
||||
mod chrono;
|
||||
mod core;
|
||||
#[cfg(any(
|
||||
feature = "rust_decimal",
|
||||
feature = "bigdecimal03",
|
||||
feature = "bigdecimal04"
|
||||
))]
|
||||
mod decimal;
|
||||
#[cfg(feature = "either")]
|
||||
mod either;
|
||||
#[cfg(feature = "enumset")]
|
||||
mod enumset;
|
||||
mod ffi;
|
||||
#[cfg(feature = "indexmap")]
|
||||
mod indexmap;
|
||||
#[cfg(feature = "indexmap2")]
|
||||
mod indexmap2;
|
||||
mod maps;
|
||||
mod nonzero_signed;
|
||||
mod nonzero_unsigned;
|
||||
mod primitives;
|
||||
#[cfg(feature = "semver")]
|
||||
mod semver;
|
||||
mod sequences;
|
||||
mod serdejson;
|
||||
#[cfg(feature = "smallvec")]
|
||||
mod smallvec;
|
||||
#[cfg(feature = "smol_str")]
|
||||
mod smol_str;
|
||||
mod time;
|
||||
mod tuple;
|
||||
#[cfg(feature = "url")]
|
||||
mod url;
|
||||
#[cfg(feature = "uuid08")]
|
||||
mod uuid08;
|
||||
mod wrapper;
|
||||
|
||||
#[cfg(std_atomic)]
|
||||
mod atomic;
|
||||
|
||||
#[cfg(feature = "arrayvec07")]
|
||||
mod arrayvec07;
|
||||
|
||||
#[cfg(feature = "bytes1")]
|
||||
mod bytes1 {
|
||||
forward_impl!(bytes1::Bytes => Vec<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")]
|
||||
mod uuid1;
|
||||
mod wrapper;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use crate::{JsonSchema, Schema};
|
||||
use std::borrow::Cow;
|
||||
use std::num::*;
|
||||
|
||||
|
@ -18,14 +17,12 @@ macro_rules! nonzero_unsigned_impl {
|
|||
}
|
||||
|
||||
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
let zero_schema: Schema = SchemaObject {
|
||||
const_value: Some(0.into()),
|
||||
..Default::default()
|
||||
}
|
||||
.into();
|
||||
let mut schema: SchemaObject = <$primitive>::json_schema(gen).into();
|
||||
schema.subschemas().not = Some(Box::from(zero_schema));
|
||||
schema.into()
|
||||
let mut schema = <$primitive>::json_schema(gen);
|
||||
let object = schema.ensure_object();
|
||||
object.insert("not".to_owned(), serde_json::json!({
|
||||
"const": 0
|
||||
}));
|
||||
schema
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::Schema;
|
||||
use crate::JsonSchema;
|
||||
use std::borrow::Cow;
|
||||
use std::num::*;
|
||||
|
@ -18,9 +18,10 @@ macro_rules! nonzero_unsigned_impl {
|
|||
}
|
||||
|
||||
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
let mut schema: SchemaObject = <$primitive>::json_schema(gen).into();
|
||||
schema.number().minimum = Some(1.0);
|
||||
schema.into()
|
||||
let mut schema = <$primitive>::json_schema(gen);
|
||||
let object = schema.ensure_object();
|
||||
object.insert("minimum".to_owned(), 1.into());
|
||||
schema
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -32,18 +33,3 @@ nonzero_unsigned_impl!(NonZeroU32 => u32);
|
|||
nonzero_unsigned_impl!(NonZeroU64 => u64);
|
||||
nonzero_unsigned_impl!(NonZeroU128 => u128);
|
||||
nonzero_unsigned_impl!(NonZeroUsize => usize);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::schema_object_for;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn schema_for_nonzero_u32() {
|
||||
let schema = schema_object_for::<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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,67 +1,30 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use crate::{json_schema, JsonSchema, Schema};
|
||||
use std::borrow::Cow;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
macro_rules! simple_impl {
|
||||
($type:ty => $instance_type:ident) => {
|
||||
simple_impl!($type => $instance_type, stringify!($instance_type), None);
|
||||
};
|
||||
($type:ty => $instance_type:ident, $format:literal) => {
|
||||
simple_impl!($type => $instance_type, $format, Some($format.to_owned()));
|
||||
};
|
||||
($type:ty => $instance_type:ident, $name:expr, $format:expr) => {
|
||||
($type:ty => $instance_type:literal) => {
|
||||
impl JsonSchema for $type {
|
||||
no_ref_schema!();
|
||||
|
||||
fn schema_name() -> String {
|
||||
$name.to_owned()
|
||||
$instance_type.to_owned()
|
||||
}
|
||||
|
||||
fn schema_id() -> Cow<'static, str> {
|
||||
Cow::Borrowed($name)
|
||||
Cow::Borrowed($instance_type)
|
||||
}
|
||||
|
||||
fn json_schema(_: &mut SchemaGenerator) -> Schema {
|
||||
SchemaObject {
|
||||
instance_type: Some(InstanceType::$instance_type.into()),
|
||||
format: $format,
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
json_schema!({
|
||||
"type": $instance_type
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
simple_impl!(str => String);
|
||||
simple_impl!(String => String);
|
||||
simple_impl!(bool => Boolean);
|
||||
simple_impl!(f32 => Number, "float");
|
||||
simple_impl!(f64 => Number, "double");
|
||||
simple_impl!(i8 => Integer, "int8");
|
||||
simple_impl!(i16 => Integer, "int16");
|
||||
simple_impl!(i32 => Integer, "int32");
|
||||
simple_impl!(i64 => Integer, "int64");
|
||||
simple_impl!(i128 => Integer, "int128");
|
||||
simple_impl!(isize => Integer, "int");
|
||||
simple_impl!(() => Null);
|
||||
|
||||
simple_impl!(Path => String);
|
||||
simple_impl!(PathBuf => String);
|
||||
|
||||
simple_impl!(Ipv4Addr => String, "ipv4");
|
||||
simple_impl!(Ipv6Addr => String, "ipv6");
|
||||
simple_impl!(IpAddr => String, "ip");
|
||||
|
||||
simple_impl!(SocketAddr => String);
|
||||
simple_impl!(SocketAddrV4 => String);
|
||||
simple_impl!(SocketAddrV6 => String);
|
||||
|
||||
macro_rules! unsigned_impl {
|
||||
($type:ty => $instance_type:ident, $format:expr) => {
|
||||
($type:ty => $instance_type:literal, $format:literal) => {
|
||||
impl JsonSchema for $type {
|
||||
no_ref_schema!();
|
||||
|
||||
|
@ -74,24 +37,69 @@ macro_rules! unsigned_impl {
|
|||
}
|
||||
|
||||
fn json_schema(_: &mut SchemaGenerator) -> Schema {
|
||||
let mut schema = SchemaObject {
|
||||
instance_type: Some(InstanceType::$instance_type.into()),
|
||||
format: Some($format.to_owned()),
|
||||
..Default::default()
|
||||
};
|
||||
schema.number().minimum = Some(0.0);
|
||||
schema.into()
|
||||
json_schema!({
|
||||
"type": $instance_type,
|
||||
"format": $format
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
unsigned_impl!(u8 => Integer, "uint8");
|
||||
unsigned_impl!(u16 => Integer, "uint16");
|
||||
unsigned_impl!(u32 => Integer, "uint32");
|
||||
unsigned_impl!(u64 => Integer, "uint64");
|
||||
unsigned_impl!(u128 => Integer, "uint128");
|
||||
unsigned_impl!(usize => Integer, "uint");
|
||||
simple_impl!(str => "string");
|
||||
simple_impl!(String => "string");
|
||||
simple_impl!(bool => "boolean");
|
||||
simple_impl!(f32 => "number", "float");
|
||||
simple_impl!(f64 => "number", "double");
|
||||
simple_impl!(i8 => "integer", "int8");
|
||||
simple_impl!(i16 => "integer", "int16");
|
||||
simple_impl!(i32 => "integer", "int32");
|
||||
simple_impl!(i64 => "integer", "int64");
|
||||
simple_impl!(i128 => "integer", "int128");
|
||||
simple_impl!(isize => "integer", "int");
|
||||
simple_impl!(() => "null");
|
||||
|
||||
simple_impl!(Path => "string");
|
||||
simple_impl!(PathBuf => "string");
|
||||
|
||||
simple_impl!(Ipv4Addr => "string", "ipv4");
|
||||
simple_impl!(Ipv6Addr => "string", "ipv6");
|
||||
simple_impl!(IpAddr => "string", "ip");
|
||||
|
||||
simple_impl!(SocketAddr => "string");
|
||||
simple_impl!(SocketAddrV4 => "string");
|
||||
simple_impl!(SocketAddrV6 => "string");
|
||||
|
||||
macro_rules! unsigned_impl {
|
||||
($type:ty => $instance_type:literal, $format:literal) => {
|
||||
impl JsonSchema for $type {
|
||||
no_ref_schema!();
|
||||
|
||||
fn schema_name() -> String {
|
||||
$format.to_owned()
|
||||
}
|
||||
|
||||
fn schema_id() -> Cow<'static, str> {
|
||||
Cow::Borrowed($format)
|
||||
}
|
||||
|
||||
fn json_schema(_: &mut SchemaGenerator) -> Schema {
|
||||
json_schema!({
|
||||
"type": $instance_type,
|
||||
"format": $format,
|
||||
"minimum": 0
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
unsigned_impl!(u8 => "integer", "uint8");
|
||||
unsigned_impl!(u16 => "integer", "uint16");
|
||||
unsigned_impl!(u32 => "integer", "uint32");
|
||||
unsigned_impl!(u64 => "integer", "uint64");
|
||||
unsigned_impl!(u128 => "integer", "uint128");
|
||||
unsigned_impl!(usize => "integer", "uint");
|
||||
|
||||
impl JsonSchema for char {
|
||||
no_ref_schema!();
|
||||
|
@ -105,15 +113,10 @@ impl JsonSchema for char {
|
|||
}
|
||||
|
||||
fn json_schema(_: &mut SchemaGenerator) -> Schema {
|
||||
SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
string: Some(Box::new(StringValidation {
|
||||
min_length: Some(1),
|
||||
max_length: Some(1),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
json_schema!({
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
24
schemars/src/json_schema_impls/semver1.rs
Normal file
24
schemars/src/json_schema_impls/semver1.rs
Normal 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-]+)*))?$"
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use crate::{json_schema, JsonSchema, Schema};
|
||||
use std::borrow::Cow;
|
||||
|
||||
macro_rules! seq_impl {
|
||||
|
@ -21,15 +20,10 @@ macro_rules! seq_impl {
|
|||
}
|
||||
|
||||
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
SchemaObject {
|
||||
instance_type: Some(InstanceType::Array.into()),
|
||||
array: Some(Box::new(ArrayValidation {
|
||||
items: Some(gen.subschema_for::<T>().into()),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
json_schema!({
|
||||
"type": "array",
|
||||
"items": gen.subschema_for::<T>(),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -53,16 +47,11 @@ macro_rules! set_impl {
|
|||
}
|
||||
|
||||
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
SchemaObject {
|
||||
instance_type: Some(InstanceType::Array.into()),
|
||||
array: Some(Box::new(ArrayValidation {
|
||||
unique_items: Some(true),
|
||||
items: Some(gen.subschema_for::<T>().into()),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
json_schema!({
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"items": gen.subschema_for::<T>(),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use crate::{json_schema, JsonSchema, Schema};
|
||||
use serde_json::{Map, Number, Value};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
|
@ -17,7 +16,7 @@ impl JsonSchema for Value {
|
|||
}
|
||||
|
||||
fn json_schema(_: &mut SchemaGenerator) -> Schema {
|
||||
Schema::Bool(true)
|
||||
true.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,11 +34,9 @@ impl JsonSchema for Number {
|
|||
}
|
||||
|
||||
fn json_schema(_: &mut SchemaGenerator) -> Schema {
|
||||
SchemaObject {
|
||||
instance_type: Some(InstanceType::Number.into()),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
json_schema!({
|
||||
"type": "number"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>);
|
|
@ -1,6 +0,0 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use smol_str::SmolStr;
|
||||
|
||||
forward_impl!(SmolStr => String);
|
|
@ -1,6 +1,5 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use crate::{json_schema, JsonSchema, Schema};
|
||||
use std::borrow::Cow;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
|
@ -14,18 +13,14 @@ impl JsonSchema for Duration {
|
|||
}
|
||||
|
||||
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
let mut schema = SchemaObject {
|
||||
instance_type: Some(InstanceType::Object.into()),
|
||||
..Default::default()
|
||||
};
|
||||
let obj = schema.object();
|
||||
obj.required.insert("secs".to_owned());
|
||||
obj.required.insert("nanos".to_owned());
|
||||
obj.properties
|
||||
.insert("secs".to_owned(), <u64>::json_schema(gen));
|
||||
obj.properties
|
||||
.insert("nanos".to_owned(), <u32>::json_schema(gen));
|
||||
schema.into()
|
||||
json_schema!({
|
||||
"type": "object",
|
||||
"required": ["secs", "nanos"],
|
||||
"properties": {
|
||||
"secs": u64::json_schema(gen),
|
||||
"nanos": u32::json_schema(gen),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,17 +34,13 @@ impl JsonSchema for SystemTime {
|
|||
}
|
||||
|
||||
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
let mut schema = SchemaObject {
|
||||
instance_type: Some(InstanceType::Object.into()),
|
||||
..Default::default()
|
||||
};
|
||||
let obj = schema.object();
|
||||
obj.required.insert("secs_since_epoch".to_owned());
|
||||
obj.required.insert("nanos_since_epoch".to_owned());
|
||||
obj.properties
|
||||
.insert("secs_since_epoch".to_owned(), <u64>::json_schema(gen));
|
||||
obj.properties
|
||||
.insert("nanos_since_epoch".to_owned(), <u32>::json_schema(gen));
|
||||
schema.into()
|
||||
json_schema!({
|
||||
"type": "object",
|
||||
"required": ["secs_since_epoch", "nanos_since_epoch"],
|
||||
"properties": {
|
||||
"secs_since_epoch": u64::json_schema(gen),
|
||||
"nanos_since_epoch": u32::json_schema(gen),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use crate::{json_schema, JsonSchema, Schema};
|
||||
use std::borrow::Cow;
|
||||
|
||||
macro_rules! tuple_impls {
|
||||
|
@ -24,20 +23,14 @@ macro_rules! tuple_impls {
|
|||
}
|
||||
|
||||
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
let items = vec![
|
||||
json_schema!({
|
||||
"type": "array",
|
||||
"items": [
|
||||
$(gen.subschema_for::<$name>()),+
|
||||
];
|
||||
SchemaObject {
|
||||
instance_type: Some(InstanceType::Array.into()),
|
||||
array: Some(Box::new(ArrayValidation {
|
||||
items: Some(items.into()),
|
||||
max_items: Some($len),
|
||||
min_items: Some($len),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
],
|
||||
"minItems": $len,
|
||||
"maxItems": $len,
|
||||
})
|
||||
}
|
||||
}
|
||||
)+
|
||||
|
@ -62,29 +55,3 @@ tuple_impls! {
|
|||
15 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14)
|
||||
16 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::{schema_for, schema_object_for};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn schema_for_map_any_value() {
|
||||
let schema = schema_object_for::<(i32, bool)>();
|
||||
assert_eq!(
|
||||
schema.instance_type,
|
||||
Some(SingleOrVec::from(InstanceType::Array))
|
||||
);
|
||||
let array_validation = schema.array.unwrap();
|
||||
assert_eq!(
|
||||
array_validation.items,
|
||||
Some(SingleOrVec::Vec(vec![
|
||||
schema_for::<i32>(),
|
||||
schema_for::<bool>()
|
||||
]))
|
||||
);
|
||||
assert_eq!(array_validation.max_items, Some(2));
|
||||
assert_eq!(array_validation.min_items, Some(2));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use crate::{json_schema, JsonSchema, Schema};
|
||||
use std::borrow::Cow;
|
||||
use url::Url;
|
||||
use url2::Url;
|
||||
|
||||
impl JsonSchema for Url {
|
||||
no_ref_schema!();
|
||||
|
@ -16,11 +15,9 @@ impl JsonSchema for Url {
|
|||
}
|
||||
|
||||
fn json_schema(_: &mut SchemaGenerator) -> Schema {
|
||||
SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
format: Some("uri".to_owned()),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
json_schema!({
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use crate::{json_schema, JsonSchema, Schema};
|
||||
use std::borrow::Cow;
|
||||
use uuid1::Uuid;
|
||||
|
||||
|
@ -16,11 +15,9 @@ impl JsonSchema for Uuid {
|
|||
}
|
||||
|
||||
fn json_schema(_: &mut SchemaGenerator) -> Schema {
|
||||
SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
format: Some("uuid".to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
json_schema!({
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use crate::gen::SchemaGenerator;
|
||||
use crate::schema::Schema;
|
||||
use crate::JsonSchema;
|
||||
|
||||
macro_rules! wrapper_impl {
|
||||
|
|
|
@ -1,32 +1,9 @@
|
|||
#![forbid(unsafe_code)]
|
||||
#![deny(unsafe_code)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
/// The map type used by schemars types.
|
||||
///
|
||||
/// Currently a `BTreeMap` or `IndexMap` can be used, but this may change to a different implementation
|
||||
/// with a similar interface in a future version of schemars.
|
||||
/// The `IndexMap` will be used when the `preserve_order` feature flag is set.
|
||||
#[cfg(not(feature = "preserve_order"))]
|
||||
pub type Map<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 json_schema_impls;
|
||||
mod schema;
|
||||
mod ser;
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
@ -36,7 +13,6 @@ mod macros;
|
|||
#[doc(hidden)]
|
||||
pub mod _private;
|
||||
pub mod gen;
|
||||
pub mod schema;
|
||||
pub mod visit;
|
||||
|
||||
#[cfg(feature = "schemars_derive")]
|
||||
|
@ -50,7 +26,7 @@ pub use schemars_derive::*;
|
|||
#[doc(hidden)]
|
||||
pub use serde_json as _serde_json;
|
||||
|
||||
use schema::Schema;
|
||||
pub use schema::Schema;
|
||||
|
||||
/// A type which can be described as a JSON Schema document.
|
||||
///
|
||||
|
@ -75,7 +51,7 @@ use schema::Schema;
|
|||
/// you will need to determine an appropriate name and ID for the type.
|
||||
/// For non-generic types, the type name/path are suitable for this:
|
||||
/// ```
|
||||
/// use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
|
||||
/// use schemars::{gen::SchemaGenerator, Schema, JsonSchema};
|
||||
/// use std::borrow::Cow;
|
||||
///
|
||||
/// struct NonGenericType;
|
||||
|
@ -101,7 +77,7 @@ use schema::Schema;
|
|||
///
|
||||
/// But generic type parameters which may affect the generated schema should typically be included in the name/ID:
|
||||
/// ```
|
||||
/// use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
|
||||
/// use schemars::{gen::SchemaGenerator, Schema, JsonSchema};
|
||||
/// use std::{borrow::Cow, marker::PhantomData};
|
||||
///
|
||||
/// struct GenericType<T>(PhantomData<T>);
|
||||
|
@ -175,24 +151,3 @@ pub trait JsonSchema {
|
|||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,3 +76,19 @@ macro_rules! schema_for_value {
|
|||
.unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
// TODO doc
|
||||
#[macro_export]
|
||||
macro_rules! json_schema {
|
||||
(
|
||||
{$($json_object:tt)*}
|
||||
) => {
|
||||
$crate::Schema::try_from($crate::_serde_json::json!({$($json_object)*})).unwrap()
|
||||
};
|
||||
(true) => {
|
||||
$crate::Schema::from(true)
|
||||
};
|
||||
(false) => {
|
||||
$crate::Schema::from(false)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,550 +2,229 @@
|
|||
JSON Schema types.
|
||||
*/
|
||||
|
||||
#[cfg(feature = "impl_json_schema")]
|
||||
use crate as schemars;
|
||||
#[cfg(feature = "impl_json_schema")]
|
||||
use crate::JsonSchema;
|
||||
use crate::{Map, Set};
|
||||
use ref_cast::ref_cast_custom;
|
||||
use ref_cast::RefCastCustom;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::ops::Deref;
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
/// A JSON Schema.
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
pub enum Schema {
|
||||
/// A trivial boolean JSON Schema.
|
||||
///
|
||||
/// The schema `true` matches everything (always passes validation), whereas the schema `false`
|
||||
/// matches nothing (always fails validation).
|
||||
Bool(bool),
|
||||
/// A JSON Schema object.
|
||||
Object(SchemaObject),
|
||||
#[derive(Debug, Clone, PartialEq, RefCastCustom)]
|
||||
#[repr(transparent)]
|
||||
pub struct Schema(Value);
|
||||
|
||||
impl<'de> Deserialize<'de> for Schema {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let value = Value::deserialize(deserializer)?;
|
||||
Schema::validate(&value)?;
|
||||
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 {
|
||||
/// Creates a new `$ref` schema.
|
||||
///
|
||||
/// The given reference string should be a URI reference. This will usually be a JSON Pointer
|
||||
/// in [URI Fragment representation](https://tools.ietf.org/html/rfc6901#section-6).
|
||||
pub fn new_ref(reference: String) -> Self {
|
||||
SchemaObject::new_ref(reference).into()
|
||||
pub fn new() -> Self {
|
||||
Self(Value::Object(Map::new()))
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is a `$ref` schema.
|
||||
///
|
||||
/// If `self` is a [`SchemaObject`] with `Some` [`reference`](struct.SchemaObject.html#structfield.reference) set, this returns `true`.
|
||||
/// Otherwise, returns `false`.
|
||||
pub fn is_ref(&self) -> bool {
|
||||
match self {
|
||||
Schema::Object(o) => o.is_ref(),
|
||||
pub fn new_ref(reference: String) -> Self {
|
||||
let mut map = Map::new();
|
||||
map.insert("$ref".to_owned(), Value::String(reference));
|
||||
Self(Value::Object(map))
|
||||
}
|
||||
|
||||
pub fn as_value(&self) -> &Value {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn as_bool(&self) -> Option<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,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the given schema (if it is a boolean schema) into an equivalent schema object.
|
||||
///
|
||||
/// If the given schema is already a schema object, this has no effect.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use schemars::schema::{Schema, SchemaObject};
|
||||
///
|
||||
/// let bool_schema = Schema::Bool(true);
|
||||
///
|
||||
/// assert_eq!(bool_schema.into_object(), SchemaObject::default());
|
||||
/// ```
|
||||
pub fn into_object(self) -> SchemaObject {
|
||||
match self {
|
||||
Schema::Object(o) => o,
|
||||
Schema::Bool(true) => SchemaObject::default(),
|
||||
Schema::Bool(false) => SchemaObject {
|
||||
subschemas: Some(Box::new(SubschemaValidation {
|
||||
not: Some(Schema::Object(Default::default()).into()),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
},
|
||||
fn validate<E: serde::de::Error>(value: &Value) -> Result<(), E> {
|
||||
use serde::de::Unexpected;
|
||||
let unexpected = match value {
|
||||
Value::Bool(_) | Value::Object(_) => return Ok(()),
|
||||
Value::Null => Unexpected::Unit,
|
||||
Value::Number(n) => {
|
||||
if let Some(u) = n.as_u64() {
|
||||
Unexpected::Unsigned(u)
|
||||
} else if let Some(i) = n.as_i64() {
|
||||
Unexpected::Signed(i)
|
||||
} else if let Some(f) = n.as_f64() {
|
||||
Unexpected::Float(f)
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
Value::String(s) => Unexpected::Str(s),
|
||||
Value::Array(_) => Unexpected::Seq,
|
||||
};
|
||||
|
||||
Err(E::invalid_type(unexpected, &"object or boolean"))
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
#[ref_cast_custom]
|
||||
fn ref_cast(value: &Value) -> &Self;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
#[ref_cast_custom]
|
||||
fn ref_cast_mut(value: &mut Value) -> &mut Self;
|
||||
}
|
||||
|
||||
impl From<Schema> for Value {
|
||||
fn from(v: Schema) -> Value {
|
||||
v.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SchemaObject> for Schema {
|
||||
fn from(o: SchemaObject) -> Self {
|
||||
Schema::Object(o)
|
||||
impl std::convert::TryFrom<Value> for Schema {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
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 {
|
||||
fn from(b: bool) -> Self {
|
||||
Schema::Bool(b)
|
||||
Schema(Value::Bool(b))
|
||||
}
|
||||
}
|
||||
|
||||
/// The root object of a JSON Schema document.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
|
||||
#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))]
|
||||
#[serde(rename_all = "camelCase", default)]
|
||||
pub struct RootSchema {
|
||||
/// The `$schema` keyword.
|
||||
///
|
||||
/// See [JSON Schema 8.1.1. The "$schema" Keyword](https://tools.ietf.org/html/draft-handrews-json-schema-02#section-8.1.1).
|
||||
#[serde(rename = "$schema", skip_serializing_if = "Option::is_none")]
|
||||
pub meta_schema: Option<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>,
|
||||
}
|
||||
mod ser {
|
||||
use serde::ser::{Serialize, SerializeMap, SerializeSeq};
|
||||
use serde_json::Value;
|
||||
|
||||
/// A JSON Schema object.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
|
||||
#[cfg_attr(feature = "impl_json_schema", derive(JsonSchema))]
|
||||
#[serde(rename_all = "camelCase", default)]
|
||||
pub struct SchemaObject {
|
||||
/// Properties which annotate the [`SchemaObject`] which typically have no effect when an object is being validated against the schema.
|
||||
#[serde(flatten, deserialize_with = "skip_if_default")]
|
||||
pub metadata: Option<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>,
|
||||
}
|
||||
const ORDERED_KEYWORDS_START: [&str; 6] =
|
||||
["$id", "$schema", "title", "description", "type", "format"];
|
||||
const ORDERED_KEYWORDS_END: [&str; 2] = ["$defs", "definitions"];
|
||||
|
||||
// Deserializing "null" to `Option<Value>` directly results in `None`,
|
||||
// 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)
|
||||
}
|
||||
pub(super) struct OrderedKeywordWrapper<'a>(pub &'a Value);
|
||||
|
||||
fn skip_if_default<'de, D, T>(deserializer: D) -> Result<Option<Box<T>>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
T: Deserialize<'de> + Default + PartialEq,
|
||||
{
|
||||
let value = T::deserialize(deserializer)?;
|
||||
if value == T::default() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(Box::new(value)))
|
||||
impl Serialize for OrderedKeywordWrapper<'_> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self.0 {
|
||||
Value::Array(array) => {
|
||||
let mut seq = serializer.serialize_seq(Some(array.len()))?;
|
||||
for value in array {
|
||||
seq.serialize_element(&OrderedKeywordWrapper(value))?;
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! get_or_insert_default_fn {
|
||||
($name:ident, $ret:ty) => {
|
||||
get_or_insert_default_fn!(
|
||||
concat!(
|
||||
"Returns a mutable reference to this schema's [`",
|
||||
stringify!($ret),
|
||||
"`](#structfield.",
|
||||
stringify!($name),
|
||||
"), creating it if it was `None`."
|
||||
),
|
||||
$name,
|
||||
$ret
|
||||
);
|
||||
};
|
||||
($doc:expr, $name:ident, $ret:ty) => {
|
||||
#[doc = $doc]
|
||||
pub fn $name(&mut self) -> &mut $ret {
|
||||
self.$name.get_or_insert_with(Default::default)
|
||||
seq.end()
|
||||
}
|
||||
};
|
||||
}
|
||||
Value::Object(object) => {
|
||||
let mut map = serializer.serialize_map(Some(object.len()))?;
|
||||
|
||||
impl SchemaObject {
|
||||
/// Creates a new `$ref` schema.
|
||||
///
|
||||
/// The given reference string should be a URI reference. This will usually be a JSON Pointer
|
||||
/// in [URI Fragment representation](https://tools.ietf.org/html/rfc6901#section-6).
|
||||
pub fn new_ref(reference: String) -> Self {
|
||||
SchemaObject {
|
||||
reference: Some(reference),
|
||||
..Default::default()
|
||||
for key in ORDERED_KEYWORDS_START {
|
||||
if let Some(value) = object.get(key) {
|
||||
map.serialize_entry(key, &OrderedKeywordWrapper(value))?;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is a `$ref` schema.
|
||||
///
|
||||
/// If `self` has `Some` [`reference`](struct.SchemaObject.html#structfield.reference) set, this returns `true`.
|
||||
/// Otherwise, returns `false`.
|
||||
pub fn is_ref(&self) -> bool {
|
||||
self.reference.is_some()
|
||||
for (key, value) in object {
|
||||
if !ORDERED_KEYWORDS_START.contains(&key.as_str())
|
||||
&& !ORDERED_KEYWORDS_END.contains(&key.as_str())
|
||||
{
|
||||
map.serialize_entry(key, &OrderedKeywordWrapper(value))?;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` accepts values of the given type, according to the [`instance_type`](struct.SchemaObject.html#structfield.instance_type) field.
|
||||
///
|
||||
/// This is a basic check that always returns `true` if no `instance_type` is specified on the schema,
|
||||
/// and does not check any subschemas. Because of this, both `{}` and `{"not": {}}` accept any type according
|
||||
/// to this method.
|
||||
pub fn has_type(&self, ty: InstanceType) -> bool {
|
||||
self.instance_type
|
||||
.as_ref()
|
||||
.map_or(true, |x| x.contains(&ty))
|
||||
for key in ORDERED_KEYWORDS_END {
|
||||
if let Some(value) = object.get(key) {
|
||||
map.serialize_entry(key, &OrderedKeywordWrapper(value))?;
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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))
|
||||
_ => self.0.serialize(serializer),
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use crate::schema::*;
|
||||
use crate::JsonSchema;
|
||||
use crate::{gen::SchemaGenerator, Map};
|
||||
use serde_json::{Error, Value};
|
||||
use std::{convert::TryInto, fmt::Display};
|
||||
use crate::gen::SchemaGenerator;
|
||||
use crate::{json_schema, JsonSchema, Schema};
|
||||
use serde_json::{Error, Map, Value};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub(crate) struct Serializer<'a> {
|
||||
pub(crate) gen: &'a mut SchemaGenerator,
|
||||
|
@ -22,7 +21,7 @@ pub(crate) struct SerializeTuple<'a> {
|
|||
|
||||
pub(crate) struct SerializeMap<'a> {
|
||||
gen: &'a mut SchemaGenerator,
|
||||
properties: Map<String, Schema>,
|
||||
properties: Map<String, Value>,
|
||||
current_key: Option<String>,
|
||||
title: &'static str,
|
||||
}
|
||||
|
@ -36,13 +35,11 @@ macro_rules! forward_to_subschema_for {
|
|||
}
|
||||
|
||||
macro_rules! return_instance_type {
|
||||
($fn:ident, $ty:ty, $instance_type:ident) => {
|
||||
($fn:ident, $ty:ty, $instance_type:expr) => {
|
||||
fn $fn(self, _value: $ty) -> Result<Self::Ok, Self::Error> {
|
||||
Ok(SchemaObject {
|
||||
instance_type: Some(InstanceType::$instance_type.into()),
|
||||
..Default::default()
|
||||
}
|
||||
.into())
|
||||
Ok(json_schema!({
|
||||
"type": $instance_type
|
||||
}))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -59,18 +56,18 @@ impl<'a> serde::Serializer for Serializer<'a> {
|
|||
type SerializeStruct = SerializeMap<'a>;
|
||||
type SerializeStructVariant = Self;
|
||||
|
||||
return_instance_type!(serialize_i8, i8, Integer);
|
||||
return_instance_type!(serialize_i16, i16, Integer);
|
||||
return_instance_type!(serialize_i32, i32, Integer);
|
||||
return_instance_type!(serialize_i64, i64, Integer);
|
||||
return_instance_type!(serialize_i128, i128, Integer);
|
||||
return_instance_type!(serialize_u8, u8, Integer);
|
||||
return_instance_type!(serialize_u16, u16, Integer);
|
||||
return_instance_type!(serialize_u32, u32, Integer);
|
||||
return_instance_type!(serialize_u64, u64, Integer);
|
||||
return_instance_type!(serialize_u128, u128, Integer);
|
||||
return_instance_type!(serialize_f32, f32, Number);
|
||||
return_instance_type!(serialize_f64, f64, Number);
|
||||
return_instance_type!(serialize_i8, i8, "integer");
|
||||
return_instance_type!(serialize_i16, i16, "integer");
|
||||
return_instance_type!(serialize_i32, i32, "integer");
|
||||
return_instance_type!(serialize_i64, i64, "integer");
|
||||
return_instance_type!(serialize_i128, i128, "integer");
|
||||
return_instance_type!(serialize_u8, u8, "integer");
|
||||
return_instance_type!(serialize_u16, u16, "integer");
|
||||
return_instance_type!(serialize_u32, u32, "integer");
|
||||
return_instance_type!(serialize_u64, u64, "integer");
|
||||
return_instance_type!(serialize_u128, u128, "integer");
|
||||
return_instance_type!(serialize_f32, f32, "number");
|
||||
return_instance_type!(serialize_f64, f64, "number");
|
||||
|
||||
forward_to_subschema_for!(serialize_bool, bool);
|
||||
forward_to_subschema_for!(serialize_char, char);
|
||||
|
@ -93,7 +90,7 @@ impl<'a> serde::Serializer for Serializer<'a> {
|
|||
let value_schema = iter
|
||||
.into_iter()
|
||||
.try_fold(None, |acc, (_, v)| {
|
||||
if acc == Some(Schema::Bool(true)) {
|
||||
if acc == Some(true.into()) {
|
||||
return Ok(acc);
|
||||
}
|
||||
|
||||
|
@ -103,21 +100,16 @@ impl<'a> serde::Serializer for Serializer<'a> {
|
|||
})?;
|
||||
Ok(match &acc {
|
||||
None => Some(schema),
|
||||
Some(items) if items != &schema => Some(Schema::Bool(true)),
|
||||
Some(items) if items != &schema => Some(true.into()),
|
||||
_ => acc,
|
||||
})
|
||||
})?
|
||||
.unwrap_or(Schema::Bool(true));
|
||||
.unwrap_or(true.into());
|
||||
|
||||
Ok(SchemaObject {
|
||||
instance_type: Some(InstanceType::Object.into()),
|
||||
object: Some(Box::new(ObjectValidation {
|
||||
additional_properties: Some(Box::new(value_schema)),
|
||||
..ObjectValidation::default()
|
||||
})),
|
||||
..SchemaObject::default()
|
||||
}
|
||||
.into())
|
||||
Ok(json_schema!({
|
||||
"type": "object",
|
||||
"additionalProperties": value_schema,
|
||||
}))
|
||||
}
|
||||
|
||||
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
|
||||
|
@ -132,52 +124,47 @@ impl<'a> serde::Serializer for Serializer<'a> {
|
|||
where
|
||||
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 {
|
||||
gen: self.gen,
|
||||
include_title: false,
|
||||
})?;
|
||||
|
||||
if self.gen.settings().option_add_null_type {
|
||||
schema = match schema {
|
||||
Schema::Bool(true) => Schema::Bool(true),
|
||||
Schema::Bool(false) => <()>::json_schema(self.gen),
|
||||
Schema::Object(SchemaObject {
|
||||
instance_type: Some(ref mut instance_type),
|
||||
..
|
||||
}) => {
|
||||
add_null_type(instance_type);
|
||||
schema
|
||||
schema = match schema.try_to_object() {
|
||||
Ok(mut obj) => {
|
||||
let value = obj.get_mut("type");
|
||||
match value {
|
||||
Some(Value::Array(array)) => {
|
||||
let null = Value::from("null");
|
||||
if !array.contains(&null) {
|
||||
array.push(null);
|
||||
}
|
||||
schema => SchemaObject {
|
||||
subschemas: Some(Box::new(SubschemaValidation {
|
||||
any_of: Some(vec![schema, <()>::json_schema(self.gen)]),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
obj.into()
|
||||
}
|
||||
.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)
|
||||
]
|
||||
}),
|
||||
}
|
||||
}
|
||||
Err(true) => true.into(),
|
||||
Err(false) => <()>::json_schema(self.gen),
|
||||
}
|
||||
}
|
||||
|
||||
if self.gen.settings().option_nullable {
|
||||
let mut schema_obj = schema.into_object();
|
||||
schema_obj
|
||||
.extensions
|
||||
.insert("nullable".to_owned(), serde_json::json!(true));
|
||||
schema = Schema::Object(schema_obj);
|
||||
schema
|
||||
.ensure_object()
|
||||
.insert("nullable".into(), true.into());
|
||||
};
|
||||
|
||||
Ok(schema)
|
||||
|
@ -193,7 +180,7 @@ impl<'a> serde::Serializer for Serializer<'a> {
|
|||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
) -> Result<Self::Ok, Self::Error> {
|
||||
Ok(Schema::Bool(true))
|
||||
Ok(true.into())
|
||||
}
|
||||
|
||||
fn serialize_newtype_struct<T: ?Sized>(
|
||||
|
@ -205,15 +192,13 @@ impl<'a> serde::Serializer for Serializer<'a> {
|
|||
T: serde::Serialize,
|
||||
{
|
||||
let include_title = self.include_title;
|
||||
let mut result = value.serialize(self);
|
||||
let mut schema = value.serialize(self)?;
|
||||
|
||||
if include_title {
|
||||
if let Ok(Schema::Object(ref mut object)) = result {
|
||||
object.metadata().title = Some(name.to_string());
|
||||
}
|
||||
if include_title && !name.is_empty() {
|
||||
schema.ensure_object().insert("title".into(), name.into());
|
||||
}
|
||||
|
||||
result
|
||||
Ok(schema)
|
||||
}
|
||||
|
||||
fn serialize_newtype_variant<T: ?Sized>(
|
||||
|
@ -226,7 +211,7 @@ impl<'a> serde::Serializer for Serializer<'a> {
|
|||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
Ok(Schema::Bool(true))
|
||||
Ok(true.into())
|
||||
}
|
||||
|
||||
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
|
||||
|
@ -313,7 +298,7 @@ impl serde::ser::SerializeTupleVariant for Serializer<'_> {
|
|||
}
|
||||
|
||||
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> {
|
||||
Ok(Schema::Bool(true))
|
||||
Ok(true.into())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -345,7 +330,7 @@ impl serde::ser::SerializeSeq for SerializeSeq<'_> {
|
|||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
if self.items != Some(Schema::Bool(true)) {
|
||||
if self.items != Some(true.into()) {
|
||||
let schema = value.serialize(Serializer {
|
||||
gen: self.gen,
|
||||
include_title: false,
|
||||
|
@ -354,7 +339,7 @@ impl serde::ser::SerializeSeq for SerializeSeq<'_> {
|
|||
None => self.items = Some(schema),
|
||||
Some(items) => {
|
||||
if items != &schema {
|
||||
self.items = Some(Schema::Bool(true))
|
||||
self.items = Some(true.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -364,16 +349,12 @@ impl serde::ser::SerializeSeq for SerializeSeq<'_> {
|
|||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||
let items = self.items.unwrap_or(Schema::Bool(true));
|
||||
Ok(SchemaObject {
|
||||
instance_type: Some(InstanceType::Array.into()),
|
||||
array: Some(Box::new(ArrayValidation {
|
||||
items: Some(items.into()),
|
||||
..ArrayValidation::default()
|
||||
})),
|
||||
..SchemaObject::default()
|
||||
}
|
||||
.into())
|
||||
let items = self.items.unwrap_or(true.into());
|
||||
|
||||
Ok(json_schema!({
|
||||
"type": "array",
|
||||
"items": items
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -394,23 +375,21 @@ impl serde::ser::SerializeTuple for SerializeTuple<'_> {
|
|||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||
let len = self.items.len().try_into().ok();
|
||||
let mut schema = SchemaObject {
|
||||
instance_type: Some(InstanceType::Array.into()),
|
||||
array: Some(Box::new(ArrayValidation {
|
||||
items: Some(SingleOrVec::Vec(self.items)),
|
||||
max_items: len,
|
||||
min_items: len,
|
||||
..ArrayValidation::default()
|
||||
})),
|
||||
..SchemaObject::default()
|
||||
};
|
||||
let len = self.items.len();
|
||||
let mut schema = json_schema!({
|
||||
"type": "array",
|
||||
"items": self.items,
|
||||
"maxItems": len,
|
||||
"minItems": len,
|
||||
});
|
||||
|
||||
if !self.title.is_empty() {
|
||||
schema.metadata().title = Some(self.title.to_owned());
|
||||
schema
|
||||
.ensure_object()
|
||||
.insert("title".into(), self.title.into());
|
||||
}
|
||||
|
||||
Ok(schema.into())
|
||||
Ok(schema)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -459,26 +438,24 @@ impl serde::ser::SerializeMap for SerializeMap<'_> {
|
|||
gen: self.gen,
|
||||
include_title: false,
|
||||
})?;
|
||||
self.properties.insert(key, schema);
|
||||
self.properties.insert(key, schema.into());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||
let mut schema = SchemaObject {
|
||||
instance_type: Some(InstanceType::Object.into()),
|
||||
object: Some(Box::new(ObjectValidation {
|
||||
properties: self.properties,
|
||||
..ObjectValidation::default()
|
||||
})),
|
||||
..SchemaObject::default()
|
||||
};
|
||||
let mut schema = json_schema!({
|
||||
"type": "object",
|
||||
"properties": self.properties,
|
||||
});
|
||||
|
||||
if !self.title.is_empty() {
|
||||
schema.metadata().title = Some(self.title.to_owned());
|
||||
schema
|
||||
.ensure_object()
|
||||
.insert("title".into(), self.title.into());
|
||||
}
|
||||
|
||||
Ok(schema.into())
|
||||
Ok(schema)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -498,7 +475,7 @@ impl serde::ser::SerializeStruct for SerializeMap<'_> {
|
|||
gen: self.gen,
|
||||
include_title: false,
|
||||
})?;
|
||||
self.properties.insert(key.to_string(), prop_schema);
|
||||
self.properties.insert(key.to_string(), prop_schema.into());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -7,116 +7,87 @@ All methods of `Visitor` have a default implementation that makes no change but
|
|||
When overriding one of these methods, you will *usually* want to still call this default implementation.
|
||||
|
||||
# Example
|
||||
To add a custom property to all schemas:
|
||||
To add a custom property to all object schemas:
|
||||
```
|
||||
use schemars::schema::SchemaObject;
|
||||
use schemars::visit::{Visitor, visit_schema_object};
|
||||
use schemars::Schema;
|
||||
use schemars::visit::{Visitor, visit_schema};
|
||||
|
||||
pub struct MyVisitor;
|
||||
|
||||
impl Visitor for MyVisitor {
|
||||
fn visit_schema_object(&mut self, schema: &mut SchemaObject) {
|
||||
fn visit_schema(&mut self, schema: &mut Schema) {
|
||||
// First, make our change to this schema
|
||||
schema.extensions.insert("my_property".to_string(), serde_json::json!("hello world"));
|
||||
if let Some(obj) = schema.as_object_mut() {
|
||||
obj.insert("my_property".to_string(), serde_json::json!("hello world"));
|
||||
}
|
||||
|
||||
// Then delegate to default implementation to visit any subschemas
|
||||
visit_schema_object(self, schema);
|
||||
visit_schema(self, schema);
|
||||
}
|
||||
}
|
||||
```
|
||||
*/
|
||||
use crate::schema::{RootSchema, Schema, SchemaObject, SingleOrVec};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::Schema;
|
||||
|
||||
/// Trait used to recursively modify a constructed schema and its subschemas.
|
||||
pub trait Visitor {
|
||||
/// Override this method to modify a [`RootSchema`] and (optionally) its subschemas.
|
||||
///
|
||||
/// When overriding this method, you will usually want to call the [`visit_root_schema`] function to visit subschemas.
|
||||
fn visit_root_schema(&mut self, root: &mut RootSchema) {
|
||||
visit_root_schema(self, root)
|
||||
}
|
||||
|
||||
/// Override this method to modify a [`Schema`] and (optionally) its subschemas.
|
||||
///
|
||||
/// When overriding this method, you will usually want to call the [`visit_schema`] function to visit subschemas.
|
||||
fn visit_schema(&mut self, schema: &mut Schema) {
|
||||
visit_schema(self, schema)
|
||||
}
|
||||
|
||||
/// Override this method to modify a [`SchemaObject`] and (optionally) its subschemas.
|
||||
///
|
||||
/// When overriding this method, you will usually want to call the [`visit_schema_object`] function to visit subschemas.
|
||||
fn visit_schema_object(&mut self, schema: &mut SchemaObject) {
|
||||
visit_schema_object(self, schema)
|
||||
}
|
||||
}
|
||||
|
||||
/// Visits all subschemas of the [`RootSchema`].
|
||||
pub fn visit_root_schema<V: 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`].
|
||||
pub fn visit_schema<V: Visitor + ?Sized>(v: &mut V, schema: &mut Schema) {
|
||||
if let Schema::Object(schema) = schema {
|
||||
v.visit_schema_object(schema)
|
||||
}
|
||||
}
|
||||
|
||||
/// Visits all subschemas of the [`SchemaObject`].
|
||||
pub fn visit_schema_object<V: Visitor + ?Sized>(v: &mut V, schema: &mut SchemaObject) {
|
||||
if let Some(sub) = &mut schema.subschemas {
|
||||
visit_vec(v, &mut sub.all_of);
|
||||
visit_vec(v, &mut sub.any_of);
|
||||
visit_vec(v, &mut sub.one_of);
|
||||
visit_box(v, &mut sub.not);
|
||||
visit_box(v, &mut sub.if_schema);
|
||||
visit_box(v, &mut sub.then_schema);
|
||||
visit_box(v, &mut sub.else_schema);
|
||||
}
|
||||
|
||||
if let Some(arr) = &mut schema.array {
|
||||
visit_single_or_vec(v, &mut arr.items);
|
||||
visit_box(v, &mut arr.additional_items);
|
||||
visit_box(v, &mut arr.contains);
|
||||
}
|
||||
|
||||
if let Some(obj) = &mut schema.object {
|
||||
visit_map_values(v, &mut obj.properties);
|
||||
visit_map_values(v, &mut obj.pattern_properties);
|
||||
visit_box(v, &mut obj.additional_properties);
|
||||
visit_box(v, &mut obj.property_names);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_box<V: Visitor + ?Sized>(v: &mut V, target: &mut Option<Box<Schema>>) {
|
||||
if let Some(s) = target {
|
||||
v.visit_schema(s)
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_vec<V: Visitor + ?Sized>(v: &mut V, target: &mut Option<Vec<Schema>>) {
|
||||
if let Some(vec) = target {
|
||||
for s in vec {
|
||||
v.visit_schema(s)
|
||||
if let Some(obj) = schema.as_object_mut() {
|
||||
for (key, value) in obj {
|
||||
match key.as_str() {
|
||||
"not"
|
||||
| "if"
|
||||
| "then"
|
||||
| "else"
|
||||
| "additionalItems"
|
||||
| "contains"
|
||||
| "additionalProperties"
|
||||
| "propertyNames" => {
|
||||
if let Ok(subschema) = value.try_into() {
|
||||
v.visit_schema(subschema)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
"allOf" | "anyOf" | "oneOf" => {
|
||||
if let Some(array) = value.as_array_mut() {
|
||||
for value in array {
|
||||
if let Ok(subschema) = value.try_into() {
|
||||
v.visit_schema(subschema)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
"items" => {
|
||||
if let Some(array) = value.as_array_mut() {
|
||||
for value in array {
|
||||
if let Ok(subschema) = value.try_into() {
|
||||
v.visit_schema(subschema)
|
||||
}
|
||||
}
|
||||
} else if let Ok(subschema) = value.try_into() {
|
||||
v.visit_schema(subschema)
|
||||
}
|
||||
}
|
||||
"properties" | "patternProperties" | "definitions" | "$defs" => {
|
||||
if let Some(obj) = value.as_object_mut() {
|
||||
for value in obj.values_mut() {
|
||||
if let Ok(subschema) = value.try_into() {
|
||||
v.visit_schema(subschema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -133,29 +104,23 @@ pub struct ReplaceBoolSchemas {
|
|||
|
||||
impl Visitor for ReplaceBoolSchemas {
|
||||
fn visit_schema(&mut self, schema: &mut Schema) {
|
||||
if let Some(obj) = schema.as_object_mut() {
|
||||
if self.skip_additional_properties {
|
||||
if let Some((ap_key, ap_value)) = obj.remove_entry("additionalProperties") {
|
||||
visit_schema(self, schema);
|
||||
|
||||
if let Schema::Bool(b) = *schema {
|
||||
*schema = Schema::Bool(b).into_object().into()
|
||||
if let Some(obj) = schema.as_object_mut() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visit_schema_object(self, schema);
|
||||
visit_schema(self, schema);
|
||||
} else {
|
||||
schema.ensure_object();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,18 +131,19 @@ impl Visitor for ReplaceBoolSchemas {
|
|||
pub struct RemoveRefSiblings;
|
||||
|
||||
impl Visitor for RemoveRefSiblings {
|
||||
fn visit_schema_object(&mut self, schema: &mut SchemaObject) {
|
||||
visit_schema_object(self, schema);
|
||||
fn visit_schema(&mut self, schema: &mut Schema) {
|
||||
visit_schema(self, schema);
|
||||
|
||||
if let Some(reference) = schema.reference.take() {
|
||||
if schema == &SchemaObject::default() {
|
||||
schema.reference = Some(reference);
|
||||
} else {
|
||||
let ref_schema = Schema::new_ref(reference);
|
||||
let all_of = &mut schema.subschemas().all_of;
|
||||
match all_of {
|
||||
Some(vec) => vec.push(ref_schema),
|
||||
None => *all_of = Some(vec![ref_schema]),
|
||||
if let Some(obj) = schema.as_object_mut() {
|
||||
if obj.len() > 1 {
|
||||
if let Some(ref_value) = obj.remove("$ref") {
|
||||
if let Value::Array(all_of) =
|
||||
obj.entry("allOf").or_insert(Value::Array(Vec::new()))
|
||||
{
|
||||
all_of.push(json!({
|
||||
"$ref": ref_value
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -188,25 +154,18 @@ impl Visitor for RemoveRefSiblings {
|
|||
///
|
||||
/// This is useful for dialects of JSON Schema (e.g. OpenAPI 3.0) that do not support the `examples` property.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SetSingleExample {
|
||||
/// When set to `true`, the `examples` property will not be removed, but its first value will still be copied to `example`.
|
||||
pub retain_examples: bool,
|
||||
}
|
||||
pub struct SetSingleExample;
|
||||
|
||||
impl Visitor for SetSingleExample {
|
||||
fn visit_schema_object(&mut self, schema: &mut SchemaObject) {
|
||||
visit_schema_object(self, schema);
|
||||
fn visit_schema(&mut self, schema: &mut Schema) {
|
||||
visit_schema(self, schema);
|
||||
|
||||
let first_example = schema.metadata.as_mut().and_then(|m| {
|
||||
if self.retain_examples {
|
||||
m.examples.first().cloned()
|
||||
} else {
|
||||
m.examples.drain(..).next()
|
||||
if let Some(obj) = schema.as_object_mut() {
|
||||
if let Some(Value::Array(examples)) = obj.remove("examples") {
|
||||
if let Some(first_example) = examples.into_iter().next() {
|
||||
obj.insert("example".into(), first_example);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(example) = first_example {
|
||||
schema.extensions.insert("example".to_owned(), example);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,6 @@
|
|||
mod 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]
|
||||
fn arrayvec07() -> TestResult {
|
||||
test_default_generated_schema::<arrayvec07::ArrayVec<i32, 16>>("arrayvec")
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
mod util;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use bytes1::{Bytes, BytesMut};
|
||||
use util::*;
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
mod util;
|
||||
use chrono::prelude::*;
|
||||
use chrono04::prelude::*;
|
||||
use schemars::JsonSchema;
|
||||
use util::*;
|
||||
|
||||
|
|
|
@ -3,12 +3,7 @@ use util::*;
|
|||
|
||||
#[test]
|
||||
fn rust_decimal() -> TestResult {
|
||||
test_default_generated_schema::<rust_decimal::Decimal>("rust_decimal")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bigdecimal03() -> TestResult {
|
||||
test_default_generated_schema::<bigdecimal03::BigDecimal>("bigdecimal03")
|
||||
test_default_generated_schema::<rust_decimal1::Decimal>("rust_decimal")
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -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));
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
mod util;
|
||||
use either::Either;
|
||||
use either1::Either;
|
||||
use util::*;
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
mod util;
|
||||
use schemars::{JsonSchema, Map};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use util::*;
|
||||
|
||||
// Ensure that schemars_derive uses the full path to std::string::String
|
||||
|
@ -20,7 +22,7 @@ struct Struct {
|
|||
#[schemars(rename_all = "camelCase")]
|
||||
enum External {
|
||||
UnitOne,
|
||||
StringMap(Map<&'static str, &'static str>),
|
||||
StringMap(BTreeMap<&'static str, &'static str>),
|
||||
UnitStructNewType(UnitStruct),
|
||||
StructNewType(Struct),
|
||||
Struct {
|
||||
|
@ -29,6 +31,7 @@ enum External {
|
|||
},
|
||||
UnitTwo,
|
||||
Tuple(i32, bool),
|
||||
// FIXME this should probably only replace the "payload" of the enum
|
||||
#[schemars(with = "i32")]
|
||||
WithInt,
|
||||
}
|
||||
|
@ -43,7 +46,7 @@ fn enum_external_tag() -> TestResult {
|
|||
#[schemars(tag = "typeProperty")]
|
||||
enum Internal {
|
||||
UnitOne,
|
||||
StringMap(Map<&'static str, &'static str>),
|
||||
StringMap(BTreeMap<&'static str, &'static str>),
|
||||
UnitStructNewType(UnitStruct),
|
||||
StructNewType(Struct),
|
||||
Struct {
|
||||
|
@ -51,6 +54,7 @@ enum Internal {
|
|||
bar: bool,
|
||||
},
|
||||
UnitTwo,
|
||||
// FIXME this should probably only replace the "payload" of the enum
|
||||
#[schemars(with = "i32")]
|
||||
WithInt,
|
||||
}
|
||||
|
@ -65,7 +69,7 @@ fn enum_internal_tag() -> TestResult {
|
|||
#[schemars(untagged)]
|
||||
enum Untagged {
|
||||
UnitOne,
|
||||
StringMap(Map<&'static str, &'static str>),
|
||||
StringMap(BTreeMap<&'static str, &'static str>),
|
||||
UnitStructNewType(UnitStruct),
|
||||
StructNewType(Struct),
|
||||
Struct {
|
||||
|
@ -73,6 +77,7 @@ enum Untagged {
|
|||
bar: bool,
|
||||
},
|
||||
Tuple(i32, bool),
|
||||
// FIXME this should probably only replace the "payload" of the enum
|
||||
#[schemars(with = "i32")]
|
||||
WithInt,
|
||||
}
|
||||
|
@ -87,7 +92,7 @@ fn enum_untagged() -> TestResult {
|
|||
#[schemars(tag = "t", content = "c")]
|
||||
enum Adjacent {
|
||||
UnitOne,
|
||||
StringMap(Map<&'static str, &'static str>),
|
||||
StringMap(BTreeMap<&'static str, &'static str>),
|
||||
UnitStructNewType(UnitStruct),
|
||||
StructNewType(Struct),
|
||||
Struct {
|
||||
|
@ -96,6 +101,7 @@ enum Adjacent {
|
|||
},
|
||||
Tuple(i32, bool),
|
||||
UnitTwo,
|
||||
// FIXME this should probably only replace the "payload" of the enum
|
||||
#[schemars(with = "i32")]
|
||||
WithInt,
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
mod util;
|
||||
use schemars::{JsonSchema, Map};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use util::*;
|
||||
|
||||
// Ensure that schemars_derive uses the full path to std::string::String
|
||||
|
@ -22,7 +24,7 @@ struct Struct {
|
|||
#[schemars(rename_all = "camelCase", deny_unknown_fields)]
|
||||
enum External {
|
||||
UnitOne,
|
||||
StringMap(Map<&'static str, &'static str>),
|
||||
StringMap(BTreeMap<&'static str, &'static str>),
|
||||
UnitStructNewType(UnitStruct),
|
||||
StructNewType(Struct),
|
||||
Struct {
|
||||
|
@ -31,6 +33,7 @@ enum External {
|
|||
},
|
||||
UnitTwo,
|
||||
Tuple(i32, bool),
|
||||
// FIXME this should probably only replace the "payload" of the enum
|
||||
#[schemars(with = "i32")]
|
||||
WithInt,
|
||||
}
|
||||
|
@ -46,7 +49,7 @@ fn enum_external_tag() -> TestResult {
|
|||
#[schemars(tag = "typeProperty", deny_unknown_fields)]
|
||||
enum Internal {
|
||||
UnitOne,
|
||||
StringMap(Map<&'static str, &'static str>),
|
||||
StringMap(BTreeMap<&'static str, &'static str>),
|
||||
UnitStructNewType(UnitStruct),
|
||||
StructNewType(Struct),
|
||||
Struct {
|
||||
|
@ -54,6 +57,7 @@ enum Internal {
|
|||
bar: bool,
|
||||
},
|
||||
UnitTwo,
|
||||
// FIXME this should only replace the "payload" of the enum (which doesn't even make sense for unit enums!)
|
||||
#[schemars(with = "i32")]
|
||||
WithInt,
|
||||
}
|
||||
|
@ -69,7 +73,7 @@ fn enum_internal_tag() -> TestResult {
|
|||
#[schemars(untagged, deny_unknown_fields)]
|
||||
enum Untagged {
|
||||
UnitOne,
|
||||
StringMap(Map<&'static str, &'static str>),
|
||||
StringMap(BTreeMap<&'static str, &'static str>),
|
||||
UnitStructNewType(UnitStruct),
|
||||
StructNewType(Struct),
|
||||
Struct {
|
||||
|
@ -77,6 +81,7 @@ enum Untagged {
|
|||
bar: bool,
|
||||
},
|
||||
Tuple(i32, bool),
|
||||
// FIXME this should probably only replace the "payload" of the enum
|
||||
#[schemars(with = "i32")]
|
||||
WithInt,
|
||||
}
|
||||
|
@ -92,7 +97,7 @@ fn enum_untagged() -> TestResult {
|
|||
#[schemars(tag = "t", content = "c", deny_unknown_fields)]
|
||||
enum Adjacent {
|
||||
UnitOne,
|
||||
StringMap(Map<&'static str, &'static str>),
|
||||
StringMap(BTreeMap<&'static str, &'static str>),
|
||||
UnitStructNewType(UnitStruct),
|
||||
StructNewType(Struct),
|
||||
Struct {
|
||||
|
@ -101,6 +106,7 @@ enum Adjacent {
|
|||
},
|
||||
Tuple(i32, bool),
|
||||
UnitTwo,
|
||||
// FIXME this should probably only replace the "payload" of the enum
|
||||
#[schemars(with = "i32")]
|
||||
WithInt,
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
mod util;
|
||||
use enumset::{EnumSet, EnumSetType};
|
||||
use enumset1::{EnumSet, EnumSetType};
|
||||
use schemars::JsonSchema;
|
||||
use util::*;
|
||||
|
||||
// needed to derive EnumSetType when using a crate alias
|
||||
extern crate enumset1 as enumset;
|
||||
|
||||
#[derive(EnumSetType, JsonSchema)]
|
||||
enum Foo {
|
||||
Bar,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "String",
|
||||
"title": "string",
|
||||
"type": "string"
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Decimal",
|
||||
"type": "string",
|
||||
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Either_int32_or_Either_Boolean_or_Null",
|
||||
"title": "Either_int32_or_Either_boolean_or_null",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "integer",
|
||||
|
|
|
@ -127,8 +127,7 @@
|
|||
"WithInt"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
"$ref": "#/definitions/Range_of_double"
|
||||
},
|
||||
"bound": {
|
||||
"$ref": "#/definitions/Bound_of_String"
|
||||
"$ref": "#/definitions/Bound_of_string"
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
|
@ -55,7 +55,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Bound_of_String": {
|
||||
"Bound_of_string": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
],
|
||||
"properties": {
|
||||
"byte_or_bool2": {
|
||||
"$ref": "#/definitions/Or_for_uint8_and_Boolean"
|
||||
"$ref": "#/definitions/Or_for_uint8_and_boolean"
|
||||
},
|
||||
"unit_or_t2": {
|
||||
"$ref": "#/definitions/Or_for_Null_and_int32"
|
||||
"$ref": "#/definitions/Or_for_null_and_int32"
|
||||
},
|
||||
"s": {
|
||||
"$ref": "#/definitions/Str"
|
||||
|
@ -30,7 +30,7 @@
|
|||
}
|
||||
},
|
||||
"definitions": {
|
||||
"Or_for_uint8_and_Boolean": {
|
||||
"Or_for_uint8_and_boolean": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "integer",
|
||||
|
@ -42,7 +42,7 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"Or_for_Null_and_int32": {
|
||||
"Or_for_null_and_int32": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "null"
|
||||
|
|
|
@ -8,14 +8,14 @@
|
|||
],
|
||||
"properties": {
|
||||
"result1": {
|
||||
"$ref": "#/definitions/Result_of_MyStruct_or_Array_of_String"
|
||||
"$ref": "#/definitions/Result_of_MyStruct_or_Array_of_string"
|
||||
},
|
||||
"result2": {
|
||||
"$ref": "#/definitions/Result_of_Boolean_or_Null"
|
||||
"$ref": "#/definitions/Result_of_boolean_or_null"
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"Result_of_MyStruct_or_Array_of_String": {
|
||||
"Result_of_MyStruct_or_Array_of_string": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
|
@ -56,7 +56,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Result_of_Boolean_or_Null": {
|
||||
"Result_of_boolean_or_null": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "a-new-name-Array_of_String-int32-int32",
|
||||
"title": "a-new-name-Array_of_string-int32-int32",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"inner",
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "MyStruct_for_int32_and_Null_and_Boolean_and_Array_of_String",
|
||||
"title": "MyStruct_for_int32_and_null_and_boolean_and_Array_of_string",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"inner",
|
||||
"t",
|
||||
"u",
|
||||
"v",
|
||||
"w"
|
||||
],
|
||||
"properties": {
|
||||
"inner": {
|
||||
"$ref": "#/definitions/MySimpleStruct"
|
||||
},
|
||||
"t": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
|
@ -25,23 +21,27 @@
|
|||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"inner": {
|
||||
"$ref": "#/definitions/MySimpleStruct"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"inner",
|
||||
"t",
|
||||
"u",
|
||||
"v",
|
||||
"w"
|
||||
],
|
||||
"definitions": {
|
||||
"MySimpleStruct": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"foo"
|
||||
],
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "MixedGenericStruct_for_MyStruct_for_int32_and_Null_and_Boolean_and_Array_of_String_and_42_and_z",
|
||||
"title": "MixedGenericStruct_for_MyStruct_for_int32_and_null_and_boolean_and_Array_of_string_and_42_and_z",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"foo",
|
||||
|
@ -12,7 +12,7 @@
|
|||
"format": "int32"
|
||||
},
|
||||
"generic": {
|
||||
"$ref": "#/definitions/MyStruct_for_int32_and_Null_and_Boolean_and_Array_of_String"
|
||||
"$ref": "#/definitions/MyStruct_for_int32_and_null_and_boolean_and_Array_of_string"
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
|
@ -28,7 +28,7 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"MyStruct_for_int32_and_Null_and_Boolean_and_Array_of_String": {
|
||||
"MyStruct_for_int32_and_null_and_boolean_and_Array_of_string": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"inner",
|
||||
|
|
|
@ -22,8 +22,8 @@
|
|||
},
|
||||
{
|
||||
"type": [
|
||||
"boolean",
|
||||
"object"
|
||||
"object",
|
||||
"boolean"
|
||||
],
|
||||
"required": [
|
||||
"typeProperty"
|
||||
|
@ -39,8 +39,8 @@
|
|||
},
|
||||
{
|
||||
"type": [
|
||||
"boolean",
|
||||
"object"
|
||||
"object",
|
||||
"boolean"
|
||||
],
|
||||
"required": [
|
||||
"typeProperty"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Array_of_String",
|
||||
"title": "Array_of_string",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "String",
|
||||
"title": "string",
|
||||
"type": "string"
|
||||
}
|
|
@ -1,13 +1,15 @@
|
|||
mod util;
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use std::hash::RandomState;
|
||||
|
||||
use indexmap2::{IndexMap, IndexSet};
|
||||
use schemars::JsonSchema;
|
||||
use util::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(JsonSchema)]
|
||||
struct IndexMapTypes {
|
||||
map: IndexMap<i32, bool>,
|
||||
set: IndexSet<isize>,
|
||||
map: IndexMap<i32, bool, RandomState>,
|
||||
set: IndexSet<isize, RandomState>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -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")
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -2,7 +2,7 @@ mod util;
|
|||
use schemars::JsonSchema;
|
||||
use util::*;
|
||||
|
||||
fn schema_fn(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
fn schema_fn(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Schema {
|
||||
<bool>::json_schema(gen)
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ pub enum External {
|
|||
#[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema,
|
||||
i32,
|
||||
),
|
||||
// FIXME this should probably only replace the "payload" of the enum
|
||||
#[schemars(schema_with = "schema_fn")]
|
||||
Unit,
|
||||
}
|
||||
|
@ -38,6 +39,7 @@ pub enum Internal {
|
|||
foo: DoesntImplementJsonSchema,
|
||||
},
|
||||
NewType(#[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema),
|
||||
// FIXME this should probably only replace the "payload" of the enum
|
||||
#[schemars(schema_with = "schema_fn")]
|
||||
Unit,
|
||||
}
|
||||
|
@ -59,6 +61,7 @@ pub enum Untagged {
|
|||
#[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema,
|
||||
i32,
|
||||
),
|
||||
// FIXME this should probably only replace the "payload" of the enum
|
||||
#[schemars(schema_with = "schema_fn")]
|
||||
Unit,
|
||||
}
|
||||
|
@ -80,6 +83,7 @@ pub enum Adjacent {
|
|||
#[schemars(schema_with = "schema_fn")] DoesntImplementJsonSchema,
|
||||
i32,
|
||||
),
|
||||
// FIXME this should probably only replace the "payload" of the enum
|
||||
#[schemars(schema_with = "schema_fn")]
|
||||
Unit,
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ mod util;
|
|||
use schemars::JsonSchema;
|
||||
use util::*;
|
||||
|
||||
fn schema_fn(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
fn schema_fn(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Schema {
|
||||
<bool>::json_schema(gen)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
mod util;
|
||||
use schemars::JsonSchema;
|
||||
use semver::Version;
|
||||
use semver1::Version;
|
||||
use util::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
mod util;
|
||||
use smallvec::SmallVec;
|
||||
use smallvec1::SmallVec;
|
||||
use util::*;
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
mod util;
|
||||
use smol_str::SmolStr;
|
||||
use smol_str02::SmolStr;
|
||||
use util::*;
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
mod util;
|
||||
use schemars::JsonSchema;
|
||||
use url::Url;
|
||||
use url2::Url;
|
||||
use util::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use pretty_assertions::assert_eq;
|
||||
use schemars::{gen::SchemaSettings, schema::RootSchema, schema_for, JsonSchema};
|
||||
use schemars::visit::Visitor;
|
||||
use schemars::{gen::SchemaSettings, schema_for, JsonSchema, Schema};
|
||||
use std::error::Error;
|
||||
use std::fs;
|
||||
|
||||
|
@ -17,7 +18,16 @@ pub fn test_default_generated_schema<T: JsonSchema>(file: &str) -> TestResult {
|
|||
test_schema(&actual, file)
|
||||
}
|
||||
|
||||
pub fn test_schema(actual: &RootSchema, file: &str) -> TestResult {
|
||||
pub fn test_schema(actual: &Schema, file: &str) -> TestResult {
|
||||
// TEMP for easier comparison of schemas handling changes that don't actually affect a schema:
|
||||
// - `required` ordering has changed
|
||||
// - previously `f64` properties may now be integers
|
||||
let actual = &{
|
||||
let mut actual = actual.clone();
|
||||
TempFixupForTests.visit_schema(&mut actual);
|
||||
actual
|
||||
};
|
||||
|
||||
let expected_json = match fs::read_to_string(format!("tests/expected/{}.json", file)) {
|
||||
Ok(j) => j,
|
||||
Err(e) => {
|
||||
|
@ -35,8 +45,30 @@ pub fn test_schema(actual: &RootSchema, file: &str) -> TestResult {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn write_actual_to_file(schema: &RootSchema, file: &str) -> TestResult {
|
||||
fn write_actual_to_file(schema: &Schema, file: &str) -> TestResult {
|
||||
let actual_json = serde_json::to_string_pretty(&schema)?;
|
||||
fs::write(format!("tests/actual/{}.json", file), actual_json)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct TempFixupForTests;
|
||||
|
||||
impl schemars::visit::Visitor for TempFixupForTests {
|
||||
fn visit_schema(&mut self, schema: &mut Schema) {
|
||||
schemars::visit::visit_schema(self, schema);
|
||||
|
||||
if let Some(object) = schema.as_object_mut() {
|
||||
if let Some(serde_json::Value::Array(required)) = object.get_mut("required") {
|
||||
required.sort_unstable_by(|a, b| a.as_str().cmp(&b.as_str()));
|
||||
}
|
||||
|
||||
for (key, value) in object {
|
||||
if key == "multipleOf" || key.ends_with("aximum") || key.ends_with("inimum") {
|
||||
if let Some(f) = value.as_f64() {
|
||||
*value = f.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
mod util;
|
||||
use util::*;
|
||||
|
||||
#[test]
|
||||
fn uuid08() -> TestResult {
|
||||
test_default_generated_schema::<uuid08::Uuid>("uuid")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uuid1() -> TestResult {
|
||||
test_default_generated_schema::<uuid1::Uuid>("uuid")
|
||||
|
|
|
@ -325,120 +325,86 @@ impl ValidationAttrs {
|
|||
}
|
||||
|
||||
pub fn apply_to_schema(&self, schema_expr: &mut TokenStream) {
|
||||
if let Some(apply_expr) = self.apply_to_schema_expr() {
|
||||
*schema_expr = quote! {
|
||||
{
|
||||
let setters = self.make_setters(quote!(&mut schema));
|
||||
if !setters.is_empty() {
|
||||
*schema_expr = quote!({
|
||||
let mut schema = #schema_expr;
|
||||
#apply_expr
|
||||
#(#setters)*
|
||||
schema
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_to_schema_expr(&self) -> Option<TokenStream> {
|
||||
let mut array_validation = Vec::new();
|
||||
let mut number_validation = Vec::new();
|
||||
let mut object_validation = Vec::new();
|
||||
let mut string_validation = Vec::new();
|
||||
fn make_setters(&self, mut_schema: impl ToTokens) -> Vec<TokenStream> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
if let Some(length_min) = self.length_min.as_ref().or(self.length_equal.as_ref()) {
|
||||
string_validation.push(quote! {
|
||||
validation.min_length = Some(#length_min as u32);
|
||||
result.push(quote! {
|
||||
schemars::_private::insert_validation_property(#mut_schema, "string", "minLength", #length_min);
|
||||
});
|
||||
array_validation.push(quote! {
|
||||
validation.min_items = Some(#length_min as u32);
|
||||
result.push(quote! {
|
||||
schemars::_private::insert_validation_property(#mut_schema, "array", "minItems", #length_min);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(length_max) = self.length_max.as_ref().or(self.length_equal.as_ref()) {
|
||||
string_validation.push(quote! {
|
||||
validation.max_length = Some(#length_max as u32);
|
||||
result.push(quote! {
|
||||
schemars::_private::insert_validation_property(#mut_schema, "string", "maxLength", #length_max);
|
||||
});
|
||||
array_validation.push(quote! {
|
||||
validation.max_items = Some(#length_max as u32);
|
||||
result.push(quote! {
|
||||
schemars::_private::insert_validation_property(#mut_schema, "array", "maxItems", #length_max);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(range_min) = &self.range_min {
|
||||
number_validation.push(quote! {
|
||||
validation.minimum = Some(#range_min as f64);
|
||||
result.push(quote! {
|
||||
schemars::_private::insert_validation_property(#mut_schema, "number", "minimum", #range_min);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(range_max) = &self.range_max {
|
||||
number_validation.push(quote! {
|
||||
validation.maximum = Some(#range_max as f64);
|
||||
result.push(quote! {
|
||||
schemars::_private::insert_validation_property(#mut_schema, "number", "maximum", #range_max);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(regex) = &self.regex {
|
||||
string_validation.push(quote! {
|
||||
validation.pattern = Some(#regex.to_string());
|
||||
result.push(quote! {
|
||||
schemars::_private::insert_validation_property(#mut_schema, "string", "pattern", #regex);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(contains) = &self.contains {
|
||||
object_validation.push(quote! {
|
||||
validation.required.insert(#contains.to_string());
|
||||
result.push(quote! {
|
||||
schemars::_private::append_required(#mut_schema, #contains);
|
||||
});
|
||||
|
||||
if self.regex.is_none() {
|
||||
let pattern = crate::regex_syntax::escape(contains);
|
||||
string_validation.push(quote! {
|
||||
validation.pattern = Some(#pattern.to_string());
|
||||
result.push(quote! {
|
||||
schemars::_private::insert_validation_property(#mut_schema, "string", "pattern", #pattern);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let format = self.format.as_ref().map(|f| {
|
||||
let f = f.schema_str();
|
||||
quote! {
|
||||
schema_object.format = Some(#f.to_string());
|
||||
}
|
||||
});
|
||||
|
||||
let inner_validation = self
|
||||
.inner
|
||||
.as_deref()
|
||||
.and_then(|inner| inner.apply_to_schema_expr())
|
||||
.map(|apply_expr| {
|
||||
quote! {
|
||||
if schema_object.has_type(schemars::schema::InstanceType::Array) {
|
||||
if let Some(schemars::schema::SingleOrVec::Single(inner_schema)) = &mut schema_object.array().items {
|
||||
let mut schema = &mut **inner_schema;
|
||||
#apply_expr
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let array_validation = wrap_array_validation(array_validation);
|
||||
let number_validation = wrap_number_validation(number_validation);
|
||||
let object_validation = wrap_object_validation(object_validation);
|
||||
let string_validation = wrap_string_validation(string_validation);
|
||||
|
||||
if array_validation.is_some()
|
||||
|| number_validation.is_some()
|
||||
|| object_validation.is_some()
|
||||
|| string_validation.is_some()
|
||||
|| format.is_some()
|
||||
|| inner_validation.is_some()
|
||||
{
|
||||
Some(quote! {
|
||||
if let schemars::schema::Schema::Object(schema_object) = &mut schema {
|
||||
#array_validation
|
||||
#number_validation
|
||||
#object_validation
|
||||
#string_validation
|
||||
#format
|
||||
#inner_validation
|
||||
}
|
||||
if let Some(format) = &self.format {
|
||||
let f = format.schema_str();
|
||||
result.push(quote! {
|
||||
schema.ensure_object().insert("format".to_owned(), #f.into());
|
||||
})
|
||||
};
|
||||
|
||||
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)* });
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_lit_into_expr_path(
|
||||
|
@ -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> {
|
||||
// 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()) {
|
||||
|
|
|
@ -68,11 +68,11 @@ fn derive_json_schema(mut input: syn::DeriveInput, repr: bool) -> syn::Result<To
|
|||
<#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)
|
||||
}
|
||||
|
||||
fn _schemars_private_non_optional_json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
fn _schemars_private_non_optional_json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Schema {
|
||||
<#ty as schemars::JsonSchema>::_schemars_private_non_optional_json_schema(gen)
|
||||
}
|
||||
|
||||
|
@ -182,7 +182,7 @@ fn derive_json_schema(mut input: syn::DeriveInput, repr: bool) -> syn::Result<To
|
|||
#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
|
||||
}
|
||||
};
|
||||
|
|
|
@ -13,32 +13,46 @@ pub struct SchemaMetadata<'a> {
|
|||
|
||||
impl<'a> SchemaMetadata<'a> {
|
||||
pub fn apply_to_schema(&self, schema_expr: &mut TokenStream) {
|
||||
let setters = self.make_setters();
|
||||
if !setters.is_empty() {
|
||||
*schema_expr = quote! {{
|
||||
let mut schema = #schema_expr;
|
||||
let obj = schema.ensure_object();
|
||||
#(#setters)*
|
||||
schema
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_setters(&self) -> Vec<TokenStream> {
|
||||
let mut setters = Vec::<TokenStream>::new();
|
||||
|
||||
if let Some(title) = &self.title {
|
||||
*schema_expr = quote! {
|
||||
schemars::_private::metadata::add_title(#schema_expr, #title)
|
||||
};
|
||||
setters.push(quote! {
|
||||
obj.insert("title".to_owned(), #title.into());
|
||||
});
|
||||
}
|
||||
if let Some(description) = &self.description {
|
||||
*schema_expr = quote! {
|
||||
schemars::_private::metadata::add_description(#schema_expr, #description)
|
||||
};
|
||||
setters.push(quote! {
|
||||
obj.insert("description".to_owned(), #description.into());
|
||||
});
|
||||
}
|
||||
|
||||
if self.deprecated {
|
||||
*schema_expr = quote! {
|
||||
schemars::_private::metadata::add_deprecated(#schema_expr, true)
|
||||
};
|
||||
setters.push(quote! {
|
||||
obj.insert("deprecated".to_owned(), true.into());
|
||||
});
|
||||
}
|
||||
|
||||
if self.read_only {
|
||||
*schema_expr = quote! {
|
||||
schemars::_private::metadata::add_read_only(#schema_expr, true)
|
||||
};
|
||||
setters.push(quote! {
|
||||
obj.insert("readOnly".to_owned(), true.into());
|
||||
});
|
||||
}
|
||||
if self.write_only {
|
||||
*schema_expr = quote! {
|
||||
schemars::_private::metadata::add_write_only(#schema_expr, true)
|
||||
};
|
||||
setters.push(quote! {
|
||||
obj.insert("writeOnly".to_owned(), true.into());
|
||||
});
|
||||
}
|
||||
|
||||
if !self.examples.is_empty() {
|
||||
|
@ -47,16 +61,19 @@ impl<'a> SchemaMetadata<'a> {
|
|||
schemars::_serde_json::value::to_value(#eg())
|
||||
}
|
||||
});
|
||||
|
||||
*schema_expr = quote! {
|
||||
schemars::_private::metadata::add_examples(#schema_expr, [#(#examples),*].into_iter().flatten())
|
||||
};
|
||||
setters.push(quote! {
|
||||
obj.insert("examples".to_owned(), schemars::_serde_json::Value::Array([#(#examples),*].into_iter().flatten().collect()));
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(default) = &self.default {
|
||||
*schema_expr = quote! {
|
||||
schemars::_private::metadata::add_default(#schema_expr, #default.and_then(|d| schemars::_schemars_maybe_to_value!(d)))
|
||||
};
|
||||
setters.push(quote! {
|
||||
if let Some(default) = #default.and_then(|d| schemars::_schemars_maybe_to_value!(d)) {
|
||||
obj.insert("default".to_owned(), default);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setters
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,9 +49,18 @@ pub fn expr_for_repr(cont: &Container) -> Result<TokenStream, syn::Error> {
|
|||
let enum_ident = &cont.ident;
|
||||
let variant_idents = variants.iter().map(|v| &v.ident);
|
||||
|
||||
let mut schema_expr = schema_object(quote! {
|
||||
instance_type: Some(schemars::schema::InstanceType::Integer.into()),
|
||||
enum_values: Some(vec![#((#enum_ident::#variant_idents as #repr_type).into()),*]),
|
||||
let mut schema_expr = quote!({
|
||||
let mut map = schemars::_serde_json::Map::new();
|
||||
map.insert("type".to_owned(), "integer".into());
|
||||
map.insert(
|
||||
"enum".to_owned(),
|
||||
schemars::_serde_json::Value::Array({
|
||||
let mut enum_values = Vec::new();
|
||||
#(enum_values.push((#enum_ident::#variant_idents as #repr_type).into());)*
|
||||
enum_values
|
||||
}),
|
||||
);
|
||||
schemars::Schema::from(map)
|
||||
});
|
||||
|
||||
cont.attrs.as_metadata().apply_to_schema(&mut schema_expr);
|
||||
|
@ -118,7 +127,7 @@ fn type_for_schema(with_attr: &WithAttr) -> (syn::Type, Option<TokenStream>) {
|
|||
))
|
||||
}
|
||||
|
||||
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Schema {
|
||||
#fun(gen)
|
||||
}
|
||||
}
|
||||
|
@ -160,9 +169,18 @@ fn expr_for_external_tagged_enum<'a>(
|
|||
})
|
||||
.partition(|v| v.is_unit() && v.attrs.is_default());
|
||||
let unit_names = unit_variants.iter().map(|v| v.name());
|
||||
let unit_schema = schema_object(quote! {
|
||||
instance_type: Some(schemars::schema::InstanceType::String.into()),
|
||||
enum_values: Some(vec![#(#unit_names.into()),*]),
|
||||
let unit_schema = quote!({
|
||||
let mut map = schemars::_serde_json::Map::new();
|
||||
map.insert("type".to_owned(), "string".into());
|
||||
map.insert(
|
||||
"enum".to_owned(),
|
||||
schemars::_serde_json::Value::Array({
|
||||
let mut enum_values = Vec::new();
|
||||
#(enum_values.push((#unit_names).into());)*
|
||||
enum_values
|
||||
}),
|
||||
);
|
||||
schemars::Schema::from(map)
|
||||
});
|
||||
|
||||
if complex_variants.is_empty() {
|
||||
|
@ -276,47 +294,44 @@ fn expr_for_adjacent_tagged_enum<'a>(
|
|||
let (add_content_to_props, add_content_to_required) = content_schema
|
||||
.map(|content_schema| {
|
||||
(
|
||||
quote!(props.insert(#content_name.to_owned(), #content_schema);),
|
||||
quote!(required.insert(#content_name.to_owned());),
|
||||
quote!(#content_name: (#content_schema),),
|
||||
quote!(#content_name,),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let name = variant.name();
|
||||
let tag_schema = schema_object(quote! {
|
||||
instance_type: Some(schemars::schema::InstanceType::String.into()),
|
||||
enum_values: Some(vec![#name.into()]),
|
||||
});
|
||||
let tag_schema = quote! {
|
||||
schemars::json_schema!({
|
||||
"type": "string",
|
||||
"enum": [#name],
|
||||
})
|
||||
};
|
||||
|
||||
let set_additional_properties = if deny_unknown_fields {
|
||||
quote! {
|
||||
additional_properties: Some(Box::new(false.into())),
|
||||
"additionalProperties": false,
|
||||
}
|
||||
} else {
|
||||
TokenStream::new()
|
||||
};
|
||||
|
||||
let mut outer_schema = schema_object(quote! {
|
||||
instance_type: Some(schemars::schema::InstanceType::Object.into()),
|
||||
object: Some(Box::new(schemars::schema::ObjectValidation {
|
||||
properties: {
|
||||
let mut props = schemars::Map::new();
|
||||
props.insert(#tag_name.to_owned(), #tag_schema);
|
||||
let mut outer_schema = quote! {
|
||||
schemars::json_schema!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
#tag_name: (#tag_schema),
|
||||
#add_content_to_props
|
||||
props
|
||||
},
|
||||
required: {
|
||||
let mut required = schemars::Set::new();
|
||||
required.insert(#tag_name.to_owned());
|
||||
"required": [
|
||||
#tag_name,
|
||||
#add_content_to_required
|
||||
required
|
||||
},
|
||||
],
|
||||
// As we're creating a "wrapper" object, we can honor the
|
||||
// disposition of deny_unknown_fields.
|
||||
#set_additional_properties
|
||||
..Default::default()
|
||||
})),
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
variant
|
||||
.attrs
|
||||
|
@ -333,21 +348,19 @@ fn expr_for_adjacent_tagged_enum<'a>(
|
|||
/// Callers must determine if all subschemas are mutually exclusive. This can
|
||||
/// be done for most tagging regimes by checking that all tag names are unique.
|
||||
fn variant_subschemas(unique: bool, schemas: Vec<TokenStream>) -> TokenStream {
|
||||
if unique {
|
||||
schema_object(quote! {
|
||||
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
|
||||
one_of: Some(vec![#(#schemas),*]),
|
||||
..Default::default()
|
||||
})),
|
||||
let keyword = if unique { "oneOf" } else { "anyOf" };
|
||||
quote!({
|
||||
let mut map = schemars::_serde_json::Map::new();
|
||||
map.insert(
|
||||
#keyword.to_owned(),
|
||||
schemars::_serde_json::Value::Array({
|
||||
let mut enum_values = Vec::new();
|
||||
#(enum_values.push(#schemas.to_value());)*
|
||||
enum_values
|
||||
}),
|
||||
);
|
||||
schemars::Schema::from(map)
|
||||
})
|
||||
} else {
|
||||
schema_object(quote! {
|
||||
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
|
||||
any_of: Some(vec![#(#schemas),*]),
|
||||
..Default::default()
|
||||
})),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn expr_for_untagged_enum_variant(variant: &Variant, deny_unknown_fields: bool) -> TokenStream {
|
||||
|
@ -412,16 +425,11 @@ fn expr_for_tuple_struct(fields: &[Field]) -> TokenStream {
|
|||
let len = fields.len() as u32;
|
||||
|
||||
quote! {
|
||||
schemars::schema::Schema::Object(
|
||||
schemars::schema::SchemaObject {
|
||||
instance_type: Some(schemars::schema::InstanceType::Array.into()),
|
||||
array: Some(Box::new(schemars::schema::ArrayValidation {
|
||||
items: Some(vec![#(#fields),*].into()),
|
||||
max_items: Some(#len),
|
||||
min_items: Some(#len),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
schemars::json_schema!({
|
||||
"type": "array",
|
||||
"items": [#((#fields)),*],
|
||||
"minItems": #len,
|
||||
"maxItems": #len,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -477,7 +485,7 @@ fn expr_for_struct(
|
|||
quote! {
|
||||
{
|
||||
#type_def
|
||||
schemars::_private::insert_object_property::<#ty>(object_validation, #name, #has_default, #required, #schema_expr);
|
||||
schemars::_private::insert_object_property::<#ty>(&mut schema, #name, #has_default, #required, #schema_expr);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -502,7 +510,7 @@ fn expr_for_struct(
|
|||
|
||||
let set_additional_properties = if deny_unknown_fields {
|
||||
quote! {
|
||||
object_validation.additional_properties = Some(Box::new(false.into()));
|
||||
"additionalProperties": false,
|
||||
}
|
||||
} else {
|
||||
TokenStream::new()
|
||||
|
@ -510,15 +518,12 @@ fn expr_for_struct(
|
|||
quote! {
|
||||
{
|
||||
#set_container_default
|
||||
let mut schema_object = schemars::schema::SchemaObject {
|
||||
instance_type: Some(schemars::schema::InstanceType::Object.into()),
|
||||
..Default::default()
|
||||
};
|
||||
let object_validation = schema_object.object();
|
||||
let mut schema = schemars::json_schema!({
|
||||
"type": "object",
|
||||
#set_additional_properties
|
||||
});
|
||||
#(#properties)*
|
||||
schemars::schema::Schema::Object(schema_object)
|
||||
#(.flatten(#flattens))*
|
||||
schema #(.flatten(#flattens))*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -578,16 +583,6 @@ fn field_default_expr(field: &Field, container_has_default: bool) -> Option<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) {
|
||||
if let Some(type_def) = type_def {
|
||||
*schema_expr = quote! {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue