use regex::Regex; use serde_json::Value; use std::collections::HashSet; use crate::context::ValidationContext; use crate::error::ValidationError; use crate::instance::ValidationInstance; use crate::result::ValidationResult; use crate::validator::{ResolvedRef, Validator}; impl<'a, I: ValidationInstance<'a>> ValidationContext<'a, I> { pub(crate) fn validate_scoped(&self) -> Result { let mut result = ValidationResult::new(); self.validate_depth()?; if self.validate_always_fail(&mut result) { return Ok(result); } if self.validate_family(&mut result) { return Ok(result); } if let Some(ref_res) = self.validate_refs()? { result.merge(ref_res); } self.validate_core(&mut result); self.validate_numeric(&mut result); self.validate_string(&mut result); self.validate_format(&mut result); self.validate_object(&mut result)?; self.validate_array(&mut result)?; self.validate_combinators(&mut result)?; self.validate_conditionals(&mut result)?; self.validate_extensible(&mut result); self.validate_strictness(&mut result); Ok(result) } fn validate_extensible(&self, result: &mut ValidationResult) { if self.extensible { let current = self.instance.as_value(); if let Some(obj) = current.as_object() { result.evaluated_keys.extend(obj.keys().cloned()); } else if let Some(arr) = current.as_array() { result.evaluated_indices.extend(0..arr.len()); } } } fn validate_depth(&self) -> Result<(), ValidationError> { if self.depth > 100 { Err(ValidationError { code: "RECURSION_LIMIT_EXCEEDED".to_string(), message: "Recursion limit exceeded".to_string(), path: self.path.to_string(), }) } else { Ok(()) } } fn validate_always_fail(&self, result: &mut ValidationResult) -> bool { if self.schema.always_fail { result.errors.push(ValidationError { code: "FALSE_SCHEMA".to_string(), message: "Schema is false".to_string(), path: self.path.to_string(), }); true } else { false } } fn validate_family(&self, result: &mut ValidationResult) -> bool { 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.ref_string.is_some() || self.schema.one_of.is_some() || self.schema.any_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(), }); return true; } } false } pub(crate) fn validate_refs(&self) -> Result, ValidationError> { let mut res = ValidationResult::new(); let mut handled = false; let effective_scope = &self.scope; let current_base_resolved = effective_scope.last().map(|s| s.as_str()).unwrap_or(""); // $ref if let Some(ref ref_string) = self.schema.ref_string { handled = true; if ref_string == "#" { let mut new_overrides = self.overrides.clone(); if let Some(props) = &self.schema.properties { new_overrides.extend(props.keys().cloned()); } let derived = self.derive( self.root, self.instance, &self.path, effective_scope.clone(), new_overrides, self.extensible, self.reporter, ); res.merge(derived.validate()?); } else { if let Some((resolved, matched_key)) = self .validator .resolve_ref(self.root, ref_string, current_base_resolved) { let (target_root, target_schema) = match &resolved { ResolvedRef::Local(s) => (self.root, *s), ResolvedRef::Global(root, s) => (*root, *s), }; let resource_base = if let Some((base, _)) = matched_key.split_once('#') { base } else { &matched_key }; let scope_to_pass = if target_schema.obj.id.is_none() { if !resource_base.is_empty() && resource_base != current_base_resolved { let mut new_scope = effective_scope.clone(); new_scope.push(resource_base.to_string()); new_scope } else { effective_scope.clone() } } else { effective_scope.clone() }; let mut new_overrides = self.overrides.clone(); if let Some(props) = &self.schema.properties { new_overrides.extend(props.keys().cloned()); } let target_ctx = ValidationContext::new( self.validator, target_root, target_schema, self.instance, scope_to_pass, new_overrides, false, self.reporter, ); let mut manual_ctx = target_ctx; manual_ctx.path = self.path.clone(); manual_ctx.depth = self.depth + 1; let target_res = manual_ctx.validate()?; res.merge(target_res); handled = true; } else { res.errors.push(ValidationError { code: "REF_RESOLUTION_FAILED".to_string(), message: format!("Could not resolve reference '{}'", ref_string), path: self.path.to_string(), }); } } } // $dynamicRef if let Some(ref d_ref) = self.schema.obj.dynamic_ref { handled = true; let anchor = if let Some(idx) = d_ref.rfind('#') { &d_ref[idx + 1..] } else { d_ref.as_str() }; let mut resolved_target: Option<(ResolvedRef, String)> = None; let local_resolution = self .validator .resolve_ref(self.root, d_ref, current_base_resolved); let is_bookended = if let Some((ResolvedRef::Local(s), _)) = &local_resolution { s.obj.dynamic_anchor.as_deref() == Some(anchor) } else { false }; if is_bookended { for base in effective_scope.iter() { let resource_base = if let Some((r, _)) = base.split_once('#') { r } else { base }; let key = format!("{}#{}", resource_base, anchor); if let Some(indexrs) = &self.root.obj.compiled_registry { if let Some(s) = indexrs.schemas.get(&key) { if s.obj.dynamic_anchor.as_deref() == Some(anchor) { resolved_target = Some((ResolvedRef::Local(s.as_ref()), key.clone())); break; } } } if resolved_target.is_none() { if let Some(registry_arc) = &self.root.obj.compiled_registry { if let Some(compiled) = registry_arc.schemas.get(resource_base) { if let Some(indexrs) = &compiled.obj.compiled_registry { if let Some(s) = indexrs.schemas.get(&key) { if s.obj.dynamic_anchor.as_deref() == Some(anchor) { resolved_target = Some(( ResolvedRef::Global(compiled.as_ref(), s.as_ref()), key.clone(), )); break; } } } } } else { if let Some(compiled) = self.validator.registry.schemas.get(resource_base) { if let Some(indexrs) = &compiled.obj.compiled_registry { if let Some(s) = indexrs.schemas.get(&key) { if s.obj.dynamic_anchor.as_deref() == Some(anchor) { resolved_target = Some(( ResolvedRef::Global(compiled.as_ref(), s.as_ref()), key.clone(), )); break; } } } } } } if resolved_target.is_some() { break; } } } if resolved_target.is_none() { resolved_target = local_resolution; } if let Some((resolved, matched_key)) = resolved_target { let (target_root, target_schema) = match &resolved { ResolvedRef::Local(s) => (self.root, *s), ResolvedRef::Global(root, s) => (*root, *s), }; let resource_base = if let Some((base, _)) = matched_key.split_once('#') { base } else { &matched_key }; let scope_to_pass = if let Some(ref tid) = target_schema.obj.id { let mut new_scope = effective_scope.clone(); new_scope.push(tid.clone()); new_scope } else { if !resource_base.is_empty() && resource_base != current_base_resolved { let mut new_scope = effective_scope.clone(); new_scope.push(resource_base.to_string()); new_scope } else { effective_scope.clone() } }; let mut new_overrides = self.overrides.clone(); if let Some(props) = &self.schema.properties { new_overrides.extend(props.keys().cloned()); } let target_ctx = ValidationContext::new( self.validator, target_root, target_schema, self.instance, scope_to_pass, new_overrides, false, self.reporter, ); let mut manual_ctx = target_ctx; manual_ctx.path = self.path.clone(); manual_ctx.depth = self.depth + 1; res.merge(manual_ctx.validate()?); } else { res.errors.push(ValidationError { code: "REF_RESOLUTION_FAILED".to_string(), message: format!("Could not resolve dynamic reference '{}'", d_ref), path: self.path.to_string(), }); } } // Family Support Map if let Some(ref family) = self.schema.obj.family { if let Some(family_schema) = self.validator.families.get(family) { let derived = self.derive_for_schema(family_schema.as_ref(), true); res.merge(derived.validate()?); handled = true; } else { res.errors.push(ValidationError { code: "FAMILY_NOT_FOUND".to_string(), message: format!("Family '{}' not found in families map", family), path: self.path.to_string(), }); handled = true; } } if handled { Ok(Some(res)) } else { Ok(None) } } pub(crate) fn validate_core(&self, result: &mut ValidationResult) { let current = self.instance.as_value(); if let Some(ref type_) = self.schema.type_ { match type_ { crate::schema::SchemaTypeOrArray::Single(t) => { if !Validator::check_type(t, current) { result.errors.push(ValidationError { code: "INVALID_TYPE".to_string(), message: format!("Expected type '{}'", t), path: self.path.to_string(), }); } } crate::schema::SchemaTypeOrArray::Multiple(types) => { let mut valid = false; for t in types { if Validator::check_type(t, current) { valid = true; break; } } if !valid { result.errors.push(ValidationError { code: "INVALID_TYPE".to_string(), message: format!("Expected one of types {:?}", types), path: self.path.to_string(), }); } } } } if let Some(ref const_val) = self.schema.const_ { if !crate::util::equals(current, const_val) { result.errors.push(ValidationError { code: "CONST_VIOLATED".to_string(), message: "Value does not match const".to_string(), path: self.path.to_string(), }); } else { if let Some(obj) = current.as_object() { result.evaluated_keys.extend(obj.keys().cloned()); } else if let Some(arr) = current.as_array() { result.evaluated_indices.extend(0..arr.len()); } } } if let Some(ref enum_vals) = self.schema.enum_ { let mut found = false; for val in enum_vals { if crate::util::equals(current, val) { found = true; break; } } if !found { result.errors.push(ValidationError { code: "ENUM_MISMATCH".to_string(), message: "Value is not in enum".to_string(), path: self.path.to_string(), }); } else { if let Some(obj) = current.as_object() { result.evaluated_keys.extend(obj.keys().cloned()); } else if let Some(arr) = current.as_array() { result.evaluated_indices.extend(0..arr.len()); } } } } pub(crate) fn validate_numeric(&self, result: &mut ValidationResult) { let current = self.instance.as_value(); if let Some(num) = current.as_f64() { if let Some(min) = self.schema.minimum { if num < min { result.errors.push(ValidationError { code: "MINIMUM_VIOLATED".to_string(), message: format!("Value {} < min {}", num, min), path: self.path.to_string(), }); } } if let Some(max) = self.schema.maximum { if num > max { result.errors.push(ValidationError { code: "MAXIMUM_VIOLATED".to_string(), message: format!("Value {} > max {}", num, max), path: self.path.to_string(), }); } } if let Some(ex_min) = self.schema.exclusive_minimum { if num <= ex_min { result.errors.push(ValidationError { code: "EXCLUSIVE_MINIMUM_VIOLATED".to_string(), message: format!("Value {} <= ex_min {}", num, ex_min), path: self.path.to_string(), }); } } if let Some(ex_max) = self.schema.exclusive_maximum { if num >= ex_max { result.errors.push(ValidationError { code: "EXCLUSIVE_MAXIMUM_VIOLATED".to_string(), message: format!("Value {} >= ex_max {}", num, ex_max), path: self.path.to_string(), }); } } if let Some(multiple_of) = self.schema.multiple_of { let val = num / multiple_of; if (val - val.round()).abs() > f64::EPSILON { result.errors.push(ValidationError { code: "MULTIPLE_OF_VIOLATED".to_string(), message: format!("Value {} not multiple of {}", num, multiple_of), path: self.path.to_string(), }); } } } } pub(crate) fn validate_string(&self, result: &mut ValidationResult) { let current = self.instance.as_value(); if let Some(s) = current.as_str() { if let Some(min) = self.schema.min_length { if (s.chars().count() as f64) < min { result.errors.push(ValidationError { code: "MIN_LENGTH_VIOLATED".to_string(), message: format!("Length < min {}", min), path: self.path.to_string(), }); } } if let Some(max) = self.schema.max_length { if (s.chars().count() as f64) > max { result.errors.push(ValidationError { code: "MAX_LENGTH_VIOLATED".to_string(), message: format!("Length > max {}", max), path: self.path.to_string(), }); } } if let Some(ref compiled_re) = self.schema.compiled_pattern { if !compiled_re.0.is_match(s) { result.errors.push(ValidationError { code: "PATTERN_VIOLATED".to_string(), message: format!("Pattern mismatch {:?}", self.schema.pattern), path: self.path.to_string(), }); } } else if let Some(ref pattern) = self.schema.pattern { if let Ok(re) = Regex::new(pattern) { if !re.is_match(s) { result.errors.push(ValidationError { code: "PATTERN_VIOLATED".to_string(), message: format!("Pattern mismatch {}", pattern), path: self.path.to_string(), }); } } } } } pub(crate) fn validate_format(&self, result: &mut ValidationResult) { let current = self.instance.as_value(); if let Some(ref compiled_fmt) = self.schema.compiled_format { match compiled_fmt { crate::compiler::CompiledFormat::Func(f) => { let should = if let Some(s) = current.as_str() { !s.is_empty() } else { true }; if should { if let Err(e) = f(current) { result.errors.push(ValidationError { code: "FORMAT_MISMATCH".to_string(), message: format!("Format error: {}", e), path: self.path.to_string(), }); } } } crate::compiler::CompiledFormat::Regex(re) => { if let Some(s) = current.as_str() { if !re.is_match(s) { result.errors.push(ValidationError { code: "FORMAT_MISMATCH".to_string(), message: "Format regex mismatch".to_string(), path: self.path.to_string(), }); } } } } } } pub(crate) fn validate_object( &self, result: &mut ValidationResult, ) -> Result<(), ValidationError> { let current = self.instance.as_value(); if let Some(obj) = current.as_object() { if let Some(min) = self.schema.min_properties { if (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 { if (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 dep_req) = self.schema.dependent_required { for (key, required_keys) in dep_req { if obj.contains_key(key) { for req_key in required_keys { if !obj.contains_key(req_key) { result.errors.push(ValidationError { code: "DEPENDENT_REQUIRED".to_string(), message: format!("Missing dependent {}", req_key), path: self.path.to_string(), }); } } } } } if let Some(ref dep_sch) = self.schema.dependent_schemas { for (key, sub_schema) in dep_sch { if obj.contains_key(key) { let derived = self.derive( sub_schema, self.instance, &self.path, self.scope.clone(), HashSet::new(), self.extensible, false, ); result.merge(derived.validate()?); } } } if let Some(props) = &self.schema.properties { for (key, sub_schema) in props { if self.overrides.contains(key) { continue; } if let Some(child_instance) = self.instance.child_at_key(key) { let new_path = format!("{}/{}", self.path, key); let is_ref = sub_schema.ref_string.is_some() || sub_schema.obj.dynamic_ref.is_some(); let next_extensible = if is_ref { false } else { self.extensible }; let derived = self.derive( sub_schema, child_instance, &new_path, self.scope.clone(), HashSet::new(), next_extensible, false, ); let item_res = derived.validate()?; result.merge(item_res); result.evaluated_keys.insert(key.clone()); } } } if let Some(ref compiled_pp) = self.schema.compiled_pattern_properties { for (compiled_re, sub_schema) in compiled_pp { for (key, _) in obj { if compiled_re.0.is_match(key) { if let Some(child_instance) = self.instance.child_at_key(key) { let new_path = format!("{}/{}", self.path, key); let is_ref = sub_schema.ref_string.is_some() || sub_schema.obj.dynamic_ref.is_some(); let next_extensible = if is_ref { false } else { self.extensible }; let derived = self.derive( sub_schema, child_instance, &new_path, self.scope.clone(), HashSet::new(), next_extensible, false, ); let item_res = derived.validate()?; result.merge(item_res); result.evaluated_keys.insert(key.clone()); } } } } } if let Some(ref additional_schema) = self.schema.additional_properties { for (key, _) in obj { let mut locally_matched = false; if let Some(props) = &self.schema.properties { if props.contains_key(key) { locally_matched = true; } } if !locally_matched { if 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 { if let Some(child_instance) = self.instance.child_at_key(key) { let new_path = format!("{}/{}", self.path, key); let is_ref = additional_schema.ref_string.is_some() || additional_schema.obj.dynamic_ref.is_some(); let next_extensible = if is_ref { false } else { self.extensible }; let derived = self.derive( additional_schema, child_instance, &new_path, self.scope.clone(), HashSet::new(), next_extensible, false, ); let item_res = derived.validate()?; result.merge(item_res); result.evaluated_keys.insert(key.clone()); } } } } 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.clone()); let ctx = ValidationContext::new( self.validator, self.root, property_names, crate::validator::ReadOnlyInstance(&val_str), self.scope.clone(), HashSet::new(), self.extensible, self.reporter, ); result.merge(ctx.validate()?); } } } if !self.extensible { self.instance.prune_object(&result.evaluated_keys); } Ok(()) } pub(crate) fn validate_array( &self, result: &mut ValidationResult, ) -> Result<(), ValidationError> { let current = self.instance.as_value(); if let Some(arr) = current.as_array() { if let Some(min) = self.schema.min_items { if (arr.len() as f64) < min { result.errors.push(ValidationError { code: "MIN_ITEMS".to_string(), message: "Too few items".to_string(), path: self.path.to_string(), }); } } if let Some(max) = self.schema.max_items { if (arr.len() as f64) > max { result.errors.push(ValidationError { code: "MAX_ITEMS".to_string(), message: "Too many items".to_string(), path: self.path.to_string(), }); } } if self.schema.unique_items.unwrap_or(false) { let mut seen: Vec<&Value> = Vec::new(); for item in arr { if seen.contains(&item) { result.errors.push(ValidationError { code: "UNIQUE_ITEMS_VIOLATED".to_string(), message: "Array has duplicate items".to_string(), path: self.path.to_string(), }); break; } seen.push(item); } } if let Some(ref contains_schema) = self.schema.contains { let mut _match_count = 0; for i in 0..arr.len() { if let Some(child_instance) = self.instance.child_at_index(i) { let derived = self.derive( contains_schema, child_instance, &self.path, self.scope.clone(), HashSet::new(), self.extensible, false, ); let check = derived.validate()?; if check.is_valid() { _match_count += 1; result.evaluated_indices.insert(i); } } } let min = self.schema.min_contains.unwrap_or(1.0) as usize; if _match_count < min { result.errors.push(ValidationError { code: "CONTAINS_VIOLATED".to_string(), message: format!("Contains matches {} < min {}", _match_count, min), path: self.path.to_string(), }); } if let Some(max) = self.schema.max_contains { if _match_count > max as usize { result.errors.push(ValidationError { code: "CONTAINS_VIOLATED".to_string(), message: format!("Contains matches {} > max {}", _match_count, max), path: self.path.to_string(), }); } } } let len = arr.len(); let mut validation_index = 0; if let Some(ref prefix) = self.schema.prefix_items { for (i, sub_schema) in prefix.iter().enumerate() { if i < len { let path = format!("{}/{}", self.path, i); if let Some(child_instance) = self.instance.child_at_index(i) { let derived = self.derive( sub_schema, child_instance, &path, self.scope.clone(), HashSet::new(), self.extensible, false, ); let item_res = derived.validate()?; result.merge(item_res); result.evaluated_indices.insert(i); validation_index += 1; } } } } if let Some(ref items_schema) = self.schema.items { for i in validation_index..len { let path = format!("{}/{}", self.path, i); if let Some(child_instance) = self.instance.child_at_index(i) { let derived = self.derive( items_schema, child_instance, &path, self.scope.clone(), HashSet::new(), self.extensible, false, ); let item_res = derived.validate()?; result.merge(item_res); result.evaluated_indices.insert(i); } } } } if !self.extensible { self.instance.prune_array(&result.evaluated_indices); } Ok(()) } pub(crate) fn validate_combinators( &self, result: &mut ValidationResult, ) -> Result<(), ValidationError> { if let Some(ref all_of) = self.schema.all_of { for sub in all_of { let derived = self.derive_for_schema(sub, true); let res = derived.validate()?; result.merge(res); } } if let Some(ref any_of) = self.schema.any_of { let mut valid = false; for sub in any_of { let derived = self.derive_for_schema(sub, true); let sub_res = derived.validate()?; if sub_res.is_valid() { valid = true; result.merge(sub_res); } } if !valid { result.errors.push(ValidationError { code: "ANY_OF_VIOLATED".to_string(), message: "Matches none of anyOf schemas".to_string(), path: self.path.to_string(), }); } } if let Some(ref one_of) = self.schema.one_of { let mut valid_count = 0; let mut valid_res = ValidationResult::new(); for sub in one_of { let derived = self.derive_for_schema(sub, true); let sub_res = derived.validate()?; if sub_res.is_valid() { valid_count += 1; valid_res = sub_res; } } if valid_count == 1 { result.merge(valid_res); } else if valid_count == 0 { result.errors.push(ValidationError { code: "ONE_OF_VIOLATED".to_string(), message: "Matches none of oneOf schemas".to_string(), path: self.path.to_string(), }); } else { result.errors.push(ValidationError { code: "ONE_OF_VIOLATED".to_string(), message: format!("Matches {} of oneOf schemas (expected 1)", valid_count), path: self.path.to_string(), }); } } if let Some(ref not_schema) = self.schema.not { let derived = self.derive_for_schema(not_schema, true); let sub_res = derived.validate()?; if sub_res.is_valid() { result.errors.push(ValidationError { code: "NOT_VIOLATED".to_string(), message: "Matched 'not' schema".to_string(), path: self.path.to_string(), }); } } Ok(()) } pub(crate) fn validate_conditionals( &self, result: &mut ValidationResult, ) -> Result<(), ValidationError> { if let Some(ref if_schema) = self.schema.if_ { let derived_if = self.derive_for_schema(if_schema, true); let if_res = derived_if.validate()?; result.evaluated_keys.extend(if_res.evaluated_keys.clone()); result .evaluated_indices .extend(if_res.evaluated_indices.clone()); if if_res.is_valid() { if let Some(ref then_schema) = self.schema.then_ { let derived_then = self.derive_for_schema(then_schema, true); result.merge(derived_then.validate()?); } } else { if let Some(ref else_schema) = self.schema.else_ { let derived_else = self.derive_for_schema(else_schema, true); result.merge(derived_else.validate()?); } } } Ok(()) } pub(crate) fn validate_strictness(&self, result: &mut ValidationResult) { if self.extensible || self.reporter { return; } if let Some(obj) = self.instance.as_value().as_object() { for key in obj.keys() { if !result.evaluated_keys.contains(key) && !self.overrides.contains(key) { result.errors.push(ValidationError { code: "STRICT_PROPERTY_VIOLATION".to_string(), message: format!("Unexpected property '{}'", key), path: format!("{}/{}", self.path, key), }); } } } if let Some(arr) = self.instance.as_value().as_array() { for i in 0..arr.len() { if !result.evaluated_indices.contains(&i) { result.errors.push(ValidationError { code: "STRICT_ITEM_VIOLATION".to_string(), message: format!("Unexpected item at index {}", i), path: format!("{}/{}", self.path, i), }); } } } } }