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>>,
}

View File

@ -142,11 +142,21 @@ impl Merger {
if let Some(disc) = schema.obj.compiled_discriminator.get() {
let val = map.get(disc).and_then(|v| v.as_str());
if let Some(v) = val {
if let Some(target_id) = options.get(v) {
if let Some(target_schema) = self.db.schemas.get(target_id) {
schema = Arc::clone(target_schema);
if let Some((idx_opt, target_id_opt)) = options.get(v) {
if let Some(target_id) = target_id_opt {
if let Some(target_schema) = self.db.schemas.get(target_id) {
schema = Arc::clone(target_schema);
} else {
return Err(format!("Polymorphic mapped target '{}' not found in database registry", target_id));
}
} else if let Some(idx) = idx_opt {
if let Some(target_schema) = schema.obj.one_of.as_ref().and_then(|options| options.get(*idx)) {
schema = Arc::clone(target_schema);
} else {
return Err(format!("Polymorphic index target '{}' not found in local oneOf array", idx));
}
} else {
return Err(format!("Polymorphic mapped target '{}' not found in database registry", target_id));
return Err(format!("Polymorphic mapped target has no path"));
}
} else {
return Err(format!("Polymorphic discriminator {}='{}' matched no compiled options", disc, v));
@ -215,7 +225,7 @@ impl Merger {
for (k, v) in obj {
// Always retain system and unmapped core fields natively implicitly mapped to the Postgres tables
if k == "id" || k == "type" || k == "created" {
entity_fields.insert(k.clone(), v.clone());
entity_fields.insert(k, v);
continue;
}
@ -234,18 +244,18 @@ impl Merger {
_ => "field", // Malformed edge data?
};
if typeof_v == "object" {
entity_objects.insert(k.clone(), (v.clone(), prop_schema.clone()));
entity_objects.insert(k, (v, prop_schema.clone()));
} else if typeof_v == "array" {
entity_arrays.insert(k.clone(), (v.clone(), prop_schema.clone()));
entity_arrays.insert(k, (v, prop_schema.clone()));
} else {
entity_fields.insert(k.clone(), v.clone());
entity_fields.insert(k, v);
}
} else {
// Not an edge! It's a raw Postgres column (e.g., JSONB, text[])
entity_fields.insert(k.clone(), v.clone());
entity_fields.insert(k, v);
}
} else if type_def.fields.contains(&k) {
entity_fields.insert(k.clone(), v.clone());
entity_fields.insert(k, v);
}
}
@ -524,7 +534,7 @@ impl Merger {
entity_change_kind = Some("create".to_string());
let mut new_fields = changes.clone();
let mut new_fields = changes;
new_fields.insert("id".to_string(), id_val);
new_fields.insert("type".to_string(), Value::String(type_name.to_string()));
new_fields.insert("created_by".to_string(), Value::String(user_id.to_string()));
@ -564,7 +574,7 @@ impl Merger {
Some("update".to_string())
};
let mut new_fields = changes.clone();
let mut new_fields = changes;
new_fields.insert(
"id".to_string(),
entity_fetched.as_ref().unwrap().get("id").unwrap().clone(),

View File

@ -18,6 +18,7 @@ pub struct Node<'a> {
pub depth: usize,
pub ast_path: String,
pub is_polymorphic_branch: bool,
pub schema_id: Option<String>,
}
impl<'a> Compiler<'a> {
@ -47,6 +48,7 @@ impl<'a> Compiler<'a> {
depth: 0,
ast_path: String::new(),
is_polymorphic_branch: false,
schema_id: Some(schema_id.to_string()),
};
let (sql, _) = compiler.compile_node(node)?;
@ -66,17 +68,31 @@ impl<'a> Compiler<'a> {
}
fn compile_array(&mut self, node: Node<'a>) -> Result<(String, String), String> {
// 1. Array of DB Entities (`type` or `$family` pointing to a table limit)
if let Some(items) = &node.schema.obj.items {
let mut resolved_type = None;
if let Some(family_target) = items.obj.family.as_ref() {
let base_type_name = family_target
.split('.')
.next_back()
.unwrap_or(family_target);
resolved_type = self.db.types.get(base_type_name);
} else if let Some(base_type_name) = items.obj.identifier() {
resolved_type = self.db.types.get(&base_type_name);
if let Some(sid) = &node.schema_id {
resolved_type = self
.db
.types
.get(&sid.split('.').next_back().unwrap_or(sid).to_string());
}
if resolved_type.is_none() {
if let Some(family_target) = items.obj.family.as_ref() {
let base_type_name = family_target
.split('.')
.next_back()
.unwrap_or(family_target);
resolved_type = self.db.types.get(base_type_name);
} else if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &items.obj.type_
{
if !crate::database::object::is_primitive_type(t) {
resolved_type = self
.db
.types
.get(&t.split('.').next_back().unwrap_or(t).to_string());
}
}
}
if let Some(type_def) = resolved_type {
@ -105,16 +121,28 @@ impl<'a> Compiler<'a> {
// Determine if this schema represents a Database Entity
let mut resolved_type = None;
if let Some(family_target) = node.schema.obj.family.as_ref() {
let base_type_name = family_target
.split('.')
.next_back()
.unwrap_or(family_target);
resolved_type = self.db.types.get(base_type_name);
} else if let Some(base_type_name) = node.schema.obj.identifier() {
if let Some(sid) = &node.schema_id {
let base_type_name = sid.split('.').next_back().unwrap_or(sid).to_string();
resolved_type = self.db.types.get(&base_type_name);
}
if resolved_type.is_none() {
if let Some(family_target) = node.schema.obj.family.as_ref() {
let base_type_name = family_target
.split('.')
.next_back()
.unwrap_or(family_target);
resolved_type = self.db.types.get(base_type_name);
} else if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) =
&node.schema.obj.type_
{
if !crate::database::object::is_primitive_type(t) {
let base_type_name = t.split('.').next_back().unwrap_or(t).to_string();
resolved_type = self.db.types.get(&base_type_name);
}
}
}
if let Some(type_def) = resolved_type {
return self.compile_entity(type_def, node.clone(), false);
}
@ -126,6 +154,7 @@ impl<'a> Compiler<'a> {
if let Some(target_schema) = self.db.schemas.get(t) {
let mut ref_node = node.clone();
ref_node.schema = Arc::clone(target_schema);
ref_node.schema_id = Some(t.clone());
return self.compile_node(ref_node);
}
return Err(format!("Unresolved schema type pointer: {}", t));
@ -133,17 +162,21 @@ impl<'a> Compiler<'a> {
}
// Handle Polymorphism fallbacks for relations
if node.schema.obj.family.is_some() || node.schema.obj.one_of.is_some() {
if let Some(options) = node.schema.obj.compiled_options.get() {
if options.len() == 1 {
let target_id = options.values().next().unwrap();
let mut bypass_schema = crate::database::schema::Schema::default();
bypass_schema.obj.type_ = Some(crate::database::object::SchemaTypeOrArray::Single(target_id.clone()));
let mut bypass_node = node.clone();
bypass_node.schema = std::sync::Arc::new(bypass_schema);
return self.compile_node(bypass_node);
if let Some(options) = node.schema.obj.compiled_options.get() {
if options.len() == 1 {
let (_, target_opt) = options.values().next().unwrap();
if let Some(target_id) = target_opt {
let mut bypass_schema = crate::database::schema::Schema::default();
bypass_schema.obj.type_ = Some(crate::database::object::SchemaTypeOrArray::Single(
target_id.clone(),
));
let mut bypass_node = node.clone();
bypass_node.schema = std::sync::Arc::new(bypass_schema);
return self.compile_node(bypass_node);
}
}
return self.compile_one_of(node);
}
}
return self.compile_one_of(node);
}
// Just an inline object definition?
@ -171,27 +204,27 @@ impl<'a> Compiler<'a> {
let (table_aliases, from_clauses) = self.compile_from_clause(r#type);
let jsonb_obj_sql = if node.schema.obj.family.is_some() || node.schema.obj.one_of.is_some() {
let base_alias = table_aliases
.get(&r#type.name)
.cloned()
.unwrap_or_else(|| node.parent_alias.to_string());
let mut case_node = node.clone();
case_node.parent_alias = base_alias.clone();
let arc_aliases = std::sync::Arc::new(table_aliases.clone());
case_node.parent_type_aliases = Some(arc_aliases);
case_node.parent_type = Some(r#type);
let base_alias = table_aliases
.get(&r#type.name)
.cloned()
.unwrap_or_else(|| node.parent_alias.to_string());
let (case_sql, _) = self.compile_one_of(case_node)?;
case_sql
let mut case_node = node.clone();
case_node.parent_alias = base_alias.clone();
let arc_aliases = std::sync::Arc::new(table_aliases.clone());
case_node.parent_type_aliases = Some(arc_aliases);
case_node.parent_type = Some(r#type);
let (case_sql, _) = self.compile_one_of(case_node)?;
case_sql
} else {
let select_args = self.compile_select_clause(r#type, &table_aliases, node.clone())?;
if select_args.is_empty() {
"jsonb_build_object()".to_string()
} else {
format!("jsonb_build_object({})", select_args.join(", "))
}
let select_args = self.compile_select_clause(r#type, &table_aliases, node.clone())?;
if select_args.is_empty() {
"jsonb_build_object()".to_string()
} else {
format!("jsonb_build_object({})", select_args.join(", "))
}
};
// 3. Build WHERE clauses
@ -249,14 +282,21 @@ impl<'a> Compiler<'a> {
Ok((combined, "object".to_string()))
}
fn compile_one_of(
&mut self,
node: Node<'a>,
) -> Result<(String, String), String> {
fn compile_one_of(&mut self, node: Node<'a>) -> Result<(String, String), String> {
let mut case_statements = Vec::new();
let options = node.schema.obj.compiled_options.get().ok_or("Missing compiled options for polymorphism")?;
let disc = node.schema.obj.compiled_discriminator.get().ok_or("Missing compiled discriminator for polymorphism")?;
let options = node
.schema
.obj
.compiled_options
.get()
.ok_or("Missing compiled options for polymorphism")?;
let disc = node
.schema
.obj
.compiled_discriminator
.get()
.ok_or("Missing compiled discriminator for polymorphism")?;
let type_col = if let Some(prop) = &node.property_name {
format!("{}_{}", prop, disc)
@ -264,36 +304,73 @@ impl<'a> Compiler<'a> {
disc.to_string()
};
for (disc_val, target_id) in options {
if let Some(target_schema) = self.db.schemas.get(target_id) {
let mut child_node = node.clone();
child_node.schema = Arc::clone(target_schema);
child_node.is_polymorphic_branch = true;
for (disc_val, (idx_opt, target_id_opt)) in options {
if let Some(target_id) = target_id_opt {
if let Some(target_schema) = self.db.schemas.get(target_id) {
let mut child_node = node.clone();
child_node.schema = Arc::clone(target_schema);
child_node.schema_id = Some(target_id.clone());
child_node.is_polymorphic_branch = true;
let val_sql = if disc == "kind" && node.parent_type.is_some() && node.parent_type_aliases.is_some() {
let aliases_arc = node.parent_type_aliases.as_ref().unwrap();
let aliases = aliases_arc.as_ref();
let p_type = node.parent_type.unwrap();
let select_args = self.compile_select_clause(p_type, aliases, child_node.clone())?;
if select_args.is_empty() {
let val_sql =
if disc == "kind" && node.parent_type.is_some() && node.parent_type_aliases.is_some() {
let aliases_arc = node.parent_type_aliases.as_ref().unwrap();
let aliases = aliases_arc.as_ref();
let p_type = node.parent_type.unwrap();
let select_args = self.compile_select_clause(p_type, aliases, child_node.clone())?;
if select_args.is_empty() {
"jsonb_build_object()".to_string()
} else {
} else {
format!("jsonb_build_object({})", select_args.join(", "))
}
} else {
let (sql, _) = self.compile_node(child_node)?;
sql
};
}
} else {
let (sql, _) = self.compile_node(child_node)?;
sql
};
case_statements.push(format!(
"WHEN {}.{} = '{}' THEN ({})",
node.parent_alias, type_col, disc_val, val_sql
));
case_statements.push(format!(
"WHEN {}.{} = '{}' THEN ({})",
node.parent_alias, type_col, disc_val, val_sql
));
}
} else if let Some(idx) = idx_opt {
if let Some(target_schema) = node
.schema
.obj
.one_of
.as_ref()
.and_then(|options| options.get(*idx))
{
let mut child_node = node.clone();
child_node.schema = Arc::clone(target_schema);
child_node.is_polymorphic_branch = true;
let val_sql = if disc == "kind" && node.parent_type.is_some() && node.parent_type_aliases.is_some() {
let aliases_arc = node.parent_type_aliases.as_ref().unwrap();
let aliases = aliases_arc.as_ref();
let p_type = node.parent_type.unwrap();
let select_args = self.compile_select_clause(p_type, aliases, child_node.clone())?;
if select_args.is_empty() {
"jsonb_build_object()".to_string()
} else {
format!("jsonb_build_object({})", select_args.join(", "))
}
} else {
let (sql, _) = self.compile_node(child_node)?;
sql
};
case_statements.push(format!(
"WHEN {}.{} = '{}' THEN ({})",
node.parent_alias, type_col, disc_val, val_sql
));
}
}
}
if case_statements.is_empty() {
return Ok(("NULL".to_string(), "string".to_string()));
}
@ -339,14 +416,14 @@ impl<'a> Compiler<'a> {
let mut select_args = Vec::new();
let grouped_fields = r#type.grouped_fields.as_ref().and_then(|v| v.as_object());
let default_props = std::collections::BTreeMap::new();
let merged_props = node.schema.obj.compiled_properties.get().unwrap_or(&default_props);
let mut sorted_keys: Vec<&String> = merged_props.keys().collect();
sorted_keys.sort();
for prop_key in sorted_keys {
let prop_schema = &merged_props[prop_key];
let merged_props = node
.schema
.obj
.compiled_properties
.get()
.unwrap_or(&default_props);
for (prop_key, prop_schema) in merged_props {
let is_object_or_array = match &prop_schema.obj.type_ {
Some(crate::database::object::SchemaTypeOrArray::Single(s)) => {
s == "object" || s == "array"
@ -410,6 +487,7 @@ impl<'a> Compiler<'a> {
format!("{}/{}", node.ast_path, prop_key)
},
is_polymorphic_branch: false,
schema_id: None,
};
let (val_sql, val_type) = self.compile_node(child_node)?;
@ -449,7 +527,7 @@ impl<'a> Compiler<'a> {
self.compile_filter_conditions(r#type, type_aliases, &node, &base_alias, &mut where_clauses);
self.compile_polymorphic_bounds(r#type, type_aliases, &node, &mut where_clauses);
let start_len = where_clauses.len();
self.compile_relation_conditions(
r#type,
@ -491,8 +569,12 @@ impl<'a> Compiler<'a> {
.unwrap_or(family_target)
.to_string(),
);
} else if let Some(lookup_key) = prop_schema.obj.identifier() {
bound_type_name = Some(lookup_key);
} else if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) =
&prop_schema.obj.type_
{
if !crate::database::object::is_primitive_type(t) {
bound_type_name = Some(t.split('.').next_back().unwrap_or(t).to_string());
}
}
if let Some(type_name) = bound_type_name {

View File

@ -1553,24 +1553,6 @@ fn test_polymorphism_4_1() {
crate::tests::runner::run_test_case(&path, 4, 1).unwrap();
}
#[test]
fn test_polymorphism_5_0() {
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 5, 0).unwrap();
}
#[test]
fn test_polymorphism_5_1() {
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 5, 1).unwrap();
}
#[test]
fn test_polymorphism_5_2() {
let path = format!("{}/fixtures/polymorphism.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 5, 2).unwrap();
}
#[test]
fn test_not_0_0() {
let path = format!("{}/fixtures/not.json", env!("CARGO_MANIFEST_DIR"));
@ -3558,9 +3540,9 @@ fn test_paths_1_0() {
}
#[test]
fn test_paths_1_1() {
fn test_paths_2_0() {
let path = format!("{}/fixtures/paths.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 1, 1).unwrap();
crate::tests::runner::run_test_case(&path, 2, 0).unwrap();
}
#[test]
@ -7740,21 +7722,9 @@ fn test_object_types_2_1() {
}
#[test]
fn test_object_types_3_0() {
fn test_object_types_2_2() {
let path = format!("{}/fixtures/objectTypes.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 3, 0).unwrap();
}
#[test]
fn test_object_types_3_1() {
let path = format!("{}/fixtures/objectTypes.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 3, 1).unwrap();
}
#[test]
fn test_object_types_3_2() {
let path = format!("{}/fixtures/objectTypes.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 3, 2).unwrap();
crate::tests::runner::run_test_case(&path, 2, 2).unwrap();
}
#[test]

View File

@ -44,27 +44,30 @@ fn test_library_api() {
"name": "source_schema",
"variations": ["source_schema"],
"hierarchy": ["source_schema", "entity"],
"schemas": [{
"$id": "source_schema",
"type": "object",
"properties": {
"name": { "type": "string" },
"target": { "type": "target_schema" }
},
"required": ["name"]
}]
"schemas": {
"source_schema": {
"type": "object",
"properties": {
"type": { "type": "string" },
"name": { "type": "string" },
"target": { "type": "target_schema" }
},
"required": ["name"]
}
}
},
{
"name": "target_schema",
"variations": ["target_schema"],
"hierarchy": ["target_schema", "entity"],
"schemas": [{
"$id": "target_schema",
"type": "object",
"properties": {
"value": { "type": "number" }
"schemas": {
"target_schema": {
"type": "object",
"properties": {
"value": { "type": "number" }
}
}
}]
}
}
]
});
@ -86,9 +89,9 @@ fn test_library_api() {
"type": "drop",
"response": {
"source_schema": {
"$id": "source_schema",
"type": "object",
"properties": {
"type": { "type": "string" },
"name": { "type": "string" },
"target": {
"type": "target_schema",
@ -96,7 +99,7 @@ fn test_library_api() {
}
},
"required": ["name"],
"compiledProperties": ["name", "target"],
"compiledProperties": ["name", "target", "type"],
"compiledEdges": {
"target": {
"constraint": "fk_test_target",
@ -104,8 +107,11 @@ fn test_library_api() {
}
}
},
"source_schema/target": {
"type": "target_schema",
"compiledProperties": ["value"]
},
"target_schema": {
"$id": "target_schema",
"type": "object",
"properties": {
"value": { "type": "number" }

View File

@ -93,9 +93,12 @@ impl<'a> ValidationContext<'a> {
if i < len {
if let Some(child_instance) = arr.get(i) {
let mut item_path = self.join_path(&i.to_string());
if let Some(obj) = child_instance.as_object() {
if let Some(id_str) = obj.get("id").and_then(|v| v.as_str()) {
item_path = self.join_path(id_str);
let is_topological = sub_schema.obj.requires_uuid_path(self.db);
if is_topological {
if let Some(obj) = child_instance.as_object() {
if let Some(id_str) = obj.get("id").and_then(|v| v.as_str()) {
item_path = self.join_path(id_str);
}
}
}
let derived = self.derive(
@ -116,12 +119,15 @@ impl<'a> ValidationContext<'a> {
}
if let Some(ref items_schema) = self.schema.items {
let is_topological = items_schema.obj.requires_uuid_path(self.db);
for i in validation_index..len {
if let Some(child_instance) = arr.get(i) {
let mut item_path = self.join_path(&i.to_string());
if let Some(obj) = child_instance.as_object() {
if let Some(id_str) = obj.get("id").and_then(|v| v.as_str()) {
item_path = self.join_path(id_str);
if is_topological {
if let Some(obj) = child_instance.as_object() {
if let Some(id_str) = obj.get("id").and_then(|v| v.as_str()) {
item_path = self.join_path(id_str);
}
}
}
let derived = self.derive(

View File

@ -13,10 +13,17 @@ impl<'a> ValidationContext<'a> {
) -> Result<bool, ValidationError> {
let current = self.instance;
if let Some(obj) = current.as_object() {
let mut schema_identifier = None;
if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &self.schema.type_ {
if !crate::database::object::is_primitive_type(t) {
schema_identifier = Some(t.clone());
}
}
// Entity implicit type validation
if let Some(schema_identifier) = self.schema.identifier() {
if let Some(ref schema_identifier_str) = schema_identifier {
// We decompose identity string routing inherently
let expected_type = schema_identifier.split('.').last().unwrap_or(&schema_identifier);
let expected_type = schema_identifier_str.split('.').last().unwrap_or(schema_identifier_str);
// Check if the identifier represents a registered global database entity boundary mathematically
if let Some(type_def) = self.db.types.get(expected_type) {
@ -46,7 +53,7 @@ impl<'a> ValidationContext<'a> {
}
// If the target mathematically declares a horizontal structural STI variation natively
if schema_identifier.contains('.') {
if schema_identifier_str.contains('.') {
if obj.get("kind").is_none() {
result.errors.push(ValidationError {
code: "MISSING_KIND".to_string(),
@ -69,7 +76,7 @@ impl<'a> ValidationContext<'a> {
}
}
if let Some(kind_val) = obj.get("kind") {
if let Some((kind_str, _)) = schema_identifier.rsplit_once('.') {
if let Some((kind_str, _)) = schema_identifier_str.rsplit_once('.') {
if let Some(actual_kind) = kind_val.as_str() {
if actual_kind == kind_str {
result.evaluated_keys.insert("kind".to_string());

View File

@ -30,77 +30,34 @@ impl<'a> ValidationContext<'a> {
if self.schema.family.is_some() {
if let Some(options) = self.schema.compiled_options.get() {
if let Some(disc) = self.schema.compiled_discriminator.get() {
return self.execute_polymorph(disc, options, result);
}
return self.execute_polymorph(options, result);
} else {
result.errors.push(ValidationError {
code: "UNCOMPILED_FAMILY".to_string(),
message: "Encountered family block that could not be mapped to deterministic options during db schema compilation.".to_string(),
path: self.path.to_string(),
});
return Ok(false);
}
}
Ok(true)
}
pub(crate) fn validate_one_of(
&self,
result: &mut ValidationResult,
) -> Result<bool, ValidationError> {
if let Some(one_of) = &self.schema.one_of {
if self.schema.one_of.is_some() {
if let Some(options) = self.schema.compiled_options.get() {
if let Some(disc) = self.schema.compiled_discriminator.get() {
return self.execute_polymorph(disc, options, result);
}
}
// Native Draft2020-12 oneOf Evaluation Fallback
let mut valid_count = 0;
let mut final_successful_result = None;
let mut failed_candidates = Vec::new();
for child_schema in one_of {
let derived = self.derive_for_schema(child_schema, false);
if let Ok(sub_res) = derived.validate_scoped() {
if sub_res.is_valid() {
valid_count += 1;
final_successful_result = Some(sub_res.clone());
} else {
failed_candidates.push(sub_res);
}
}
}
if valid_count == 1 {
if let Some(successful_res) = final_successful_result {
result.merge(successful_res);
}
return Ok(true);
} else if valid_count == 0 {
result.errors.push(ValidationError {
code: "NO_ONEOF_MATCH".to_string(),
message: "Payload matches none of the required candidate sub-schemas natively".to_string(),
path: self.path.to_string(),
});
if let Some(first) = failed_candidates.first() {
let mut shared_errors = first.errors.clone();
for sub_res in failed_candidates.iter().skip(1) {
shared_errors.retain(|e1| {
sub_res.errors.iter().any(|e2| e1.code == e2.code && e1.path == e2.path)
});
}
for e in shared_errors {
if !result.errors.iter().any(|existing| existing.code == e.code && existing.path == e.path) {
result.errors.push(e);
}
}
}
return Ok(false);
return self.execute_polymorph(options, result);
} else {
result.errors.push(ValidationError {
code: "AMBIGUOUS_POLYMORPHIC_MATCH".to_string(),
message: "Matches multiple polymorphic candidates inextricably natively".to_string(),
result.errors.push(ValidationError {
code: "UNCOMPILED_ONEOF".to_string(),
message: "Encountered oneOf block that could not be mapped to deterministic compiled options natively.".to_string(),
path: self.path.to_string(),
});
return Ok(false);
return Ok(false);
}
}
Ok(true)
@ -108,46 +65,115 @@ impl<'a> ValidationContext<'a> {
pub(crate) fn execute_polymorph(
&self,
disc: &str,
options: &std::collections::BTreeMap<String, String>,
options: &std::collections::BTreeMap<String, (Option<usize>, Option<String>)>,
result: &mut ValidationResult,
) -> Result<bool, ValidationError> {
// 1. O(1) Fast-Path Router & Extractor
let instance_val = self.instance.as_object().and_then(|o| o.get(disc)).and_then(|t| t.as_str());
let instance_val = if let Some(disc) = self.schema.compiled_discriminator.get() {
let val = self
.instance
.as_object()
.and_then(|o| o.get(disc))
.and_then(|t| t.as_str());
if val.is_some() {
result.evaluated_keys.insert(disc.to_string());
}
val.map(|s| s.to_string())
} else {
match self.instance {
serde_json::Value::Null => Some("null".to_string()),
serde_json::Value::Bool(_) => Some("boolean".to_string()),
serde_json::Value::Number(n) => {
if n.is_i64() || n.is_u64() {
Some("integer".to_string())
} else {
Some("number".to_string())
}
}
serde_json::Value::String(_) => Some("string".to_string()),
serde_json::Value::Array(_) => Some("array".to_string()),
serde_json::Value::Object(_) => Some("object".to_string()),
}
};
if let Some(val) = instance_val {
result.evaluated_keys.insert(disc.to_string());
if let Some(target_id) = options.get(val) {
if let Some((idx_opt, target_id_opt)) = options.get(&val) {
if let Some(target_id) = target_id_opt {
if let Some(target_schema) = self.db.schemas.get(target_id) {
let derived = self.derive_for_schema(target_schema.as_ref(), false);
let sub_res = derived.validate()?;
let is_valid = sub_res.is_valid();
result.merge(sub_res);
return Ok(is_valid);
let derived = self.derive_for_schema(target_schema.as_ref(), false);
let sub_res = derived.validate()?;
let is_valid = sub_res.is_valid();
result.merge(sub_res);
return Ok(is_valid);
} else {
result.errors.push(ValidationError {
code: "MISSING_COMPILED_SCHEMA".to_string(),
message: format!("Polymorphic router target '{}' does not exist in the database schemas map", target_id),
path: self.path.to_string(),
});
return Ok(false);
result.errors.push(ValidationError {
code: "MISSING_COMPILED_SCHEMA".to_string(),
message: format!(
"Polymorphic router target '{}' does not exist in the database schemas map",
target_id
),
path: self.path.to_string(),
});
return Ok(false);
}
} else {
result.errors.push(ValidationError {
code: if self.schema.family.is_some() { "NO_FAMILY_MATCH".to_string() } else { "NO_ONEOF_MATCH".to_string() },
message: format!("Payload provided discriminator {}='{}' which matches none of the required candidate sub-schemas", disc, val),
path: self.path.to_string(),
});
} else if let Some(idx) = idx_opt {
if let Some(target_schema) = self
.schema
.one_of
.as_ref()
.and_then(|options| options.get(*idx))
{
let derived = self.derive_for_schema(target_schema.as_ref(), false);
let sub_res = derived.validate()?;
let is_valid = sub_res.is_valid();
result.merge(sub_res);
return Ok(is_valid);
} else {
result.errors.push(ValidationError {
code: "MISSING_COMPILED_SCHEMA".to_string(),
message: format!(
"Polymorphic index target '{}' does not exist in the local oneOf array",
idx
),
path: self.path.to_string(),
});
return Ok(false);
}
} else {
return Ok(false);
}
} else {
}
} else {
let disc_msg = if let Some(d) = self.schema.compiled_discriminator.get() {
format!("discriminator {}='{}'", d, val)
} else {
format!("structural JSON base primitive '{}'", val)
};
result.errors.push(ValidationError {
code: "MISSING_TYPE".to_string(),
message: format!("Missing '{}' discriminator. Unable to resolve polymorphic boundaries", disc),
code: if self.schema.family.is_some() {
"NO_FAMILY_MATCH".to_string()
} else {
"NO_ONEOF_MATCH".to_string()
},
message: format!(
"Payload matched no candidate boundaries based on its {}",
disc_msg
),
path: self.path.to_string(),
});
return Ok(false);
}
} else {
if let Some(d) = self.schema.compiled_discriminator.get() {
result.errors.push(ValidationError {
code: "MISSING_TYPE".to_string(),
message: format!(
"Missing explicit '{}' discriminator. Unable to resolve polymorphic boundaries",
d
),
path: self.path.to_string(),
});
}
return Ok(false);
}
}
@ -179,14 +205,16 @@ impl<'a> ValidationContext<'a> {
}
}
Some(crate::database::object::SchemaTypeOrArray::Multiple(arr)) => {
if arr.contains(&payload_primitive.to_string()) || (payload_primitive == "integer" && arr.contains(&"number".to_string())) {
// It natively matched a primitive in the array options, skip forcing custom proxy fallback
if arr.contains(&payload_primitive.to_string())
|| (payload_primitive == "integer" && arr.contains(&"number".to_string()))
{
// It natively matched a primitive in the array options, skip forcing custom proxy fallback
} else {
for t in arr {
if !crate::database::object::is_primitive_type(t) {
custom_types.push(t.clone());
}
}
for t in arr {
if !crate::database::object::is_primitive_type(t) {
custom_types.push(t.clone());
}
}
}
}
None => {}