Prevent stack overflow when using inline_subschemas
This commit is contained in:
parent
d85eec3b7a
commit
1017506ce6
4 changed files with 170 additions and 31 deletions
|
@ -11,7 +11,7 @@ use crate::flatten::Merge;
|
||||||
use crate::schema::*;
|
use crate::schema::*;
|
||||||
use crate::{visit::*, JsonSchema, Map};
|
use crate::{visit::*, JsonSchema, Map};
|
||||||
use dyn_clone::DynClone;
|
use dyn_clone::DynClone;
|
||||||
use std::{any::Any, fmt::Debug};
|
use std::{any::Any, collections::HashSet, fmt::Debug};
|
||||||
|
|
||||||
/// Settings to customize how Schemas are generated.
|
/// Settings to customize how Schemas are generated.
|
||||||
///
|
///
|
||||||
|
@ -146,10 +146,21 @@ impl SchemaSettings {
|
||||||
/// let gen = SchemaGenerator::default();
|
/// let gen = SchemaGenerator::default();
|
||||||
/// let schema = gen.into_root_schema_for::<MyStruct>();
|
/// let schema = gen.into_root_schema_for::<MyStruct>();
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default)]
|
||||||
pub struct SchemaGenerator {
|
pub struct SchemaGenerator {
|
||||||
settings: SchemaSettings,
|
settings: SchemaSettings,
|
||||||
definitions: Map<String, Schema>,
|
definitions: Map<String, Schema>,
|
||||||
|
pending_schema_names: HashSet<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for SchemaGenerator {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
settings: self.settings.clone(),
|
||||||
|
definitions: self.definitions.clone(),
|
||||||
|
pending_schema_names: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SchemaSettings> for SchemaGenerator {
|
impl From<SchemaSettings> for SchemaGenerator {
|
||||||
|
@ -203,23 +214,28 @@ 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
|
||||||
/// add them to the `SchemaGenerator`'s schema definitions.
|
/// add them to the `SchemaGenerator`'s schema definitions.
|
||||||
pub fn subschema_for<T: ?Sized + JsonSchema>(&mut self) -> Schema {
|
pub fn subschema_for<T: ?Sized + JsonSchema>(&mut self) -> Schema {
|
||||||
if !T::is_referenceable() || self.settings.inline_subschemas {
|
|
||||||
return T::json_schema(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = T::schema_name();
|
let name = T::schema_name();
|
||||||
|
let return_ref = T::is_referenceable()
|
||||||
|
&& (!self.settings.inline_subschemas || self.pending_schema_names.contains(&name));
|
||||||
|
|
||||||
|
if return_ref {
|
||||||
let reference = format!("{}{}", self.settings().definitions_path, name);
|
let reference = format!("{}{}", self.settings().definitions_path, name);
|
||||||
if !self.definitions.contains_key(&name) {
|
if !self.definitions.contains_key(&name) {
|
||||||
self.insert_new_subschema_for::<T>(name);
|
self.insert_new_subschema_for::<T>(name);
|
||||||
}
|
}
|
||||||
Schema::new_ref(reference)
|
Schema::new_ref(reference)
|
||||||
|
} else {
|
||||||
|
self.json_schema_internal::<T>(&name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_new_subschema_for<T: ?Sized + JsonSchema>(&mut self, name: String) {
|
fn insert_new_subschema_for<T: ?Sized + JsonSchema>(&mut self, name: String) {
|
||||||
let dummy = Schema::Bool(false);
|
let dummy = Schema::Bool(false);
|
||||||
// insert into definitions BEFORE calling json_schema to avoid infinite recursion
|
// insert into definitions BEFORE calling json_schema to avoid infinite recursion
|
||||||
self.definitions.insert(name.clone(), dummy);
|
self.definitions.insert(name.clone(), dummy);
|
||||||
let schema = T::json_schema(self);
|
|
||||||
|
let schema = self.json_schema_internal::<T>(&name);
|
||||||
|
|
||||||
self.definitions.insert(name, schema);
|
self.definitions.insert(name, schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,8 +275,9 @@ 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_object();
|
let name = T::schema_name();
|
||||||
schema.metadata().title.get_or_insert_with(T::schema_name);
|
let mut schema = self.json_schema_internal::<T>(&name).into_object();
|
||||||
|
schema.metadata().title.get_or_insert(name);
|
||||||
let mut root = 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(),
|
||||||
|
@ -279,8 +296,9 @@ 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_object();
|
let name = T::schema_name();
|
||||||
schema.metadata().title.get_or_insert_with(T::schema_name);
|
let mut schema = self.json_schema_internal::<T>(&name).into_object();
|
||||||
|
schema.metadata().title.get_or_insert(name);
|
||||||
let mut root = RootSchema {
|
let mut root = RootSchema {
|
||||||
meta_schema: self.settings.meta_schema,
|
meta_schema: self.settings.meta_schema,
|
||||||
definitions: self.definitions,
|
definitions: self.definitions,
|
||||||
|
@ -352,6 +370,14 @@ impl SchemaGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn json_schema_internal<T: ?Sized + JsonSchema>(&mut self, name: &str) -> Schema {
|
||||||
|
self.pending_schema_names.insert(name.to_owned());
|
||||||
|
let schema = T::json_schema(self);
|
||||||
|
// FIXME schema name not removed if previous line panics
|
||||||
|
self.pending_schema_names.remove(name);
|
||||||
|
schema
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A [Visitor](Visitor) which implements additional traits required to be included in a [SchemaSettings].
|
/// A [Visitor](Visitor) which implements additional traits required to be included in a [SchemaSettings].
|
||||||
|
|
95
schemars/tests/expected/inline-subschemas-recursive.json
Normal file
95
schemars/tests/expected/inline-subschemas-recursive.json
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "RecursiveOuter",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"direct": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/RecursiveOuter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indirect": {
|
||||||
|
"type": [
|
||||||
|
"object",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"required": [
|
||||||
|
"recursive"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"recursive": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"direct": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/RecursiveOuter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indirect": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/RecursiveInner"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"RecursiveOuter": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"direct": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/RecursiveOuter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indirect": {
|
||||||
|
"type": [
|
||||||
|
"object",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"required": [
|
||||||
|
"recursive"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"recursive": {
|
||||||
|
"$ref": "#/definitions/RecursiveOuter"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"RecursiveInner": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"recursive"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"recursive": {
|
||||||
|
"$ref": "#/definitions/RecursiveOuter"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,23 +1,23 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"properties": {
|
"title": "MyJob",
|
||||||
"spec": {
|
"type": "object",
|
||||||
"properties": {
|
|
||||||
"replicas": {
|
|
||||||
"format": "uint32",
|
|
||||||
"minimum": 0,
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"replicas"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
"required": [
|
||||||
"spec"
|
"spec"
|
||||||
],
|
],
|
||||||
"title": "MyJob",
|
"properties": {
|
||||||
"type": "object"
|
"spec": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"replicas"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"replicas": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint32",
|
||||||
|
"minimum": 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -15,7 +15,25 @@ pub struct MyJobSpec {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn struct_normal() -> TestResult {
|
fn struct_normal() -> TestResult {
|
||||||
let mut settings = SchemaSettings::openapi3();
|
let mut settings = SchemaSettings::default();
|
||||||
settings.inline_subschemas = true;
|
settings.inline_subschemas = true;
|
||||||
test_generated_schema::<MyJob>("inline-subschemas", settings)
|
test_generated_schema::<MyJob>("inline-subschemas", settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, JsonSchema)]
|
||||||
|
pub struct RecursiveOuter {
|
||||||
|
pub direct: Option<Box<RecursiveOuter>>,
|
||||||
|
pub indirect: Option<Box<RecursiveInner>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, JsonSchema)]
|
||||||
|
pub struct RecursiveInner {
|
||||||
|
pub recursive: RecursiveOuter,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn struct_recursive() -> TestResult {
|
||||||
|
let mut settings = SchemaSettings::default();
|
||||||
|
settings.inline_subschemas = true;
|
||||||
|
test_generated_schema::<RecursiveOuter>("inline-subschemas-recursive", settings)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue