use std::collections::HashSet; use serde_json::Value; use crate::validator::context::ValidationContext; use crate::validator::error::ValidationError; use crate::validator::result::ValidationResult; impl<'a> ValidationContext<'a> { pub(crate) fn validate_object( &self, result: &mut ValidationResult, ) -> Result { let current = self.instance; if let Some(obj) = current.as_object() { // Entity implicit type validation if let Some(schema_identifier) = self.schema.identifier() { // We decompose identity string routing inherently let expected_type = schema_identifier.split('.').last().unwrap_or(&schema_identifier); // Check if the identifier represents a registered global database entity boundary mathematically if let Some(type_def) = self.db.types.get(expected_type) { if let Some(type_val) = obj.get("type") { if let Some(type_str) = type_val.as_str() { if type_def.variations.contains(type_str) { // The instance is validly declaring a known structural descent result.evaluated_keys.insert("type".to_string()); } else { result.errors.push(ValidationError { code: "CONST_VIOLATED".to_string(), // Aligning with original const override errors natively message: format!( "Type '{}' is not a valid descendant for this entity bound schema", type_str ), path: self.join_path("type"), }); } } } else { // Because it's a global entity target, the payload must structurally provide a discriminator natively result.errors.push(ValidationError { code: "MISSING_TYPE".to_string(), message: format!("Schema mechanically requires type discrimination '{}'", expected_type), path: self.path.clone(), // Empty boundary }); } // If the target mathematically declares a horizontal structural STI variation natively if schema_identifier.contains('.') { if obj.get("kind").is_none() { result.errors.push(ValidationError { code: "MISSING_KIND".to_string(), message: "Schema mechanically requires horizontal kind discrimination".to_string(), path: self.path.clone(), }); } else { result.evaluated_keys.insert("kind".to_string()); } } } else { // If it isn't registered globally, it might be a nested Ad-Hoc candidate running via O(1) union routers. // Because they lack manual type property descriptors, we natively shield "type" and "kind" keys from // triggering additionalProperty violations natively IF they precisely correspond to their fast-path boundaries if let Some(type_val) = obj.get("type") { if let Some(type_str) = type_val.as_str() { if type_str == expected_type { result.evaluated_keys.insert("type".to_string()); } } } if let Some(kind_val) = obj.get("kind") { if let Some((kind_str, _)) = schema_identifier.rsplit_once('.') { if let Some(actual_kind) = kind_val.as_str() { if actual_kind == kind_str { result.evaluated_keys.insert("kind".to_string()); } } } } } } if let Some(min) = self.schema.min_properties && (obj.len() as f64) < min { result.errors.push(ValidationError { code: "MIN_PROPERTIES".to_string(), message: "Too few properties".to_string(), path: self.path.to_string(), }); } if let Some(max) = self.schema.max_properties && (obj.len() as f64) > max { result.errors.push(ValidationError { code: "MAX_PROPERTIES".to_string(), message: "Too many properties".to_string(), path: self.path.to_string(), }); } if let Some(ref req) = self.schema.required { for field in req { if !obj.contains_key(field) { if field == "type" { result.errors.push(ValidationError { code: "MISSING_TYPE".to_string(), message: "Missing type discriminator".to_string(), path: self.join_path(field), }); } else { result.errors.push(ValidationError { code: "REQUIRED_FIELD_MISSING".to_string(), message: format!("Missing {}", field), path: self.join_path(field), }); } } } } if let Some(ref deps) = self.schema.dependencies { for (prop, dep) in deps { if obj.contains_key(prop) { match dep { crate::database::schema::Dependency::Props(required_props) => { for req_prop in required_props { if !obj.contains_key(req_prop) { result.errors.push(ValidationError { code: "DEPENDENCY_MISSING".to_string(), message: format!("Property '{}' requires property '{}'", prop, req_prop), path: self.path.to_string(), }); } } } crate::database::schema::Dependency::Schema(dep_schema) => { let derived = self.derive_for_schema(dep_schema, false); let dep_res = derived.validate()?; result.evaluated_keys.extend(dep_res.evaluated_keys.clone()); result.merge(dep_res); } } } } } if let Some(props) = &self.schema.properties { for (key, sub_schema) in props { if self.overrides.contains(key) { continue; // Skip validation if exactly this property was overridden by a child } if let Some(child_instance) = obj.get(key) { let new_path = self.join_path(key); let is_ref = match &sub_schema.type_ { Some(crate::database::schema::SchemaTypeOrArray::Single(t)) => !crate::database::schema::is_primitive_type(t), _ => false, }; let next_extensible = if is_ref { false } else { self.extensible }; let derived = self.derive( sub_schema, child_instance, &new_path, HashSet::new(), next_extensible, false, ); let item_res = derived.validate()?; result.merge(item_res); result.evaluated_keys.insert(key.to_string()); } } } if let Some(compiled_pp) = self.schema.compiled_pattern_properties.get() { for (compiled_re, sub_schema) in compiled_pp { for (key, child_instance) in obj { if compiled_re.0.is_match(key) { let new_path = self.join_path(key); let is_ref = match &sub_schema.type_ { Some(crate::database::schema::SchemaTypeOrArray::Single(t)) => !crate::database::schema::is_primitive_type(t), _ => false, }; let next_extensible = if is_ref { false } else { self.extensible }; let derived = self.derive( sub_schema, child_instance, &new_path, HashSet::new(), next_extensible, false, ); let item_res = derived.validate()?; result.merge(item_res); result.evaluated_keys.insert(key.to_string()); } } } } if let Some(ref additional_schema) = self.schema.additional_properties { for (key, child_instance) in obj { let mut locally_matched = false; if let Some(props) = &self.schema.properties && props.contains_key(&key.to_string()) { locally_matched = true; } if !locally_matched && let Some(compiled_pp) = self.schema.compiled_pattern_properties.get() { for (compiled_re, _) in compiled_pp { if compiled_re.0.is_match(key) { locally_matched = true; break; } } } if !locally_matched { let new_path = self.join_path(key); let is_ref = match &additional_schema.type_ { Some(crate::database::schema::SchemaTypeOrArray::Single(t)) => !crate::database::schema::is_primitive_type(t), _ => false, }; let next_extensible = if is_ref { false } else { self.extensible }; let derived = self.derive( additional_schema, child_instance, &new_path, HashSet::new(), next_extensible, false, ); let item_res = derived.validate()?; result.merge(item_res); result.evaluated_keys.insert(key.to_string()); } } } if let Some(ref property_names) = self.schema.property_names { for key in obj.keys() { let _new_path = self.join_path(&format!("propertyNames/{}", key)); let val_str = Value::String(key.to_string()); let ctx = ValidationContext::new( self.db, self.root, property_names, &val_str, HashSet::new(), self.extensible, self.reporter, ); result.merge(ctx.validate()?); } } } Ok(true) } }