chore: JSPG Engine tuple decoupling and core routing optimizations

This commit is contained in:
2026-04-13 22:41:32 -04:00
parent 665a821bf9
commit 0017c598e1
57 changed files with 5510 additions and 5166 deletions

View File

@ -1,5 +1,6 @@
use crate::database::schema::Schema;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
@ -8,5 +9,6 @@ pub struct Enum {
pub module: String,
pub source: String,
pub values: Vec<String>,
pub schemas: Vec<Schema>,
#[serde(default)]
pub schemas: std::collections::BTreeMap<String, Arc<Schema>>,
}

View File

@ -125,22 +125,16 @@ impl Database {
}
}
if let Some(arr) = val.get("schemas").and_then(|v| v.as_array()) {
for (i, item) in arr.iter().enumerate() {
if let Some(map) = val.get("schemas").and_then(|v| v.as_object()) {
for (key, item) in map.iter() {
match serde_json::from_value::<Schema>(item.clone()) {
Ok(mut schema) => {
let id = schema
.obj
.id
.clone()
.unwrap_or_else(|| format!("schema_{}", i));
schema.obj.id = Some(id.clone());
db.schemas.insert(id, Arc::new(schema));
Ok(schema) => {
db.schemas.insert(key.clone(), Arc::new(schema));
}
Err(e) => {
errors.push(crate::drop::Error {
code: "DATABASE_SCHEMA_PARSE_FAILED".to_string(),
message: format!("Failed to parse database schema: {}", e),
message: format!("Failed to parse database schema key '{}': {}", key, e),
details: crate::drop::ErrorDetails::default(),
});
}
@ -185,21 +179,21 @@ impl Database {
pub fn compile(&mut self, errors: &mut Vec<crate::drop::Error>) {
let mut harvested = Vec::new();
for schema_arc in self.schemas.values_mut() {
if let Some(s) = std::sync::Arc::get_mut(schema_arc) {
s.collect_schemas(None, &mut harvested, errors);
}
for (id, schema_arc) in &self.schemas {
crate::database::schema::Schema::collect_schemas(schema_arc, id, id.clone(), &mut harvested, errors);
}
for (id, schema) in harvested {
self.schemas.insert(id, Arc::new(schema));
for (id, schema_arc) in harvested {
self.schemas.insert(id, schema_arc);
}
self.collect_schemas(errors);
// Mathematically evaluate all property inheritances, formats, schemas, and foreign key edges topographically over OnceLocks
let mut visited = std::collections::HashSet::new();
for schema_arc in self.schemas.values() {
schema_arc.as_ref().compile(self, &mut visited, errors);
for (id, schema_arc) in &self.schemas {
// First compile pass initializes exact structural root_id mapping to resolve DB constraints
let root_id = id.split('/').next().unwrap_or(id);
schema_arc.as_ref().compile(self, root_id, id.clone(), &mut visited, errors);
}
}
@ -209,23 +203,26 @@ impl Database {
// Pass 1: Extract all Schemas structurally off top level definitions into the master registry.
// Validate every node recursively via string filters natively!
for type_def in self.types.values() {
for mut schema in type_def.schemas.clone() {
schema.collect_schemas(None, &mut to_insert, errors);
for (id, schema_arc) in &type_def.schemas {
to_insert.push((id.clone(), Arc::clone(schema_arc)));
crate::database::schema::Schema::collect_schemas(schema_arc, id, id.clone(), &mut to_insert, errors);
}
}
for punc_def in self.puncs.values() {
for mut schema in punc_def.schemas.clone() {
schema.collect_schemas(None, &mut to_insert, errors);
for (id, schema_arc) in &punc_def.schemas {
to_insert.push((id.clone(), Arc::clone(schema_arc)));
crate::database::schema::Schema::collect_schemas(schema_arc, id, id.clone(), &mut to_insert, errors);
}
}
for enum_def in self.enums.values() {
for mut schema in enum_def.schemas.clone() {
schema.collect_schemas(None, &mut to_insert, errors);
for (id, schema_arc) in &enum_def.schemas {
to_insert.push((id.clone(), Arc::clone(schema_arc)));
crate::database::schema::Schema::collect_schemas(schema_arc, id, id.clone(), &mut to_insert, errors);
}
}
for (id, schema) in to_insert {
self.schemas.insert(id, Arc::new(schema));
for (id, schema_arc) in to_insert {
self.schemas.insert(id, schema_arc);
}
}

View File

@ -1,9 +1,9 @@
use crate::database::schema::Schema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
use std::sync::Arc;
use std::sync::OnceLock;
use crate::database::schema::Schema;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Case {
@ -19,9 +19,6 @@ pub struct Case {
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SchemaObject {
// Core Schema Keywords
#[serde(rename = "$id")]
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
@ -176,7 +173,7 @@ pub struct SchemaObject {
#[serde(skip_deserializing)]
#[serde(skip_serializing_if = "crate::database::object::is_once_lock_map_empty")]
#[serde(serialize_with = "crate::database::object::serialize_once_lock")]
pub compiled_options: OnceLock<BTreeMap<String, String>>,
pub compiled_options: OnceLock<BTreeMap<String, (Option<usize>, Option<String>)>>,
#[serde(rename = "compiledEdges")]
#[serde(skip_deserializing)]
@ -275,93 +272,49 @@ pub fn is_primitive_type(t: &str) -> bool {
}
impl SchemaObject {
pub fn identifier(&self) -> Option<String> {
if let Some(id) = &self.id {
return Some(id.split('.').next_back().unwrap_or("").to_string());
}
if let Some(SchemaTypeOrArray::Single(t)) = &self.type_ {
if !is_primitive_type(t) {
return Some(t.split('.').next_back().unwrap_or("").to_string());
}
}
None
}
pub fn get_discriminator_value(&self, dim: &str) -> Option<String> {
pub fn get_discriminator_value(&self, dim: &str, schema_id: &str) -> Option<String> {
let is_split = self
.compiled_properties
.get()
.map_or(false, |p| p.contains_key("kind"));
if let Some(id) = &self.id {
if id.contains("light.person") || id.contains("light.organization") {
println!(
"[DEBUG SPLIT] ID: {}, dim: {}, is_split: {:?}, props: {:?}",
id,
dim,
is_split,
self
.compiled_properties
.get()
.map(|p| p.keys().cloned().collect::<Vec<_>>())
);
}
}
if let Some(props) = self.compiled_properties.get() {
if let Some(prop_schema) = props.get(dim) {
if let Some(c) = &prop_schema.obj.const_ {
if let Some(s) = c.as_str() {
return Some(s.to_string());
}
}
if let Some(e) = &prop_schema.obj.enum_ {
if e.len() == 1 {
if let Some(s) = e[0].as_str() {
return Some(s.to_string());
}
}
}
}
}
if dim == "kind" {
if let Some(id) = &self.id {
let base = id.split('/').last().unwrap_or(id);
if let Some(idx) = base.rfind('.') {
return Some(base[..idx].to_string());
}
}
if let Some(SchemaTypeOrArray::Single(t)) = &self.type_ {
if !is_primitive_type(t) {
let base = t.split('/').last().unwrap_or(t);
if let Some(idx) = base.rfind('.') {
return Some(base[..idx].to_string());
}
}
let base = schema_id.split('/').last().unwrap_or(schema_id);
if let Some(idx) = base.rfind('.') {
return Some(base[..idx].to_string());
}
}
if dim == "type" {
if let Some(id) = &self.id {
let base = id.split('/').last().unwrap_or(id);
if is_split {
return Some(base.split('.').next_back().unwrap_or(base).to_string());
} else {
return Some(base.to_string());
}
}
if let Some(SchemaTypeOrArray::Single(t)) = &self.type_ {
if !is_primitive_type(t) {
let base = t.split('/').last().unwrap_or(t);
if is_split {
return Some(base.split('.').next_back().unwrap_or(base).to_string());
} else {
return Some(base.to_string());
}
}
let base = schema_id.split('/').last().unwrap_or(schema_id);
if is_split {
return Some(base.split('.').next_back().unwrap_or(base).to_string());
} else {
return Some(base.to_string());
}
}
None
}
pub fn requires_uuid_path(&self, db: &crate::database::Database) -> bool {
// 1. Explicitly defines "id" either directly or via inheritance/extension?
if self
.compiled_properties
.get()
.map_or(false, |p| p.contains_key("id"))
{
return true;
}
// 2. Implicit table-backed rule: Does its $family boundary map directly to the global database catalog?
if let Some(family) = &self.family {
let base = family.split('.').next_back().unwrap_or(family);
if db.types.contains_key(base) {
return true;
}
}
false
}
}

View File

@ -1,6 +1,7 @@
use crate::database::page::Page;
use crate::database::schema::Schema;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
@ -16,5 +17,5 @@ pub struct Punc {
pub get: Option<String>,
pub page: Option<Page>,
#[serde(default)]
pub schemas: Vec<Schema>,
pub schemas: std::collections::BTreeMap<String, Arc<Schema>>,
}

View File

@ -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,
);
}
}
}

View File

@ -2,6 +2,7 @@ use std::collections::HashSet;
use crate::database::schema::Schema;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
@ -38,5 +39,5 @@ pub struct Type {
pub default_fields: Vec<String>,
pub field_types: Option<Value>,
#[serde(default)]
pub schemas: Vec<Schema>,
pub schemas: std::collections::BTreeMap<String, Arc<Schema>>,
}