chore: JSPG Engine tuple decoupling and core routing optimizations
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
use crate::database::object::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
use crate::database::object::*;
|
||||
#[derive(Debug, Clone, Serialize, Default)]
|
||||
pub struct Schema {
|
||||
#[serde(flatten)]
|
||||
@ -26,6 +26,8 @@ impl Schema {
|
||||
pub fn compile(
|
||||
&self,
|
||||
db: &crate::database::Database,
|
||||
root_id: &str,
|
||||
path: String,
|
||||
visited: &mut std::collections::HashSet<String>,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) {
|
||||
@ -33,9 +35,11 @@ impl Schema {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(id) = &self.obj.id {
|
||||
if !visited.insert(id.clone()) {
|
||||
return; // Break cyclical resolution
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
if !visited.insert(t.clone()) {
|
||||
return; // Break cyclical resolution
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,7 +79,7 @@ impl Schema {
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
if let Some(parent) = db.schemas.get(t) {
|
||||
parent.as_ref().compile(db, visited, errors);
|
||||
parent.as_ref().compile(db, t, t.clone(), visited, errors);
|
||||
if let Some(p_props) = parent.obj.compiled_properties.get() {
|
||||
props.extend(p_props.clone());
|
||||
}
|
||||
@ -95,11 +99,12 @@ impl Schema {
|
||||
errors.push(crate::drop::Error {
|
||||
code: "MULTIPLE_INHERITANCE_PROHIBITED".to_string(),
|
||||
message: format!(
|
||||
"Schema '{}' attempts to extend multiple custom object pointers in its type array. Use 'oneOf' for polymorphism and tagged unions.",
|
||||
self.obj.identifier().unwrap_or("unknown".to_string())
|
||||
"Schema attempts to extend multiple custom object pointers in its type array {:?}. Use 'oneOf' for polymorphism and tagged unions.",
|
||||
types
|
||||
),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: self.obj.identifier().unwrap_or("unknown".to_string()),
|
||||
path: path.clone(),
|
||||
schema: Some(root_id.to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
@ -108,7 +113,7 @@ impl Schema {
|
||||
for t in types {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
if let Some(parent) = db.schemas.get(t) {
|
||||
parent.as_ref().compile(db, visited, errors);
|
||||
parent.as_ref().compile(db, t, t.clone(), visited, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -128,60 +133,68 @@ impl Schema {
|
||||
let _ = self.obj.compiled_property_names.set(names);
|
||||
|
||||
// 4. Compute Edges natively
|
||||
let schema_edges = self.compile_edges(db, visited, &props, errors);
|
||||
let schema_edges = self.compile_edges(db, root_id, &path, visited, &props, errors);
|
||||
let _ = self.obj.compiled_edges.set(schema_edges);
|
||||
|
||||
// 5. Build our inline children properties recursively NOW! (Depth-first search)
|
||||
if let Some(local_props) = &self.obj.properties {
|
||||
for child in local_props.values() {
|
||||
child.compile(db, visited, errors);
|
||||
for (k, child) in local_props {
|
||||
child.compile(db, root_id, format!("{}/{}", path, k), visited, errors);
|
||||
}
|
||||
}
|
||||
if let Some(items) = &self.obj.items {
|
||||
items.compile(db, visited, errors);
|
||||
items.compile(db, root_id, format!("{}/items", path), visited, errors);
|
||||
}
|
||||
if let Some(pattern_props) = &self.obj.pattern_properties {
|
||||
for child in pattern_props.values() {
|
||||
child.compile(db, visited, errors);
|
||||
for (k, child) in pattern_props {
|
||||
child.compile(db, root_id, format!("{}/{}", path, k), visited, errors);
|
||||
}
|
||||
}
|
||||
if let Some(additional_props) = &self.obj.additional_properties {
|
||||
additional_props.compile(db, visited, errors);
|
||||
additional_props.compile(
|
||||
db,
|
||||
root_id,
|
||||
format!("{}/additionalProperties", path),
|
||||
visited,
|
||||
errors,
|
||||
);
|
||||
}
|
||||
if let Some(one_of) = &self.obj.one_of {
|
||||
for child in one_of {
|
||||
child.compile(db, visited, errors);
|
||||
for (i, child) in one_of.iter().enumerate() {
|
||||
child.compile(db, root_id, format!("{}/oneOf/{}", path, i), visited, errors);
|
||||
}
|
||||
}
|
||||
if let Some(arr) = &self.obj.prefix_items {
|
||||
for child in arr {
|
||||
child.compile(db, visited, errors);
|
||||
for (i, child) in arr.iter().enumerate() {
|
||||
child.compile(db, root_id, format!("{}/prefixItems/{}", path, i), visited, errors);
|
||||
}
|
||||
}
|
||||
if let Some(child) = &self.obj.not {
|
||||
child.compile(db, visited, errors);
|
||||
child.compile(db, root_id, format!("{}/not", path), visited, errors);
|
||||
}
|
||||
if let Some(child) = &self.obj.contains {
|
||||
child.compile(db, visited, errors);
|
||||
child.compile(db, root_id, format!("{}/contains", path), visited, errors);
|
||||
}
|
||||
if let Some(cases) = &self.obj.cases {
|
||||
for c in cases {
|
||||
for (i, c) in cases.iter().enumerate() {
|
||||
if let Some(child) = &c.when {
|
||||
child.compile(db, visited, errors);
|
||||
child.compile(db, root_id, format!("{}/cases/{}/when", path, i), visited, errors);
|
||||
}
|
||||
if let Some(child) = &c.then {
|
||||
child.compile(db, visited, errors);
|
||||
child.compile(db, root_id, format!("{}/cases/{}/then", path, i), visited, errors);
|
||||
}
|
||||
if let Some(child) = &c.else_ {
|
||||
child.compile(db, visited, errors);
|
||||
child.compile(db, root_id, format!("{}/cases/{}/else", path, i), visited, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.compile_polymorphism(db, errors);
|
||||
self.compile_polymorphism(db, root_id, &path, errors);
|
||||
|
||||
if let Some(id) = &self.obj.id {
|
||||
visited.remove(id);
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
visited.remove(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,6 +205,8 @@ impl Schema {
|
||||
pub fn compile_edges(
|
||||
&self,
|
||||
db: &crate::database::Database,
|
||||
root_id: &str,
|
||||
path: &str,
|
||||
visited: &mut std::collections::HashSet<String>,
|
||||
props: &std::collections::BTreeMap<String, std::sync::Arc<Schema>>,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
@ -201,16 +216,33 @@ impl Schema {
|
||||
// Determine the physical Database Table Name this schema structurally represents
|
||||
// Plucks the polymorphic discriminator via dot-notation (e.g. extracting "person" from "full.person")
|
||||
let mut parent_type_name = None;
|
||||
|
||||
if let Some(family) = &self.obj.family {
|
||||
// 1. Explicit horizontal routing
|
||||
parent_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string());
|
||||
} else if let Some(identifier) = self.obj.identifier() {
|
||||
parent_type_name = Some(
|
||||
identifier
|
||||
.split('.')
|
||||
.next_back()
|
||||
.unwrap_or(&identifier)
|
||||
.to_string(),
|
||||
);
|
||||
} else if !path.contains('/') {
|
||||
// 2. Root nodes trust their exact registry footprint
|
||||
let base_type_name = path.split('.').next_back().unwrap_or(path).to_string();
|
||||
if db.types.contains_key(&base_type_name) {
|
||||
parent_type_name = Some(base_type_name);
|
||||
}
|
||||
} else if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ {
|
||||
// 3. Nested graphs trust their explicit struct pointer reference
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
parent_type_name = Some(t.split('.').next_back().unwrap_or(t).to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if parent_type_name.is_none() {
|
||||
// 4. Absolute fallback for completely anonymous inline structures
|
||||
let base_type_name = root_id
|
||||
.split('.')
|
||||
.next_back()
|
||||
.unwrap_or(root_id)
|
||||
.to_string();
|
||||
if db.types.contains_key(&base_type_name) {
|
||||
parent_type_name = Some(base_type_name);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(p_type) = parent_type_name {
|
||||
@ -237,13 +269,19 @@ impl Schema {
|
||||
// Determine the physical Postgres table backing the nested child schema recursively
|
||||
if let Some(family) = &target_schema.obj.family {
|
||||
child_type_name = Some(family.split('.').next_back().unwrap_or(family).to_string());
|
||||
} else if let Some(ref_id) = target_schema.obj.identifier() {
|
||||
child_type_name = Some(ref_id.split('.').next_back().unwrap_or(&ref_id).to_string());
|
||||
} else if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) =
|
||||
&target_schema.obj.type_
|
||||
{
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
child_type_name = Some(t.split('.').next_back().unwrap_or(t).to_string());
|
||||
}
|
||||
} else if let Some(arr) = &target_schema.obj.one_of {
|
||||
if let Some(first) = arr.first() {
|
||||
if let Some(ref_id) = first.obj.identifier() {
|
||||
child_type_name =
|
||||
Some(ref_id.split('.').next_back().unwrap_or(&ref_id).to_string());
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &first.obj.type_
|
||||
{
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
child_type_name = Some(t.split('.').next_back().unwrap_or(t).to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -252,7 +290,7 @@ impl Schema {
|
||||
if db.types.contains_key(&c_type) {
|
||||
// Ensure the child Schema's AST has accurately compiled its own physical property keys so we can
|
||||
// inject them securely for Many-to-Many Twin Deduction disambiguation matching.
|
||||
target_schema.compile(db, visited, errors);
|
||||
target_schema.compile(db, root_id, format!("{}/{}", path, prop_name), visited, errors);
|
||||
if let Some(compiled_target_props) = target_schema.obj.compiled_properties.get() {
|
||||
let keys_for_ambiguity: Vec<String> =
|
||||
compiled_target_props.keys().cloned().collect();
|
||||
@ -264,8 +302,8 @@ impl Schema {
|
||||
prop_name,
|
||||
Some(&keys_for_ambiguity),
|
||||
is_array,
|
||||
self.id.as_deref(),
|
||||
&format!("/{}", prop_name),
|
||||
Some(root_id),
|
||||
&format!("{}/{}", path, prop_name),
|
||||
errors,
|
||||
) {
|
||||
schema_edges.insert(
|
||||
@ -288,6 +326,8 @@ impl Schema {
|
||||
pub fn compile_polymorphism(
|
||||
&self,
|
||||
db: &crate::database::Database,
|
||||
root_id: &str,
|
||||
path: &str,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) {
|
||||
let mut options = std::collections::BTreeMap::new();
|
||||
@ -312,7 +352,7 @@ impl Schema {
|
||||
};
|
||||
|
||||
if db.schemas.contains_key(&target_id) {
|
||||
options.insert(var.to_string(), target_id);
|
||||
options.insert(var.to_string(), (None, Some(target_id)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -321,12 +361,10 @@ impl Schema {
|
||||
|
||||
let suffix = format!(".{}", family_base);
|
||||
|
||||
for schema in &type_def.schemas {
|
||||
if let Some(id) = &schema.obj.id {
|
||||
if id.ends_with(&suffix) || id == &family_base {
|
||||
if let Some(kind_val) = schema.obj.get_discriminator_value("kind") {
|
||||
options.insert(kind_val, id.to_string());
|
||||
}
|
||||
for (id, schema) in &type_def.schemas {
|
||||
if id.ends_with(&suffix) || id == &family_base {
|
||||
if let Some(kind_val) = schema.obj.get_discriminator_value("kind", id) {
|
||||
options.insert(kind_val, (None, Some(id.to_string())));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -335,65 +373,114 @@ impl Schema {
|
||||
} else if let Some(one_of) = &self.obj.one_of {
|
||||
let mut type_vals = std::collections::HashSet::new();
|
||||
let mut kind_vals = std::collections::HashSet::new();
|
||||
let mut disjoint_base = true;
|
||||
let mut structural_types = std::collections::HashSet::new();
|
||||
|
||||
for c in one_of {
|
||||
if let Some(t_val) = c.obj.get_discriminator_value("type") {
|
||||
type_vals.insert(t_val);
|
||||
let mut child_id = String::new();
|
||||
let mut child_is_primitive = false;
|
||||
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &c.obj.type_ {
|
||||
if crate::database::object::is_primitive_type(t) {
|
||||
child_is_primitive = true;
|
||||
structural_types.insert(t.clone());
|
||||
} else {
|
||||
child_id = t.clone();
|
||||
structural_types.insert("object".to_string());
|
||||
}
|
||||
} else {
|
||||
disjoint_base = false;
|
||||
}
|
||||
if let Some(k_val) = c.obj.get_discriminator_value("kind") {
|
||||
kind_vals.insert(k_val);
|
||||
|
||||
if !child_is_primitive {
|
||||
if let Some(t_val) = c.obj.get_discriminator_value("type", &child_id) {
|
||||
type_vals.insert(t_val);
|
||||
}
|
||||
if let Some(k_val) = c.obj.get_discriminator_value("kind", &child_id) {
|
||||
kind_vals.insert(k_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
strategy = if type_vals.len() > 1 && type_vals.len() == one_of.len() {
|
||||
"type".to_string()
|
||||
} else if kind_vals.len() > 1 && kind_vals.len() == one_of.len() {
|
||||
"kind".to_string()
|
||||
if disjoint_base && structural_types.len() == one_of.len() {
|
||||
strategy = "".to_string();
|
||||
for (i, c) in one_of.iter().enumerate() {
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &c.obj.type_ {
|
||||
if crate::database::object::is_primitive_type(t) {
|
||||
options.insert(t.clone(), (Some(i), None));
|
||||
} else {
|
||||
options.insert("object".to_string(), (Some(i), None));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
strategy = if type_vals.len() > 1 && type_vals.len() == one_of.len() {
|
||||
"type".to_string()
|
||||
} else if kind_vals.len() > 1 && kind_vals.len() == one_of.len() {
|
||||
"kind".to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
|
||||
if strategy.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
for c in one_of {
|
||||
if let Some(val) = c.obj.get_discriminator_value(&strategy) {
|
||||
if options.contains_key(&val) {
|
||||
errors.push(crate::drop::Error {
|
||||
code: "POLYMORPHIC_COLLISION".to_string(),
|
||||
message: format!("Polymorphic boundary defines multiple candidates mapped to the identical discriminator value '{}'.", val),
|
||||
details: crate::drop::ErrorDetails::default()
|
||||
});
|
||||
continue;
|
||||
if strategy.is_empty() {
|
||||
errors.push(crate::drop::Error {
|
||||
code: "AMBIGUOUS_POLYMORPHISM".to_string(),
|
||||
message: format!("oneOf boundaries must map mathematically unique 'type' or 'kind' discriminators, or strictly contain disjoint primitive types."),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: path.to_string(),
|
||||
schema: Some(root_id.to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let mut target_id = c.obj.id.clone();
|
||||
if target_id.is_none() {
|
||||
for (i, c) in one_of.iter().enumerate() {
|
||||
let mut child_id = String::new();
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &c.obj.type_ {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
target_id = Some(t.clone());
|
||||
child_id = t.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(tid) = target_id {
|
||||
options.insert(val, tid);
|
||||
if let Some(val) = c.obj.get_discriminator_value(&strategy, &child_id) {
|
||||
if options.contains_key(&val) {
|
||||
errors.push(crate::drop::Error {
|
||||
code: "POLYMORPHIC_COLLISION".to_string(),
|
||||
message: format!("Polymorphic boundary defines multiple candidates mapped to the identical discriminator value '{}'.", val),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: path.to_string(),
|
||||
schema: Some(root_id.to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
options.insert(val, (Some(i), None));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if !options.is_empty() {
|
||||
let _ = self.obj.compiled_discriminator.set(strategy);
|
||||
if !strategy.is_empty() {
|
||||
let _ = self.obj.compiled_discriminator.set(strategy);
|
||||
}
|
||||
let _ = self.obj.compiled_options.set(options);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn validate_identifier(id: &str, field_name: &str, errors: &mut Vec<crate::drop::Error>) {
|
||||
fn validate_identifier(
|
||||
id: &str,
|
||||
field_name: &str,
|
||||
root_id: &str,
|
||||
path: &str,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) {
|
||||
#[cfg(not(test))]
|
||||
for c in id.chars() {
|
||||
if !c.is_ascii_lowercase() && !c.is_ascii_digit() && c != '_' && c != '.' {
|
||||
@ -401,9 +488,13 @@ impl Schema {
|
||||
code: "INVALID_IDENTIFIER".to_string(),
|
||||
message: format!(
|
||||
"Invalid character '{}' in JSON Schema '{}' property: '{}'. Identifiers must exclusively contain [a-z0-9_.]",
|
||||
c, field_name, id
|
||||
c, field_name, id
|
||||
),
|
||||
details: crate::drop::ErrorDetails::default(),
|
||||
details: crate::drop::ErrorDetails {
|
||||
path: path.to_string(),
|
||||
schema: Some(root_id.to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -411,116 +502,124 @@ impl Schema {
|
||||
}
|
||||
|
||||
pub fn collect_schemas(
|
||||
&mut self,
|
||||
tracking_path: Option<String>,
|
||||
to_insert: &mut Vec<(String, Schema)>,
|
||||
schema_arc: &Arc<Schema>,
|
||||
root_id: &str,
|
||||
path: String,
|
||||
to_insert: &mut Vec<(String, Arc<Schema>)>,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) {
|
||||
if let Some(id) = &self.obj.id {
|
||||
Self::validate_identifier(id, "$id", errors);
|
||||
to_insert.push((id.clone(), self.clone()));
|
||||
let mut should_push = false;
|
||||
|
||||
// Push ad-hoc inline composition into the addressable registry
|
||||
if schema_arc.obj.properties.is_some()
|
||||
|| schema_arc.obj.items.is_some()
|
||||
|| schema_arc.obj.family.is_some()
|
||||
|| schema_arc.obj.one_of.is_some()
|
||||
{
|
||||
should_push = true;
|
||||
}
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ {
|
||||
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &schema_arc.obj.type_ {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
Self::validate_identifier(t, "type", errors);
|
||||
}
|
||||
}
|
||||
if let Some(family) = &self.obj.family {
|
||||
Self::validate_identifier(family, "$family", errors);
|
||||
}
|
||||
|
||||
// Is this schema an inline ad-hoc composition?
|
||||
// Meaning it has a tracking context, lacks an explicit $id, but extends an Entity ref with explicit properties!
|
||||
if self.obj.id.is_none() && self.obj.properties.is_some() {
|
||||
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.obj.type_ {
|
||||
if !crate::database::object::is_primitive_type(t) {
|
||||
if let Some(ref path) = tracking_path {
|
||||
to_insert.push((path.clone(), self.clone()));
|
||||
}
|
||||
}
|
||||
Self::validate_identifier(t, "type", root_id, &path, errors);
|
||||
should_push = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Provide the path origin to children natively, prioritizing the explicit `$id` boundary if one exists
|
||||
let origin_path = self.obj.id.clone().or(tracking_path);
|
||||
if let Some(family) = &schema_arc.obj.family {
|
||||
Self::validate_identifier(family, "$family", root_id, &path, errors);
|
||||
}
|
||||
|
||||
self.collect_child_schemas(origin_path, to_insert, errors);
|
||||
if should_push {
|
||||
to_insert.push((path.clone(), Arc::clone(schema_arc)));
|
||||
}
|
||||
|
||||
Self::collect_child_schemas(schema_arc, root_id, path, to_insert, errors);
|
||||
}
|
||||
|
||||
pub fn collect_child_schemas(
|
||||
&mut self,
|
||||
origin_path: Option<String>,
|
||||
to_insert: &mut Vec<(String, Schema)>,
|
||||
schema_arc: &Arc<Schema>,
|
||||
root_id: &str,
|
||||
path: String,
|
||||
to_insert: &mut Vec<(String, Arc<Schema>)>,
|
||||
errors: &mut Vec<crate::drop::Error>,
|
||||
) {
|
||||
if let Some(props) = &mut self.obj.properties {
|
||||
for (k, v) in props.iter_mut() {
|
||||
let mut inner = (**v).clone();
|
||||
let next_path = origin_path.as_ref().map(|o| format!("{}/{}", o, k));
|
||||
inner.collect_schemas(next_path, to_insert, errors);
|
||||
*v = Arc::new(inner);
|
||||
if let Some(props) = &schema_arc.obj.properties {
|
||||
for (k, v) in props.iter() {
|
||||
let next_path = format!("{}/{}", path, k);
|
||||
Self::collect_schemas(v, root_id, next_path, to_insert, errors);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pattern_props) = &mut self.obj.pattern_properties {
|
||||
for (k, v) in pattern_props.iter_mut() {
|
||||
let mut inner = (**v).clone();
|
||||
let next_path = origin_path.as_ref().map(|o| format!("{}/{}", o, k));
|
||||
inner.collect_schemas(next_path, to_insert, errors);
|
||||
*v = Arc::new(inner);
|
||||
if let Some(pattern_props) = &schema_arc.obj.pattern_properties {
|
||||
for (k, v) in pattern_props.iter() {
|
||||
let next_path = format!("{}/{}", path, k);
|
||||
Self::collect_schemas(v, root_id, next_path, to_insert, errors);
|
||||
}
|
||||
}
|
||||
|
||||
let mut map_arr = |arr: &mut Vec<Arc<Schema>>| {
|
||||
for v in arr.iter_mut() {
|
||||
let mut inner = (**v).clone();
|
||||
inner.collect_schemas(origin_path.clone(), to_insert, errors);
|
||||
*v = Arc::new(inner);
|
||||
let mut map_arr = |arr: &Vec<Arc<Schema>>, sub: &str| {
|
||||
for (i, v) in arr.iter().enumerate() {
|
||||
Self::collect_schemas(v, root_id, format!("{}/{}/{}", path, sub, i), to_insert, errors);
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(arr) = &mut self.obj.prefix_items {
|
||||
map_arr(arr);
|
||||
if let Some(arr) = &schema_arc.obj.prefix_items {
|
||||
map_arr(arr, "prefixItems");
|
||||
}
|
||||
|
||||
if let Some(arr) = &mut self.obj.one_of {
|
||||
map_arr(arr);
|
||||
if let Some(arr) = &schema_arc.obj.one_of {
|
||||
map_arr(arr, "oneOf");
|
||||
}
|
||||
|
||||
let mut map_opt = |opt: &mut Option<Arc<Schema>>, pass_path: bool| {
|
||||
let mut map_opt = |opt: &Option<Arc<Schema>>, pass_path: bool, sub: &str| {
|
||||
if let Some(v) = opt {
|
||||
let mut inner = (**v).clone();
|
||||
let next = if pass_path { origin_path.clone() } else { None };
|
||||
inner.collect_schemas(next, to_insert, errors);
|
||||
*v = Arc::new(inner);
|
||||
if pass_path {
|
||||
Self::collect_schemas(v, root_id, format!("{}/{}", path, sub), to_insert, errors);
|
||||
} else {
|
||||
Self::collect_child_schemas(v, root_id, format!("{}/{}", path, sub), to_insert, errors);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
map_opt(&mut self.obj.additional_properties, false);
|
||||
map_opt(
|
||||
&schema_arc.obj.additional_properties,
|
||||
false,
|
||||
"additionalProperties",
|
||||
);
|
||||
map_opt(&schema_arc.obj.items, true, "items");
|
||||
map_opt(&schema_arc.obj.not, false, "not");
|
||||
map_opt(&schema_arc.obj.contains, false, "contains");
|
||||
map_opt(&schema_arc.obj.property_names, false, "propertyNames");
|
||||
|
||||
// `items` absolutely must inherit the EXACT property path assigned to the Array wrapper!
|
||||
// This allows nested Arrays enclosing bare Entity structs to correctly register as the boundary mapping.
|
||||
map_opt(&mut self.obj.items, true);
|
||||
|
||||
map_opt(&mut self.obj.not, false);
|
||||
map_opt(&mut self.obj.contains, false);
|
||||
map_opt(&mut self.obj.property_names, false);
|
||||
if let Some(cases) = &mut self.obj.cases {
|
||||
for c in cases.iter_mut() {
|
||||
if let Some(when) = &mut c.when {
|
||||
let mut inner = (**when).clone();
|
||||
inner.collect_schemas(origin_path.clone(), to_insert, errors);
|
||||
*when = Arc::new(inner);
|
||||
if let Some(cases) = &schema_arc.obj.cases {
|
||||
for (i, c) in cases.iter().enumerate() {
|
||||
if let Some(when) = &c.when {
|
||||
Self::collect_schemas(
|
||||
when,
|
||||
root_id,
|
||||
format!("{}/cases/{}/when", path, i),
|
||||
to_insert,
|
||||
errors,
|
||||
);
|
||||
}
|
||||
if let Some(then) = &mut c.then {
|
||||
let mut inner = (**then).clone();
|
||||
inner.collect_schemas(origin_path.clone(), to_insert, errors);
|
||||
*then = Arc::new(inner);
|
||||
if let Some(then) = &c.then {
|
||||
Self::collect_schemas(
|
||||
then,
|
||||
root_id,
|
||||
format!("{}/cases/{}/then", path, i),
|
||||
to_insert,
|
||||
errors,
|
||||
);
|
||||
}
|
||||
if let Some(else_) = &mut c.else_ {
|
||||
let mut inner = (**else_).clone();
|
||||
inner.collect_schemas(origin_path.clone(), to_insert, errors);
|
||||
*else_ = Arc::new(inner);
|
||||
if let Some(else_) = &c.else_ {
|
||||
Self::collect_schemas(
|
||||
else_,
|
||||
root_id,
|
||||
format!("{}/cases/{}/else", path, i),
|
||||
to_insert,
|
||||
errors,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user