use crate::{
compiler,
error::{no_error, ErrorIterator, ValidationError},
evaluation::{Annotations, ErrorDescription, EvaluationNode},
keywords::CompilationResult,
node::SchemaNode,
options::PatternEngineOptions,
paths::{LazyLocation, Location, RefTracker},
properties::{
are_properties_valid, compile_big_map, compile_dynamic_prop_map_validator,
compile_fancy_regex_patterns, compile_regex_patterns, compile_small_map, BigValidatorsMap,
CompiledPattern, PropertiesValidatorsMap, SmallValidatorsMap, HASHMAP_THRESHOLD,
},
regex::RegexEngine,
types::JsonType,
validator::{EvaluationResult, Validate, ValidationContext},
};
use ahash::AHashMap;
use referencing::Uri;
use serde_json::{Map, Value};
use std::sync::Arc;
pub(crate) struct AdditionalPropertiesValidator {
node: SchemaNode,
}
impl AdditionalPropertiesValidator {
#[inline]
pub(crate) fn compile<'a>(schema: &'a Value, ctx: &compiler::Context) -> CompilationResult<'a> {
let ctx = ctx.new_at_location("additionalProperties");
Ok(Box::new(AdditionalPropertiesValidator {
node: compiler::compile(&ctx, ctx.as_resource_ref(schema))?,
}))
}
}
impl Validate for AdditionalPropertiesValidator {
fn is_valid(&self, instance: &Value, ctx: &mut ValidationContext) -> bool {
if let Value::Object(item) = instance {
item.values().all(|i| self.node.is_valid(i, ctx))
} else {
true
}
}
fn validate<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> Result<(), ValidationError<'i>> {
if let Value::Object(item) = instance {
for (name, value) in item {
self.node
.validate(value, &location.push(name), tracker, ctx)?;
}
}
Ok(())
}
fn iter_errors<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> ErrorIterator<'i> {
if let Value::Object(item) = instance {
let mut errors = Vec::new();
for (name, value) in item {
errors.extend(self.node.iter_errors(
value,
&location.push(name.as_str()),
tracker,
ctx,
));
}
ErrorIterator::from_iterator(errors.into_iter())
} else {
no_error()
}
}
fn evaluate(
&self,
instance: &Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> EvaluationResult {
if let Value::Object(item) = instance {
let mut children = Vec::with_capacity(item.len());
for (name, value) in item {
children.push(self.node.evaluate_instance(
value,
&location.push(name.as_str()),
tracker,
ctx,
));
}
let mut result = EvaluationResult::from_children(children);
let annotated_props = item
.keys()
.cloned()
.map(serde_json::Value::String)
.collect();
result.annotate(Annotations::new(serde_json::Value::Array(annotated_props)));
result
} else {
EvaluationResult::valid_empty()
}
}
}
pub(crate) struct AdditionalPropertiesFalseValidator {
location: Location,
}
impl AdditionalPropertiesFalseValidator {
#[inline]
pub(crate) fn compile<'a>(location: Location) -> CompilationResult<'a> {
Ok(Box::new(AdditionalPropertiesFalseValidator { location }))
}
}
impl Validate for AdditionalPropertiesFalseValidator {
fn is_valid(&self, instance: &Value, _ctx: &mut ValidationContext) -> bool {
if let Value::Object(item) = instance {
item.iter().next().is_none()
} else {
true
}
}
fn validate<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
_ctx: &mut ValidationContext,
) -> Result<(), ValidationError<'i>> {
if let Value::Object(item) = instance {
if let Some((_, value)) = item.iter().next() {
return Err(ValidationError::false_schema(
self.location.clone(),
crate::paths::capture_evaluation_path(tracker, &self.location),
location.into(),
value,
));
}
}
Ok(())
}
}
pub(crate) struct AdditionalPropertiesNotEmptyFalseValidator<M: PropertiesValidatorsMap> {
properties: M,
location: Location,
}
impl AdditionalPropertiesNotEmptyFalseValidator<SmallValidatorsMap> {
#[inline]
pub(crate) fn compile<'a>(
map: &'a Map<String, Value>,
ctx: &compiler::Context,
) -> CompilationResult<'a> {
Ok(Box::new(AdditionalPropertiesNotEmptyFalseValidator {
properties: compile_small_map(ctx, map)?,
location: ctx.location().join("additionalProperties"),
}))
}
}
impl AdditionalPropertiesNotEmptyFalseValidator<BigValidatorsMap> {
#[inline]
pub(crate) fn compile<'a>(
map: &'a Map<String, Value>,
ctx: &compiler::Context,
) -> CompilationResult<'a> {
Ok(Box::new(AdditionalPropertiesNotEmptyFalseValidator {
properties: compile_big_map(ctx, map)?,
location: ctx.location().join("additionalProperties"),
}))
}
}
impl<M: PropertiesValidatorsMap> Validate for AdditionalPropertiesNotEmptyFalseValidator<M> {
fn is_valid(&self, instance: &Value, ctx: &mut ValidationContext) -> bool {
if let Value::Object(props) = instance {
are_properties_valid(&self.properties, props, ctx, |_, _| false)
} else {
true
}
}
fn validate<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> Result<(), ValidationError<'i>> {
if let Value::Object(item) = instance {
for (property, value) in item {
if let Some((name, node)) = self.properties.get_key_validator(property) {
node.validate(value, &location.push(name), tracker, ctx)?;
} else {
return Err(ValidationError::additional_properties(
self.location.clone(),
crate::paths::capture_evaluation_path(tracker, &self.location),
location.into(),
instance,
vec![property.clone()],
));
}
}
}
Ok(())
}
fn iter_errors<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> ErrorIterator<'i> {
if let Value::Object(item) = instance {
let mut errors = vec![];
let mut unexpected = vec![];
for (property, value) in item {
if let Some((name, node)) = self.properties.get_key_validator(property) {
errors.extend(node.iter_errors(
value,
&location.push(name.as_str()),
tracker,
ctx,
));
} else {
unexpected.push(property.clone());
}
}
if !unexpected.is_empty() {
errors.push(ValidationError::additional_properties(
self.location.clone(),
crate::paths::capture_evaluation_path(tracker, &self.location),
location.into(),
instance,
unexpected,
));
}
ErrorIterator::from_iterator(errors.into_iter())
} else {
no_error()
}
}
fn evaluate(
&self,
instance: &Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> EvaluationResult {
if let Value::Object(item) = instance {
let mut unexpected = Vec::with_capacity(item.len());
let mut children = Vec::with_capacity(item.len());
for (property, value) in item {
if let Some((_name, node)) = self.properties.get_key_validator(property) {
children.push(node.evaluate_instance(
value,
&location.push(property.as_str()),
tracker,
ctx,
));
} else {
unexpected.push(property.clone());
}
}
let mut result = EvaluationResult::from_children(children);
if !unexpected.is_empty() {
let eval_path = crate::paths::capture_evaluation_path(tracker, &self.location);
result.mark_errored(ErrorDescription::from_validation_error(
&ValidationError::additional_properties(
self.location.clone(),
eval_path,
location.into(),
instance,
unexpected,
),
));
}
result
} else {
EvaluationResult::valid_empty()
}
}
}
pub(crate) struct AdditionalPropertiesNotEmptyFalseWithRequired1Validator<
M: PropertiesValidatorsMap,
> {
properties: M,
required: String,
location: Location,
required_location: Location,
}
impl AdditionalPropertiesNotEmptyFalseWithRequired1Validator<SmallValidatorsMap> {
#[inline]
pub(crate) fn compile<'a>(
map: &'a Map<String, Value>,
ctx: &compiler::Context,
required: String,
) -> CompilationResult<'a> {
Ok(Box::new(
AdditionalPropertiesNotEmptyFalseWithRequired1Validator {
properties: compile_small_map(ctx, map)?,
required,
location: ctx.location().join("additionalProperties"),
required_location: ctx.location().join("required"),
},
))
}
}
impl AdditionalPropertiesNotEmptyFalseWithRequired1Validator<BigValidatorsMap> {
#[inline]
pub(crate) fn compile<'a>(
map: &'a Map<String, Value>,
ctx: &compiler::Context,
required: String,
) -> CompilationResult<'a> {
Ok(Box::new(
AdditionalPropertiesNotEmptyFalseWithRequired1Validator {
properties: compile_big_map(ctx, map)?,
required,
location: ctx.location().join("additionalProperties"),
required_location: ctx.location().join("required"),
},
))
}
}
impl<M: PropertiesValidatorsMap> Validate
for AdditionalPropertiesNotEmptyFalseWithRequired1Validator<M>
{
fn is_valid(&self, instance: &Value, ctx: &mut ValidationContext) -> bool {
if let Value::Object(props) = instance {
if props.is_empty() {
return false;
}
let mut found_required = false;
for (property, value) in props {
if let Some(node) = self.properties.get_validator(property) {
if !node.is_valid(value, ctx) {
return false;
}
if property == &self.required {
found_required = true;
}
} else {
return false;
}
}
found_required
} else {
true
}
}
fn validate<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> Result<(), ValidationError<'i>> {
if let Value::Object(item) = instance {
let mut found_required = false;
for (property, value) in item {
if let Some((name, node)) = self.properties.get_key_validator(property) {
node.validate(value, &location.push(name), tracker, ctx)?;
if property == &self.required {
found_required = true;
}
} else {
return Err(ValidationError::additional_properties(
self.location.clone(),
crate::paths::capture_evaluation_path(tracker, &self.location),
location.into(),
instance,
vec![property.clone()],
));
}
}
if !found_required {
return Err(ValidationError::required(
self.required_location.clone(),
crate::paths::capture_evaluation_path(tracker, &self.required_location),
location.into(),
instance,
Value::String(self.required.clone()),
));
}
}
Ok(())
}
fn iter_errors<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> ErrorIterator<'i> {
if let Value::Object(item) = instance {
let mut errors = vec![];
let mut unexpected = vec![];
let mut found_required = false;
for (property, value) in item {
if let Some((name, node)) = self.properties.get_key_validator(property) {
errors.extend(node.iter_errors(
value,
&location.push(name.as_str()),
tracker,
ctx,
));
if property == &self.required {
found_required = true;
}
} else {
unexpected.push(property.clone());
}
}
if !unexpected.is_empty() {
errors.push(ValidationError::additional_properties(
self.location.clone(),
crate::paths::capture_evaluation_path(tracker, &self.location),
location.into(),
instance,
unexpected,
));
}
if !found_required {
errors.push(ValidationError::required(
self.required_location.clone(),
crate::paths::capture_evaluation_path(tracker, &self.required_location),
location.into(),
instance,
Value::String(self.required.clone()),
));
}
ErrorIterator::from_iterator(errors.into_iter())
} else {
no_error()
}
}
fn evaluate(
&self,
instance: &Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> EvaluationResult {
if let Value::Object(item) = instance {
let mut unexpected = Vec::with_capacity(item.len());
let mut children = Vec::with_capacity(item.len());
let mut found_required = false;
for (property, value) in item {
if let Some((_name, node)) = self.properties.get_key_validator(property) {
children.push(node.evaluate_instance(
value,
&location.push(property.as_str()),
tracker,
ctx,
));
if property == &self.required {
found_required = true;
}
} else {
unexpected.push(property.clone());
}
}
let mut result = EvaluationResult::from_children(children);
if !unexpected.is_empty() {
let eval_path = crate::paths::capture_evaluation_path(tracker, &self.location);
result.mark_errored(ErrorDescription::from_validation_error(
&ValidationError::additional_properties(
self.location.clone(),
eval_path,
location.into(),
instance,
unexpected,
),
));
}
if !found_required {
let eval_path =
crate::paths::capture_evaluation_path(tracker, &self.required_location);
result.mark_errored(ErrorDescription::from_validation_error(
&ValidationError::required(
self.required_location.clone(),
eval_path,
location.into(),
instance,
Value::String(self.required.clone()),
),
));
}
result
} else {
EvaluationResult::valid_empty()
}
}
}
pub(crate) struct AdditionalPropertiesNotEmptyValidator<M: PropertiesValidatorsMap> {
node: SchemaNode,
properties: M,
}
impl AdditionalPropertiesNotEmptyValidator<SmallValidatorsMap> {
#[inline]
pub(crate) fn compile<'a>(
map: &'a Map<String, Value>,
ctx: &compiler::Context,
schema: &'a Value,
) -> CompilationResult<'a> {
let kctx = ctx.new_at_location("additionalProperties");
Ok(Box::new(AdditionalPropertiesNotEmptyValidator {
properties: compile_small_map(ctx, map)?,
node: compiler::compile(&kctx, kctx.as_resource_ref(schema))?,
}))
}
}
impl AdditionalPropertiesNotEmptyValidator<BigValidatorsMap> {
#[inline]
pub(crate) fn compile<'a>(
map: &'a Map<String, Value>,
ctx: &compiler::Context,
schema: &'a Value,
) -> CompilationResult<'a> {
let kctx = ctx.new_at_location("additionalProperties");
Ok(Box::new(AdditionalPropertiesNotEmptyValidator {
properties: compile_big_map(ctx, map)?,
node: compiler::compile(&kctx, kctx.as_resource_ref(schema))?,
}))
}
}
impl<M: PropertiesValidatorsMap> Validate for AdditionalPropertiesNotEmptyValidator<M> {
fn is_valid(&self, instance: &Value, ctx: &mut ValidationContext) -> bool {
if let Value::Object(props) = instance {
are_properties_valid(&self.properties, props, ctx, |instance, ctx| {
self.node.is_valid(instance, ctx)
})
} else {
true
}
}
fn validate<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> Result<(), ValidationError<'i>> {
if let Value::Object(props) = instance {
for (property, value) in props {
let property_location = location.push(property);
if let Some(validator) = self.properties.get_validator(property) {
validator.validate(value, &property_location, tracker, ctx)?;
} else {
self.node
.validate(value, &property_location, tracker, ctx)?;
}
}
}
Ok(())
}
fn iter_errors<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> ErrorIterator<'i> {
if let Value::Object(map) = instance {
let mut errors = vec![];
for (property, value) in map {
if let Some((name, property_validators)) =
self.properties.get_key_validator(property)
{
errors.extend(property_validators.iter_errors(
value,
&location.push(name.as_str()),
tracker,
ctx,
));
} else {
errors.extend(self.node.iter_errors(
value,
&location.push(property.as_str()),
tracker,
ctx,
));
}
}
ErrorIterator::from_iterator(errors.into_iter())
} else {
no_error()
}
}
fn evaluate(
&self,
instance: &Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> EvaluationResult {
if let Value::Object(map) = instance {
let mut matched_propnames = Vec::with_capacity(map.len());
let mut children = Vec::with_capacity(map.len());
for (property, value) in map {
let path = location.push(property.as_str());
if let Some((_name, property_validators)) =
self.properties.get_key_validator(property)
{
children
.push(property_validators.evaluate_instance(value, &path, tracker, ctx));
} else {
children.push(self.node.evaluate_instance(value, &path, tracker, ctx));
matched_propnames.push(property.clone());
}
}
let mut result = EvaluationResult::from_children(children);
if !matched_propnames.is_empty() {
result.annotate(Annotations::new(Value::from(matched_propnames)));
}
result
} else {
EvaluationResult::valid_empty()
}
}
}
pub(crate) struct AdditionalPropertiesWithPatternsValidator<R> {
node: SchemaNode,
patterns: Vec<(R, SchemaNode)>,
pattern_keyword_path: Location,
pattern_keyword_absolute_location: Option<Arc<Uri<String>>>,
}
impl<R: RegexEngine> Validate for AdditionalPropertiesWithPatternsValidator<R> {
fn is_valid(&self, instance: &Value, ctx: &mut ValidationContext) -> bool {
if let Value::Object(item) = instance {
for (property, value) in item {
let mut has_match = false;
for (re, node) in &self.patterns {
if re.is_match(property).unwrap_or(false) {
has_match = true;
if !node.is_valid(value, ctx) {
return false;
}
}
}
if !has_match && !self.node.is_valid(value, ctx) {
return false;
}
}
}
true
}
fn validate<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> Result<(), ValidationError<'i>> {
if let Value::Object(item) = instance {
for (property, value) in item {
let property_location = location.push(property);
let mut has_match = false;
for (re, node) in &self.patterns {
if re.is_match(property).unwrap_or(false) {
has_match = true;
node.validate(value, &property_location, tracker, ctx)?;
}
}
if !has_match {
self.node
.validate(value, &property_location, tracker, ctx)?;
}
}
}
Ok(())
}
fn iter_errors<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> ErrorIterator<'i> {
if let Value::Object(item) = instance {
let mut errors = vec![];
for (property, value) in item {
let mut has_match = false;
for (re, node) in &self.patterns {
if re.is_match(property).unwrap_or(false) {
has_match = true;
errors.extend(node.iter_errors(
value,
&location.push(property.as_str()),
tracker,
ctx,
));
}
}
if !has_match {
errors.extend(self.node.iter_errors(
value,
&location.push(property.as_str()),
tracker,
ctx,
));
}
}
ErrorIterator::from_iterator(errors.into_iter())
} else {
no_error()
}
}
fn evaluate(
&self,
instance: &Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> EvaluationResult {
if let Value::Object(item) = instance {
let mut pattern_matched_propnames = Vec::with_capacity(item.len());
let mut additional_matched_propnames = Vec::with_capacity(item.len());
let mut children = Vec::with_capacity(item.len());
for (property, value) in item {
let path = location.push(property.as_str());
let mut has_match = false;
for (pattern, node) in &self.patterns {
if pattern.is_match(property).unwrap_or(false) {
has_match = true;
pattern_matched_propnames.push(property.clone());
children.push(node.evaluate_instance(value, &path, tracker, ctx));
}
}
if !has_match {
additional_matched_propnames.push(property.clone());
children.push(self.node.evaluate_instance(value, &path, tracker, ctx));
}
}
if !pattern_matched_propnames.is_empty() {
let annotation = Annotations::new(Value::from(pattern_matched_propnames));
let schema_location = crate::evaluation::format_schema_location(
&self.pattern_keyword_path,
self.pattern_keyword_absolute_location.as_ref(),
);
children.push(EvaluationNode::valid(
self.pattern_keyword_path.clone(),
self.pattern_keyword_absolute_location.clone(),
schema_location,
location.into(),
Some(annotation),
Vec::new(),
));
}
let mut result = EvaluationResult::from_children(children);
if !additional_matched_propnames.is_empty() {
result.annotate(Annotations::new(Value::from(additional_matched_propnames)));
}
result
} else {
EvaluationResult::valid_empty()
}
}
}
pub(crate) struct AdditionalPropertiesWithPatternsFalseValidator<R> {
patterns: Vec<(R, SchemaNode)>,
location: Location,
pattern_keyword_path: Location,
pattern_keyword_absolute_location: Option<Arc<Uri<String>>>,
}
impl<R: RegexEngine> Validate for AdditionalPropertiesWithPatternsFalseValidator<R> {
fn is_valid(&self, instance: &Value, ctx: &mut ValidationContext) -> bool {
if let Value::Object(item) = instance {
for (property, value) in item {
let mut has_match = false;
for (re, node) in &self.patterns {
if re.is_match(property).unwrap_or(false) {
has_match = true;
if !node.is_valid(value, ctx) {
return false;
}
}
}
if !has_match {
return false;
}
}
}
true
}
fn validate<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> Result<(), ValidationError<'i>> {
if let Value::Object(item) = instance {
for (property, value) in item {
let property_location = location.push(property);
let mut has_match = false;
for (re, node) in &self.patterns {
if re.is_match(property).unwrap_or(false) {
has_match = true;
node.validate(value, &property_location, tracker, ctx)?;
}
}
if !has_match {
return Err(ValidationError::additional_properties(
self.location.clone(),
crate::paths::capture_evaluation_path(tracker, &self.location),
location.into(),
instance,
vec![property.clone()],
));
}
}
}
Ok(())
}
fn iter_errors<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> ErrorIterator<'i> {
if let Value::Object(item) = instance {
let mut errors = vec![];
let mut unexpected = vec![];
for (property, value) in item {
let mut has_match = false;
for (re, node) in &self.patterns {
if re.is_match(property).unwrap_or(false) {
has_match = true;
errors.extend(node.iter_errors(
value,
&location.push(property.as_str()),
tracker,
ctx,
));
}
}
if !has_match {
unexpected.push(property.clone());
}
}
if !unexpected.is_empty() {
errors.push(ValidationError::additional_properties(
self.location.clone(),
crate::paths::capture_evaluation_path(tracker, &self.location),
location.into(),
instance,
unexpected,
));
}
ErrorIterator::from_iterator(errors.into_iter())
} else {
no_error()
}
}
fn evaluate(
&self,
instance: &Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> EvaluationResult {
if let Value::Object(item) = instance {
let mut unexpected = Vec::with_capacity(item.len());
let mut pattern_matched_props = Vec::with_capacity(item.len());
let mut children = Vec::with_capacity(item.len());
for (property, value) in item {
let path = location.push(property.as_str());
let mut has_match = false;
for (pattern, node) in &self.patterns {
if pattern.is_match(property).unwrap_or(false) {
has_match = true;
pattern_matched_props.push(property.clone());
children.push(node.evaluate_instance(value, &path, tracker, ctx));
}
}
if !has_match {
unexpected.push(property.clone());
}
}
if !pattern_matched_props.is_empty() {
let annotation = Annotations::new(Value::from(pattern_matched_props));
let schema_location = crate::evaluation::format_schema_location(
&self.pattern_keyword_path,
self.pattern_keyword_absolute_location.as_ref(),
);
children.push(EvaluationNode::valid(
self.pattern_keyword_path.clone(),
self.pattern_keyword_absolute_location.clone(),
schema_location,
location.into(),
Some(annotation),
Vec::new(),
));
}
let mut result = EvaluationResult::from_children(children);
if !unexpected.is_empty() {
let eval_path = crate::paths::capture_evaluation_path(tracker, &self.location);
result.mark_errored(ErrorDescription::from_validation_error(
&ValidationError::additional_properties(
self.location.clone(),
eval_path,
location.into(),
instance,
unexpected,
),
));
}
result
} else {
EvaluationResult::valid_empty()
}
}
}
pub(crate) struct AdditionalPropertiesWithPatternsNotEmptyValidator<M: PropertiesValidatorsMap, R> {
node: SchemaNode,
properties: M,
patterns: Vec<(R, SchemaNode)>,
property_pattern_indices: AHashMap<String, Box<[usize]>>,
}
impl<M: PropertiesValidatorsMap, R: RegexEngine> Validate
for AdditionalPropertiesWithPatternsNotEmptyValidator<M, R>
{
fn is_valid(&self, instance: &Value, ctx: &mut ValidationContext) -> bool {
if let Value::Object(item) = instance {
for (property, value) in item {
if let Some(node) = self.properties.get_validator(property) {
if !node.is_valid(value, ctx) {
return false;
}
if let Some(pattern_indices) = self.property_pattern_indices.get(property) {
for &idx in pattern_indices {
if !self.patterns[idx].1.is_valid(value, ctx) {
return false;
}
}
}
} else {
let mut has_match = false;
for (re, node) in &self.patterns {
if re.is_match(property).unwrap_or(false) {
has_match = true;
if !node.is_valid(value, ctx) {
return false;
}
}
}
if !has_match && !self.node.is_valid(value, ctx) {
return false;
}
}
}
true
} else {
true
}
}
fn validate<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> Result<(), ValidationError<'i>> {
if let Value::Object(item) = instance {
for (property, value) in item {
if let Some((name, node)) = self.properties.get_key_validator(property) {
let name_location = location.push(name);
node.validate(value, &name_location, tracker, ctx)?;
if let Some(pattern_indices) = self.property_pattern_indices.get(property) {
for &idx in pattern_indices {
self.patterns[idx]
.1
.validate(value, &name_location, tracker, ctx)?;
}
}
} else {
let property_location = location.push(property);
let mut has_match = false;
for (re, node) in &self.patterns {
if re.is_match(property).unwrap_or(false) {
has_match = true;
node.validate(value, &property_location, tracker, ctx)?;
}
}
if !has_match {
self.node
.validate(value, &property_location, tracker, ctx)?;
}
}
}
}
Ok(())
}
fn iter_errors<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> ErrorIterator<'i> {
if let Value::Object(item) = instance {
let mut errors = vec![];
for (property, value) in item {
if let Some((name, node)) = self.properties.get_key_validator(property) {
let name_location = location.push(name.as_str());
errors.extend(node.iter_errors(value, &name_location, tracker, ctx));
if let Some(pattern_indices) = self.property_pattern_indices.get(property) {
for &idx in pattern_indices {
errors.extend(self.patterns[idx].1.iter_errors(
value,
&name_location,
tracker,
ctx,
));
}
}
} else {
let mut has_match = false;
for (re, node) in &self.patterns {
if re.is_match(property).unwrap_or(false) {
has_match = true;
errors.extend(node.iter_errors(
value,
&location.push(property.as_str()),
tracker,
ctx,
));
}
}
if !has_match {
errors.extend(self.node.iter_errors(
value,
&location.push(property.as_str()),
tracker,
ctx,
));
}
}
}
ErrorIterator::from_iterator(errors.into_iter())
} else {
no_error()
}
}
fn evaluate(
&self,
instance: &Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> EvaluationResult {
if let Value::Object(item) = instance {
let mut additional_matches = Vec::with_capacity(item.len());
let mut children = Vec::with_capacity(item.len());
for (property, value) in item {
let path = location.push(property.as_str());
if let Some((_name, node)) = self.properties.get_key_validator(property) {
children.push(node.evaluate_instance(value, &path, tracker, ctx));
if let Some(pattern_indices) = self.property_pattern_indices.get(property) {
for &idx in pattern_indices {
children.push(
self.patterns[idx]
.1
.evaluate_instance(value, &path, tracker, ctx),
);
}
}
} else {
let mut has_match = false;
for (pattern, node) in &self.patterns {
if pattern.is_match(property).unwrap_or(false) {
has_match = true;
children.push(node.evaluate_instance(value, &path, tracker, ctx));
}
}
if !has_match {
additional_matches.push(property.clone());
children.push(self.node.evaluate_instance(value, &path, tracker, ctx));
}
}
}
let mut result = EvaluationResult::from_children(children);
result.annotate(Annotations::new(Value::from(additional_matches)));
result
} else {
EvaluationResult::valid_empty()
}
}
}
pub(crate) struct AdditionalPropertiesWithPatternsNotEmptyFalseValidator<
M: PropertiesValidatorsMap,
R,
> {
properties: M,
patterns: Vec<(R, SchemaNode)>,
property_pattern_indices: AHashMap<String, Box<[usize]>>,
location: Location,
}
impl<M: PropertiesValidatorsMap, R: RegexEngine> Validate
for AdditionalPropertiesWithPatternsNotEmptyFalseValidator<M, R>
{
fn is_valid(&self, instance: &Value, ctx: &mut ValidationContext) -> bool {
if let Value::Object(item) = instance {
for (property, value) in item {
if let Some(node) = self.properties.get_validator(property) {
if !node.is_valid(value, ctx) {
return false;
}
if let Some(pattern_indices) = self.property_pattern_indices.get(property) {
for &idx in pattern_indices {
if !self.patterns[idx].1.is_valid(value, ctx) {
return false;
}
}
}
} else {
let mut has_match = false;
for (re, node) in &self.patterns {
if re.is_match(property).unwrap_or(false) {
has_match = true;
if !node.is_valid(value, ctx) {
return false;
}
}
}
if !has_match {
return false;
}
}
}
}
true
}
fn validate<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> Result<(), ValidationError<'i>> {
if let Value::Object(item) = instance {
for (property, value) in item {
if let Some((name, node)) = self.properties.get_key_validator(property) {
let name_location = location.push(name);
node.validate(value, &name_location, tracker, ctx)?;
if let Some(pattern_indices) = self.property_pattern_indices.get(property) {
for &idx in pattern_indices {
self.patterns[idx]
.1
.validate(value, &name_location, tracker, ctx)?;
}
}
} else {
let property_location = location.push(property);
let mut has_match = false;
for (re, node) in &self.patterns {
if re.is_match(property).unwrap_or(false) {
has_match = true;
node.validate(value, &property_location, tracker, ctx)?;
}
}
if !has_match {
return Err(ValidationError::additional_properties(
self.location.clone(),
crate::paths::capture_evaluation_path(tracker, &self.location),
location.into(),
instance,
vec![property.clone()],
));
}
}
}
}
Ok(())
}
fn iter_errors<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> ErrorIterator<'i> {
if let Value::Object(item) = instance {
let mut errors = vec![];
let mut unexpected = vec![];
for (property, value) in item {
if let Some((name, node)) = self.properties.get_key_validator(property) {
let name_location = location.push(name.as_str());
errors.extend(node.iter_errors(value, &name_location, tracker, ctx));
if let Some(pattern_indices) = self.property_pattern_indices.get(property) {
for &idx in pattern_indices {
errors.extend(self.patterns[idx].1.iter_errors(
value,
&name_location,
tracker,
ctx,
));
}
}
} else {
let mut has_match = false;
for (re, node) in &self.patterns {
if re.is_match(property).unwrap_or(false) {
has_match = true;
errors.extend(node.iter_errors(
value,
&location.push(property.as_str()),
tracker,
ctx,
));
}
}
if !has_match {
unexpected.push(property.clone());
}
}
}
if !unexpected.is_empty() {
errors.push(ValidationError::additional_properties(
self.location.clone(),
crate::paths::capture_evaluation_path(tracker, &self.location),
location.into(),
instance,
unexpected,
));
}
ErrorIterator::from_iterator(errors.into_iter())
} else {
no_error()
}
}
fn evaluate(
&self,
instance: &Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> EvaluationResult {
if let Value::Object(item) = instance {
let mut unexpected = vec![];
let mut children = Vec::with_capacity(item.len());
for (property, value) in item {
let path = location.push(property.as_str());
if let Some((_name, node)) = self.properties.get_key_validator(property) {
children.push(node.evaluate_instance(value, &path, tracker, ctx));
if let Some(pattern_indices) = self.property_pattern_indices.get(property) {
for &idx in pattern_indices {
children.push(
self.patterns[idx]
.1
.evaluate_instance(value, &path, tracker, ctx),
);
}
}
} else {
let mut has_match = false;
for (pattern, node) in &self.patterns {
if pattern.is_match(property).unwrap_or(false) {
has_match = true;
children.push(node.evaluate_instance(value, &path, tracker, ctx));
}
}
if !has_match {
unexpected.push(property.clone());
}
}
}
let mut result = EvaluationResult::from_children(children);
if !unexpected.is_empty() {
let eval_path = crate::paths::capture_evaluation_path(tracker, &self.location);
result.mark_errored(ErrorDescription::from_validation_error(
&ValidationError::additional_properties(
self.location.clone(),
eval_path,
location.into(),
instance,
unexpected,
),
));
}
result
} else {
EvaluationResult::valid_empty()
}
}
}
macro_rules! try_compile {
($expr:expr) => {
match $expr {
Ok(result) => result,
Err(error) => return Some(Err(error)),
}
};
}
fn precompute_property_pattern_indices<R: RegexEngine>(
property_names: impl Iterator<Item = impl AsRef<str> + Clone + Into<String>>,
patterns: &[(R, SchemaNode)],
) -> AHashMap<String, Box<[usize]>> {
let mut result = AHashMap::new();
for prop_name in property_names {
let matching_indices: Vec<usize> = patterns
.iter()
.enumerate()
.filter(|(_, (re, _))| re.is_match(prop_name.as_ref()).unwrap_or(false))
.map(|(i, _)| i)
.collect();
if !matching_indices.is_empty() {
result.insert(prop_name.into(), matching_indices.into_boxed_slice());
}
}
result
}
fn compile_pattern_non_empty<'a, R>(
ctx: &compiler::Context,
map: &'a Map<String, Value>,
patterns: Vec<(R, SchemaNode)>,
schema: &'a Value,
) -> Option<CompilationResult<'a>>
where
R: RegexEngine + 'static,
{
let kctx = ctx.new_at_location("additionalProperties");
let property_pattern_indices = precompute_property_pattern_indices(map.keys(), &patterns);
if map.len() < HASHMAP_THRESHOLD {
Some(Ok(Box::new(
AdditionalPropertiesWithPatternsNotEmptyValidator::<SmallValidatorsMap, R> {
node: try_compile!(compiler::compile(&kctx, kctx.as_resource_ref(schema))),
properties: try_compile!(compile_small_map(ctx, map)),
patterns,
property_pattern_indices,
},
)))
} else {
Some(Ok(Box::new(
AdditionalPropertiesWithPatternsNotEmptyValidator::<BigValidatorsMap, R> {
node: try_compile!(compiler::compile(&kctx, kctx.as_resource_ref(schema))),
properties: try_compile!(compile_big_map(ctx, map)),
patterns,
property_pattern_indices,
},
)))
}
}
fn compile_pattern_non_empty_false<'a, R>(
ctx: &compiler::Context,
map: &'a Map<String, Value>,
patterns: Vec<(R, SchemaNode)>,
) -> Option<CompilationResult<'a>>
where
R: RegexEngine + 'static,
{
let kctx = ctx.new_at_location("additionalProperties");
let property_pattern_indices = precompute_property_pattern_indices(map.keys(), &patterns);
if map.len() < HASHMAP_THRESHOLD {
Some(Ok(Box::new(
AdditionalPropertiesWithPatternsNotEmptyFalseValidator::<SmallValidatorsMap, R> {
properties: try_compile!(compile_small_map(ctx, map)),
patterns,
property_pattern_indices,
location: kctx.location().clone(),
},
)))
} else {
Some(Ok(Box::new(
AdditionalPropertiesWithPatternsNotEmptyFalseValidator::<BigValidatorsMap, R> {
properties: try_compile!(compile_big_map(ctx, map)),
patterns,
property_pattern_indices,
location: kctx.location().clone(),
},
)))
}
}
#[inline]
pub(crate) fn compile<'a>(
ctx: &compiler::Context,
parent: &'a Map<String, Value>,
schema: &'a Value,
) -> Option<CompilationResult<'a>> {
let properties = parent.get("properties");
if let Some(patterns) = parent.get("patternProperties") {
if let Value::Object(obj) = patterns {
match ctx.config().pattern_options() {
PatternEngineOptions::FancyRegex { .. } => {
let patterns = match compile_fancy_regex_patterns(ctx, obj) {
Ok(patterns) => patterns,
Err(error) => return Some(Err(error)),
};
match schema {
Value::Bool(true) => None, Value::Bool(false) => {
if let Some(properties) = properties {
if let Value::Object(map) = properties {
compile_pattern_non_empty_false::<
CompiledPattern<fancy_regex::Regex>,
>(ctx, map, patterns)
} else {
let location = ctx.location().join("properties");
Some(Err(ValidationError::compile_error(
location.clone(),
location,
Location::new(),
properties,
"Unexpected type",
)))
}
} else {
Some(Ok(Box::new(
AdditionalPropertiesWithPatternsFalseValidator {
patterns,
location: ctx.location().join("additionalProperties"),
pattern_keyword_path: ctx
.location()
.join("patternProperties"),
pattern_keyword_absolute_location: ctx
.new_at_location("patternProperties")
.base_uri(),
},
)))
}
}
_ => {
if let Some(properties) = properties {
if let Value::Object(map) = properties {
compile_pattern_non_empty::<CompiledPattern<fancy_regex::Regex>>(
ctx, map, patterns, schema,
)
} else {
let location = ctx.location().join("properties");
Some(Err(ValidationError::compile_error(
location.clone(),
location,
Location::new(),
properties,
"Unexpected type",
)))
}
} else {
let kctx = ctx.new_at_location("additionalProperties");
Some(Ok(Box::new(AdditionalPropertiesWithPatternsValidator {
node: try_compile!(compiler::compile(
&kctx,
kctx.as_resource_ref(schema),
)),
patterns,
pattern_keyword_path: ctx.location().join("patternProperties"),
pattern_keyword_absolute_location: ctx
.new_at_location("patternProperties")
.base_uri(),
})))
}
}
}
}
PatternEngineOptions::Regex { .. } => {
let patterns = match compile_regex_patterns(ctx, obj) {
Ok(patterns) => patterns,
Err(error) => return Some(Err(error)),
};
match schema {
Value::Bool(true) => None, Value::Bool(false) => {
if let Some(properties) = properties {
if let Value::Object(map) = properties {
compile_pattern_non_empty_false::<CompiledPattern<regex::Regex>>(
ctx, map, patterns,
)
} else {
let location = ctx.location().join("properties");
Some(Err(ValidationError::compile_error(
location.clone(),
location,
Location::new(),
properties,
"Unexpected type",
)))
}
} else {
Some(Ok(Box::new(
AdditionalPropertiesWithPatternsFalseValidator {
patterns,
location: ctx.location().join("additionalProperties"),
pattern_keyword_path: ctx
.location()
.join("patternProperties"),
pattern_keyword_absolute_location: ctx
.new_at_location("patternProperties")
.base_uri(),
},
)))
}
}
_ => {
if let Some(properties) = properties {
if let Value::Object(map) = properties {
compile_pattern_non_empty::<CompiledPattern<regex::Regex>>(
ctx, map, patterns, schema,
)
} else {
let location = ctx.location().join("properties");
Some(Err(ValidationError::compile_error(
location.clone(),
location,
Location::new(),
properties,
"Unexpected type",
)))
}
} else {
let kctx = ctx.new_at_location("additionalProperties");
Some(Ok(Box::new(AdditionalPropertiesWithPatternsValidator {
node: try_compile!(compiler::compile(
&kctx,
kctx.as_resource_ref(schema),
)),
patterns,
pattern_keyword_path: ctx.location().join("patternProperties"),
pattern_keyword_absolute_location: ctx
.new_at_location("patternProperties")
.base_uri(),
})))
}
}
}
}
}
} else {
let location = ctx.location().join("patternProperties");
Some(Err(ValidationError::single_type_error(
location.clone(),
location,
Location::new(),
patterns,
JsonType::Object,
)))
}
} else {
match schema {
Value::Bool(true) => None, Value::Bool(false) => {
if let Some(properties) = properties {
if let Some(Value::Array(required)) = parent.get("required") {
if required.len() == 1 {
if let Some(Value::String(req)) = required.first() {
if let Value::Object(map) = properties {
return if map.len() < HASHMAP_THRESHOLD {
Some(
AdditionalPropertiesNotEmptyFalseWithRequired1Validator::<
SmallValidatorsMap,
>::compile(
map, ctx, req.clone()
),
)
} else {
Some(
AdditionalPropertiesNotEmptyFalseWithRequired1Validator::<
BigValidatorsMap,
>::compile(
map, ctx, req.clone()
),
)
};
}
}
}
}
compile_dynamic_prop_map_validator!(
AdditionalPropertiesNotEmptyFalseValidator,
properties,
ctx,
)
} else {
let location = ctx.location().join("additionalProperties");
Some(AdditionalPropertiesFalseValidator::compile(location))
}
}
_ => {
if let Some(properties) = properties {
compile_dynamic_prop_map_validator!(
AdditionalPropertiesNotEmptyValidator,
properties,
ctx,
schema,
)
} else {
Some(AdditionalPropertiesValidator::compile(schema, ctx))
}
}
}
}
}
#[cfg(test)]
mod tests {
use crate::tests_util;
use serde_json::{json, Value};
use test_case::test_case;
fn schema_1() -> Value {
json!({
"additionalProperties": false,
"properties": {
"foo": {"type": "string"},
"barbaz": {"type": "integer", "multipleOf": 3},
},
"patternProperties": {
"^bar": {"type": "integer", "minimum": 5},
"spam$": {"type": "integer", "maximum": 10},
}
})
}
#[test_case(&json!([1]))]
#[test_case(&json!({}))]
#[test_case(&json!({"foo": "a"}))]
#[test_case(&json!({"barbaz": 6}))]
#[test_case(&json!({"bar": 6}))]
#[test_case(&json!({"spam": 7}))]
#[test_case(&json!({"bar": 6, "spam": 7}))]
#[test_case(&json!({"barspam": 7}))]
#[test_case(&json!({"barspam": 7, "bar": 6, "spam": 7, "foo": "a", "barbaz": 6}))]
fn schema_1_valid(instance: &Value) {
let schema = schema_1();
tests_util::is_valid(&schema, instance);
}
#[test_case(&json!({"foo": 3}), &["3 is not of type \"string\""], &["/properties/foo/type"])]
#[test_case(&json!({"faz": 1}), &["Additional properties are not allowed (\'faz\' was unexpected)"], &["/additionalProperties"])]
#[test_case(&json!({"faz": 1, "haz": 1}), &["Additional properties are not allowed (\'faz\', \'haz\' were unexpected)"], &["/additionalProperties"])]
#[test_case(&json!({"foo": 3, "bar": 4}), &["3 is not of type \"string\"","4 is less than the minimum of 5"], &["/patternProperties/^bar/minimum","/properties/foo/type"])]
#[test_case(&json!({"barbaz": 3}), &["3 is less than the minimum of 5"], &["/patternProperties/^bar/minimum"])]
#[test_case(&json!({"bar": 4}), &["4 is less than the minimum of 5"], &["/patternProperties/^bar/minimum"])]
#[test_case(&json!({"spam": 11}), &["11 is greater than the maximum of 10"], &["/patternProperties/spam$/maximum"])]
#[test_case(&json!({"bar": 4, "spam": 11}), &["11 is greater than the maximum of 10","4 is less than the minimum of 5"], &["/patternProperties/^bar/minimum", "/patternProperties/spam$/maximum"])]
#[test_case(&json!({"bar": 6, "spam": 11}), &["11 is greater than the maximum of 10"], &["/patternProperties/spam$/maximum"])]
#[test_case(&json!({"bar": 4, "spam": 8}), &["4 is less than the minimum of 5"], &["/patternProperties/^bar/minimum"])]
#[test_case(&json!({"barspam": 4}), &["4 is less than the minimum of 5"], &["/patternProperties/^bar/minimum"])]
#[test_case(&json!({"barspam": 11}), &["11 is greater than the maximum of 10"], &["/patternProperties/spam$/maximum"])]
#[test_case(
&json!({"bar": 4, "spam": 11, "foo": 3, "faz": 1}),
&[
"11 is greater than the maximum of 10",
"3 is not of type \"string\"",
"4 is less than the minimum of 5",
"Additional properties are not allowed (\'faz\' was unexpected)",
],
&[
"/additionalProperties",
"/patternProperties/^bar/minimum",
"/patternProperties/spam$/maximum",
"/properties/foo/type",
]
)]
fn schema_1_invalid(instance: &Value, expected: &[&str], locations: &[&str]) {
let schema = schema_1();
tests_util::is_not_valid(&schema, instance);
tests_util::expect_errors(&schema, instance, expected);
tests_util::assert_locations(&schema, instance, locations);
}
fn schema_2() -> Value {
json!({
"additionalProperties": false,
"patternProperties": {
"^bar": {"type": "integer", "minimum": 5},
"spam$": {"type": "integer", "maximum": 10},
}
})
}
#[test_case(&json!([1]))]
#[test_case(&json!({}))]
#[test_case(&json!({"bar": 6}))]
#[test_case(&json!({"spam": 7}))]
#[test_case(&json!({"bar": 6, "spam": 7}))]
#[test_case(&json!({"barspam": 7}))]
#[test_case(&json!({"barspam": 7, "bar": 6, "spam": 7}))]
fn schema_2_valid(instance: &Value) {
let schema = schema_2();
tests_util::is_valid(&schema, instance);
}
#[test_case(&json!({"faz": "a"}), &["Additional properties are not allowed (\'faz\' was unexpected)"], &["/additionalProperties"])]
#[test_case(&json!({"bar": 4}), &["4 is less than the minimum of 5"], &["/patternProperties/^bar/minimum"])]
#[test_case(&json!({"spam": 11}), &["11 is greater than the maximum of 10"], &["/patternProperties/spam$/maximum"])]
#[test_case(&json!({"bar": 4, "spam": 11}), &["11 is greater than the maximum of 10","4 is less than the minimum of 5"], &["/patternProperties/^bar/minimum", "/patternProperties/spam$/maximum"])]
#[test_case(&json!({"bar": 6, "spam": 11}), &["11 is greater than the maximum of 10"], &["/patternProperties/spam$/maximum"])]
#[test_case(&json!({"bar": 4, "spam": 8}), &["4 is less than the minimum of 5"], &["/patternProperties/^bar/minimum"])]
#[test_case(&json!({"barspam": 4}), &["4 is less than the minimum of 5"], &["/patternProperties/^bar/minimum"])]
#[test_case(&json!({"barspam": 11}), &["11 is greater than the maximum of 10"], &["/patternProperties/spam$/maximum"])]
#[test_case(
&json!({"bar": 4, "spam": 11, "faz": 1}),
&[
"11 is greater than the maximum of 10",
"4 is less than the minimum of 5",
"Additional properties are not allowed (\'faz\' was unexpected)",
],
&[
"/additionalProperties",
"/patternProperties/^bar/minimum",
"/patternProperties/spam$/maximum",
]
)]
fn schema_2_invalid(instance: &Value, expected: &[&str], locations: &[&str]) {
let schema = schema_2();
tests_util::is_not_valid(&schema, instance);
tests_util::expect_errors(&schema, instance, expected);
tests_util::assert_locations(&schema, instance, locations);
}
fn schema_3() -> Value {
json!({
"additionalProperties": false,
"properties": {
"foo": {"type": "string"}
}
})
}
#[test_case(&json!([1]))]
#[test_case(&json!({}))]
#[test_case(&json!({"foo": "a"}))]
fn schema_3_valid(instance: &Value) {
let schema = schema_3();
tests_util::is_valid(&schema, instance);
}
#[test_case(&json!({"foo": 3}), &["3 is not of type \"string\""], &["/properties/foo/type"])]
#[test_case(&json!({"faz": "a"}), &["Additional properties are not allowed (\'faz\' was unexpected)"], &["/additionalProperties"])]
#[test_case(
&json!(
{"foo": 3, "faz": "a"}),
&[
"3 is not of type \"string\"",
"Additional properties are not allowed (\'faz\' was unexpected)",
],
&[
"/additionalProperties",
"/properties/foo/type",
]
)]
fn schema_3_invalid(instance: &Value, expected: &[&str], locations: &[&str]) {
let schema = schema_3();
tests_util::is_not_valid(&schema, instance);
tests_util::expect_errors(&schema, instance, expected);
tests_util::assert_locations(&schema, instance, locations);
}
fn schema_4() -> Value {
json!({
"additionalProperties": {"type": "integer"},
"properties": {
"foo": {"type": "string"}
}
})
}
#[test_case(&json!([1]))]
#[test_case(&json!({}))]
#[test_case(&json!({"foo": "a"}))]
#[test_case(&json!({"bar": 4}))]
#[test_case(&json!({"foo": "a", "bar": 4}))]
fn schema_4_valid(instance: &Value) {
let schema = schema_4();
tests_util::is_valid(&schema, instance);
}
#[test_case(&json!({"foo": 3}), &["3 is not of type \"string\""], &["/properties/foo/type"])]
#[test_case(&json!({"bar": "a"}), &["\"a\" is not of type \"integer\""], &["/additionalProperties/type"])]
#[test_case(
&json!(
{"foo": 3, "bar": "a"}),
&[
"\"a\" is not of type \"integer\"",
"3 is not of type \"string\"",
],
&[
"/additionalProperties/type",
"/properties/foo/type",
]
)]
fn schema_4_invalid(instance: &Value, expected: &[&str], locations: &[&str]) {
let schema = schema_4();
tests_util::is_not_valid(&schema, instance);
tests_util::expect_errors(&schema, instance, expected);
tests_util::assert_locations(&schema, instance, locations);
}
fn schema_5() -> Value {
json!({
"additionalProperties": {"type": "integer"},
"properties": {
"foo": {"type": "string"},
"barbaz": {"type": "integer", "multipleOf": 3},
},
"patternProperties": {
"^bar": {"type": "integer", "minimum": 5},
"spam$": {"type": "integer", "maximum": 10},
}
})
}
#[test_case(&json!([1]))]
#[test_case(&json!({}))]
#[test_case(&json!({"foo": "a"}))]
#[test_case(&json!({"faz": 42}))]
#[test_case(&json!({"barbaz": 6}))]
#[test_case(&json!({"bar": 6}))]
#[test_case(&json!({"spam": 7}))]
#[test_case(&json!({"bar": 6, "spam": 7}))]
#[test_case(&json!({"barspam": 7}))]
#[test_case(&json!({"barspam": 7, "bar": 6, "spam": 7, "foo": "a", "barbaz": 6, "faz": 42}))]
fn schema_5_valid(instance: &Value) {
let schema = schema_5();
tests_util::is_valid(&schema, instance);
}
#[test_case(&json!({"foo": 3}), &["3 is not of type \"string\""], &["/properties/foo/type"])]
#[test_case(&json!({"faz": "a"}), &["\"a\" is not of type \"integer\""], &["/additionalProperties/type"])]
#[test_case(&json!({"faz": "a", "haz": "a"}), &["\"a\" is not of type \"integer\"", "\"a\" is not of type \"integer\""], &["/additionalProperties/type", "/additionalProperties/type"])]
#[test_case(&json!({"foo": 3, "bar": 4}), &["3 is not of type \"string\"","4 is less than the minimum of 5"], &["/patternProperties/^bar/minimum","/properties/foo/type"])]
#[test_case(&json!({"barbaz": 3}), &["3 is less than the minimum of 5"], &["/patternProperties/^bar/minimum"])]
#[test_case(&json!({"bar": 4}), &["4 is less than the minimum of 5"], &["/patternProperties/^bar/minimum"])]
#[test_case(&json!({"spam": 11}), &["11 is greater than the maximum of 10"], &["/patternProperties/spam$/maximum"])]
#[test_case(&json!({"bar": 4, "spam": 11}), &["11 is greater than the maximum of 10","4 is less than the minimum of 5"], &["/patternProperties/^bar/minimum", "/patternProperties/spam$/maximum"])]
#[test_case(&json!({"bar": 6, "spam": 11}), &["11 is greater than the maximum of 10"], &["/patternProperties/spam$/maximum"])]
#[test_case(&json!({"bar": 4, "spam": 8}), &["4 is less than the minimum of 5"], &["/patternProperties/^bar/minimum"])]
#[test_case(&json!({"barspam": 4}), &["4 is less than the minimum of 5"], &["/patternProperties/^bar/minimum"])]
#[test_case(&json!({"barspam": 11}), &["11 is greater than the maximum of 10"], &["/patternProperties/spam$/maximum"])]
#[test_case(
&json!({"bar": 4, "spam": 11, "foo": 3, "faz": "a", "fam": 42}),
&[
"\"a\" is not of type \"integer\"",
"11 is greater than the maximum of 10",
"3 is not of type \"string\"",
"4 is less than the minimum of 5",
],
&[
"/additionalProperties/type",
"/patternProperties/^bar/minimum",
"/patternProperties/spam$/maximum",
"/properties/foo/type",
]
)]
fn schema_5_invalid(instance: &Value, expected: &[&str], locations: &[&str]) {
let schema = schema_5();
tests_util::is_not_valid(&schema, instance);
tests_util::expect_errors(&schema, instance, expected);
tests_util::assert_locations(&schema, instance, locations);
}
fn schema_6() -> Value {
json!({
"additionalProperties": {"type": "integer"},
"patternProperties": {
"^bar": {"type": "integer", "minimum": 5},
"spam$": {"type": "integer", "maximum": 10},
}
})
}
#[test_case(&json!([1]))]
#[test_case(&json!({}))]
#[test_case(&json!({"faz": 42}))]
#[test_case(&json!({"bar": 6}))]
#[test_case(&json!({"spam": 7}))]
#[test_case(&json!({"bar": 6, "spam": 7}))]
#[test_case(&json!({"barspam": 7}))]
#[test_case(&json!({"barspam": 7, "bar": 6, "spam": 7, "faz": 42}))]
fn schema_6_valid(instance: &Value) {
let schema = schema_6();
tests_util::is_valid(&schema, instance);
}
#[test_case(&json!({"faz": "a"}), &["\"a\" is not of type \"integer\""], &["/additionalProperties/type"])]
#[test_case(&json!({"faz": "a", "haz": "a"}), &["\"a\" is not of type \"integer\"", "\"a\" is not of type \"integer\""], &["/additionalProperties/type", "/additionalProperties/type"])]
#[test_case(&json!({"foo": "a", "bar": 4}), &["\"a\" is not of type \"integer\"","4 is less than the minimum of 5"], &["/additionalProperties/type","/patternProperties/^bar/minimum"])]
#[test_case(&json!({"bar": 4}), &["4 is less than the minimum of 5"], &["/patternProperties/^bar/minimum"])]
#[test_case(&json!({"spam": 11}), &["11 is greater than the maximum of 10"], &["/patternProperties/spam$/maximum"])]
#[test_case(&json!({"bar": 4, "spam": 11}), &["11 is greater than the maximum of 10","4 is less than the minimum of 5"], &["/patternProperties/^bar/minimum", "/patternProperties/spam$/maximum"])]
#[test_case(&json!({"bar": 6, "spam": 11}), &["11 is greater than the maximum of 10"], &["/patternProperties/spam$/maximum"])]
#[test_case(&json!({"bar": 4, "spam": 8}), &["4 is less than the minimum of 5"], &["/patternProperties/^bar/minimum"])]
#[test_case(&json!({"barspam": 4}), &["4 is less than the minimum of 5"], &["/patternProperties/^bar/minimum"])]
#[test_case(&json!({"barspam": 11}), &["11 is greater than the maximum of 10"], &["/patternProperties/spam$/maximum"])]
#[test_case(
&json!({"bar": 4, "spam": 11, "faz": "a", "fam": 42}),
&[
"\"a\" is not of type \"integer\"",
"11 is greater than the maximum of 10",
"4 is less than the minimum of 5",
],
&[
"/additionalProperties/type",
"/patternProperties/^bar/minimum",
"/patternProperties/spam$/maximum",
]
)]
fn schema_6_invalid(instance: &Value, expected: &[&str], locations: &[&str]) {
let schema = schema_6();
tests_util::is_not_valid(&schema, instance);
tests_util::expect_errors(&schema, instance, expected);
tests_util::assert_locations(&schema, instance, locations);
}
#[test_case(&json!({"additionalProperties": false, "patternProperties": {"[invalid": {"type": "string"}}}))]
#[test_case(&json!({"additionalProperties": {"type": "integer"}, "patternProperties": {"[invalid": {"type": "string"}}}))]
#[test_case(&json!({"properties": {"foo": {"type": "string"}}, "additionalProperties": false, "patternProperties": {"[invalid": {"type": "string"}}}))]
fn invalid_pattern_properties_fancy_regex(schema: &Value) {
let error = crate::validator_for(schema).expect_err("Should fail to compile");
assert!(error.to_string().contains("regex"));
}
#[test_case(&json!({"additionalProperties": false, "patternProperties": {"[invalid": {"type": "string"}}}))]
#[test_case(&json!({"additionalProperties": {"type": "integer"}, "patternProperties": {"[invalid": {"type": "string"}}}))]
#[test_case(&json!({"properties": {"foo": {"type": "string"}}, "additionalProperties": false, "patternProperties": {"[invalid": {"type": "string"}}}))]
fn invalid_pattern_properties_standard_regex(schema: &Value) {
use crate::PatternOptions;
let error = crate::options()
.with_pattern_options(PatternOptions::regex())
.build(schema)
.expect_err("Should fail to compile");
assert!(error.to_string().contains("regex"));
}
#[test_case("^x-", "x-custom", true)]
#[test_case("^x-", "y-other", false)]
#[test_case("^eo_", "eo_bands", true)]
#[test_case("^eo_", "other", false)]
fn prefix_pattern_with_additional_false(pattern: &str, key: &str, matches_pattern: bool) {
let schema = json!({
"additionalProperties": false,
"patternProperties": {
pattern: {"type": "string"}
}
});
let validator = crate::validator_for(&schema).unwrap();
let string_instance = json!({ key: "value" });
assert_eq!(validator.is_valid(&string_instance), matches_pattern);
let number_instance = json!({ key: 42 });
assert!(!validator.is_valid(&number_instance));
}
#[test]
fn prefix_pattern_with_additional_schema() {
let schema = json!({
"additionalProperties": {"type": "integer"},
"patternProperties": {
"^x-": {"type": "string"}
}
});
let validator = crate::validator_for(&schema).unwrap();
assert!(validator.is_valid(&json!({"x-foo": "bar"})));
assert!(!validator.is_valid(&json!({"x-foo": 42})));
assert!(validator.is_valid(&json!({"other": 42})));
assert!(!validator.is_valid(&json!({"other": "str"})));
assert!(validator.is_valid(&json!({"x-foo": "bar", "other": 42})));
assert!(!validator.is_valid(&json!({"x-foo": 42, "other": "str"})));
}
#[test]
fn prefix_pattern_with_properties_and_additional_false() {
let schema = json!({
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false,
"patternProperties": {
"^x-": {"type": "string"}
}
});
let validator = crate::validator_for(&schema).unwrap();
assert!(validator.is_valid(&json!({"name": "test"})));
assert!(!validator.is_valid(&json!({"name": 42})));
assert!(validator.is_valid(&json!({"x-foo": "bar"})));
assert!(!validator.is_valid(&json!({"x-foo": 42})));
assert!(!validator.is_valid(&json!({"other": "value"})));
assert!(validator.is_valid(&json!({"name": "test", "x-foo": "bar"})));
}
#[test]
fn fused_additional_properties_false_with_required_1() {
let schema = json!({
"properties": {
"id": {"type": "string"},
"name": {"type": "string"}
},
"additionalProperties": false,
"required": ["id"]
});
let validator = crate::validator_for(&schema).unwrap();
assert!(validator.is_valid(&json!({"id": "123"})));
assert!(validator.is_valid(&json!({"id": "123", "name": "test"})));
assert!(!validator.is_valid(&json!({})));
assert!(!validator.is_valid(&json!({"name": "test"})));
assert!(!validator.is_valid(&json!({"id": "123", "extra": "val"})));
assert!(!validator.is_valid(&json!({"id": 123})));
assert!(validator.is_valid(&json!("string")));
assert!(validator.is_valid(&json!([1, 2, 3])));
}
#[test]
fn fused_additional_properties_false_with_required_1_errors() {
let schema = json!({
"properties": {
"id": {"type": "string"},
"name": {"type": "string"}
},
"additionalProperties": false,
"required": ["id"]
});
let validator = crate::validator_for(&schema).unwrap();
let instance = json!({});
let errors: Vec<_> = validator.iter_errors(&instance).collect();
assert_eq!(errors.len(), 1);
assert!(errors[0].to_string().contains("required"));
let instance = json!({"name": "test"});
let errors: Vec<_> = validator.iter_errors(&instance).collect();
assert_eq!(errors.len(), 1);
assert!(errors[0].to_string().contains("required"));
let instance = json!({"id": "123", "extra": "val"});
let errors: Vec<_> = validator.iter_errors(&instance).collect();
assert_eq!(errors.len(), 1);
assert!(errors[0].to_string().contains("Additional properties"));
let instance = json!({"extra": "val"});
let errors: Vec<_> = validator.iter_errors(&instance).collect();
assert_eq!(errors.len(), 2);
}
}