Replace visit::Visitor
with transform::Transform
This commit is contained in:
parent
71b45a8ba3
commit
324be32de6
5 changed files with 432 additions and 273 deletions
|
@ -8,7 +8,7 @@ There are two main types in this module:
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::Schema;
|
use crate::Schema;
|
||||||
use crate::{visit::*, JsonSchema};
|
use crate::{transform::*, JsonSchema};
|
||||||
use dyn_clone::DynClone;
|
use dyn_clone::DynClone;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
|
@ -44,8 +44,8 @@ pub struct SchemaSettings {
|
||||||
///
|
///
|
||||||
/// Defaults to `"https://json-schema.org/draft/2020-12/schema"`.
|
/// Defaults to `"https://json-schema.org/draft/2020-12/schema"`.
|
||||||
pub meta_schema: Option<String>,
|
pub meta_schema: Option<String>,
|
||||||
/// A list of visitors that get applied to all generated schemas.
|
/// A list of [`Transform`]s that get applied to generated root schemas.
|
||||||
pub visitors: Vec<Box<dyn GenVisitor>>,
|
pub transforms: Vec<Box<dyn GenTransform>>,
|
||||||
/// Inline all subschemas instead of using references.
|
/// Inline all subschemas instead of using references.
|
||||||
///
|
///
|
||||||
/// Some references may still be generated in schemas for recursive types.
|
/// Some references may still be generated in schemas for recursive types.
|
||||||
|
@ -70,7 +70,7 @@ impl SchemaSettings {
|
||||||
option_add_null_type: true,
|
option_add_null_type: true,
|
||||||
definitions_path: "/definitions".to_owned(),
|
definitions_path: "/definitions".to_owned(),
|
||||||
meta_schema: Some("http://json-schema.org/draft-07/schema#".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,
|
inline_subschemas: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ impl SchemaSettings {
|
||||||
option_add_null_type: true,
|
option_add_null_type: true,
|
||||||
definitions_path: "/$defs".to_owned(),
|
definitions_path: "/$defs".to_owned(),
|
||||||
meta_schema: Some("https://json-schema.org/draft/2019-09/schema".to_owned()),
|
meta_schema: Some("https://json-schema.org/draft/2019-09/schema".to_owned()),
|
||||||
visitors: vec![Box::new(ReplacePrefixItems)],
|
transforms: vec![Box::new(ReplacePrefixItems)],
|
||||||
inline_subschemas: false,
|
inline_subschemas: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ impl SchemaSettings {
|
||||||
option_add_null_type: true,
|
option_add_null_type: true,
|
||||||
definitions_path: "/$defs".to_owned(),
|
definitions_path: "/$defs".to_owned(),
|
||||||
meta_schema: Some("https://json-schema.org/draft/2020-12/schema".to_owned()),
|
meta_schema: Some("https://json-schema.org/draft/2020-12/schema".to_owned()),
|
||||||
visitors: Vec::new(),
|
transforms: Vec::new(),
|
||||||
inline_subschemas: false,
|
inline_subschemas: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,7 @@ impl SchemaSettings {
|
||||||
"https://spec.openapis.org/oas/3.0/schema/2021-09-28#/definitions/Schema"
|
"https://spec.openapis.org/oas/3.0/schema/2021-09-28#/definitions/Schema"
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
),
|
),
|
||||||
visitors: vec for these `SchemaSettings`.
|
/// Appends the given transform to the list of [transforms](SchemaSettings::transforms) for these `SchemaSettings`.
|
||||||
pub fn with_visitor(mut self, visitor: impl Visitor + Debug + Clone + 'static) -> Self {
|
pub fn with_transform(mut self, transform: impl Transform + Clone + 'static) -> Self {
|
||||||
self.visitors.push(Box::new(visitor));
|
self.transforms.push(Box::new(transform));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,9 +297,9 @@ impl SchemaGenerator {
|
||||||
std::mem::take(&mut self.definitions)
|
std::mem::take(&mut self.definitions)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator over the [visitors](SchemaSettings::visitors) being used by this `SchemaGenerator`.
|
/// Returns an iterator over the [transforms](SchemaSettings::transforms) being used by this `SchemaGenerator`.
|
||||||
pub fn visitors_mut(&mut self) -> impl Iterator<Item = &mut dyn GenVisitor> {
|
pub fn transforms_mut(&mut self) -> impl Iterator<Item = &mut dyn GenTransform> {
|
||||||
self.settings.visitors.iter_mut().map(|v| v.as_mut())
|
self.settings.transforms.iter_mut().map(|v| v.as_mut())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a JSON Schema for the type `T`.
|
/// Generates a JSON Schema for the type `T`.
|
||||||
|
@ -320,7 +320,7 @@ impl SchemaGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.add_definitions(object, self.definitions.clone());
|
self.add_definitions(object, self.definitions.clone());
|
||||||
self.run_visitors(&mut schema);
|
self.apply_transforms(&mut schema);
|
||||||
|
|
||||||
schema
|
schema
|
||||||
}
|
}
|
||||||
|
@ -344,7 +344,7 @@ impl SchemaGenerator {
|
||||||
|
|
||||||
let definitions = self.take_definitions();
|
let definitions = self.take_definitions();
|
||||||
self.add_definitions(object, definitions);
|
self.add_definitions(object, definitions);
|
||||||
self.run_visitors(&mut schema);
|
self.apply_transforms(&mut schema);
|
||||||
|
|
||||||
schema
|
schema
|
||||||
}
|
}
|
||||||
|
@ -375,7 +375,7 @@ impl SchemaGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.add_definitions(object, self.definitions.clone());
|
self.add_definitions(object, self.definitions.clone());
|
||||||
self.run_visitors(&mut schema);
|
self.apply_transforms(&mut schema);
|
||||||
|
|
||||||
Ok(schema)
|
Ok(schema)
|
||||||
}
|
}
|
||||||
|
@ -407,7 +407,7 @@ impl SchemaGenerator {
|
||||||
|
|
||||||
let definitions = self.take_definitions();
|
let definitions = self.take_definitions();
|
||||||
self.add_definitions(object, definitions);
|
self.add_definitions(object, definitions);
|
||||||
self.run_visitors(&mut schema);
|
self.apply_transforms(&mut schema);
|
||||||
|
|
||||||
Ok(schema)
|
Ok(schema)
|
||||||
}
|
}
|
||||||
|
@ -456,26 +456,9 @@ impl SchemaGenerator {
|
||||||
target.append(&mut definitions);
|
target.append(&mut definitions);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_visitors(&mut self, schema: &mut Schema) {
|
fn apply_transforms(&mut self, schema: &mut Schema) {
|
||||||
for visitor in self.visitors_mut() {
|
for transform in self.transforms_mut() {
|
||||||
visitor.visit_schema(schema);
|
transform.transform(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -518,43 +501,48 @@ fn json_pointer_mut<'a>(
|
||||||
Some(object)
|
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:
|
/// You will rarely need to use this trait directly as it is automatically implemented for any type which implements all of:
|
||||||
/// - [`Visitor`]
|
/// - [`Transform`]
|
||||||
/// - [`std::fmt::Debug`]
|
|
||||||
/// - [`std::any::Any`] (implemented for all `'static` types)
|
/// - [`std::any::Any`] (implemented for all `'static` types)
|
||||||
/// - [`std::clone::Clone`]
|
/// - [`std::clone::Clone`]
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// use schemars::visit::Visitor;
|
/// use schemars::transform::Transform;
|
||||||
/// use schemars::gen::GenVisitor;
|
/// use schemars::gen::GenTransform;
|
||||||
///
|
///
|
||||||
/// #[derive(Debug, Clone)]
|
/// #[derive(Debug, Clone)]
|
||||||
/// struct MyVisitor;
|
/// struct MyTransform;
|
||||||
///
|
///
|
||||||
/// impl Visitor for MyVisitor {
|
/// impl Transform for MyTransform {
|
||||||
/// fn visit_schema(&mut self, schema: &mut schemars::Schema) {
|
/// fn transform(&mut self, schema: &mut schemars::Schema) {
|
||||||
/// todo!()
|
/// todo!()
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let v: &dyn GenVisitor = &MyVisitor;
|
/// let v: &dyn GenTransform = &MyTransform;
|
||||||
/// assert!(v.as_any().is::<MyVisitor>());
|
/// assert!(v.as_any().is::<MyTransform>());
|
||||||
/// ```
|
/// ```
|
||||||
pub trait GenVisitor: Visitor + Debug + DynClone + Any {
|
pub trait GenTransform: Transform + DynClone + Any {
|
||||||
/// Upcasts this visitor into an `Any`, which can be used to inspect and manipulate it as its concrete type.
|
/// 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;
|
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
|
where
|
||||||
T: Visitor + Debug + Clone + Any,
|
T: Transform + Clone + Any,
|
||||||
{
|
{
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for Box<dyn GenTransform> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self._debug_type_name(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,8 +13,8 @@ mod macros;
|
||||||
pub mod _private;
|
pub mod _private;
|
||||||
/// Types for generating JSON schemas.
|
/// Types for generating JSON schemas.
|
||||||
pub mod gen;
|
pub mod gen;
|
||||||
/// Types for recursively modifying JSON schemas.
|
/// Types for defining modifications to JSON schemas.
|
||||||
pub mod visit;
|
pub mod transform;
|
||||||
|
|
||||||
#[cfg(feature = "schemars_derive")]
|
#[cfg(feature = "schemars_derive")]
|
||||||
extern crate schemars_derive;
|
extern crate schemars_derive;
|
||||||
|
|
371
schemars/src/transform.rs
Normal file
371
schemars/src/transform.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
mod util;
|
mod util;
|
||||||
use schemars::gen::SchemaSettings;
|
use schemars::gen::SchemaSettings;
|
||||||
use schemars::JsonSchema;
|
use schemars::{JsonSchema, Schema};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use util::*;
|
use util::*;
|
||||||
|
@ -47,5 +47,22 @@ fn schema_matches_2020_12() -> TestResult {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn schema_matches_openapi3() -> TestResult {
|
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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue