Replace visit::Visitor with transform::Transform

This commit is contained in:
Graham Esau 2024-08-07 19:20:01 +01:00
parent 71b45a8ba3
commit 324be32de6
5 changed files with 432 additions and 273 deletions

View file

@ -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<String>,
/// A list of visitors that get applied to all generated schemas.
pub visitors: Vec<Box<dyn GenVisitor>>,
/// A list of [`Transform`]s that get applied to generated root schemas.
pub transforms: Vec<Box<dyn GenTransform>>,
/// 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<Item = &mut dyn GenVisitor> {
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<Item = &mut dyn GenTransform> {
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::<MyVisitor>());
/// let v: &dyn GenTransform = &MyTransform;
/// assert!(v.as_any().is::<MyTransform>());
/// ```
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<T> GenVisitor for T
impl<T> GenTransform for T
where
T: Visitor + Debug + Clone + Any,
T: Transform + Clone + Any,
{
fn as_any(&self) -> &dyn Any {
self
}
}
impl Debug for Box<dyn GenTransform> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self._debug_type_name(f)
}
}

View file

@ -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;

371
schemars/src/transform.rs Normal file
View file

@ -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<dyn GenTransform> 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::<Self>())
}
}
impl<F> 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: Transform + ?Sized>(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<T>(pub T);
impl<T> Transform for RecursiveTransform<T>
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);
}
}
}
}
}

View file

@ -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: Visitor + ?Sized>(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);
}
}
}
}
}

View file

@ -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::<Outer>("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::<Outer>("schema_settings-openapi3", settings)
}