1009 lines
32 KiB
Rust
1009 lines
32 KiB
Rust
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<ValidationResult, ValidationError> {
|
|
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<Option<ValidationResult>, 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),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|