use crate::validator::context::ValidationContext; use crate::validator::error::ValidationError; use crate::validator::result::ValidationResult; impl<'a> ValidationContext<'a> { pub(crate) fn validate_family( &self, result: &mut ValidationResult, ) -> Result { if self.schema.family.is_some() { let conflicts = self.schema.type_.is_some() || self.schema.properties.is_some() || self.schema.required.is_some() || self.schema.additional_properties.is_some() || self.schema.items.is_some() || self.schema.r#ref.is_some() || self.schema.one_of.is_some() || self.schema.all_of.is_some() || self.schema.enum_.is_some() || self.schema.const_.is_some(); if conflicts { result.errors.push(ValidationError { code: "INVALID_SCHEMA".to_string(), message: "$family must be used exclusively without other constraints".to_string(), path: self.path.to_string(), }); // Short-circuit: the schema formulation is broken return Ok(false); } } if let Some(family_target) = &self.schema.family { // The descendants map is keyed by the schema's own $id, not the target string. if let Some(schema_id) = &self.schema.id && let Some(descendants) = self.db.descendants.get(schema_id) { // Validate against all descendants simulating strict oneOf logic let mut passed_candidates: Vec<(String, usize, ValidationResult)> = Vec::new(); // The target itself is also an implicitly valid candidate let mut all_targets = vec![family_target.clone()]; all_targets.extend(descendants.clone()); for child_id in &all_targets { if let Some(child_schema) = self.db.schemas.get(child_id) { let derived = self.derive( child_schema, self.instance, &self.path, self.overrides.clone(), self.extensible, self.reporter, // Inherit parent reporter flag, do not bypass strictness! ); // Explicitly run validate_scoped to accurately test candidates with strictness checks enabled let res = derived.validate_scoped()?; if res.is_valid() { let depth = self.db.depths.get(child_id).copied().unwrap_or(0); passed_candidates.push((child_id.clone(), depth, res)); } } } if passed_candidates.len() == 1 { result.merge(passed_candidates.pop().unwrap().2); } else if passed_candidates.is_empty() { result.errors.push(ValidationError { code: "NO_FAMILY_MATCH".to_string(), message: format!( "Payload did not match any descendants of family '{}'", family_target ), path: self.path.to_string(), }); } else { // Apply depth heuristic tie-breaker let mut best_depth: Option = None; let mut ambiguous = false; let mut best_res = None; for (_, depth, res) in passed_candidates.into_iter() { if let Some(current_best) = best_depth { if depth > current_best { best_depth = Some(depth); best_res = Some(res); ambiguous = false; // Broke the tie } else if depth == current_best { ambiguous = true; // Tie at the highest level } } else { best_depth = Some(depth); best_res = Some(res); } } if !ambiguous { if let Some(res) = best_res { result.merge(res); return Ok(true); } } result.errors.push(ValidationError { code: "AMBIGUOUS_FAMILY_MATCH".to_string(), message: format!( "Payload matched multiple descendants of family '{}' without a clear depth winner", family_target ), path: self.path.to_string(), }); } } } Ok(true) } pub(crate) fn validate_refs( &self, result: &mut ValidationResult, ) -> Result { // 1. Core $ref logic relies on the fast O(1) map to allow cycles and proper nesting if let Some(ref_str) = &self.schema.r#ref { if let Some(global_schema) = self.db.schemas.get(ref_str) { let mut new_overrides = self.overrides.clone(); if let Some(props) = &self.schema.properties { new_overrides.extend(props.keys().map(|k| k.to_string())); } let mut shadow = self.derive( global_schema, self.instance, &self.path, new_overrides, self.extensible, true, ); shadow.root = global_schema; result.merge(shadow.validate()?); } else { result.errors.push(ValidationError { code: "REF_RESOLUTION_FAILED".to_string(), message: format!( "Reference pointer to '{}' was not found in schema registry", ref_str ), path: self.path.to_string(), }); } } Ok(true) } }