Add Visitor trait, update changelog
This commit is contained in:
parent
4b37f96c99
commit
a829267111
11 changed files with 244 additions and 206 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -1,5 +1,17 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## In-dev - version TBC
|
||||||
|
### Added:
|
||||||
|
- `visit::Visitor`, a trait for updating a schema and all schemas it contains recursively. A `SchemaSettings` can now contain a list of visitors.
|
||||||
|
- `into_object()` method added to `Schema` as a shortcut for `into::<SchemaObject>()`
|
||||||
|
|
||||||
|
### Removed (**BREAKING CHANGES**):
|
||||||
|
- `SchemaSettings::bool_schemas` - this has been superseded by the `ReplaceBoolSchemas` visitor
|
||||||
|
- `SchemaSettings::allow_ref_siblings` - this has been superseded by the `RemoveRefSiblings` visitor
|
||||||
|
|
||||||
|
### Deprecated:
|
||||||
|
- `make_extensible`, `schema_for_any`, and `schema_for_none` methods on `SchemaGenerator`
|
||||||
|
|
||||||
## [0.7.6] - 2020-05-17
|
## [0.7.6] - 2020-05-17
|
||||||
### Added:
|
### Added:
|
||||||
- `#[schemars(example = "...")]` attribute for setting examples on generated schemas (https://github.com/GREsau/schemars/issues/23)
|
- `#[schemars(example = "...")]` attribute for setting examples on generated schemas (https://github.com/GREsau/schemars/issues/23)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::flatten::Merge;
|
use crate::flatten::Merge;
|
||||||
use crate::schema::*;
|
use crate::schema::*;
|
||||||
use crate::{JsonSchema, Map};
|
use crate::{visit::*, JsonSchema, Map};
|
||||||
|
use std::{fmt::Debug, sync::Arc};
|
||||||
|
|
||||||
/// Settings to customize how Schemas are generated.
|
/// Settings to customize how Schemas are generated.
|
||||||
///
|
///
|
||||||
|
@ -18,10 +19,6 @@ pub struct SchemaSettings {
|
||||||
///
|
///
|
||||||
/// Defaults to `true`.
|
/// Defaults to `true`.
|
||||||
pub option_add_null_type: bool,
|
pub option_add_null_type: bool,
|
||||||
/// Controls whether trivial [`Bool`](../schema/enum.Schema.html#variant.Bool) schemas may be generated.
|
|
||||||
///
|
|
||||||
/// Defaults to [`BoolSchemas::Enabled`].
|
|
||||||
pub bool_schemas: BoolSchemas,
|
|
||||||
/// A JSON pointer to the expected location of referenceable subschemas within the resulting root schema.
|
/// A JSON pointer to the expected location of referenceable subschemas within the resulting root schema.
|
||||||
///
|
///
|
||||||
/// Defaults to `"#/definitions/"`.
|
/// Defaults to `"#/definitions/"`.
|
||||||
|
@ -30,24 +27,11 @@ pub struct SchemaSettings {
|
||||||
///
|
///
|
||||||
/// Defaults to `"http://json-schema.org/draft-07/schema#"`.
|
/// Defaults to `"http://json-schema.org/draft-07/schema#"`.
|
||||||
pub meta_schema: Option<String>,
|
pub meta_schema: Option<String>,
|
||||||
/// Whether schemas with a `$ref` property may have other properties set.
|
/// TODO document
|
||||||
///
|
pub visitors: Visitors,
|
||||||
/// Defaults to `false`.
|
|
||||||
pub allow_ref_siblings: bool,
|
|
||||||
_hidden: (),
|
_hidden: (),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Controls whether trivial [`Bool`](../schema/enum.Schema.html#variant.Bool) schemas may be generated.
|
|
||||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
|
||||||
pub enum BoolSchemas {
|
|
||||||
/// `Bool` schemas may be used.
|
|
||||||
Enabled,
|
|
||||||
/// `Bool` schemas may only be used in a schema's [`additionalProperties`](../schema/struct.ObjectValidation.html#structfield.additional_properties) field.
|
|
||||||
AdditionalPropertiesOnly,
|
|
||||||
/// `Bool` schemas will never be used.
|
|
||||||
Disabled,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SchemaSettings {
|
impl Default for SchemaSettings {
|
||||||
fn default() -> SchemaSettings {
|
fn default() -> SchemaSettings {
|
||||||
SchemaSettings::draft07()
|
SchemaSettings::draft07()
|
||||||
|
@ -60,10 +44,9 @@ impl SchemaSettings {
|
||||||
SchemaSettings {
|
SchemaSettings {
|
||||||
option_nullable: false,
|
option_nullable: false,
|
||||||
option_add_null_type: true,
|
option_add_null_type: true,
|
||||||
bool_schemas: BoolSchemas::Enabled,
|
|
||||||
definitions_path: "#/definitions/".to_owned(),
|
definitions_path: "#/definitions/".to_owned(),
|
||||||
meta_schema: Some("http://json-schema.org/draft-07/schema#".to_owned()),
|
meta_schema: Some("http://json-schema.org/draft-07/schema#".to_owned()),
|
||||||
allow_ref_siblings: false,
|
visitors: Visitors(vec![Arc::new(RemoveRefSiblings)]),
|
||||||
_hidden: (),
|
_hidden: (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,10 +56,9 @@ impl SchemaSettings {
|
||||||
SchemaSettings {
|
SchemaSettings {
|
||||||
option_nullable: false,
|
option_nullable: false,
|
||||||
option_add_null_type: true,
|
option_add_null_type: true,
|
||||||
bool_schemas: BoolSchemas::Enabled,
|
|
||||||
definitions_path: "#/definitions/".to_owned(),
|
definitions_path: "#/definitions/".to_owned(),
|
||||||
meta_schema: Some("https://json-schema.org/draft/2019-09/schema".to_owned()),
|
meta_schema: Some("https://json-schema.org/draft/2019-09/schema".to_owned()),
|
||||||
allow_ref_siblings: true,
|
visitors: Visitors::default(),
|
||||||
_hidden: (),
|
_hidden: (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,13 +68,17 @@ impl SchemaSettings {
|
||||||
SchemaSettings {
|
SchemaSettings {
|
||||||
option_nullable: true,
|
option_nullable: true,
|
||||||
option_add_null_type: false,
|
option_add_null_type: false,
|
||||||
bool_schemas: BoolSchemas::AdditionalPropertiesOnly,
|
|
||||||
definitions_path: "#/components/schemas/".to_owned(),
|
definitions_path: "#/components/schemas/".to_owned(),
|
||||||
meta_schema: Some(
|
meta_schema: Some(
|
||||||
"https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema"
|
"https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema"
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
),
|
),
|
||||||
allow_ref_siblings: false,
|
visitors: Visitors(vec![
|
||||||
|
Arc::new(RemoveRefSiblings),
|
||||||
|
Arc::new(ReplaceBoolSchemas {
|
||||||
|
skip_additional_properties: true,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
_hidden: (),
|
_hidden: (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,12 +100,35 @@ impl SchemaSettings {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// TODO document
|
||||||
|
pub fn with_visitor(mut self, visitor: impl Visitor) -> Self {
|
||||||
|
self.visitors.0.push(Arc::new(visitor));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new [`SchemaGenerator`] using these settings.
|
/// Creates a new [`SchemaGenerator`] using these settings.
|
||||||
pub fn into_generator(self) -> SchemaGenerator {
|
pub fn into_generator(self) -> SchemaGenerator {
|
||||||
SchemaGenerator::new(self)
|
SchemaGenerator::new(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// TODO document
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct Visitors(Vec<Arc<dyn Visitor>>);
|
||||||
|
|
||||||
|
impl PartialEq for Visitors {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
if self.0.len() != other.0.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.zip(other.0.iter())
|
||||||
|
.all(|(a, b)| Arc::ptr_eq(a, b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The main type used to generate JSON Schemas.
|
/// The main type used to generate JSON Schemas.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
@ -170,59 +179,17 @@ impl SchemaGenerator {
|
||||||
&self.settings
|
&self.settings
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modifies the given `SchemaObject` so that it may have validation, metadata or other properties set on it.
|
#[deprecated = "This method no longer has any effect."]
|
||||||
///
|
pub fn make_extensible(&self, _schema: &mut SchemaObject) {}
|
||||||
/// If `schema` is not a `$ref` schema, then this does not modify `schema`. Otherwise, depending on this generator's settings,
|
|
||||||
/// this may wrap the `$ref` in another schema. This is required because in many JSON Schema implementations, a schema with `$ref`
|
|
||||||
/// set may not include other properties.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// use schemars::{gen::SchemaGenerator, schema::SchemaObject};
|
|
||||||
///
|
|
||||||
/// let gen = SchemaGenerator::default();
|
|
||||||
///
|
|
||||||
/// let ref_schema = SchemaObject::new_ref("foo".to_owned());
|
|
||||||
/// assert!(ref_schema.is_ref());
|
|
||||||
///
|
|
||||||
/// let mut extensible_schema = ref_schema.clone();
|
|
||||||
/// gen.make_extensible(&mut extensible_schema);
|
|
||||||
/// assert_ne!(ref_schema, extensible_schema);
|
|
||||||
/// assert!(!extensible_schema.is_ref());
|
|
||||||
///
|
|
||||||
/// let mut extensible_schema2 = extensible_schema.clone();
|
|
||||||
/// gen.make_extensible(&mut extensible_schema);
|
|
||||||
/// assert_eq!(extensible_schema, extensible_schema2);
|
|
||||||
/// ```
|
|
||||||
pub fn make_extensible(&self, schema: &mut SchemaObject) {
|
|
||||||
if schema.is_ref() && !self.settings().allow_ref_siblings {
|
|
||||||
let original = std::mem::replace(schema, SchemaObject::default());
|
|
||||||
schema.subschemas().all_of = Some(vec![original.into()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a `Schema` that matches everything, such as the empty schema `{}`.
|
#[deprecated = "Use `Schema::Bool(true)` instead"]
|
||||||
///
|
|
||||||
/// The exact value returned depends on this generator's [`BoolSchemas`](struct.SchemaSettings.html#structfield.bool_schemas) setting.
|
|
||||||
pub fn schema_for_any(&self) -> Schema {
|
pub fn schema_for_any(&self) -> Schema {
|
||||||
let schema: Schema = true.into();
|
Schema::Bool(true)
|
||||||
if self.settings().bool_schemas == BoolSchemas::Enabled {
|
|
||||||
schema
|
|
||||||
} else {
|
|
||||||
Schema::Object(schema.into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a `Schema` that matches nothing, such as the schema `{ "not":{} }`.
|
#[deprecated = "Use `Schema::Bool(false)` instead"]
|
||||||
///
|
|
||||||
/// The exact value returned depends on this generator's [`BoolSchemas`](struct.SchemaSettings.html#structfield.bool_schemas) setting.
|
|
||||||
pub fn schema_for_none(&self) -> Schema {
|
pub fn schema_for_none(&self) -> Schema {
|
||||||
let schema: Schema = false.into();
|
Schema::Bool(false)
|
||||||
if self.settings().bool_schemas == BoolSchemas::Enabled {
|
|
||||||
schema
|
|
||||||
} else {
|
|
||||||
Schema::Object(schema.into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a JSON Schema for the type `T`, and returns either the schema itself or a `$ref` schema referencing `T`'s schema.
|
/// Generates a JSON Schema for the type `T`, and returns either the schema itself or a `$ref` schema referencing `T`'s schema.
|
||||||
|
@ -253,7 +220,7 @@ impl SchemaGenerator {
|
||||||
self.definitions.insert(name, schema);
|
self.definitions.insert(name, schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated.
|
/// Borrows the collection of all [referenceable](JsonSchema::is_referenceable) schemas that have been generated.
|
||||||
///
|
///
|
||||||
/// The keys of the returned `Map` are the [schema names](JsonSchema::schema_name), and the values are the schemas
|
/// The keys of the returned `Map` are the [schema names](JsonSchema::schema_name), and the values are the schemas
|
||||||
/// themselves.
|
/// themselves.
|
||||||
|
@ -275,14 +242,19 @@ impl SchemaGenerator {
|
||||||
/// add them to the `SchemaGenerator`'s schema definitions and include them in the returned `SchemaObject`'s
|
/// add them to the `SchemaGenerator`'s schema definitions and include them in the returned `SchemaObject`'s
|
||||||
/// [`definitions`](../schema/struct.Metadata.html#structfield.definitions)
|
/// [`definitions`](../schema/struct.Metadata.html#structfield.definitions)
|
||||||
pub fn root_schema_for<T: ?Sized + JsonSchema>(&mut self) -> RootSchema {
|
pub fn root_schema_for<T: ?Sized + JsonSchema>(&mut self) -> RootSchema {
|
||||||
let mut schema = T::json_schema(self).into();
|
let mut schema = T::json_schema(self).into_object();
|
||||||
self.make_extensible(&mut schema);
|
|
||||||
schema.metadata().title.get_or_insert_with(T::schema_name);
|
schema.metadata().title.get_or_insert_with(T::schema_name);
|
||||||
RootSchema {
|
let mut root = RootSchema {
|
||||||
meta_schema: self.settings.meta_schema.clone(),
|
meta_schema: self.settings.meta_schema.clone(),
|
||||||
definitions: self.definitions.clone(),
|
definitions: self.definitions.clone(),
|
||||||
schema,
|
schema,
|
||||||
|
};
|
||||||
|
|
||||||
|
for visitor in &self.settings.visitors.0 {
|
||||||
|
visitor.visit_root_schema(&mut root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
root
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consumes `self` and generates a root JSON Schema for the type `T`.
|
/// Consumes `self` and generates a root JSON Schema for the type `T`.
|
||||||
|
@ -290,14 +262,19 @@ impl SchemaGenerator {
|
||||||
/// If `T`'s schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will
|
/// If `T`'s schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will
|
||||||
/// include them in the returned `SchemaObject`'s [`definitions`](../schema/struct.Metadata.html#structfield.definitions)
|
/// include them in the returned `SchemaObject`'s [`definitions`](../schema/struct.Metadata.html#structfield.definitions)
|
||||||
pub fn into_root_schema_for<T: ?Sized + JsonSchema>(mut self) -> RootSchema {
|
pub fn into_root_schema_for<T: ?Sized + JsonSchema>(mut self) -> RootSchema {
|
||||||
let mut schema = T::json_schema(&mut self).into();
|
let mut schema = T::json_schema(&mut self).into_object();
|
||||||
self.make_extensible(&mut schema);
|
|
||||||
schema.metadata().title.get_or_insert_with(T::schema_name);
|
schema.metadata().title.get_or_insert_with(T::schema_name);
|
||||||
RootSchema {
|
let mut root = RootSchema {
|
||||||
meta_schema: self.settings.meta_schema,
|
meta_schema: self.settings.meta_schema,
|
||||||
definitions: self.definitions,
|
definitions: self.definitions,
|
||||||
schema,
|
schema,
|
||||||
|
};
|
||||||
|
|
||||||
|
for visitor in &self.settings.visitors.0 {
|
||||||
|
visitor.visit_root_schema(&mut root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
root
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attemps to find the schema that the given `schema` is referencing.
|
/// Attemps to find the schema that the given `schema` is referencing.
|
||||||
|
@ -352,13 +329,70 @@ impl SchemaGenerator {
|
||||||
None => return schema,
|
None => return schema,
|
||||||
Some(ref metadata) if *metadata == Metadata::default() => return schema,
|
Some(ref metadata) if *metadata == Metadata::default() => return schema,
|
||||||
Some(metadata) => {
|
Some(metadata) => {
|
||||||
let mut schema_obj = schema.into();
|
let mut schema_obj = schema.into_object();
|
||||||
|
|
||||||
self.make_extensible(&mut schema_obj);
|
|
||||||
schema_obj.metadata = Some(Box::new(metadata)).merge(schema_obj.metadata);
|
schema_obj.metadata = Some(Box::new(metadata)).merge(schema_obj.metadata);
|
||||||
|
|
||||||
Schema::Object(schema_obj)
|
Schema::Object(schema_obj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// TODO document
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ReplaceBoolSchemas {
|
||||||
|
pub skip_additional_properties: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Visitor for ReplaceBoolSchemas {
|
||||||
|
fn visit_schema(&self, schema: &mut Schema) {
|
||||||
|
if let Schema::Bool(b) = *schema {
|
||||||
|
*schema = Schema::Bool(b).into_object().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
visit_schema(self, schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_schema_object(&self, schema: &mut SchemaObject) {
|
||||||
|
if self.skip_additional_properties {
|
||||||
|
let mut additional_properties = None;
|
||||||
|
if let Some(obj) = &mut schema.object {
|
||||||
|
if let Some(ap) = &obj.additional_properties {
|
||||||
|
if let Schema::Bool(_) = ap.as_ref() {
|
||||||
|
additional_properties = obj.additional_properties.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visit_schema_object(self, schema);
|
||||||
|
|
||||||
|
if additional_properties.is_some() {
|
||||||
|
schema.object().additional_properties = additional_properties;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
visit_schema_object(self, schema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TODO document
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RemoveRefSiblings;
|
||||||
|
|
||||||
|
impl Visitor for RemoveRefSiblings {
|
||||||
|
fn visit_schema_object(&self, schema: &mut SchemaObject) {
|
||||||
|
visit_schema_object(self, schema);
|
||||||
|
|
||||||
|
if let Some(reference) = schema.reference.take() {
|
||||||
|
if schema == &SchemaObject::default() {
|
||||||
|
schema.reference = Some(reference);
|
||||||
|
} else {
|
||||||
|
let ref_schema = Schema::new_ref(reference);
|
||||||
|
let all_of = &mut schema.subschemas().all_of;
|
||||||
|
match all_of {
|
||||||
|
Some(vec) => vec.push(ref_schema),
|
||||||
|
None => *all_of = Some(vec![ref_schema]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -35,8 +35,7 @@ impl<T: JsonSchema> JsonSchema for Option<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if gen.settings().option_nullable {
|
if gen.settings().option_nullable {
|
||||||
let mut schema_obj = schema.into();
|
let mut schema_obj = schema.into_object();
|
||||||
gen.make_extensible(&mut schema_obj);
|
|
||||||
schema_obj
|
schema_obj
|
||||||
.extensions
|
.extensions
|
||||||
.insert("nullable".to_owned(), json!(true));
|
.insert("nullable".to_owned(), json!(true));
|
||||||
|
@ -178,8 +177,7 @@ forward_impl!((<'a> JsonSchema for std::fmt::Arguments<'a>) => String);
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::gen::*;
|
use crate::tests::{schema_for, schema_object_for};
|
||||||
use crate::tests::{custom_schema_object_for, schema_for, schema_object_for};
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -209,21 +207,6 @@ mod tests {
|
||||||
assert_eq!(any_of[1], schema_for::<()>());
|
assert_eq!(any_of[1], schema_for::<()>());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn schema_for_option_with_nullable() {
|
|
||||||
let settings = SchemaSettings::default().with(|s| {
|
|
||||||
s.option_nullable = true;
|
|
||||||
s.option_add_null_type = false;
|
|
||||||
});
|
|
||||||
let schema = custom_schema_object_for::<Option<i32>>(settings);
|
|
||||||
assert_eq!(
|
|
||||||
schema.instance_type,
|
|
||||||
Some(SingleOrVec::from(InstanceType::Integer))
|
|
||||||
);
|
|
||||||
assert_eq!(schema.extensions.get("nullable"), Some(&json!(true)));
|
|
||||||
assert_eq!(schema.subschemas.is_none(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn schema_for_result() {
|
fn schema_for_result() {
|
||||||
let schema = schema_object_for::<Result<bool, String>>();
|
let schema = schema_object_for::<Result<bool, String>>();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::gen::{BoolSchemas, SchemaGenerator};
|
use crate::gen::SchemaGenerator;
|
||||||
use crate::schema::*;
|
use crate::schema::*;
|
||||||
use crate::JsonSchema;
|
use crate::JsonSchema;
|
||||||
|
|
||||||
|
@ -16,18 +16,10 @@ macro_rules! map_impl {
|
||||||
|
|
||||||
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||||
let subschema = gen.subschema_for::<V>();
|
let subschema = gen.subschema_for::<V>();
|
||||||
let json_schema_bool = gen.settings().bool_schemas == BoolSchemas::AdditionalPropertiesOnly
|
|
||||||
&& subschema == gen.schema_for_any();
|
|
||||||
let additional_properties =
|
|
||||||
if json_schema_bool {
|
|
||||||
true.into()
|
|
||||||
} else {
|
|
||||||
subschema.into()
|
|
||||||
};
|
|
||||||
SchemaObject {
|
SchemaObject {
|
||||||
instance_type: Some(InstanceType::Object.into()),
|
instance_type: Some(InstanceType::Object.into()),
|
||||||
object: Some(Box::new(ObjectValidation {
|
object: Some(Box::new(ObjectValidation {
|
||||||
additional_properties: Some(Box::new(additional_properties)),
|
additional_properties: Some(Box::new(subschema)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})),
|
})),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -40,68 +32,3 @@ macro_rules! map_impl {
|
||||||
|
|
||||||
map_impl!(<K, V> JsonSchema for std::collections::BTreeMap<K, V>);
|
map_impl!(<K, V> JsonSchema for std::collections::BTreeMap<K, V>);
|
||||||
map_impl!(<K, V, H> JsonSchema for std::collections::HashMap<K, V, H>);
|
map_impl!(<K, V, H> JsonSchema for std::collections::HashMap<K, V, H>);
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::gen::*;
|
|
||||||
use crate::tests::{custom_schema_object_for, schema_for};
|
|
||||||
use pretty_assertions::assert_eq;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn schema_for_map_any_value() {
|
|
||||||
for bool_schemas in &[BoolSchemas::Enabled, BoolSchemas::AdditionalPropertiesOnly] {
|
|
||||||
let settings = SchemaSettings::default().with(|s| s.bool_schemas = *bool_schemas);
|
|
||||||
let schema = custom_schema_object_for::<BTreeMap<String, serde_json::Value>>(settings);
|
|
||||||
assert_eq!(
|
|
||||||
schema.instance_type,
|
|
||||||
Some(SingleOrVec::from(InstanceType::Object))
|
|
||||||
);
|
|
||||||
let additional_properties = schema
|
|
||||||
.object
|
|
||||||
.unwrap()
|
|
||||||
.additional_properties
|
|
||||||
.expect("additionalProperties field present");
|
|
||||||
assert_eq!(*additional_properties, Schema::Bool(true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn schema_for_map_any_value_no_bool_schema() {
|
|
||||||
let settings = SchemaSettings::default().with(|s| s.bool_schemas = BoolSchemas::Disabled);
|
|
||||||
let schema = custom_schema_object_for::<BTreeMap<String, serde_json::Value>>(settings);
|
|
||||||
assert_eq!(
|
|
||||||
schema.instance_type,
|
|
||||||
Some(SingleOrVec::from(InstanceType::Object))
|
|
||||||
);
|
|
||||||
let additional_properties = schema
|
|
||||||
.object
|
|
||||||
.unwrap()
|
|
||||||
.additional_properties
|
|
||||||
.expect("additionalProperties field present");
|
|
||||||
assert_eq!(*additional_properties, Schema::Object(Default::default()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn schema_for_map_int_value() {
|
|
||||||
for bool_schemas in &[
|
|
||||||
BoolSchemas::Enabled,
|
|
||||||
BoolSchemas::Disabled,
|
|
||||||
BoolSchemas::AdditionalPropertiesOnly,
|
|
||||||
] {
|
|
||||||
let settings = SchemaSettings::default().with(|s| s.bool_schemas = *bool_schemas);
|
|
||||||
let schema = custom_schema_object_for::<BTreeMap<String, i32>>(settings);
|
|
||||||
assert_eq!(
|
|
||||||
schema.instance_type,
|
|
||||||
Some(SingleOrVec::from(InstanceType::Object))
|
|
||||||
);
|
|
||||||
let additional_properties = schema
|
|
||||||
.object
|
|
||||||
.unwrap()
|
|
||||||
.additional_properties
|
|
||||||
.expect("additionalProperties field present");
|
|
||||||
assert_eq!(*additional_properties, schema_for::<i32>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,8 +11,8 @@ impl JsonSchema for Value {
|
||||||
"AnyValue".to_owned()
|
"AnyValue".to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
fn json_schema(_: &mut SchemaGenerator) -> Schema {
|
||||||
gen.schema_for_any()
|
Schema::Bool(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -230,6 +230,8 @@ mod macros;
|
||||||
pub mod gen;
|
pub mod gen;
|
||||||
/// JSON Schema types.
|
/// JSON Schema types.
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
|
/// TODO document
|
||||||
|
pub mod visit;
|
||||||
|
|
||||||
#[cfg(feature = "schemars_derive")]
|
#[cfg(feature = "schemars_derive")]
|
||||||
extern crate schemars_derive;
|
extern crate schemars_derive;
|
||||||
|
@ -323,18 +325,9 @@ pub mod tests {
|
||||||
schema_object(schema_for::<T>())
|
schema_object(schema_for::<T>())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn custom_schema_object_for<T: JsonSchema>(
|
|
||||||
settings: gen::SchemaSettings,
|
|
||||||
) -> schema::SchemaObject {
|
|
||||||
schema_object(custom_schema_for::<T>(settings))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn schema_for<T: JsonSchema>() -> schema::Schema {
|
pub fn schema_for<T: JsonSchema>() -> schema::Schema {
|
||||||
custom_schema_for::<T>(Default::default())
|
let mut gen = gen::SchemaGenerator::default();
|
||||||
}
|
T::json_schema(&mut gen)
|
||||||
|
|
||||||
pub fn custom_schema_for<T: JsonSchema>(settings: gen::SchemaSettings) -> schema::Schema {
|
|
||||||
T::json_schema(&mut gen::SchemaGenerator::new(settings))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn schema_object(schema: schema::Schema) -> schema::SchemaObject {
|
pub fn schema_object(schema: schema::Schema) -> schema::SchemaObject {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/// Generates a [`Schema`](schema::Schema) for the given type using default settings.
|
/// Generates a [`RootSchema`](schema::RootSchema) for the given type using default settings.
|
||||||
///
|
///
|
||||||
/// The type must implement [`JsonSchema`].
|
/// The type must implement [`JsonSchema`].
|
||||||
///
|
///
|
||||||
|
|
|
@ -40,6 +40,21 @@ impl Schema {
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// TODO document
|
||||||
|
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()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SchemaObject> for Schema {
|
impl From<SchemaObject> for Schema {
|
||||||
|
@ -204,17 +219,7 @@ impl SchemaObject {
|
||||||
|
|
||||||
impl From<Schema> for SchemaObject {
|
impl From<Schema> for SchemaObject {
|
||||||
fn from(schema: Schema) -> Self {
|
fn from(schema: Schema) -> Self {
|
||||||
match schema {
|
schema.into_object()
|
||||||
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()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
84
schemars/src/visit.rs
Normal file
84
schemars/src/visit.rs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
use crate::schema::{RootSchema, Schema, SchemaObject, SingleOrVec};
|
||||||
|
use std::{any::Any, fmt::Debug};
|
||||||
|
|
||||||
|
pub trait Visitor: Debug + Any {
|
||||||
|
fn visit_root_schema(&self, root: &mut RootSchema) {
|
||||||
|
visit_root_schema(self, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_schema(&self, schema: &mut Schema) {
|
||||||
|
visit_schema(self, schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_schema_object(&self, schema: &mut SchemaObject) {
|
||||||
|
visit_schema_object(self, schema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn visit_root_schema<V: Visitor + ?Sized>(v: &V, root: &mut RootSchema) {
|
||||||
|
v.visit_schema_object(&mut root.schema);
|
||||||
|
visit_map_values(v, &mut root.definitions);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn visit_schema<V: Visitor + ?Sized>(v: &V, schema: &mut Schema) {
|
||||||
|
if let Schema::Object(schema) = schema {
|
||||||
|
v.visit_schema_object(schema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn visit_schema_object<V: Visitor + ?Sized>(v: &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: &V, target: &mut Option<Box<Schema>>) {
|
||||||
|
if let Some(s) = target {
|
||||||
|
v.visit_schema(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_vec<V: Visitor + ?Sized>(v: &V, target: &mut Option<Vec<Schema>>) {
|
||||||
|
if let Some(vec) = target {
|
||||||
|
for s in vec {
|
||||||
|
v.visit_schema(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_map_values<V: Visitor + ?Sized>(v: &V, target: &mut crate::Map<String, Schema>) {
|
||||||
|
for s in target.values_mut() {
|
||||||
|
v.visit_schema(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_single_or_vec<V: Visitor + ?Sized>(v: &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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -58,7 +58,7 @@ fn doc_comments_struct() -> TestResult {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn doc_comments_struct_ref_siblings() -> TestResult {
|
fn doc_comments_struct_ref_siblings() -> TestResult {
|
||||||
let settings = SchemaSettings::draft07().with(|s| s.allow_ref_siblings = true);
|
let settings = SchemaSettings::draft2019_09();
|
||||||
test_generated_schema::<MyStruct>("doc_comments_struct_ref_siblings", settings)
|
test_generated_schema::<MyStruct>("doc_comments_struct_ref_siblings", settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "https://json-schema.org/draft/2019-09/schema",
|
||||||
"title": "This is the struct's title",
|
"title": "This is the struct's title",
|
||||||
"description": "This is the struct's description.",
|
"description": "This is the struct's description.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue