use crate::database::object::*; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::sync::Arc; #[derive(Debug, Clone, Serialize, Default)] pub struct Schema { #[serde(flatten)] pub obj: SchemaObject, #[serde(skip)] pub always_fail: bool, } impl std::ops::Deref for Schema { type Target = SchemaObject; fn deref(&self) -> &Self::Target { &self.obj } } impl std::ops::DerefMut for Schema { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.obj } } impl Schema { pub fn compile( &self, db: &crate::database::Database, root_id: &str, path: String, errors: &mut Vec, ) { if self.obj.compiled_properties.get().is_some() { return; } if let Some(format_str) = &self.obj.format { if let Some(fmt) = crate::database::formats::FORMATS.get(format_str.as_str()) { let _ = self .obj .compiled_format .set(crate::database::object::CompiledFormat::Func(fmt.func)); } } if let Some(pattern_str) = &self.obj.pattern { if let Ok(re) = regex::Regex::new(pattern_str) { let _ = self .obj .compiled_pattern .set(crate::database::object::CompiledRegex(re)); } } if let Some(pattern_props) = &self.obj.pattern_properties { let mut compiled = Vec::new(); for (k, v) in pattern_props { if let Ok(re) = regex::Regex::new(k) { compiled.push((crate::database::object::CompiledRegex(re), v.clone())); } } if !compiled.is_empty() { let _ = self.obj.compiled_pattern_properties.set(compiled); } } let mut props = std::collections::BTreeMap::new(); // 1. Resolve INHERITANCE dependencies first 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, t, t.clone(), errors); if let Some(p_props) = parent.obj.compiled_properties.get() { props.extend(p_props.clone()); } } } } if let Some(crate::database::object::SchemaTypeOrArray::Multiple(types)) = &self.obj.type_ { let mut custom_type_count = 0; for t in types { if !crate::database::object::is_primitive_type(t) { custom_type_count += 1; } } if custom_type_count > 1 { 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.", types ), details: crate::drop::ErrorDetails { path: Some(path.clone()), schema: Some(root_id.to_string()), ..Default::default() } }); } 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, t, t.clone(), errors); } } } } // 2. Add local properties if let Some(local_props) = &self.obj.properties { for (k, v) in local_props { props.insert(k.clone(), v.clone()); } } // 3. Set the OnceLock! let _ = self.obj.compiled_properties.set(props.clone()); let mut names: Vec = props.keys().cloned().collect(); names.sort(); let _ = self.obj.compiled_property_names.set(names); // 4. Compute Edges natively let schema_edges = self.compile_edges(db, root_id, &path, &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 (k, child) in local_props { child.compile(db, root_id, format!("{}/{}", path, k), errors); } } if let Some(items) = &self.obj.items { items.compile(db, root_id, format!("{}/items", path), errors); } if let Some(pattern_props) = &self.obj.pattern_properties { for (k, child) in pattern_props { child.compile(db, root_id, format!("{}/{}", path, k), errors); } } if let Some(additional_props) = &self.obj.additional_properties { additional_props.compile( db, root_id, format!("{}/additionalProperties", path), errors, ); } if let Some(one_of) = &self.obj.one_of { for (i, child) in one_of.iter().enumerate() { child.compile( db, root_id, format!("{}/oneOf/{}", path, i), errors, ); } } if let Some(arr) = &self.obj.prefix_items { for (i, child) in arr.iter().enumerate() { child.compile( db, root_id, format!("{}/prefixItems/{}", path, i), errors, ); } } if let Some(child) = &self.obj.not { child.compile(db, root_id, format!("{}/not", path), errors); } if let Some(child) = &self.obj.contains { child.compile(db, root_id, format!("{}/contains", path), errors); } if let Some(cases) = &self.obj.cases { for (i, c) in cases.iter().enumerate() { if let Some(child) = &c.when { child.compile( db, root_id, format!("{}/cases/{}/when", path, i), errors, ); } if let Some(child) = &c.then { child.compile( db, root_id, format!("{}/cases/{}/then", path, i), errors, ); } if let Some(child) = &c.else_ { child.compile( db, root_id, format!("{}/cases/{}/else", path, i), errors, ); } } } self.compile_polymorphism(db, root_id, &path, errors); } /// Dynamically infers and compiles all structural database relationships between this Schema /// and its nested children. This functions recursively traverses the JSON Schema abstract syntax /// tree, identifies physical PostgreSQL table boundaries, and locks the resulting relation /// constraint paths directly onto the `compiled_edges` map in O(1) memory. pub fn compile_edges( &self, db: &crate::database::Database, root_id: &str, path: &str, props: &std::collections::BTreeMap>, errors: &mut Vec, ) -> std::collections::BTreeMap { let mut schema_edges = std::collections::BTreeMap::new(); // 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 path == root_id { // 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 let Some(p_type) = parent_type_name { // Proceed only if the resolved table physically exists within the Postgres Type hierarchy if let Some(type_def) = db.types.get(&p_type) { // Iterate over all discovered schema boundaries mapped inside the object for (prop_name, prop_schema) in props { let mut child_type_name = None; let mut target_schema = prop_schema.clone(); let mut is_array = false; // Structurally unpack the inner target entity if the object maps to an array list if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &prop_schema.obj.type_ { if t == "array" { is_array = true; if let Some(items) = &prop_schema.obj.items { target_schema = items.clone(); } } } // 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(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(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()); } } } } if let Some(c_type) = child_type_name { // Skip edge compilation for JSONB columns — they store data inline, not relationally. // The physical column type from field_types is the single source of truth. if let Some(ft) = type_def.field_types.as_ref() .and_then(|v| v.get(prop_name.as_str())) .and_then(|v| v.as_str()) { if ft == "jsonb" { continue; } } 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, root_id, format!("{}/{}", path, prop_name), errors, ); if let Some(compiled_target_props) = target_schema.obj.compiled_properties.get() { let keys_for_ambiguity: Vec = compiled_target_props.keys().cloned().collect(); // Interrogate the Database catalog graph to discover the exact Foreign Key Constraint connecting the components if let Some((relation, is_forward)) = db.resolve_relation( &p_type, &c_type, prop_name, Some(&keys_for_ambiguity), is_array, Some(root_id), &format!("{}/{}", path, prop_name), errors, ) { schema_edges.insert( prop_name.clone(), crate::database::edge::Edge { constraint: relation.constraint.clone(), forward: is_forward, }, ); } } } } } } } schema_edges } pub fn compile_polymorphism( &self, db: &crate::database::Database, root_id: &str, path: &str, errors: &mut Vec, ) { let mut options = std::collections::BTreeMap::new(); let mut strategy = String::new(); if let Some(family) = &self.obj.family { let family_base = family.split('.').next_back().unwrap_or(family).to_string(); let family_prefix = family .strip_suffix(&family_base) .unwrap_or("") .trim_end_matches('.'); if let Some(type_def) = db.types.get(&family_base) { if type_def.variations.len() > 1 && type_def.variations.iter().any(|v| v != &family_base) { // Scenario A / B: Table Variations strategy = "type".to_string(); for var in &type_def.variations { let target_id = if family_prefix.is_empty() { var.to_string() } else { format!("{}.{}", family_prefix, var) }; if db.schemas.contains_key(&target_id) { options.insert(var.to_string(), (None, Some(target_id))); } } } else { // Scenario C: Single Table Inheritance (Horizontal) strategy = "kind".to_string(); let suffix = format!(".{}", family_base); 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()))); } } } } } } 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 { 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 !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); } } } 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 { 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() { 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: Some(path.to_string()), schema: Some(root_id.to_string()), ..Default::default() } }); return; } 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) { child_id = t.clone(); } } 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: Some(path.to_string()), schema: Some(root_id.to_string()), ..Default::default() } }); continue; } options.insert(val, (Some(i), None)); } } } } else { return; } if !options.is_empty() { 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, root_id: &str, path: &str, errors: &mut Vec, ) { #[cfg(not(test))] for c in id.chars() { if !c.is_ascii_lowercase() && !c.is_ascii_digit() && c != '_' && c != '.' { errors.push(crate::drop::Error { code: "INVALID_IDENTIFIER".to_string(), message: format!( "Invalid character '{}' in JSON Schema '{}' property: '{}'. Identifiers must exclusively contain [a-z0-9_.]", c, field_name, id ), details: crate::drop::ErrorDetails { path: Some(path.to_string()), schema: Some(root_id.to_string()), ..Default::default() }, }); return; } } } pub fn collect_schemas( schema_arc: &Arc, root_id: &str, path: String, to_insert: &mut Vec<(String, Arc)>, errors: &mut Vec, ) { if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &schema_arc.obj.type_ { if t == "array" { if let Some(items) = &schema_arc.obj.items { if let Some(crate::database::object::SchemaTypeOrArray::Single(it)) = &items.obj.type_ { if !crate::database::object::is_primitive_type(it) { if items.obj.properties.is_some() || items.obj.cases.is_some() { to_insert.push((path.clone(), Arc::clone(schema_arc))); } } } } } else if !crate::database::object::is_primitive_type(t) { Self::validate_identifier(t, "type", root_id, &path, errors); // Is this an explicit inline ad-hoc composition? if schema_arc.obj.properties.is_some() || schema_arc.obj.cases.is_some() { to_insert.push((path.clone(), Arc::clone(schema_arc))); } } } if let Some(family) = &schema_arc.obj.family { Self::validate_identifier(family, "$family", root_id, &path, errors); } Self::collect_child_schemas(schema_arc, root_id, path, to_insert, errors); } pub fn collect_child_schemas( schema_arc: &Arc, root_id: &str, path: String, to_insert: &mut Vec<(String, Arc)>, errors: &mut Vec, ) { 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) = &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: &Vec>, 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) = &schema_arc.obj.prefix_items { map_arr(arr, "prefixItems"); } if let Some(arr) = &schema_arc.obj.one_of { map_arr(arr, "oneOf"); } let mut map_opt = |opt: &Option>, pass_path: bool, sub: &str| { if let Some(v) = opt { if pass_path { // Arrays explicitly push their wrapper natively. // 'items' becomes a transparent conduit, bypassing self-promotion and skipping the '/items' suffix. Self::collect_child_schemas(v, root_id, path.clone(), to_insert, errors); } else { Self::collect_child_schemas(v, root_id, format!("{}/{}", path, sub), to_insert, errors); } } }; 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"); 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) = &c.then { Self::collect_schemas( then, root_id, format!("{}/cases/{}/then", path, i), to_insert, errors, ); } if let Some(else_) = &c.else_ { Self::collect_schemas( else_, root_id, format!("{}/cases/{}/else", path, i), to_insert, errors, ); } } } } } impl<'de> Deserialize<'de> for Schema { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let v: Value = Deserialize::deserialize(deserializer)?; if let Some(b) = v.as_bool() { let mut obj = SchemaObject::default(); if b { obj.extensible = Some(true); } return Ok(Schema { obj, always_fail: !b, }); } let mut obj: SchemaObject = serde_json::from_value(v.clone()).map_err(serde::de::Error::custom)?; // If a schema is effectively empty (except for potentially carrying an ID), // it functions as a boolean `true` schema in Draft2020 which means it should not // restrict additional properties natively let is_empty = obj.type_.is_none() && obj.properties.is_none() && obj.pattern_properties.is_none() && obj.additional_properties.is_none() && obj.required.is_none() && obj.dependencies.is_none() && obj.items.is_none() && obj.prefix_items.is_none() && obj.contains.is_none() && obj.format.is_none() && obj.enum_.is_none() && obj.const_.is_none() && obj.cases.is_none() && obj.one_of.is_none() && obj.not.is_none() && obj.family.is_none(); if is_empty && obj.extensible.is_none() { obj.extensible = Some(true); } Ok(Schema { obj, always_fail: false, }) } }