unevaluatedProperties now cascade infinitely down their leaf when strict validation mode is on

This commit is contained in:
2025-10-02 18:15:07 -04:00
parent 6b6647f2d6
commit a8d726ec73
5 changed files with 98 additions and 16 deletions

View File

@ -194,7 +194,7 @@ impl Schemas {
&'s self,
v: &'v Value,
sch_index: SchemaIndex,
options: Option<&'s ValidationOptions>,
options: Option<ValidationOptions>,
) -> Result<(), ValidationError<'s, 'v>> {
let Some(sch) = self.list.get(sch_index.0) else {
panic!("Schemas::validate: schema index out of bounds");

View File

@ -20,7 +20,7 @@ pub(crate) fn validate<'s, 'v>(
v: &'v Value,
schema: &'s Schema,
schemas: &'s Schemas,
options: Option<&'s ValidationOptions>,
options: Option<ValidationOptions>,
) -> Result<(), ValidationError<'s, 'v>> {
let scope = Scope {
sch: schema.idx,
@ -29,7 +29,7 @@ pub(crate) fn validate<'s, 'v>(
parent: None,
};
let mut vloc = Vec::with_capacity(8);
let be_strict = options.map_or(false, |o| o.be_strict);
let options = options.unwrap_or_default();
let (result, _) = Validator {
v,
vloc: &mut vloc,
@ -37,7 +37,7 @@ pub(crate) fn validate<'s, 'v>(
schemas,
scope,
options,
uneval: Uneval::from(v, schema, be_strict),
uneval: Uneval::from(v, schema, options.be_strict),
errors: vec![],
bool_result: false,
}
@ -89,7 +89,7 @@ struct Validator<'v, 's, 'd, 'e> {
schema: &'s Schema,
schemas: &'s Schemas,
scope: Scope<'d>,
options: Option<&'s ValidationOptions>,
options: ValidationOptions,
uneval: Uneval<'v>,
errors: Vec<ValidationError<'s, 'v>>,
bool_result: bool, // is interested to know valid or not (but not actuall error)
@ -296,7 +296,7 @@ impl<'v> Validator<'v, '_, '_, '_> {
if let Some(sch) = &s.property_names {
for pname in obj.keys() {
let v = Value::String(pname.to_owned());
if let Err(mut e) = self.schemas.validate(&v, *sch, self.options) {
if let Err(mut e) = self.schemas.validate(&v, *sch, Some(self.options)) {
e.schema_url = &s.loc;
e.kind = ErrorKind::PropertyName {
prop: pname.to_owned(),
@ -510,7 +510,7 @@ impl<'v> Validator<'v, '_, '_, '_> {
// contentSchema --
if let (Some(sch), Some(v)) = (s.content_schema, deserialized) {
if let Err(mut e) = self.schemas.validate(&v, sch, self.options) {
if let Err(mut e) = self.schemas.validate(&v, sch, Some(self.options)) {
e.schema_url = &s.loc;
e.kind = kind!(ContentSchema);
self.errors.push(e.clone_static());
@ -762,8 +762,6 @@ impl Validator<'_, '_, '_, '_> {
};
}
let be_strict = self.options.map_or(false, |o| o.be_strict);
// unevaluatedProperties --
if let Value::Object(obj) = v {
if let Some(sch_idx) = s.unevaluated_properties {
@ -786,7 +784,7 @@ impl Validator<'_, '_, '_, '_> {
}
self.uneval.props.clear();
}
} else if be_strict && !self.bool_result {
} else if self.options.be_strict && !self.bool_result {
// 2. Runtime strictness check
if !self.uneval.props.is_empty() {
let props: Vec<Cow<str>> = self.uneval.props.iter().map(|p| Cow::from((*p).as_str())).collect();
@ -824,15 +822,27 @@ impl<'v, 's> Validator<'v, 's, '_, '_> {
}
let scope = self.scope.child(sch, None, self.scope.vid + 1);
let schema = &self.schemas.get(sch);
let be_strict = self.options.map_or(false, |o| o.be_strict);
// Check if the new schema turns off strictness
let allows_unevaluated = schema.boolean == Some(true) ||
if let Some(idx) = schema.unevaluated_properties {
self.schemas.get(idx).boolean == Some(true)
} else {
false
};
let mut new_options = self.options;
if allows_unevaluated {
new_options.be_strict = false;
}
let (result, _reply) = Validator {
v,
vloc: self.vloc,
schema,
schemas: self.schemas,
scope,
options: self.options,
uneval: Uneval::from(v, schema, be_strict || !self.uneval.is_empty()),
options: new_options,
uneval: Uneval::from(v, schema, new_options.be_strict || !self.uneval.is_empty()),
errors: vec![],
bool_result: self.bool_result,
}
@ -849,13 +859,26 @@ impl<'v, 's> Validator<'v, 's, '_, '_> {
) -> Result<(), ValidationError<'s, 'v>> {
let scope = self.scope.child(sch, ref_kw, self.scope.vid);
let schema = &self.schemas.get(sch);
// Check if the new schema turns off strictness
let allows_unevaluated = schema.boolean == Some(true) ||
if let Some(idx) = schema.unevaluated_properties {
self.schemas.get(idx).boolean == Some(true)
} else {
false
};
let mut new_options = self.options;
if allows_unevaluated {
new_options.be_strict = false;
}
let (result, reply) = Validator {
v: self.v,
vloc: self.vloc,
schema,
schemas: self.schemas,
scope,
options: self.options,
options: new_options,
uneval: self.uneval.clone(),
errors: vec![],
bool_result: self.bool_result || bool_result,