Revert to previous behaviour regarding visitors running on $defs
/definitions
lazily (only if/when root schema is created).
Visitors will now always descend into `$defs`/`definitions`. If a generator is configured to use a different definitions path, then the visitor will also descend into that path (but a plain `Visitor` would NOT.
This commit is contained in:
parent
d32231c082
commit
1247b8975e
2 changed files with 80 additions and 53 deletions
|
@ -35,7 +35,9 @@ pub struct SchemaSettings {
|
||||||
pub option_add_null_type: bool,
|
pub option_add_null_type: bool,
|
||||||
/// A JSON pointer to the expected location of referenceable subschemas within the resulting root schema.
|
/// A JSON pointer to the expected location of referenceable subschemas within the resulting root schema.
|
||||||
///
|
///
|
||||||
/// Defaults to `"#/definitions/"`.
|
/// A single leading `#` and/or single trailing `/` are ignored.
|
||||||
|
///
|
||||||
|
/// Defaults to `/$defs`.
|
||||||
pub definitions_path: String,
|
pub definitions_path: String,
|
||||||
/// The URI of the meta-schema describing the structure of the generated schemas.
|
/// The URI of the meta-schema describing the structure of the generated schemas.
|
||||||
///
|
///
|
||||||
|
@ -63,7 +65,7 @@ impl SchemaSettings {
|
||||||
SchemaSettings {
|
SchemaSettings {
|
||||||
option_nullable: false,
|
option_nullable: false,
|
||||||
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)],
|
visitors: vec![Box::new(RemoveRefSiblings), Box::new(ReplacePrefixItems)],
|
||||||
inline_subschemas: false,
|
inline_subschemas: false,
|
||||||
|
@ -75,7 +77,7 @@ impl SchemaSettings {
|
||||||
SchemaSettings {
|
SchemaSettings {
|
||||||
option_nullable: false,
|
option_nullable: false,
|
||||||
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)],
|
visitors: vec![Box::new(ReplacePrefixItems)],
|
||||||
inline_subschemas: false,
|
inline_subschemas: false,
|
||||||
|
@ -87,7 +89,7 @@ impl SchemaSettings {
|
||||||
SchemaSettings {
|
SchemaSettings {
|
||||||
option_nullable: false,
|
option_nullable: false,
|
||||||
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(),
|
visitors: Vec::new(),
|
||||||
inline_subschemas: false,
|
inline_subschemas: false,
|
||||||
|
@ -99,7 +101,7 @@ impl SchemaSettings {
|
||||||
SchemaSettings {
|
SchemaSettings {
|
||||||
option_nullable: true,
|
option_nullable: true,
|
||||||
option_add_null_type: false,
|
option_add_null_type: false,
|
||||||
definitions_path: "#/components/schemas/".to_owned(),
|
definitions_path: "/components/schemas".to_owned(),
|
||||||
meta_schema: Some(
|
meta_schema: Some(
|
||||||
"https://spec.openapis.org/oas/3.0/schema/2021-09-28#/definitions/Schema"
|
"https://spec.openapis.org/oas/3.0/schema/2021-09-28#/definitions/Schema"
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
|
@ -247,7 +249,7 @@ impl SchemaGenerator {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let reference = format!("{}{}", self.settings.definitions_path, name);
|
let reference = format!("#{}/{}", self.definitions_path_stripped(), name);
|
||||||
if !self.definitions.contains_key(&name) {
|
if !self.definitions.contains_key(&name) {
|
||||||
self.insert_new_subschema_for::<T>(name, id);
|
self.insert_new_subschema_for::<T>(name, id);
|
||||||
}
|
}
|
||||||
|
@ -266,8 +268,7 @@ impl SchemaGenerator {
|
||||||
// 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 mut schema = self.json_schema_internal::<T>(id);
|
let schema = self.json_schema_internal::<T>(id);
|
||||||
Self::run_visitors(&mut schema, &mut self.settings.visitors);
|
|
||||||
|
|
||||||
self.definitions.insert(name, schema.to_value());
|
self.definitions.insert(name, schema.to_value());
|
||||||
}
|
}
|
||||||
|
@ -320,12 +321,8 @@ impl SchemaGenerator {
|
||||||
object.insert("$schema".into(), meta_schema.into());
|
object.insert("$schema".into(), meta_schema.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::add_definitions(
|
self.add_definitions(object, self.definitions.clone());
|
||||||
object,
|
self.run_visitors(&mut schema);
|
||||||
self.definitions.clone(),
|
|
||||||
&self.settings.definitions_path,
|
|
||||||
);
|
|
||||||
Self::run_visitors(&mut schema, &mut self.settings.visitors);
|
|
||||||
|
|
||||||
schema
|
schema
|
||||||
}
|
}
|
||||||
|
@ -343,12 +340,13 @@ impl SchemaGenerator {
|
||||||
.entry("title")
|
.entry("title")
|
||||||
.or_insert_with(|| T::schema_name().into());
|
.or_insert_with(|| T::schema_name().into());
|
||||||
|
|
||||||
if let Some(meta_schema) = self.settings.meta_schema {
|
if let Some(meta_schema) = std::mem::take(&mut self.settings.meta_schema) {
|
||||||
object.insert("$schema".into(), meta_schema.into());
|
object.insert("$schema".into(), meta_schema.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::add_definitions(object, self.definitions, &self.settings.definitions_path);
|
let definitions = self.take_definitions();
|
||||||
Self::run_visitors(&mut schema, &mut self.settings.visitors);
|
self.add_definitions(object, definitions);
|
||||||
|
self.run_visitors(&mut schema);
|
||||||
|
|
||||||
schema
|
schema
|
||||||
}
|
}
|
||||||
|
@ -376,12 +374,8 @@ impl SchemaGenerator {
|
||||||
object.insert("$schema".into(), meta_schema.into());
|
object.insert("$schema".into(), meta_schema.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::add_definitions(
|
self.add_definitions(object, self.definitions.clone());
|
||||||
object,
|
self.run_visitors(&mut schema);
|
||||||
self.definitions.clone(),
|
|
||||||
&self.settings.definitions_path,
|
|
||||||
);
|
|
||||||
Self::run_visitors(&mut schema, &mut self.settings.visitors);
|
|
||||||
|
|
||||||
Ok(schema)
|
Ok(schema)
|
||||||
}
|
}
|
||||||
|
@ -405,12 +399,13 @@ impl SchemaGenerator {
|
||||||
object.insert("examples".into(), vec![example].into());
|
object.insert("examples".into(), vec![example].into());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(meta_schema) = self.settings.meta_schema {
|
if let Some(meta_schema) = std::mem::take(&mut self.settings.meta_schema) {
|
||||||
object.insert("$schema".into(), meta_schema.into());
|
object.insert("$schema".into(), meta_schema.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::add_definitions(object, self.definitions, &self.settings.definitions_path);
|
let definitions = self.take_definitions();
|
||||||
Self::run_visitors(&mut schema, &mut self.settings.visitors);
|
self.add_definitions(object, definitions);
|
||||||
|
self.run_visitors(&mut schema);
|
||||||
|
|
||||||
Ok(schema)
|
Ok(schema)
|
||||||
}
|
}
|
||||||
|
@ -442,15 +437,16 @@ impl SchemaGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_definitions(
|
fn add_definitions(
|
||||||
|
&mut self,
|
||||||
schema_object: &mut Map<String, Value>,
|
schema_object: &mut Map<String, Value>,
|
||||||
mut definitions: Map<String, Value>,
|
mut definitions: Map<String, Value>,
|
||||||
path: &str,
|
|
||||||
) {
|
) {
|
||||||
if definitions.is_empty() {
|
if definitions.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let target = match Self::json_pointer(schema_object, path) {
|
let pointer = self.definitions_path_stripped();
|
||||||
|
let target = match json_pointer_mut(schema_object, pointer, true) {
|
||||||
Some(d) => d,
|
Some(d) => d,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
@ -458,33 +454,64 @@ impl SchemaGenerator {
|
||||||
target.append(&mut definitions);
|
target.append(&mut definitions);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn json_pointer<'a>(
|
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 definitions_path_stripped(&self) -> &str {
|
||||||
|
let path = &self.settings.definitions_path;
|
||||||
|
let path = path.strip_prefix('#').unwrap_or(path);
|
||||||
|
path.strip_suffix('/').unwrap_or(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_pointer_mut<'a>(
|
||||||
mut object: &'a mut Map<String, Value>,
|
mut object: &'a mut Map<String, Value>,
|
||||||
pointer: &str,
|
pointer: &str,
|
||||||
) -> Option<&'a mut Map<String, Value>> {
|
create_if_missing: bool,
|
||||||
let segments = pointer.strip_prefix("#/")?.strip_suffix('/')?.split('/');
|
) -> Option<&'a mut Map<String, Value>> {
|
||||||
|
let pointer = pointer.strip_prefix('/')?;
|
||||||
|
if pointer.is_empty() {
|
||||||
|
return Some(object);
|
||||||
|
}
|
||||||
|
|
||||||
for mut segment in segments {
|
for mut segment in pointer.split('/') {
|
||||||
let replaced: String;
|
let replaced: String;
|
||||||
if segment.contains('~') {
|
if segment.contains('~') {
|
||||||
replaced = segment.replace("~1", "/").replace("~0", "~");
|
replaced = segment.replace("~1", "/").replace("~0", "~");
|
||||||
segment = &replaced;
|
segment = &replaced;
|
||||||
}
|
}
|
||||||
|
|
||||||
object = object
|
use serde_json::map::Entry;
|
||||||
.entry(segment)
|
let next_value = match object.entry(segment) {
|
||||||
.or_insert(Value::Object(Map::default()))
|
Entry::Occupied(o) => o.into_mut(),
|
||||||
.as_object_mut()?;
|
Entry::Vacant(v) if create_if_missing => v.insert(Value::Object(Map::default())),
|
||||||
|
Entry::Vacant(_) => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
object = next_value.as_object_mut()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(object)
|
Some(object)
|
||||||
}
|
|
||||||
|
|
||||||
fn run_visitors(schema: &mut Schema, visitors: &mut [Box<dyn GenVisitor>]) {
|
|
||||||
for visitor in visitors {
|
|
||||||
visitor.visit_schema(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].
|
||||||
|
|
|
@ -84,7 +84,7 @@ pub fn visit_schema<V: Visitor + ?Sized>(v: &mut V, schema: &mut Schema) {
|
||||||
v.visit_schema(subschema)
|
v.visit_schema(subschema)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"properties" | "patternProperties" => {
|
"properties" | "patternProperties" | "$defs" | "definitions" => {
|
||||||
if let Some(obj) = value.as_object_mut() {
|
if let Some(obj) = value.as_object_mut() {
|
||||||
for value in obj.values_mut() {
|
for value in obj.values_mut() {
|
||||||
if let Ok(subschema) = value.try_into() {
|
if let Ok(subschema) = value.try_into() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue