fix: `unevaluatedProperties` doesn't correctly handle subschema validation (#423)

* fix: unevaluatedProperties doesn't correctly handle subschema validation

* fix clippy lints and feature flag stuff

* trying to optimize slightly

* tweak

* make additionalProperties a first-class subvalidator
This commit is contained in:
Toby Lawrence 2023-07-05 04:53:33 -04:00 committed by GitHub
parent 066a0da459
commit fde9c4405c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 474 additions and 108 deletions

View File

@ -26,6 +26,7 @@ use serde_json::{Map, Value};
struct UnevaluatedPropertiesValidator {
schema_path: JSONPointer,
unevaluated: UnevaluatedSubvalidator,
additional: Option<UnevaluatedSubvalidator>,
properties: Option<PropertySubvalidator>,
patterns: Option<PatternSubvalidator>,
conditional: Option<Box<ConditionalSubvalidator>>,
@ -40,7 +41,20 @@ impl UnevaluatedPropertiesValidator {
schema: &'a Value,
context: &CompilationContext,
) -> Result<Self, ValidationError<'a>> {
let unevaluated = UnevaluatedSubvalidator::from_value(parent, schema, context)?;
let unevaluated = UnevaluatedSubvalidator::from_value(
schema,
&context.with_path("unevaluatedProperties"),
)?;
let additional = parent
.get("additionalProperties")
.map(|additional_properties| {
UnevaluatedSubvalidator::from_value(
additional_properties,
&context.with_path("additionalProperties"),
)
})
.transpose()?;
let properties = parent
.get("properties")
@ -57,7 +71,7 @@ impl UnevaluatedPropertiesValidator {
let success = parent.get("then");
let failure = parent.get("else");
ConditionalSubvalidator::from_values(condition, success, failure, context)
ConditionalSubvalidator::from_values(schema, condition, success, failure, context)
.map(Box::new)
})
.transpose()?;
@ -65,29 +79,44 @@ impl UnevaluatedPropertiesValidator {
let dependent = parent
.get("dependentSchemas")
.map(|dependent_schemas| {
DependentSchemaSubvalidator::from_value(dependent_schemas, context)
DependentSchemaSubvalidator::from_value(schema, dependent_schemas, context)
})
.transpose()?;
let reference = parent
.get("$ref")
.map(|reference| ReferenceSubvalidator::from_value(reference, context))
.map(|reference| ReferenceSubvalidator::from_value(schema, reference, context))
.transpose()?
.flatten();
let mut subschema_validators = vec![];
if let Some(Value::Array(subschemas)) = parent.get("allOf") {
let validator = SubschemaSubvalidator::from_values(subschemas, context)?;
let validator = SubschemaSubvalidator::from_values(
schema,
subschemas,
SubschemaBehavior::All,
context,
)?;
subschema_validators.push(validator);
}
if let Some(Value::Array(subschemas)) = parent.get("anyOf") {
let validator = SubschemaSubvalidator::from_values(subschemas, context)?;
let validator = SubschemaSubvalidator::from_values(
schema,
subschemas,
SubschemaBehavior::Any,
context,
)?;
subschema_validators.push(validator);
}
if let Some(Value::Array(subschemas)) = parent.get("oneOf") {
let validator = SubschemaSubvalidator::from_values(subschemas, context)?;
let validator = SubschemaSubvalidator::from_values(
schema,
subschemas,
SubschemaBehavior::One,
context,
)?;
subschema_validators.push(validator);
}
@ -100,6 +129,7 @@ impl UnevaluatedPropertiesValidator {
Ok(Self {
schema_path: JSONPointer::from(&context.schema_path),
unevaluated,
additional,
properties,
patterns,
conditional,
@ -145,6 +175,11 @@ impl UnevaluatedPropertiesValidator {
})
})
})
.or_else(|| {
self.additional.as_ref().and_then(|additional| {
additional.is_valid_property(property_instance, property_name)
})
})
.or_else(|| {
self.unevaluated
.is_valid_property(property_instance, property_name)
@ -203,7 +238,7 @@ impl UnevaluatedPropertiesValidator {
})
})
.or_else(|| {
let result = self.subschemas.as_ref().and_then(|subschemas| {
self.subschemas.as_ref().and_then(|subschemas| {
subschemas.iter().find_map(|subschema| {
subschema.validate_property(
instance,
@ -213,9 +248,12 @@ impl UnevaluatedPropertiesValidator {
property_name,
)
})
});
result
})
})
.or_else(|| {
self.additional.as_ref().and_then(|additional| {
additional.validate_property(property_path, property_instance, property_name)
})
})
.or_else(|| {
self.unevaluated
@ -289,6 +327,11 @@ impl UnevaluatedPropertiesValidator {
result
})
.or_else(|| {
self.additional.as_ref().and_then(|additional| {
additional.apply_property(property_path, property_instance, property_name)
})
})
.or_else(|| {
self.unevaluated
.apply_property(property_path, property_instance, property_name)
@ -315,7 +358,7 @@ impl Validate for UnevaluatedPropertiesValidator {
) -> ErrorIterator<'instance> {
if let Value::Object(props) = instance {
let mut errors = vec![];
let mut unexpected = vec![];
let mut unevaluated = vec![];
for (property_name, property_instance) in props {
let property_path = instance_path.push(property_name.clone());
@ -330,21 +373,20 @@ impl Validate for UnevaluatedPropertiesValidator {
match maybe_property_errors {
Some(property_errors) => errors.extend(property_errors),
None => {
// If we can't validate, that means that "unevaluatedProperties" is
// "false", which means that this property was not expected.
unexpected.push(property_name.to_string());
unevaluated.push(property_name.to_string());
}
}
}
if !unexpected.is_empty() {
if !unevaluated.is_empty() {
errors.push(ValidationError::unevaluated_properties(
self.schema_path.clone(),
instance_path.into(),
instance,
unexpected,
))
unevaluated,
));
}
Box::new(errors.into_iter())
} else {
no_error()
@ -358,7 +400,7 @@ impl Validate for UnevaluatedPropertiesValidator {
) -> PartialApplication<'a> {
if let Value::Object(props) = instance {
let mut output = BasicOutput::default();
let mut unexpected = vec![];
let mut unevaluated = vec![];
for (property_name, property_instance) in props {
let property_path = instance_path.push(property_name.clone());
@ -373,21 +415,19 @@ impl Validate for UnevaluatedPropertiesValidator {
match maybe_property_output {
Some(property_output) => output += property_output,
None => {
// If we can't validate, that means that "unevaluatedProperties" is
// "false", which means that this property was not expected.
unexpected.push(property_name.to_string());
unevaluated.push(property_name.to_string());
}
}
}
let mut result: PartialApplication = output.into();
if !unexpected.is_empty() {
if !unevaluated.is_empty() {
result.mark_errored(
ValidationError::unevaluated_properties(
self.schema_path.clone(),
instance_path.into(),
instance,
unexpected,
unevaluated,
)
.into(),
)
@ -503,8 +543,7 @@ impl PatternSubvalidator {
}
}
let errors: ErrorIterator<'instance> = Box::new(errors.into_iter());
had_match.then(|| errors)
had_match.then(|| boxed_errors(errors))
}
fn apply_property<'a>(
@ -529,36 +568,65 @@ impl PatternSubvalidator {
}
}
/// Subschema validator behavior.
#[derive(Debug)]
enum SubschemaBehavior {
/// Properties must be valid for all subschemas that would evaluate them.
All,
/// Properties must be valid for exactly one subschema that would evaluate them.
One,
/// Properties must be valid for at least one subschema that would evaluate them.
Any,
}
impl SubschemaBehavior {
const fn as_str(&self) -> &'static str {
match self {
SubschemaBehavior::All => "allOf",
SubschemaBehavior::One => "oneOf",
SubschemaBehavior::Any => "anyOf",
}
}
}
/// A subvalidator for subschema validation such as `allOf`, `oneOf`, and `anyOf`.
///
/// Unlike the validation logic for `allOf`/`oneOf`/`anyOf` themselves, this subvalidator searches
/// configured subvalidators in a first-match-wins process. For example, a property will be
/// considered evaluated against subschemas defined via `oneOf` so long as one subschema would evaluate
/// the property, even if, say, more than one subschema in `oneOf` is technically valid, which would
/// otherwise be a failure for validation of `oneOf` in and of itself.
#[derive(Debug)]
struct SubschemaSubvalidator {
subvalidators: Vec<UnevaluatedPropertiesValidator>,
behavior: SubschemaBehavior,
subvalidators: Vec<(SchemaNode, UnevaluatedPropertiesValidator)>,
}
impl SubschemaSubvalidator {
fn from_values<'a>(
parent: &'a Value,
values: &'a [Value],
behavior: SubschemaBehavior,
context: &CompilationContext,
) -> Result<Self, ValidationError<'a>> {
let mut subvalidators = vec![];
for value in values {
let keyword_context = context.with_path(behavior.as_str());
for (i, value) in values.iter().enumerate() {
if let Value::Object(subschema) = value {
let subschema_context = keyword_context.with_path(i.to_string());
let node = compile_validators(value, &subschema_context)?;
let subvalidator = UnevaluatedPropertiesValidator::compile(
subschema,
get_unevaluated_props_schema(subschema),
context,
get_transitive_unevaluated_props_schema(subschema, parent),
&subschema_context,
)?;
subvalidators.push(subvalidator);
subvalidators.push((node, subvalidator));
}
}
Ok(Self { subvalidators })
Ok(Self {
behavior,
subvalidators,
})
}
fn is_valid_property(
@ -567,9 +635,58 @@ impl SubschemaSubvalidator {
property_instance: &Value,
property_name: &str,
) -> Option<bool> {
self.subvalidators.iter().find_map(|subvalidator| {
subvalidator.is_valid_property(instance, property_instance, property_name)
})
let mapped = self.subvalidators.iter().map(|(node, subvalidator)| {
(
subvalidator.is_valid_property(instance, property_instance, property_name),
node.is_valid(instance),
)
});
match self.behavior {
// The instance must be valid against _all_ subschemas, and the property must be
// evaluated by at least one subschema.
SubschemaBehavior::All => {
let results = mapped.collect::<Vec<_>>();
let all_subschemas_valid =
results.iter().all(|(_, instance_valid)| *instance_valid);
all_subschemas_valid.then(|| {
// We only need to find the first valid evaluation because we know if that
// all subschemas were valid against the instance that there can't actually
// be any subschemas where the property was evaluated but invalid.
results
.iter()
.any(|(property_result, _)| matches!(property_result, Some(true)))
})
}
// The instance must be valid against only _one_ subschema, and for that subschema, the
// property must be evaluated by it.
SubschemaBehavior::One => {
let mut evaluated_property = None;
for (property_result, instance_valid) in mapped {
if instance_valid {
if evaluated_property.is_some() {
// We already found a subschema that the instance was valid against, and
// had evaluated the property, which means this `oneOf` is not valid
// overall, and so the property is not considered evaluated.
return None;
}
evaluated_property = property_result;
}
}
evaluated_property
}
// The instance must be valid against _at least_ one subschema, and for that subschema,
// the property must be evaluated by it.
SubschemaBehavior::Any => mapped
.filter_map(|(property_result, instance_valid)| {
instance_valid.then(|| property_result).flatten()
})
.find(|x| *x),
}
}
fn validate_property<'instance>(
@ -580,15 +697,78 @@ impl SubschemaSubvalidator {
property_instance: &'instance Value,
property_name: &str,
) -> Option<ErrorIterator<'instance>> {
self.subvalidators.iter().find_map(|subvalidator| {
subvalidator.validate_property(
instance,
instance_path,
property_path,
property_instance,
property_name,
)
})
let mapped = self.subvalidators.iter().map(|(node, subvalidator)| {
let property_result = subvalidator
.validate_property(
instance,
instance_path,
property_path,
property_instance,
property_name,
)
.map(|errs| errs.collect::<Vec<_>>());
let instance_result = node.validate(instance, instance_path).collect::<Vec<_>>();
(property_result, instance_result)
});
match self.behavior {
// The instance must be valid against _all_ subschemas, and the property must be
// evaluated by at least one subschema. We group the errors for the property itself
// across all subschemas, though.
SubschemaBehavior::All => {
let results = mapped.collect::<Vec<_>>();
let all_subschemas_valid = results
.iter()
.all(|(_, instance_errors)| instance_errors.is_empty());
all_subschemas_valid
.then(|| {
results
.into_iter()
.filter_map(|(property_errors, _)| property_errors)
.reduce(|mut previous, current| {
previous.extend(current);
previous
})
.map(boxed_errors)
})
.flatten()
}
// The instance must be valid against only _one_ subschema, and for that subschema, the
// property must be evaluated by it.
SubschemaBehavior::One => {
let mut evaluated_property_errors = None;
for (property_errors, instance_errors) in mapped {
if instance_errors.is_empty() {
if evaluated_property_errors.is_some() {
// We already found a subschema that the instance was valid against, and
// had evaluated the property, which means this `oneOf` is not valid
// overall, and so the property is not considered evaluated.
return None;
}
evaluated_property_errors = property_errors.map(boxed_errors);
}
}
evaluated_property_errors
}
// The instance must be valid against _at least_ one subschema, and for that subschema,
// the property must be evaluated by it.
SubschemaBehavior::Any => mapped
.filter_map(|(property_errors, instance_errors)| {
instance_errors
.is_empty()
.then(|| property_errors)
.flatten()
})
.filter(|x| x.is_empty())
.map(boxed_errors)
.next(),
}
}
fn apply_property<'a>(
@ -599,15 +779,73 @@ impl SubschemaSubvalidator {
property_instance: &Value,
property_name: &str,
) -> Option<BasicOutput<'a>> {
self.subvalidators.iter().find_map(|subvalidator| {
subvalidator.apply_property(
let mapped = self.subvalidators.iter().map(|(node, subvalidator)| {
let property_result = subvalidator.apply_property(
instance,
instance_path,
property_path,
property_instance,
property_name,
)
})
);
let instance_result = node.apply(instance, instance_path);
(property_result, instance_result)
});
match self.behavior {
// The instance must be valid against _all_ subschemas, and the property must be
// evaluated by at least one subschema. We group the errors for the property itself
// across all subschemas, though.
SubschemaBehavior::All => {
let results = mapped.collect::<Vec<_>>();
let all_subschemas_valid = results
.iter()
.all(|(_, instance_output)| instance_output.is_valid());
all_subschemas_valid
.then(|| {
results
.into_iter()
.filter_map(|(property_output, _)| property_output)
.reduce(|mut previous, current| {
previous += current;
previous
})
})
.flatten()
}
// The instance must be valid against only _one_ subschema, and for that subschema, the
// property must be evaluated by it.
SubschemaBehavior::One => {
let mut evaluated_property_output = None;
for (property_output, instance_output) in mapped {
if instance_output.is_valid() {
if evaluated_property_output.is_some() {
// We already found a subschema that the instance was valid against, and
// had evaluated the property, which means this `oneOf` is not valid
// overall, and so the property is not considered evaluated.
return None;
}
evaluated_property_output = property_output;
}
}
evaluated_property_output
}
// The instance must be valid against _at least_ one subschema, and for that subschema,
// the property must be evaluated by it.
SubschemaBehavior::Any => mapped
.filter_map(|(property_output, instance_output)| {
instance_output
.is_valid()
.then(|| property_output)
.flatten()
})
.find(|x| x.is_valid()),
}
}
}
@ -633,30 +871,13 @@ struct UnevaluatedSubvalidator {
impl UnevaluatedSubvalidator {
fn from_value<'a>(
parent: &'a Map<String, Value>,
value: &'a Value,
context: &CompilationContext,
) -> Result<Self, ValidationError<'a>> {
// We also examine the value of `additionalProperties` here, if present, because if it's
// specified as `true`, it can potentially override the behavior of the validator depending
// on the value of `unevaluatedProperties`.
//
// TODO: We probably need to think about this more because `unevaluatedProperties` affects
// subschema validation, when really all we want to have this do (based on the JSON Schema
// test suite cases) is disable the `unevaluatedProperties: false` bit _just_ for normal
// properties on the top-level instance.
let additional_properties = parent.get("additionalProperties");
let behavior = match (value, additional_properties) {
(Value::Bool(false), None) | (Value::Bool(false), Some(Value::Bool(false))) => {
UnevaluatedBehavior::Deny
}
(Value::Bool(true), _) | (Value::Bool(false), Some(Value::Bool(true))) => {
UnevaluatedBehavior::Allow
}
_ => UnevaluatedBehavior::IfValid(compile_validators(
value,
&context.with_path("unevaluatedProperties"),
)?),
let behavior = match value {
Value::Bool(false) => UnevaluatedBehavior::Deny,
Value::Bool(true) => UnevaluatedBehavior::Allow,
_ => UnevaluatedBehavior::IfValid(compile_validators(value, context)?),
};
Ok(Self { behavior })
@ -720,39 +941,41 @@ struct ConditionalSubvalidator {
impl ConditionalSubvalidator {
fn from_values<'a>(
parent: &'a Value,
schema: &'a Value,
success: Option<&'a Value>,
failure: Option<&'a Value>,
context: &CompilationContext,
) -> Result<Self, ValidationError<'a>> {
compile_validators(schema, context).and_then(|condition| {
let if_context = context.with_path("if");
compile_validators(schema, &if_context).and_then(|condition| {
let node = schema
.as_object()
.map(|parent| {
.map(|node_schema| {
UnevaluatedPropertiesValidator::compile(
parent,
get_unevaluated_props_schema(parent),
context,
node_schema,
get_transitive_unevaluated_props_schema(node_schema, parent),
&if_context,
)
})
.transpose()?;
let success = success
.and_then(|value| value.as_object())
.map(|parent| {
.map(|success_schema| {
UnevaluatedPropertiesValidator::compile(
parent,
get_unevaluated_props_schema(parent),
context,
success_schema,
get_transitive_unevaluated_props_schema(success_schema, parent),
&context.with_path("then"),
)
})
.transpose()?;
let failure = failure
.and_then(|value| value.as_object())
.map(|parent| {
.map(|failure_schema| {
UnevaluatedPropertiesValidator::compile(
parent,
get_unevaluated_props_schema(parent),
context,
failure_schema,
get_transitive_unevaluated_props_schema(failure_schema, parent),
&context.with_path("else"),
)
})
.transpose()?;
@ -886,22 +1109,26 @@ struct DependentSchemaSubvalidator {
impl DependentSchemaSubvalidator {
fn from_value<'a>(
parent: &'a Value,
value: &'a Value,
context: &CompilationContext,
) -> Result<Self, ValidationError<'a>> {
let keyword_context = context.with_path("dependentSchemas");
let schemas = value
.as_object()
.ok_or_else(|| unexpected_type(context, value, PrimitiveType::Object))?;
.ok_or_else(|| unexpected_type(&keyword_context, value, PrimitiveType::Object))?;
let mut nodes = AHashMap::new();
for (dependent_property_name, dependent_schema) in schemas {
let parent = dependent_schema
let dependent_schema = dependent_schema
.as_object()
.ok_or_else(ValidationError::null_schema)?;
let schema_context = keyword_context.with_path(dependent_property_name.to_string());
let node = UnevaluatedPropertiesValidator::compile(
parent,
get_unevaluated_props_schema(parent),
context,
dependent_schema,
get_transitive_unevaluated_props_schema(dependent_schema, parent),
&schema_context,
)?;
nodes.insert(dependent_property_name.to_string(), node);
}
@ -985,31 +1212,34 @@ struct ReferenceSubvalidator {
impl ReferenceSubvalidator {
fn from_value<'a>(
parent: &'a Value,
value: &'a Value,
context: &CompilationContext,
) -> Result<Option<Self>, ValidationError<'a>> {
let keyword_context = context.with_path("$ref");
let reference = value
.as_str()
.ok_or_else(|| unexpected_type(context, value, PrimitiveType::String))?;
.ok_or_else(|| unexpected_type(&keyword_context, value, PrimitiveType::String))?;
let reference_url = context.build_url(reference)?;
let (scope, resolved) = context
let (scope, resolved) = keyword_context
.resolver
.resolve_fragment(context.config.draft(), &reference_url, reference)
.resolve_fragment(keyword_context.config.draft(), &reference_url, reference)
.map_err(|e| e.into_owned())?;
let ref_context = CompilationContext::new(
let mut ref_context = CompilationContext::new(
scope.into(),
Arc::clone(&context.config),
Arc::clone(&context.resolver),
);
ref_context.schema_path = keyword_context.schema_path.clone();
resolved
.as_object()
.map(|parent| {
.map(|ref_schema| {
UnevaluatedPropertiesValidator::compile(
parent,
get_unevaluated_props_schema(parent),
ref_schema,
get_transitive_unevaluated_props_schema(ref_schema, parent),
&ref_context,
)
.map(|validator| ReferenceSubvalidator {
@ -1072,10 +1302,15 @@ fn value_has_object_key(value: &Value, key: &str) -> bool {
}
}
fn get_unevaluated_props_schema(parent: &Map<String, Value>) -> &Value {
parent
.get("unevaluatedProperties")
.unwrap_or(&Value::Bool(false))
fn get_transitive_unevaluated_props_schema<'a>(
leaf: &'a Map<String, Value>,
parent: &'a Value,
) -> &'a Value {
// We first try and if the leaf schema has `unevaluatedProperties` defined, and if so, we use
// that. Otherwise, we fallback to the parent schema value, which is the value of
// `unevaluatedProperties` as defined at the level of the schema right where `leaf_schema`
// lives.
leaf.get("unevaluatedProperties").unwrap_or(parent)
}
pub(crate) fn compile<'a>(
@ -1094,6 +1329,11 @@ pub(crate) fn compile<'a>(
}
}
fn boxed_errors<'a>(errors: Vec<ValidationError<'a>>) -> ErrorIterator<'a> {
let boxed_errors: ErrorIterator<'a> = Box::new(errors.into_iter());
boxed_errors
}
fn unexpected_type<'a>(
context: &CompilationContext,
instance: &'a Value,
@ -1106,3 +1346,103 @@ fn unexpected_type<'a>(
expected_type,
)
}
#[cfg(test)]
mod tests {
use crate::{tests_util, Draft};
use serde_json::json;
#[cfg(all(feature = "draft201909", not(feature = "draft202012")))]
const fn get_draft_version() -> Draft {
Draft::Draft201909
}
#[cfg(all(feature = "draft202012", not(feature = "draft201909")))]
const fn get_draft_version() -> Draft {
Draft::Draft202012
}
#[cfg(all(feature = "draft201909", feature = "draft202012"))]
const fn get_draft_version() -> Draft {
Draft::Draft202012
}
#[test]
fn one_of() {
tests_util::is_valid_with_draft(
get_draft_version(),
&json!({
"oneOf": [
{ "properties": { "foo": { "const": "bar" } } },
{ "properties": { "foo": { "const": "quux" } } }
],
"unevaluatedProperties": false
}),
&json!({ "foo": "quux" }),
)
}
#[test]
fn any_of() {
tests_util::is_valid_with_draft(
get_draft_version(),
&json!({
"anyOf": [
{ "properties": { "foo": { "minLength": 10 } } },
{ "properties": { "foo": { "type": "string" } } }
],
"unevaluatedProperties": false
}),
&json!({ "foo": "rut roh" }),
)
}
#[test]
fn all_of() {
tests_util::is_not_valid_with_draft(
get_draft_version(),
&json!({
"allOf": [
{ "properties": { "foo": { "type": "string" } } },
{ "properties": { "foo": { "minLength": 10 } } }
],
"unevaluatedProperties": false
}),
&json!({ "foo": "rut roh" }),
)
}
#[test]
fn all_of_with_additional_props_subschema() {
let schema = json!({
"allOf": [
{
"type": "object",
"required": [
"foo"
],
"properties": {
"foo": { "type": "string" }
}
},
{
"type": "object",
"additionalProperties": { "type": "string" }
}
],
"unevaluatedProperties": false
});
tests_util::is_valid_with_draft(
get_draft_version(),
&schema,
&json!({ "foo": "wee", "another": "thing" }),
);
tests_util::is_not_valid_with_draft(
get_draft_version(),
&schema,
&json!({ "foo": "wee", "another": false }),
);
}
}

View File

@ -128,8 +128,7 @@ pub(crate) mod tests_util {
use crate::ValidationError;
use serde_json::Value;
pub(crate) fn is_not_valid(schema: &Value, instance: &Value) {
let compiled = JSONSchema::compile(schema).unwrap();
fn is_not_valid_inner(compiled: &JSONSchema, instance: &Value) {
assert!(
!compiled.is_valid(instance),
"{} should not be valid (via is_valid)",
@ -147,6 +146,20 @@ pub(crate) mod tests_util {
);
}
pub(crate) fn is_not_valid(schema: &Value, instance: &Value) {
let compiled = JSONSchema::compile(schema).unwrap();
is_not_valid_inner(&compiled, instance)
}
#[cfg(any(feature = "draft201909", feature = "draft202012"))]
pub(crate) fn is_not_valid_with_draft(draft: crate::Draft, schema: &Value, instance: &Value) {
let compiled = JSONSchema::options()
.with_draft(draft)
.compile(schema)
.unwrap();
is_not_valid_inner(&compiled, instance)
}
pub(crate) fn expect_errors(schema: &Value, instance: &Value, errors: &[&str]) {
assert_eq!(
JSONSchema::compile(schema)
@ -159,8 +172,7 @@ pub(crate) mod tests_util {
)
}
pub(crate) fn is_valid(schema: &Value, instance: &Value) {
let compiled = JSONSchema::compile(schema).unwrap();
fn is_valid_inner(compiled: &JSONSchema, instance: &Value) {
assert!(
compiled.is_valid(instance),
"{} should be valid (via is_valid)",
@ -178,6 +190,20 @@ pub(crate) mod tests_util {
);
}
pub(crate) fn is_valid(schema: &Value, instance: &Value) {
let compiled = JSONSchema::compile(schema).unwrap();
is_valid_inner(&compiled, instance);
}
#[cfg(any(feature = "draft201909", feature = "draft202012"))]
pub(crate) fn is_valid_with_draft(draft: crate::Draft, schema: &Value, instance: &Value) {
let compiled = JSONSchema::options()
.with_draft(draft)
.compile(schema)
.unwrap();
is_valid_inner(&compiled, instance)
}
pub(crate) fn validate(schema: &Value, instance: &Value) -> ValidationError<'static> {
let compiled = JSONSchema::compile(schema).unwrap();
let err = compiled