feat: add support for unevaluatedProperties to draft 2019-09 and 2020-12
This commit is contained in:
parent
848b7b1284
commit
63494600b8
|
@ -9,6 +9,9 @@
|
|||
- Bump `fraction` to `0.13`.
|
||||
- Bump `iso8601` to `0.6`.
|
||||
- Replace `lazy_static` with `once_cell`.
|
||||
- Add support for `unevaluatedProperties`. (gated by the `draft201909`/`draft202012` feature flags)
|
||||
- When using the draft 2019-09 or draft 2020-12 specification, `$ref` is now evaluated alongside
|
||||
other keywords.
|
||||
|
||||
## [0.16.1] - 2022-10-20
|
||||
|
||||
|
|
|
@ -154,7 +154,15 @@ pub(crate) fn compile_validators<'a>(
|
|||
)),
|
||||
},
|
||||
Value::Object(object) => {
|
||||
if let Some(reference) = object.get("$ref") {
|
||||
// In Draft 2019-09 and later, `$ref` can be evaluated alongside other attribute aka
|
||||
// adjacent validation. We check here to see if adjacent validation is supported, and if
|
||||
// so, we use the normal keyword validator collection logic.
|
||||
//
|
||||
// Otherwise, we isolate `$ref` and generate a schema reference validator directly.
|
||||
let maybe_reference = object
|
||||
.get("$ref")
|
||||
.filter(|_| !keywords::ref_::supports_adjacent_validation(context.config.draft()));
|
||||
if let Some(reference) = maybe_reference {
|
||||
let unmatched_keywords = object
|
||||
.iter()
|
||||
.filter_map(|(k, v)| {
|
||||
|
@ -165,24 +173,16 @@ pub(crate) fn compile_validators<'a>(
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
let mut validators = Vec::new();
|
||||
if let Value::String(reference) = reference {
|
||||
let validator = keywords::ref_::compile(schema, reference, &context)
|
||||
.expect("Should always return Some")?;
|
||||
validators.push(("$ref".to_string(), validator));
|
||||
|
||||
let validator = keywords::ref_::compile(object, reference, &context)
|
||||
.expect("should always return Some")?;
|
||||
|
||||
let validators = vec![("$ref".to_string(), validator)];
|
||||
Ok(SchemaNode::new_from_keywords(
|
||||
&context,
|
||||
validators,
|
||||
Some(unmatched_keywords),
|
||||
))
|
||||
} else {
|
||||
Err(ValidationError::single_type_error(
|
||||
JSONPointer::default(),
|
||||
relative_path,
|
||||
reference,
|
||||
PrimitiveType::String,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
let mut validators = Vec::with_capacity(object.len());
|
||||
let mut unmatched_keywords = AHashMap::new();
|
||||
|
|
|
@ -138,6 +138,8 @@ pub enum ValidationErrorKind {
|
|||
Schema,
|
||||
/// When the input value doesn't match one or multiple required types.
|
||||
Type { kind: TypeKind },
|
||||
/// Unexpected properties.
|
||||
UnevaluatedProperties { unexpected: Vec<String> },
|
||||
/// When the input array has non-unique elements.
|
||||
UniqueItems,
|
||||
/// Reference contains unknown scheme.
|
||||
|
@ -692,6 +694,19 @@ impl<'a> ValidationError<'a> {
|
|||
schema_path,
|
||||
}
|
||||
}
|
||||
pub(crate) const fn unevaluated_properties(
|
||||
schema_path: JSONPointer,
|
||||
instance_path: JSONPointer,
|
||||
instance: &'a Value,
|
||||
unexpected: Vec<String>,
|
||||
) -> ValidationError<'a> {
|
||||
ValidationError {
|
||||
instance_path,
|
||||
instance: Cow::Borrowed(instance),
|
||||
kind: ValidationErrorKind::UnevaluatedProperties { unexpected },
|
||||
schema_path,
|
||||
}
|
||||
}
|
||||
pub(crate) const fn unique_items(
|
||||
schema_path: JSONPointer,
|
||||
instance_path: JSONPointer,
|
||||
|
@ -937,6 +952,25 @@ impl fmt::Display for ValidationError<'_> {
|
|||
ValidationErrorKind::MultipleOf { multiple_of } => {
|
||||
write!(f, "{} is not a multiple of {}", self.instance, multiple_of)
|
||||
}
|
||||
ValidationErrorKind::UnevaluatedProperties { unexpected } => {
|
||||
let verb = {
|
||||
if unexpected.len() == 1 {
|
||||
"was"
|
||||
} else {
|
||||
"were"
|
||||
}
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
"Unevaluated properties are not allowed ({} {} unexpected)",
|
||||
unexpected
|
||||
.iter()
|
||||
.map(|x| format!("'{}'", x))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", "),
|
||||
verb
|
||||
)
|
||||
}
|
||||
ValidationErrorKind::UniqueItems => {
|
||||
write!(f, "{} has non-unique elements", self.instance)
|
||||
}
|
||||
|
|
|
@ -12,76 +12,12 @@ use crate::{
|
|||
keywords::CompilationResult,
|
||||
output::{Annotations, BasicOutput, OutputUnit},
|
||||
paths::{AbsolutePath, InstancePath, JSONPointer},
|
||||
properties::*,
|
||||
schema_node::SchemaNode,
|
||||
validator::{format_validators, PartialApplication, Validate},
|
||||
};
|
||||
use ahash::AHashMap;
|
||||
use fancy_regex::Regex;
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
pub(crate) type PatternedValidators = Vec<(Regex, SchemaNode)>;
|
||||
|
||||
/// Provide mapping API to get validators associated with a property from the underlying storage.
|
||||
pub(crate) trait PropertiesValidatorsMap: Send + Sync {
|
||||
fn get_validator(&self, property: &str) -> Option<&SchemaNode>;
|
||||
fn get_key_validator(&self, property: &str) -> Option<(&String, &SchemaNode)>;
|
||||
}
|
||||
|
||||
// Iterating over a small vector and comparing strings is faster than a map lookup
|
||||
const MAP_SIZE_THRESHOLD: usize = 40;
|
||||
pub(crate) type SmallValidatorsMap = Vec<(String, SchemaNode)>;
|
||||
pub(crate) type BigValidatorsMap = AHashMap<String, SchemaNode>;
|
||||
|
||||
impl PropertiesValidatorsMap for SmallValidatorsMap {
|
||||
#[inline]
|
||||
fn get_validator(&self, property: &str) -> Option<&SchemaNode> {
|
||||
for (prop, node) in self {
|
||||
if prop == property {
|
||||
return Some(node);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
#[inline]
|
||||
fn get_key_validator(&self, property: &str) -> Option<(&String, &SchemaNode)> {
|
||||
for (prop, node) in self {
|
||||
if prop == property {
|
||||
return Some((prop, node));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl PropertiesValidatorsMap for BigValidatorsMap {
|
||||
#[inline]
|
||||
fn get_validator(&self, property: &str) -> Option<&SchemaNode> {
|
||||
self.get(property)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_key_validator(&self, property: &str) -> Option<(&String, &SchemaNode)> {
|
||||
self.get_key_value(property)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! dynamic_map {
|
||||
($validator:tt, $properties:ident, $( $arg:expr ),* $(,)*) => {{
|
||||
if let Value::Object(map) = $properties {
|
||||
if map.len() < MAP_SIZE_THRESHOLD {
|
||||
Some($validator::<SmallValidatorsMap>::compile(
|
||||
map, $($arg, )*
|
||||
))
|
||||
} else {
|
||||
Some($validator::<BigValidatorsMap>::compile(
|
||||
map, $($arg, )*
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Some(Err(ValidationError::null_schema()))
|
||||
}
|
||||
}};
|
||||
}
|
||||
macro_rules! is_valid {
|
||||
($node:expr, $value:ident) => {{
|
||||
$node.is_valid($value)
|
||||
|
@ -124,37 +60,6 @@ macro_rules! validate {
|
|||
}};
|
||||
}
|
||||
|
||||
fn compile_small_map<'a>(
|
||||
map: &'a Map<String, Value>,
|
||||
context: &CompilationContext,
|
||||
) -> Result<SmallValidatorsMap, ValidationError<'a>> {
|
||||
let mut properties = Vec::with_capacity(map.len());
|
||||
let keyword_context = context.with_path("properties");
|
||||
for (key, subschema) in map {
|
||||
let property_context = keyword_context.with_path(key.clone());
|
||||
properties.push((
|
||||
key.clone(),
|
||||
compile_validators(subschema, &property_context)?,
|
||||
));
|
||||
}
|
||||
Ok(properties)
|
||||
}
|
||||
fn compile_big_map<'a>(
|
||||
map: &'a Map<String, Value>,
|
||||
context: &CompilationContext,
|
||||
) -> Result<BigValidatorsMap, ValidationError<'a>> {
|
||||
let mut properties = AHashMap::with_capacity(map.len());
|
||||
let keyword_context = context.with_path("properties");
|
||||
for (key, subschema) in map {
|
||||
let property_context = keyword_context.with_path(key.clone());
|
||||
properties.insert(
|
||||
key.clone(),
|
||||
compile_validators(subschema, &property_context)?,
|
||||
);
|
||||
}
|
||||
Ok(properties)
|
||||
}
|
||||
|
||||
/// # Schema example
|
||||
///
|
||||
/// ```json
|
||||
|
@ -345,17 +250,12 @@ impl AdditionalPropertiesNotEmptyFalseValidator<BigValidatorsMap> {
|
|||
}
|
||||
impl<M: PropertiesValidatorsMap> Validate for AdditionalPropertiesNotEmptyFalseValidator<M> {
|
||||
fn is_valid(&self, instance: &Value) -> bool {
|
||||
if let Value::Object(item) = instance {
|
||||
for (property, value) in item {
|
||||
if let Some(node) = self.properties.get_validator(property) {
|
||||
is_valid_pattern_schema!(node, value)
|
||||
}
|
||||
// No extra properties are allowed
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Value::Object(props) = instance {
|
||||
are_properties_valid(&self.properties, props, |_| false)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn validate<'instance>(
|
||||
&self,
|
||||
|
@ -484,18 +384,14 @@ impl AdditionalPropertiesNotEmptyValidator<BigValidatorsMap> {
|
|||
}
|
||||
impl<M: PropertiesValidatorsMap> Validate for AdditionalPropertiesNotEmptyValidator<M> {
|
||||
fn is_valid(&self, instance: &Value) -> bool {
|
||||
if let Value::Object(map) = instance {
|
||||
for (property, value) in map {
|
||||
if let Some(property_validators) = self.properties.get_validator(property) {
|
||||
is_valid_pattern_schema!(property_validators, value)
|
||||
}
|
||||
if !self.node.is_valid(value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Value::Object(props) = instance {
|
||||
are_properties_valid(&self.properties, props, |instance| {
|
||||
self.node.is_valid(instance)
|
||||
})
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn validate<'instance>(
|
||||
&self,
|
||||
|
@ -1263,7 +1159,7 @@ pub(crate) fn compile<'a>(
|
|||
Value::Bool(true) => None, // "additionalProperties" are "true" by default
|
||||
Value::Bool(false) => {
|
||||
if let Some(properties) = properties {
|
||||
dynamic_map!(
|
||||
compile_dynamic_prop_map_validator!(
|
||||
AdditionalPropertiesWithPatternsNotEmptyFalseValidator,
|
||||
properties,
|
||||
compiled_patterns,
|
||||
|
@ -1278,7 +1174,7 @@ pub(crate) fn compile<'a>(
|
|||
}
|
||||
_ => {
|
||||
if let Some(properties) = properties {
|
||||
dynamic_map!(
|
||||
compile_dynamic_prop_map_validator!(
|
||||
AdditionalPropertiesWithPatternsNotEmptyValidator,
|
||||
properties,
|
||||
schema,
|
||||
|
@ -1305,7 +1201,7 @@ pub(crate) fn compile<'a>(
|
|||
Value::Bool(true) => None, // "additionalProperties" are "true" by default
|
||||
Value::Bool(false) => {
|
||||
if let Some(properties) = properties {
|
||||
dynamic_map!(
|
||||
compile_dynamic_prop_map_validator!(
|
||||
AdditionalPropertiesNotEmptyFalseValidator,
|
||||
properties,
|
||||
context
|
||||
|
@ -1317,7 +1213,7 @@ pub(crate) fn compile<'a>(
|
|||
}
|
||||
_ => {
|
||||
if let Some(properties) = properties {
|
||||
dynamic_map!(
|
||||
compile_dynamic_prop_map_validator!(
|
||||
AdditionalPropertiesNotEmptyValidator,
|
||||
properties,
|
||||
schema,
|
||||
|
@ -1331,31 +1227,6 @@ pub(crate) fn compile<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
/// Create a vector of pattern-validators pairs.
|
||||
#[inline]
|
||||
fn compile_patterns<'a>(
|
||||
obj: &'a Map<String, Value>,
|
||||
context: &CompilationContext,
|
||||
) -> Result<PatternedValidators, ValidationError<'a>> {
|
||||
let keyword_context = context.with_path("patternProperties");
|
||||
let mut compiled_patterns = Vec::with_capacity(obj.len());
|
||||
for (pattern, subschema) in obj {
|
||||
let pattern_context = keyword_context.with_path(pattern.to_string());
|
||||
if let Ok(compiled_pattern) = Regex::new(pattern) {
|
||||
let node = compile_validators(subschema, &pattern_context)?;
|
||||
compiled_patterns.push((compiled_pattern, node));
|
||||
} else {
|
||||
return Err(ValidationError::format(
|
||||
JSONPointer::default(),
|
||||
keyword_context.clone().into_pointer(),
|
||||
subschema,
|
||||
"regex",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(compiled_patterns)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests_util;
|
||||
|
|
|
@ -34,6 +34,8 @@ pub(crate) mod property_names;
|
|||
pub(crate) mod ref_;
|
||||
pub(crate) mod required;
|
||||
pub(crate) mod type_;
|
||||
#[cfg(any(feature = "draft201909", feature = "draft202012"))]
|
||||
pub(crate) mod unevaluated_properties;
|
||||
pub(crate) mod unique_items;
|
||||
use crate::{error, validator::Validate};
|
||||
|
||||
|
|
|
@ -3,13 +3,14 @@ use crate::{
|
|||
error::{error, ErrorIterator},
|
||||
keywords::CompilationResult,
|
||||
paths::{InstancePath, JSONPointer},
|
||||
primitive_type::PrimitiveType,
|
||||
resolver::Resolver,
|
||||
schema_node::SchemaNode,
|
||||
validator::Validate,
|
||||
CompilationOptions,
|
||||
CompilationOptions, Draft, ValidationError,
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
use serde_json::Value;
|
||||
use serde_json::{Map, Value};
|
||||
use std::sync::Arc;
|
||||
use url::Url;
|
||||
|
||||
|
@ -123,11 +124,33 @@ impl core::fmt::Display for RefValidator {
|
|||
|
||||
#[inline]
|
||||
pub(crate) fn compile<'a>(
|
||||
_: &'a Value,
|
||||
reference: &'a str,
|
||||
_: &'a Map<String, Value>,
|
||||
schema: &'a Value,
|
||||
context: &CompilationContext,
|
||||
) -> Option<CompilationResult<'a>> {
|
||||
Some(RefValidator::compile(reference, context))
|
||||
Some(
|
||||
schema
|
||||
.as_str()
|
||||
.ok_or_else(|| {
|
||||
ValidationError::single_type_error(
|
||||
JSONPointer::default(),
|
||||
context.clone().into_pointer(),
|
||||
schema,
|
||||
PrimitiveType::String,
|
||||
)
|
||||
})
|
||||
.and_then(|reference| RefValidator::compile(reference, context)),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) const fn supports_adjacent_validation(draft: Draft) -> bool {
|
||||
match draft {
|
||||
#[cfg(feature = "draft201909")]
|
||||
Draft::Draft201909 => true,
|
||||
#[cfg(feature = "draft202012")]
|
||||
Draft::Draft202012 => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -91,6 +91,7 @@ mod keywords;
|
|||
pub mod output;
|
||||
pub mod paths;
|
||||
pub mod primitive_type;
|
||||
pub(crate) mod properties;
|
||||
mod resolver;
|
||||
mod schema_node;
|
||||
mod schemas;
|
||||
|
|
|
@ -213,6 +213,12 @@ impl From<&[PathChunk]> for JSONPointer {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&str> for JSONPointer {
|
||||
fn from(value: &str) -> Self {
|
||||
JSONPointer(vec![value.to_string().into()])
|
||||
}
|
||||
}
|
||||
|
||||
/// An absolute reference
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AbsolutePath(url::Url);
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
use ahash::AHashMap;
|
||||
use fancy_regex::Regex;
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use crate::{
|
||||
compilation::{compile_validators, context::CompilationContext},
|
||||
paths::JSONPointer,
|
||||
schema_node::SchemaNode,
|
||||
validator::Validate,
|
||||
ValidationError,
|
||||
};
|
||||
|
||||
pub(crate) type PatternedValidators = Vec<(Regex, SchemaNode)>;
|
||||
|
||||
/// A value that can look up property validators by name.
|
||||
pub(crate) trait PropertiesValidatorsMap: Send + Sync {
|
||||
fn from_map<'a>(
|
||||
map: &'a Map<String, Value>,
|
||||
context: &CompilationContext,
|
||||
) -> Result<Self, ValidationError<'a>>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn get_validator(&self, property: &str) -> Option<&SchemaNode>;
|
||||
fn get_key_validator(&self, property: &str) -> Option<(&String, &SchemaNode)>;
|
||||
}
|
||||
|
||||
// We're defining two different property validator map implementations, one for small map sizes and
|
||||
// one for large map sizes, to optimize the performance depending on the number of properties
|
||||
// present.
|
||||
//
|
||||
// Implementors should use `compile_dynamic_prop_map_validator!` for building their validator maps
|
||||
// at runtime, as it wraps up all of the logic to choose the right map size and then build and
|
||||
// compile the validator.
|
||||
pub(crate) type SmallValidatorsMap = Vec<(String, SchemaNode)>;
|
||||
pub(crate) type BigValidatorsMap = AHashMap<String, SchemaNode>;
|
||||
|
||||
impl PropertiesValidatorsMap for SmallValidatorsMap {
|
||||
fn from_map<'a>(
|
||||
map: &'a Map<String, Value>,
|
||||
context: &CompilationContext,
|
||||
) -> Result<Self, ValidationError<'a>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
compile_small_map(map, context)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_validator(&self, property: &str) -> Option<&SchemaNode> {
|
||||
for (prop, node) in self {
|
||||
if prop == property {
|
||||
return Some(node);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
#[inline]
|
||||
fn get_key_validator(&self, property: &str) -> Option<(&String, &SchemaNode)> {
|
||||
for (prop, node) in self {
|
||||
if prop == property {
|
||||
return Some((prop, node));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl PropertiesValidatorsMap for BigValidatorsMap {
|
||||
fn from_map<'a>(
|
||||
map: &'a Map<String, Value>,
|
||||
context: &CompilationContext,
|
||||
) -> Result<Self, ValidationError<'a>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
compile_big_map(map, context)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_validator(&self, property: &str) -> Option<&SchemaNode> {
|
||||
self.get(property)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_key_validator(&self, property: &str) -> Option<(&String, &SchemaNode)> {
|
||||
self.get_key_value(property)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn compile_small_map<'a>(
|
||||
map: &'a Map<String, Value>,
|
||||
context: &CompilationContext,
|
||||
) -> Result<SmallValidatorsMap, ValidationError<'a>> {
|
||||
let mut properties = Vec::with_capacity(map.len());
|
||||
let keyword_context = context.with_path("properties");
|
||||
for (key, subschema) in map {
|
||||
let property_context = keyword_context.with_path(key.clone());
|
||||
properties.push((
|
||||
key.clone(),
|
||||
compile_validators(subschema, &property_context)?,
|
||||
));
|
||||
}
|
||||
Ok(properties)
|
||||
}
|
||||
|
||||
pub(crate) fn compile_big_map<'a>(
|
||||
map: &'a Map<String, Value>,
|
||||
context: &CompilationContext,
|
||||
) -> Result<BigValidatorsMap, ValidationError<'a>> {
|
||||
let mut properties = AHashMap::with_capacity(map.len());
|
||||
let keyword_context = context.with_path("properties");
|
||||
for (key, subschema) in map {
|
||||
let property_context = keyword_context.with_path(key.clone());
|
||||
properties.insert(
|
||||
key.clone(),
|
||||
compile_validators(subschema, &property_context)?,
|
||||
);
|
||||
}
|
||||
Ok(properties)
|
||||
}
|
||||
|
||||
pub(crate) fn are_properties_valid<M, F>(prop_map: &M, props: &Map<String, Value>, check: F) -> bool
|
||||
where
|
||||
M: PropertiesValidatorsMap,
|
||||
F: Fn(&Value) -> bool,
|
||||
{
|
||||
props.iter().all(|(property, instance)| {
|
||||
if let Some(validator) = prop_map.get_validator(property) {
|
||||
validator.is_valid(instance)
|
||||
} else {
|
||||
check(instance)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a vector of pattern-validators pairs.
|
||||
#[inline]
|
||||
pub(crate) fn compile_patterns<'a>(
|
||||
obj: &'a Map<String, Value>,
|
||||
context: &CompilationContext,
|
||||
) -> Result<PatternedValidators, ValidationError<'a>> {
|
||||
let keyword_context = context.with_path("patternProperties");
|
||||
let mut compiled_patterns = Vec::with_capacity(obj.len());
|
||||
for (pattern, subschema) in obj {
|
||||
let pattern_context = keyword_context.with_path(pattern.to_string());
|
||||
if let Ok(compiled_pattern) = Regex::new(pattern) {
|
||||
let node = compile_validators(subschema, &pattern_context)?;
|
||||
compiled_patterns.push((compiled_pattern, node));
|
||||
} else {
|
||||
return Err(ValidationError::format(
|
||||
JSONPointer::default(),
|
||||
keyword_context.clone().into_pointer(),
|
||||
subschema,
|
||||
"regex",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(compiled_patterns)
|
||||
}
|
||||
|
||||
macro_rules! compile_dynamic_prop_map_validator {
|
||||
($validator:tt, $properties:ident, $( $arg:expr ),* $(,)*) => {{
|
||||
if let Value::Object(map) = $properties {
|
||||
if map.len() < 40 {
|
||||
Some($validator::<SmallValidatorsMap>::compile(
|
||||
map, $($arg, )*
|
||||
))
|
||||
} else {
|
||||
Some($validator::<BigValidatorsMap>::compile(
|
||||
map, $($arg, )*
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Some(Err(ValidationError::null_schema()))
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) use compile_dynamic_prop_map_validator;
|
|
@ -50,6 +50,7 @@ impl Draft {
|
|||
#[allow(clippy::match_same_arms)]
|
||||
pub(crate) fn get_validator(self, keyword: &str) -> Option<CompileFunc> {
|
||||
match keyword {
|
||||
"$ref" => Some(keywords::ref_::compile),
|
||||
"additionalItems" => Some(keywords::additional_items::compile),
|
||||
"additionalProperties" => Some(keywords::additional_properties::compile),
|
||||
"allOf" => Some(keywords::all_of::compile),
|
||||
|
@ -167,6 +168,13 @@ impl Draft {
|
|||
#[cfg(feature = "draft202012")]
|
||||
Draft::Draft202012 => Some(keywords::type_::compile),
|
||||
},
|
||||
"unevaluatedProperties" => match self {
|
||||
#[cfg(feature = "draft201909")]
|
||||
Draft::Draft201909 => Some(keywords::unevaluated_properties::compile),
|
||||
#[cfg(feature = "draft202012")]
|
||||
Draft::Draft202012 => Some(keywords::unevaluated_properties::compile),
|
||||
_ => None,
|
||||
},
|
||||
"uniqueItems" => Some(keywords::unique_items::compile),
|
||||
_ => None,
|
||||
}
|
||||
|
|
|
@ -129,6 +129,15 @@ impl<'a> PartialApplication<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// A shortcut to check whether the partial represents passed validation.
|
||||
#[must_use]
|
||||
pub(crate) const fn is_valid(&self) -> bool {
|
||||
match self {
|
||||
Self::Valid { .. } => true,
|
||||
Self::Invalid { .. } => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the annotation that will be returned for the current validator. If this
|
||||
/// `PartialApplication` is invalid then this method does nothing
|
||||
pub(crate) fn annotate(&mut self, new_annotations: Annotations<'a>) {
|
||||
|
|
|
@ -13,8 +13,6 @@ use std::fs;
|
|||
// These depend on the new `$defs` keyword (which is renamed from `definitions`)
|
||||
r"id_0_[0-6]",
|
||||
// Various types of new behavior used in the `$ref` context
|
||||
"ref_5_1",
|
||||
"ref_13_0",
|
||||
"refRemote_4_0",
|
||||
"refRemote_4_1",
|
||||
"recursiveRef_0_3",
|
||||
|
@ -35,7 +33,6 @@ use std::fs;
|
|||
r"optional_format_duration_.+", // https://github.com/Stranger6667/jsonschema-rs/issues/265
|
||||
r"optional_format_uuid_.+", // https://github.com/Stranger6667/jsonschema-rs/issues/266
|
||||
r"unevaluatedItems_.+",
|
||||
r"unevaluatedProperties_.+",
|
||||
}))]
|
||||
#[cfg_attr(feature = "draft202012", json_schema_test_suite("tests/suite", "draft2020-12", {
|
||||
r"optional_format_idn_hostname_0_\d+", // https://github.com/Stranger6667/jsonschema-rs/issues/101
|
||||
|
@ -43,8 +40,6 @@ use std::fs;
|
|||
// These depend on the new `$defs` keyword (which is renamed from `definitions`)
|
||||
r"id_0_[0-6]",
|
||||
// Various types of new behavior used in the `$ref` context
|
||||
"ref_5_1",
|
||||
"ref_13_0",
|
||||
"refRemote_4_0",
|
||||
"refRemote_4_1",
|
||||
"recursiveRef_0_3",
|
||||
|
@ -72,7 +67,6 @@ use std::fs;
|
|||
r"optional_format_uri_reference_.+",
|
||||
r"optional_format_uri_template_.+",
|
||||
r"unevaluatedItems_.+",
|
||||
r"unevaluatedProperties_.+",
|
||||
}))]
|
||||
fn test_draft(_server_address: &str, test_case: TestCase) {
|
||||
let draft_version = match test_case.draft_version.as_ref() {
|
||||
|
@ -91,7 +85,7 @@ fn test_draft(_server_address: &str, test_case: TestCase) {
|
|||
.with_meta_schemas()
|
||||
.should_validate_formats(true)
|
||||
.compile(&test_case.schema)
|
||||
.unwrap();
|
||||
.expect("should not fail to compile schema");
|
||||
|
||||
let result = compiled.validate(&test_case.instance);
|
||||
|
||||
|
@ -100,52 +94,74 @@ fn test_draft(_server_address: &str, test_case: TestCase) {
|
|||
let first_error = errors_iterator.next();
|
||||
assert!(
|
||||
first_error.is_none(),
|
||||
"Schema: {}\nInstance: {}\nError: {:?}",
|
||||
"Test case should not have validation errors:\nGroup: {}\nTest case: {}\nSchema: {}\nInstance: {}\nError: {:?}",
|
||||
test_case.group_description,
|
||||
test_case.test_case_description,
|
||||
test_case.schema,
|
||||
test_case.instance,
|
||||
first_error,
|
||||
);
|
||||
}
|
||||
if !compiled.is_valid(&test_case.instance) {
|
||||
panic!(
|
||||
"Schema: {}\nInstance: {}\nError: It is supposed to be VALID!",
|
||||
test_case.schema, test_case.instance,
|
||||
);
|
||||
}
|
||||
let output = compiled.apply(&test_case.instance).basic();
|
||||
if !output.is_valid() {
|
||||
panic!(
|
||||
"Schema: {}\nInstance: {}\nError: {:?}",
|
||||
test_case.schema, test_case.instance, output
|
||||
);
|
||||
}
|
||||
} else {
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"Schema: {}\nInstance: {}\nError: It is supposed to be INVALID!",
|
||||
compiled.is_valid(&test_case.instance),
|
||||
"Test case should be valid:\nGroup: {}\nTest case: {}\nSchema: {}\nInstance: {}",
|
||||
test_case.group_description,
|
||||
test_case.test_case_description,
|
||||
test_case.schema,
|
||||
test_case.instance,
|
||||
);
|
||||
let errors: Vec<_> = result.expect_err("Errors").collect();
|
||||
let output = compiled.apply(&test_case.instance).basic();
|
||||
assert!(
|
||||
output.is_valid(),
|
||||
"Test case should be valid via basic output:\nGroup: {}\nTest case: {}\nSchema: {}\nInstance: {}\nError: {:?}",
|
||||
test_case.group_description,
|
||||
test_case.test_case_description,
|
||||
test_case.schema,
|
||||
test_case.instance,
|
||||
output
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"Test case should have validation errors:\nGroup: {}\nTest case: {}\nSchema: {}\nInstance: {}",
|
||||
test_case.group_description,
|
||||
test_case.test_case_description,
|
||||
test_case.schema,
|
||||
test_case.instance,
|
||||
);
|
||||
let errors = result.unwrap_err();
|
||||
for error in errors {
|
||||
let pointer = error.instance_path.to_string();
|
||||
assert_eq!(test_case.instance.pointer(&pointer), Some(&*error.instance))
|
||||
}
|
||||
if compiled.is_valid(&test_case.instance) {
|
||||
panic!(
|
||||
"Schema: {}\nInstance: {}\nError: It is supposed to be INVALID!",
|
||||
test_case.schema, test_case.instance,
|
||||
assert_eq!(
|
||||
test_case.instance.pointer(&pointer), Some(&*error.instance),
|
||||
"Expected error instance did not match actual error instance:\nGroup: {}\nTest case: {}\nSchema: {}\nInstance: {}\nExpected pointer: {:#?}\nActual pointer: {:#?}",
|
||||
test_case.group_description,
|
||||
test_case.test_case_description,
|
||||
test_case.schema,
|
||||
test_case.instance,
|
||||
&*error.instance,
|
||||
&pointer,
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
!compiled.is_valid(&test_case.instance),
|
||||
"Test case should be invalid:\nGroup: {}\nTest case: {}\nSchema: {}\nInstance: {}",
|
||||
test_case.group_description,
|
||||
test_case.test_case_description,
|
||||
test_case.schema,
|
||||
test_case.instance,
|
||||
);
|
||||
let output = compiled.apply(&test_case.instance).basic();
|
||||
if output.is_valid() {
|
||||
panic!(
|
||||
"Schema: {}\nInstance: {}\nError: It is supposed to be INVALID!",
|
||||
test_case.schema, test_case.instance,
|
||||
assert!(
|
||||
!output.is_valid(),
|
||||
"Test case should be invalid via basic output:\nGroup: {}\nTest case: {}\nSchema: {}\nInstance: {}",
|
||||
test_case.group_description,
|
||||
test_case.test_case_description,
|
||||
test_case.schema,
|
||||
test_case.instance,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instance_path() {
|
||||
|
|
Loading…
Reference in New Issue