diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index 1dc4bcfe..781a2e55 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -8,7 +8,7 @@ There are two main types in this module: */ use crate::Schema; -use crate::{visit::*, JsonSchema}; +use crate::{transform::*, JsonSchema}; use dyn_clone::DynClone; use serde::Serialize; use serde_json::{Map, Value}; @@ -44,8 +44,8 @@ pub struct SchemaSettings { /// /// Defaults to `"https://json-schema.org/draft/2020-12/schema"`. pub meta_schema: Option, - /// A list of visitors that get applied to all generated schemas. - pub visitors: Vec>, + /// A list of [`Transform`]s that get applied to generated root schemas. + pub transforms: Vec>, /// Inline all subschemas instead of using references. /// /// Some references may still be generated in schemas for recursive types. @@ -70,7 +70,7 @@ impl SchemaSettings { option_add_null_type: true, definitions_path: "/definitions".to_owned(), meta_schema: Some("http://json-schema.org/draft-07/schema#".to_owned()), - visitors: vec![Box::new(RemoveRefSiblings), Box::new(ReplacePrefixItems)], + transforms: vec![Box::new(RemoveRefSiblings), Box::new(ReplacePrefixItems)], inline_subschemas: false, } } @@ -82,7 +82,7 @@ impl SchemaSettings { option_add_null_type: true, definitions_path: "/$defs".to_owned(), meta_schema: Some("https://json-schema.org/draft/2019-09/schema".to_owned()), - visitors: vec![Box::new(ReplacePrefixItems)], + transforms: vec![Box::new(ReplacePrefixItems)], inline_subschemas: false, } } @@ -94,7 +94,7 @@ impl SchemaSettings { option_add_null_type: true, definitions_path: "/$defs".to_owned(), meta_schema: Some("https://json-schema.org/draft/2020-12/schema".to_owned()), - visitors: Vec::new(), + transforms: Vec::new(), inline_subschemas: false, } } @@ -109,7 +109,7 @@ impl SchemaSettings { "https://spec.openapis.org/oas/3.0/schema/2021-09-28#/definitions/Schema" .to_owned(), ), - visitors: vec![ + transforms: vec![ Box::new(RemoveRefSiblings), Box::new(ReplaceBoolSchemas { skip_additional_properties: true, @@ -139,9 +139,9 @@ impl SchemaSettings { self } - /// Appends the given visitor to the list of [visitors](SchemaSettings::visitors) for these `SchemaSettings`. - pub fn with_visitor(mut self, visitor: impl Visitor + Debug + Clone + 'static) -> Self { - self.visitors.push(Box::new(visitor)); + /// Appends the given transform to the list of [transforms](SchemaSettings::transforms) for these `SchemaSettings`. + pub fn with_transform(mut self, transform: impl Transform + Clone + 'static) -> Self { + self.transforms.push(Box::new(transform)); self } @@ -297,9 +297,9 @@ impl SchemaGenerator { std::mem::take(&mut self.definitions) } - /// Returns an iterator over the [visitors](SchemaSettings::visitors) being used by this `SchemaGenerator`. - pub fn visitors_mut(&mut self) -> impl Iterator { - self.settings.visitors.iter_mut().map(|v| v.as_mut()) + /// Returns an iterator over the [transforms](SchemaSettings::transforms) being used by this `SchemaGenerator`. + pub fn transforms_mut(&mut self) -> impl Iterator { + self.settings.transforms.iter_mut().map(|v| v.as_mut()) } /// Generates a JSON Schema for the type `T`. @@ -320,7 +320,7 @@ impl SchemaGenerator { } self.add_definitions(object, self.definitions.clone()); - self.run_visitors(&mut schema); + self.apply_transforms(&mut schema); schema } @@ -344,7 +344,7 @@ impl SchemaGenerator { let definitions = self.take_definitions(); self.add_definitions(object, definitions); - self.run_visitors(&mut schema); + self.apply_transforms(&mut schema); schema } @@ -375,7 +375,7 @@ impl SchemaGenerator { } self.add_definitions(object, self.definitions.clone()); - self.run_visitors(&mut schema); + self.apply_transforms(&mut schema); Ok(schema) } @@ -407,7 +407,7 @@ impl SchemaGenerator { let definitions = self.take_definitions(); self.add_definitions(object, definitions); - self.run_visitors(&mut schema); + self.apply_transforms(&mut schema); Ok(schema) } @@ -456,26 +456,9 @@ impl SchemaGenerator { target.append(&mut definitions); } - fn run_visitors(&mut self, schema: &mut Schema) { - for visitor in self.visitors_mut() { - visitor.visit_schema(schema); - } - - let pointer = self.definitions_path_stripped(); - // `$defs` and `definitions` are both handled internally by `Visitor::visit_schema`. - // If the definitions are in any other location, explicitly visit them here to ensure - // they're run against any referenced subschemas. - if pointer != "/$defs" && pointer != "/definitions" { - if let Some(definitions) = schema - .as_object_mut() - .and_then(|so| json_pointer_mut(so, pointer, false)) - { - for subschema in definitions.values_mut().flat_map(<&mut Schema>::try_from) { - for visitor in self.visitors_mut() { - visitor.visit_schema(subschema); - } - } - } + fn apply_transforms(&mut self, schema: &mut Schema) { + for transform in self.transforms_mut() { + transform.transform(schema); } } @@ -518,43 +501,48 @@ fn json_pointer_mut<'a>( Some(object) } -/// A [Visitor] which implements additional traits required to be included in a [SchemaSettings]. +/// A [Transform] which implements additional traits required to be included in a [SchemaSettings]. /// /// You will rarely need to use this trait directly as it is automatically implemented for any type which implements all of: -/// - [`Visitor`] -/// - [`std::fmt::Debug`] +/// - [`Transform`] /// - [`std::any::Any`] (implemented for all `'static` types) /// - [`std::clone::Clone`] /// /// # Example /// ``` -/// use schemars::visit::Visitor; -/// use schemars::gen::GenVisitor; +/// use schemars::transform::Transform; +/// use schemars::gen::GenTransform; /// /// #[derive(Debug, Clone)] -/// struct MyVisitor; +/// struct MyTransform; /// -/// impl Visitor for MyVisitor { -/// fn visit_schema(&mut self, schema: &mut schemars::Schema) { +/// impl Transform for MyTransform { +/// fn transform(&mut self, schema: &mut schemars::Schema) { /// todo!() /// } /// } /// -/// let v: &dyn GenVisitor = &MyVisitor; -/// assert!(v.as_any().is::()); +/// let v: &dyn GenTransform = &MyTransform; +/// assert!(v.as_any().is::()); /// ``` -pub trait GenVisitor: Visitor + Debug + DynClone + Any { - /// Upcasts this visitor into an `Any`, which can be used to inspect and manipulate it as its concrete type. +pub trait GenTransform: Transform + DynClone + Any { + /// Upcasts this transform into an [`Any`], which can be used to inspect and manipulate it as its concrete type. fn as_any(&self) -> &dyn Any; } -dyn_clone::clone_trait_object!(GenVisitor); +dyn_clone::clone_trait_object!(GenTransform); -impl GenVisitor for T +impl GenTransform for T where - T: Visitor + Debug + Clone + Any, + T: Transform + Clone + Any, { fn as_any(&self) -> &dyn Any { self } } + +impl Debug for Box { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self._debug_type_name(f) + } +} diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs index dbb307ab..afe49d12 100644 --- a/schemars/src/lib.rs +++ b/schemars/src/lib.rs @@ -13,8 +13,8 @@ mod macros; pub mod _private; /// Types for generating JSON schemas. pub mod gen; -/// Types for recursively modifying JSON schemas. -pub mod visit; +/// Types for defining modifications to JSON schemas. +pub mod transform; #[cfg(feature = "schemars_derive")] extern crate schemars_derive; diff --git a/schemars/src/transform.rs b/schemars/src/transform.rs new file mode 100644 index 00000000..7482b31b --- /dev/null +++ b/schemars/src/transform.rs @@ -0,0 +1,371 @@ +/*! +Contains the [`Transform`] trait, used to modify a constructed schema and optionally its subschemas. +This trait is automatically implemented for functions of the form `fn(&mut Schema) -> ()`. + +# Recursive Transforms + +To make a transform recursive (i.e. apply it to subschemas), you have two options: +1. call the [`transform_subschemas`] function within the transform function +2. wrap the `Transform` in a [`RecursiveTransform`] + +# Examples + +To add a custom property to all object schemas: + +``` +# use schemars::{Schema, json_schema}; +use schemars::transform::{Transform, transform_subschemas}; + +pub struct MyTransform; + +impl Transform for MyTransform { + fn transform(&mut self, schema: &mut Schema) { + // First, make our change to this schema + if let Some(obj) = schema.as_object_mut() { + obj.insert("my_property".to_string(), serde_json::json!("hello world")); + } + + // Then apply the transform to any subschemas + transform_subschemas(self, schema); + } +} + +let mut schema = json_schema!({ + "type": "array", + "items": {} +}); + +MyTransform.transform(&mut schema); + +assert_eq!( + schema, + json_schema!({ + "type": "array", + "items": { + "my_property": "hello world" + }, + "my_property": "hello world" + }) +); +``` + +The same example with a `fn` transform`: +``` +# use schemars::{Schema, json_schema}; +use schemars::transform::transform_subschemas; + +fn add_property(schema: &mut Schema) { + if let Some(obj) = schema.as_object_mut() { + obj.insert("my_property".to_string(), serde_json::json!("hello world")); + } + + transform_subschemas(&mut add_property, schema) +} + +let mut schema = json_schema!({ + "type": "array", + "items": {} +}); + +add_property(&mut schema); + +assert_eq!( + schema, + json_schema!({ + "type": "array", + "items": { + "my_property": "hello world" + }, + "my_property": "hello world" + }) +); +``` + +And the same example using a closure wrapped in a `RecursiveTransform`: +``` +# use schemars::{Schema, json_schema}; +use schemars::transform::{Transform, RecursiveTransform}; + +let mut transform = RecursiveTransform(|schema: &mut Schema| { + if let Some(obj) = schema.as_object_mut() { + obj.insert("my_property".to_string(), serde_json::json!("hello world")); + } +}); + +let mut schema = json_schema!({ + "type": "array", + "items": {} +}); + +transform.transform(&mut schema); + +assert_eq!( + schema, + json_schema!({ + "type": "array", + "items": { + "my_property": "hello world" + }, + "my_property": "hello world" + }) +); +``` + +*/ +use serde_json::{json, Value}; + +use crate::Schema; + +/// Trait used to modify a constructed schema and optionally its subschemas. +/// +/// See the [module documentation](self) for more details on implementing this trait. +pub trait Transform { + /// Applies the transform to the given [`Schema`]. + /// + /// When overriding this method, you may want to call the [`transform_subschemas`] function to also transform any subschemas. + fn transform(&mut self, schema: &mut Schema); + + // Not public API + // Hack to enable implementing Debug on Box even though closures don't implement Debug + #[doc(hidden)] + fn _debug_type_name(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(std::any::type_name::()) + } +} + +impl Transform for F +where + F: FnMut(&mut Schema), +{ + fn transform(&mut self, schema: &mut Schema) { + self(schema) + } +} + +/// Applies the given [`Transform`] to all direct subschemas of the [`Schema`]. +pub fn transform_subschemas(t: &mut T, schema: &mut Schema) { + if let Some(obj) = schema.as_object_mut() { + for (key, value) in obj { + // This is intentionally written to work with multiple JSON Schema versions, so that + // users can add their own transforms on the end of e.g. `SchemaSettings::draft07()` and + // they will still apply to all subschemas "as expected". + // This is why this match statement contains both `additionalProperties` (which was + // dropped in draft 2020-12) and `prefixItems` (which was added in draft 2020-12). + match key.as_str() { + "not" + | "if" + | "then" + | "else" + | "contains" + | "additionalProperties" + | "propertyNames" + | "additionalItems" => { + if let Ok(subschema) = value.try_into() { + t.transform(subschema) + } + } + "allOf" | "anyOf" | "oneOf" | "prefixItems" => { + if let Some(array) = value.as_array_mut() { + for value in array { + if let Ok(subschema) = value.try_into() { + t.transform(subschema) + } + } + } + } + // Support `items` array even though this is not allowed in draft 2020-12 (see above comment) + "items" => { + if let Some(array) = value.as_array_mut() { + for value in array { + if let Ok(subschema) = value.try_into() { + t.transform(subschema) + } + } + } else if let Ok(subschema) = value.try_into() { + t.transform(subschema) + } + } + "properties" | "patternProperties" | "$defs" | "definitions" => { + if let Some(obj) = value.as_object_mut() { + for value in obj.values_mut() { + if let Ok(subschema) = value.try_into() { + t.transform(subschema) + } + } + } + } + _ => {} + } + } + } +} + +/// A helper struct that can wrap a non-recursive [`Transform`] (i.e. one that does not apply to subschemas) into a recursive one. +/// +/// Its implementation of `Transform` will first apply the inner transform to the "parent" schema, and then its subschemas (and their subschemas, and so on). +/// +/// # Example +/// ``` +/// # use schemars::{Schema, json_schema}; +/// use schemars::transform::{Transform, RecursiveTransform}; +/// +/// let mut transform = RecursiveTransform(|schema: &mut Schema| { +/// if let Some(obj) = schema.as_object_mut() { +/// obj.insert("my_property".to_string(), serde_json::json!("hello world")); +/// } +/// }); +/// +/// let mut schema = json_schema!({ +/// "type": "array", +/// "items": {} +/// }); +/// +/// transform.transform(&mut schema); +/// +/// assert_eq!( +/// schema, +/// json_schema!({ +/// "type": "array", +/// "items": { +/// "my_property": "hello world" +/// }, +/// "my_property": "hello world" +/// }) +/// ); +/// ``` +#[derive(Debug, Clone)] +pub struct RecursiveTransform(pub T); + +impl Transform for RecursiveTransform +where + T: Transform, +{ + fn transform(&mut self, schema: &mut Schema) { + self.0.transform(schema); + transform_subschemas(self, schema); + } +} + +/// Replaces boolean JSON Schemas with equivalent object schemas. +/// This also applies to subschemas. +/// +/// This is useful for dialects of JSON Schema (e.g. OpenAPI 3.0) that do not support booleans as schemas. +#[derive(Debug, Clone)] +pub struct ReplaceBoolSchemas { + /// When set to `true`, a schema's `additionalProperties` property will not be changed from a boolean. + pub skip_additional_properties: bool, +} + +impl Transform for ReplaceBoolSchemas { + fn transform(&mut self, schema: &mut Schema) { + if let Some(obj) = schema.as_object_mut() { + if self.skip_additional_properties { + if let Some((ap_key, ap_value)) = obj.remove_entry("additionalProperties") { + transform_subschemas(self, schema); + + if let Some(obj) = schema.as_object_mut() { + obj.insert(ap_key, ap_value); + } + + return; + } + } + + transform_subschemas(self, schema); + } else { + schema.ensure_object(); + } + } +} + +/// Restructures JSON Schema objects so that the `$ref` property will never appear alongside any other properties. +/// This also applies to subschemas. +/// +/// This is useful for versions of JSON Schema (e.g. Draft 7) that do not support other properties alongside `$ref`. +#[derive(Debug, Clone)] +pub struct RemoveRefSiblings; + +impl Transform for RemoveRefSiblings { + fn transform(&mut self, schema: &mut Schema) { + transform_subschemas(self, schema); + + if let Some(obj) = schema.as_object_mut() { + if obj.len() > 1 { + if let Some(ref_value) = obj.remove("$ref") { + if let Value::Array(all_of) = + obj.entry("allOf").or_insert(Value::Array(Vec::new())) + { + all_of.push(json!({ + "$ref": ref_value + })); + } + } + } + } + } +} + +/// Removes the `examples` schema property and (if present) set its first value as the `example` property. +/// This also applies to subschemas. +/// +/// This is useful for dialects of JSON Schema (e.g. OpenAPI 3.0) that do not support the `examples` property. +#[derive(Debug, Clone)] +pub struct SetSingleExample; + +impl Transform for SetSingleExample { + fn transform(&mut self, schema: &mut Schema) { + transform_subschemas(self, schema); + + if let Some(obj) = schema.as_object_mut() { + if let Some(Value::Array(examples)) = obj.remove("examples") { + if let Some(first_example) = examples.into_iter().next() { + obj.insert("example".into(), first_example); + } + } + } + } +} + +/// Replaces the `const` schema property with a single-valued `enum` property. +/// This also applies to subschemas. +/// +/// This is useful for dialects of JSON Schema (e.g. OpenAPI 3.0) that do not support the `const` property. +#[derive(Debug, Clone)] +pub struct ReplaceConstValue; + +impl Transform for ReplaceConstValue { + fn transform(&mut self, schema: &mut Schema) { + transform_subschemas(self, schema); + + if let Some(obj) = schema.as_object_mut() { + if let Some(value) = obj.remove("const") { + obj.insert("enum".into(), Value::Array(vec![value])); + } + } + } +} + +/// Rename the `prefixItems` schema property to `items`. +/// This also applies to subschemas. +/// +/// If the schema contains both `prefixItems` and `items`, then this additionally renames `items` to `additionalItems`. +/// +/// This is useful for versions of JSON Schema (e.g. Draft 7) that do not support the `prefixItems` property. +#[derive(Debug, Clone)] +pub struct ReplacePrefixItems; + +impl Transform for ReplacePrefixItems { + fn transform(&mut self, schema: &mut Schema) { + transform_subschemas(self, schema); + + if let Some(obj) = schema.as_object_mut() { + if let Some(prefix_items) = obj.remove("prefixItems") { + let previous_items = obj.insert("items".to_owned(), prefix_items); + + if let Some(previous_items) = previous_items { + obj.insert("additionalItems".to_owned(), previous_items); + } + } + } + } +} diff --git a/schemars/src/visit.rs b/schemars/src/visit.rs deleted file mode 100644 index 35453e04..00000000 --- a/schemars/src/visit.rs +++ /dev/null @@ -1,217 +0,0 @@ -/*! -Contains the [`Visitor`] trait, used to recursively modify a constructed schema and its subschemas. - -Sometimes you may want to apply a change to a schema, as well as all schemas contained within it. -The easiest way to achieve this is by defining a type that implements [`Visitor`]. -All methods of `Visitor` have a default implementation that makes no change but recursively visits all subschemas. -When overriding one of these methods, you will *usually* want to still call this default implementation. - -# Example -To add a custom property to all object schemas: -``` -use schemars::Schema; -use schemars::visit::{Visitor, visit_schema}; - -pub struct MyVisitor; - -impl Visitor for MyVisitor { - fn visit_schema(&mut self, schema: &mut Schema) { - // First, make our change to this schema - if let Some(obj) = schema.as_object_mut() { - obj.insert("my_property".to_string(), serde_json::json!("hello world")); - } - - // Then delegate to default implementation to visit any subschemas - visit_schema(self, schema); - } -} -``` -*/ -use serde_json::{json, Value}; - -use crate::Schema; - -/// Trait used to recursively modify a constructed schema and its subschemas. -pub trait Visitor { - /// Override this method to modify a [`Schema`] and (optionally) its subschemas. - /// - /// When overriding this method, you will usually want to call the [`visit_schema`] function to visit subschemas. - fn visit_schema(&mut self, schema: &mut Schema); -} - -/// Visits all subschemas of the [`Schema`]. -pub fn visit_schema(v: &mut V, schema: &mut Schema) { - if let Some(obj) = schema.as_object_mut() { - for (key, value) in obj { - // This is intentionally written to work with multiple JSON Schema versions, so that - // users can add their own visitors on the end of e.g. `SchemaSettings::draft07()` and - // they will still apply to all subschemas "as expected". - // This is why this match statement contains both `additionalProperties` (which was - // dropped in draft 2020-12) and `prefixItems` (which was added in draft 2020-12). - match key.as_str() { - "not" - | "if" - | "then" - | "else" - | "contains" - | "additionalProperties" - | "propertyNames" - | "additionalItems" => { - if let Ok(subschema) = value.try_into() { - v.visit_schema(subschema) - } - } - "allOf" | "anyOf" | "oneOf" | "prefixItems" => { - if let Some(array) = value.as_array_mut() { - for value in array { - if let Ok(subschema) = value.try_into() { - v.visit_schema(subschema) - } - } - } - } - // Support `items` array even though this is not allowed in draft 2020-12 (see above comment) - "items" => { - if let Some(array) = value.as_array_mut() { - for value in array { - if let Ok(subschema) = value.try_into() { - v.visit_schema(subschema) - } - } - } else if let Ok(subschema) = value.try_into() { - v.visit_schema(subschema) - } - } - "properties" | "patternProperties" | "$defs" | "definitions" => { - if let Some(obj) = value.as_object_mut() { - for value in obj.values_mut() { - if let Ok(subschema) = value.try_into() { - v.visit_schema(subschema) - } - } - } - } - _ => {} - } - } - } -} - -/// This visitor will replace all boolean JSON Schemas with equivalent object schemas. -/// -/// This is useful for dialects of JSON Schema (e.g. OpenAPI 3.0) that do not support booleans as schemas. -#[derive(Debug, Clone)] -pub struct ReplaceBoolSchemas { - /// When set to `true`, a schema's `additionalProperties` property will not be changed from a boolean. - pub skip_additional_properties: bool, -} - -impl Visitor for ReplaceBoolSchemas { - fn visit_schema(&mut self, schema: &mut Schema) { - if let Some(obj) = schema.as_object_mut() { - if self.skip_additional_properties { - if let Some((ap_key, ap_value)) = obj.remove_entry("additionalProperties") { - visit_schema(self, schema); - - if let Some(obj) = schema.as_object_mut() { - obj.insert(ap_key, ap_value); - } - - return; - } - } - - visit_schema(self, schema); - } else { - schema.ensure_object(); - } - } -} - -/// This visitor will restructure JSON Schema objects so that the `$ref` property will never appear alongside any other properties. -/// -/// This is useful for versions of JSON Schema (e.g. Draft 7) that do not support other properties alongside `$ref`. -#[derive(Debug, Clone)] -pub struct RemoveRefSiblings; - -impl Visitor for RemoveRefSiblings { - fn visit_schema(&mut self, schema: &mut Schema) { - visit_schema(self, schema); - - if let Some(obj) = schema.as_object_mut() { - if obj.len() > 1 { - if let Some(ref_value) = obj.remove("$ref") { - if let Value::Array(all_of) = - obj.entry("allOf").or_insert(Value::Array(Vec::new())) - { - all_of.push(json!({ - "$ref": ref_value - })); - } - } - } - } - } -} - -/// This visitor will remove the `examples` schema property and (if present) set its first value as the `example` property. -/// -/// This is useful for dialects of JSON Schema (e.g. OpenAPI 3.0) that do not support the `examples` property. -#[derive(Debug, Clone)] -pub struct SetSingleExample; - -impl Visitor for SetSingleExample { - fn visit_schema(&mut self, schema: &mut Schema) { - visit_schema(self, schema); - - if let Some(obj) = schema.as_object_mut() { - if let Some(Value::Array(examples)) = obj.remove("examples") { - if let Some(first_example) = examples.into_iter().next() { - obj.insert("example".into(), first_example); - } - } - } - } -} - -/// This visitor will replace the `const` schema property with a single-valued `enum` property. -/// -/// This is useful for dialects of JSON Schema (e.g. OpenAPI 3.0) that do not support the `const` property. -#[derive(Debug, Clone)] -pub struct ReplaceConstValue; - -impl Visitor for ReplaceConstValue { - fn visit_schema(&mut self, schema: &mut Schema) { - visit_schema(self, schema); - - if let Some(obj) = schema.as_object_mut() { - if let Some(value) = obj.remove("const") { - obj.insert("enum".into(), Value::Array(vec![value])); - } - } - } -} - -/// This visitor will rename the `prefixItems` schema property to `items`. -/// -/// If the schema contains both `prefixItems` and `items`, then this additionally renames `items` to `additionalItems`. -/// -/// This is useful for versions of JSON Schema (e.g. Draft 7) that do not support the `prefixItems` property. -#[derive(Debug, Clone)] -pub struct ReplacePrefixItems; - -impl Visitor for ReplacePrefixItems { - fn visit_schema(&mut self, schema: &mut Schema) { - visit_schema(self, schema); - - if let Some(obj) = schema.as_object_mut() { - if let Some(prefix_items) = obj.remove("prefixItems") { - let previous_items = obj.insert("items".to_owned(), prefix_items); - - if let Some(previous_items) = previous_items { - obj.insert("additionalItems".to_owned(), previous_items); - } - } - } - } -} diff --git a/schemars/tests/schema_settings.rs b/schemars/tests/schema_settings.rs index 3fe489ab..0c8741ce 100644 --- a/schemars/tests/schema_settings.rs +++ b/schemars/tests/schema_settings.rs @@ -1,6 +1,6 @@ mod util; use schemars::gen::SchemaSettings; -use schemars::JsonSchema; +use schemars::{JsonSchema, Schema}; use serde_json::Value; use std::collections::BTreeMap; use util::*; @@ -47,5 +47,22 @@ fn schema_matches_2020_12() -> TestResult { #[test] fn schema_matches_openapi3() -> TestResult { - test_generated_schema::("schema_settings-openapi3", SchemaSettings::openapi3()) + let mut settings = SchemaSettings::openapi3(); + + // Hack to apply recursive transforms to schemas at components.schemas: + // First, move them to $defs, then run the transforms, then move them back again. + settings.transforms.insert( + 0, + Box::new(|s: &mut Schema| { + let obj = s.ensure_object(); + let defs = obj["components"]["schemas"].take(); + obj.insert("$defs".to_owned(), defs); + }), + ); + settings.transforms.push(Box::new(|s: &mut Schema| { + let obj = s.ensure_object(); + obj["components"]["schemas"] = obj.remove("$defs").unwrap(); + })); + + test_generated_schema::("schema_settings-openapi3", settings) }