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 Bound Implicit Type Validation if let Some(lookup_key) = self.schema.id.as_ref().or(self.schema.r#ref.as_ref()) { let base_type_name = lookup_key.split('.').next_back().unwrap_or("").to_string(); if let Some(type_def) = self.db.types.get(&base_type_name) && let Some(type_val) = obj.get("type") && let Some(type_str) = type_val.as_str() { if type_def.variations.contains(type_str) { // Ensure it passes strict mode result.evaluated_keys.insert("type".to_string()); } else { result.errors.push(ValidationError { code: "CONST_VIOLATED".to_string(), // Aligning with original const override errors message: format!( "Type '{}' is not a valid descendant for this entity bound schema", type_str ), path: format!("{}/type", self.path), }); } } } 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) { result.errors.push(ValidationError { code: "REQUIRED_FIELD_MISSING".to_string(), message: format!("Missing {}", field), path: format!("{}/{}", self.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 = format!("{}/{}", self.path, key); let is_ref = sub_schema.r#ref.is_some(); 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 mut item_res = derived.validate()?; // Entity Bound Implicit Type Interception if key == "type" && let Some(lookup_key) = sub_schema.id.as_ref().or(sub_schema.r#ref.as_ref()) { let base_type_name = lookup_key.split('.').next_back().unwrap_or("").to_string(); if let Some(type_def) = self.db.types.get(&base_type_name) && let Some(instance_type) = child_instance.as_str() && type_def.variations.contains(instance_type) { item_res .errors .retain(|e| e.code != "CONST_VIOLATED" && e.code != "ENUM_VIOLATED"); } } result.merge(item_res); result.evaluated_keys.insert(key.to_string()); } } } if let Some(ref compiled_pp) = self.schema.compiled_pattern_properties { for (compiled_re, sub_schema) in compiled_pp { for (key, child_instance) in obj { if compiled_re.0.is_match(key) { let new_path = format!("{}/{}", self.path, key); let is_ref = sub_schema.r#ref.is_some(); 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(ref compiled_pp) = self.schema.compiled_pattern_properties { for (compiled_re, _) in compiled_pp { if compiled_re.0.is_match(key) { locally_matched = true; break; } } } if !locally_matched { let new_path = format!("{}/{}", self.path, key); let is_ref = additional_schema.r#ref.is_some(); 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 = format!("{}/propertyNames/{}", self.path, 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) } }