use referencing::Draft;
use serde_json::{Map, Value};
use std::sync::{Arc, OnceLock};
use crate::{
compiler,
evaluation::ErrorDescription,
node::SchemaNode,
paths::{LazyLocation, Location, RefTracker},
validator::{EvaluationResult, Validate, ValidationContext},
ValidationError,
};
use super::CompilationResult;
pub(crate) type PendingItemsValidators = Arc<OnceLock<ItemsValidators>>;
#[derive(Debug, Clone)]
pub(crate) struct ItemsValidators {
unevaluated: Option<SchemaNode>,
contains: Option<SchemaNode>,
ref_: Option<RefValidator>,
dynamic_ref: Option<Box<ItemsValidators>>,
recursive_ref: Option<PendingItemsValidators>,
items_limit: Option<usize>,
items_all: bool,
prefix_items: Option<usize>,
conditional: Option<Box<ConditionalValidators>>,
all_of: Option<Vec<(SchemaNode, ItemsValidators)>>,
any_of: Option<Vec<(SchemaNode, ItemsValidators)>>,
one_of: Option<Vec<(SchemaNode, ItemsValidators)>>,
}
#[derive(Debug, Clone)]
struct RefValidator(Box<ItemsValidators>);
#[derive(Debug, Clone)]
struct ConditionalValidators {
condition: SchemaNode,
if_: ItemsValidators,
then_: Option<ItemsValidators>,
else_: Option<ItemsValidators>,
}
impl ItemsValidators {
fn mark_evaluated_indexes_impl(
&self,
instance: &Value,
indexes: &mut Vec<bool>,
ctx: &mut ValidationContext,
include_unevaluated: bool,
) {
if self.items_all {
for idx in indexes.iter_mut() {
*idx = true;
}
return;
}
if let Some(ref_) = &self.ref_ {
ref_.0.mark_evaluated_indexes(instance, indexes, ctx);
}
if let Some(recursive_ref) = &self.recursive_ref {
if let Some(validators) = recursive_ref.get() {
validators.mark_evaluated_indexes(instance, indexes, ctx);
}
}
if let Some(dynamic_ref) = &self.dynamic_ref {
dynamic_ref.mark_evaluated_indexes(instance, indexes, ctx);
}
if let Some(limit) = self.items_limit {
for idx in indexes.iter_mut().take(limit) {
*idx = true;
}
}
if let Some(limit) = self.prefix_items {
for idx in indexes.iter_mut().take(limit) {
*idx = true;
}
}
if indexes.iter().all(|&evaluated| evaluated) {
return;
}
if let Value::Array(items) = instance {
for (item, is_evaluated) in items.iter().zip(indexes.iter_mut()) {
if *is_evaluated {
continue;
}
if let Some(validator) = &self.contains {
if validator.is_valid(item, ctx) {
*is_evaluated = true;
continue;
}
}
if include_unevaluated {
if let Some(validator) = &self.unevaluated {
if validator.is_valid(item, ctx) {
*is_evaluated = true;
}
}
}
}
}
if let Some(conditional) = &self.conditional {
conditional.mark_evaluated_indexes(instance, indexes, ctx);
}
if let Some(all_of) = &self.all_of {
for (validator, item_validators) in all_of {
if validator.is_valid(instance, ctx) {
item_validators.mark_evaluated_indexes(instance, indexes, ctx);
}
}
}
if let Some(any_of) = &self.any_of {
for (validator, item_validators) in any_of {
if validator.is_valid(instance, ctx) {
item_validators.mark_evaluated_indexes(instance, indexes, ctx);
}
}
}
if let Some(one_of) = &self.one_of {
let mut match_count = 0;
let mut matched_validators = None;
for (node, validators) in one_of {
if node.is_valid(instance, ctx) {
match_count += 1;
if match_count > 1 {
break; }
matched_validators = Some(validators);
}
}
if match_count == 1 {
if let Some(validators) = matched_validators {
validators.mark_evaluated_indexes(instance, indexes, ctx);
}
}
}
}
fn mark_evaluated_indexes(
&self,
instance: &Value,
indexes: &mut Vec<bool>,
ctx: &mut ValidationContext,
) {
self.mark_evaluated_indexes_impl(instance, indexes, ctx, true);
}
fn mark_evaluated_indexes_by_other_keywords(
&self,
instance: &Value,
indexes: &mut Vec<bool>,
ctx: &mut ValidationContext,
) {
self.mark_evaluated_indexes_impl(instance, indexes, ctx, false);
}
}
impl ConditionalValidators {
fn mark_evaluated_indexes(
&self,
instance: &Value,
indexes: &mut Vec<bool>,
ctx: &mut ValidationContext,
) {
if self.condition.is_valid(instance, ctx) {
self.if_.mark_evaluated_indexes(instance, indexes, ctx);
if let Some(then_) = &self.then_ {
then_.mark_evaluated_indexes(instance, indexes, ctx);
}
} else if let Some(else_) = &self.else_ {
else_.mark_evaluated_indexes(instance, indexes, ctx);
}
}
}
fn compile_items_validators<'a>(
ctx: &compiler::Context<'_>,
parent: &'a Map<String, Value>,
) -> Result<ItemsValidators, ValidationError<'a>> {
let unevaluated = compile_unevaluated(ctx, parent)?;
let contains = compile_contains(ctx, parent)?;
let ref_ = compile_ref(ctx, parent)?;
let dynamic_ref = compile_dynamic_ref(ctx, parent)?;
let recursive_ref = compile_recursive_ref(ctx, parent)?;
let (items_limit, items_all) = compile_items(ctx, parent)?;
let prefix_items = compile_prefix_items(ctx, parent)?;
let conditional = compile_conditional(ctx, parent)?;
let all_of = compile_all_of(ctx, parent)?;
let any_of = compile_any_of(ctx, parent)?;
let one_of = compile_one_of(ctx, parent)?;
Ok(ItemsValidators {
unevaluated,
contains,
ref_,
dynamic_ref,
recursive_ref,
items_limit,
items_all,
prefix_items,
conditional,
all_of,
any_of,
one_of,
})
}
fn compile_unevaluated<'a>(
ctx: &compiler::Context<'_>,
parent: &'a Map<String, Value>,
) -> Result<Option<SchemaNode>, ValidationError<'a>> {
if let Some(subschema) = parent.get("unevaluatedItems") {
let unevaluated_ctx = ctx.new_at_location("unevaluatedItems");
Ok(Some(
compiler::compile(&unevaluated_ctx, unevaluated_ctx.as_resource_ref(subschema))
.map_err(ValidationError::to_owned)?,
))
} else {
Ok(None)
}
}
fn compile_contains<'a>(
ctx: &compiler::Context<'_>,
parent: &'a Map<String, Value>,
) -> Result<Option<SchemaNode>, ValidationError<'a>> {
if let Some(subschema) = parent.get("contains") {
let contains_ctx = ctx.new_at_location("contains");
Ok(Some(
compiler::compile(&contains_ctx, contains_ctx.as_resource_ref(subschema))
.map_err(ValidationError::to_owned)?,
))
} else {
Ok(None)
}
}
fn compile_ref<'a>(
ctx: &compiler::Context<'_>,
parent: &'a Map<String, Value>,
) -> Result<Option<RefValidator>, ValidationError<'a>> {
if let Some(Value::String(reference)) = parent.get("$ref") {
let resolved = ctx.lookup(reference)?;
if let Value::Object(subschema) = resolved.contents() {
let validators =
compile_items_validators(ctx, subschema).map_err(ValidationError::to_owned)?;
return Ok(Some(RefValidator(Box::new(validators))));
}
}
Ok(None)
}
fn compile_dynamic_ref<'a>(
ctx: &compiler::Context<'_>,
parent: &'a Map<String, Value>,
) -> Result<Option<Box<ItemsValidators>>, ValidationError<'a>> {
if let Some(Value::String(reference)) = parent.get("$dynamicRef") {
let resolved = ctx.lookup(reference)?;
if let Value::Object(subschema) = resolved.contents() {
let validators =
compile_items_validators(ctx, subschema).map_err(ValidationError::to_owned)?;
return Ok(Some(Box::new(validators)));
}
}
Ok(None)
}
fn compile_recursive_ref<'a>(
ctx: &compiler::Context<'_>,
parent: &Map<String, Value>,
) -> Result<Option<PendingItemsValidators>, ValidationError<'a>> {
if !parent.contains_key("$recursiveRef") {
return Ok(None);
}
let resolved = ctx
.lookup_recursive_reference()
.map_err(ValidationError::from)?;
let (contents, resolver, draft) = resolved.into_inner();
if let Value::Object(subschema) = &contents {
let vocabularies = ctx.find_vocabularies(draft, contents);
let ref_ctx =
ctx.with_resolver_and_draft(resolver, draft, vocabularies, ctx.location().clone());
if let Some(pending) = ref_ctx.get_pending_items_validators_for_schema(subschema) {
return Ok(Some(pending));
}
let cache_key = ref_ctx.location_cache_key();
if let Some(pending) = ref_ctx.get_pending_items_validators(&cache_key) {
return Ok(Some(pending));
}
let validators =
compile_items_validators(&ref_ctx, subschema).map_err(ValidationError::to_owned)?;
let pending = Arc::new(OnceLock::new());
let _ = pending.set(validators);
Ok(Some(pending))
} else {
Ok(None)
}
}
fn compile_items<'a>(
ctx: &compiler::Context<'_>,
parent: &'a Map<String, Value>,
) -> Result<(Option<usize>, bool), ValidationError<'a>> {
if let Some(subschema) = parent.get("items") {
if ctx.draft() == Draft::Draft201909
|| ctx.draft() == Draft::Draft7
|| ctx.draft() == Draft::Draft6
|| ctx.draft() == Draft::Draft4
{
let limit = if parent.contains_key("additionalItems") || subschema.is_object() {
usize::MAX
} else {
subschema.as_array().map_or(usize::MAX, std::vec::Vec::len)
};
Ok((Some(limit), false))
} else {
Ok((None, true))
}
} else {
Ok((None, false))
}
}
fn compile_prefix_items<'a>(
_ctx: &compiler::Context<'_>,
parent: &'a Map<String, Value>,
) -> Result<Option<usize>, ValidationError<'a>> {
if let Some(Some(items)) = parent.get("prefixItems").map(Value::as_array) {
Ok(Some(items.len()))
} else {
Ok(None)
}
}
fn compile_conditional<'a>(
ctx: &compiler::Context<'_>,
parent: &'a Map<String, Value>,
) -> Result<Option<Box<ConditionalValidators>>, ValidationError<'a>> {
if let Some(subschema) = parent.get("if") {
if let Value::Object(if_parent) = subschema {
let if_ctx = ctx.new_at_location("if");
let mut then_ = None;
if let Some(Value::Object(subschema)) = parent.get("then") {
let then_ctx = ctx.new_at_location("then");
then_ = Some(
compile_items_validators(&then_ctx, subschema)
.map_err(ValidationError::to_owned)?,
);
}
let mut else_ = None;
if let Some(Value::Object(subschema)) = parent.get("else") {
let else_ctx = ctx.new_at_location("else");
else_ = Some(
compile_items_validators(&else_ctx, subschema)
.map_err(ValidationError::to_owned)?,
);
}
return Ok(Some(Box::new(ConditionalValidators {
condition: compiler::compile(&if_ctx, if_ctx.as_resource_ref(subschema))
.map_err(ValidationError::to_owned)?,
if_: compile_items_validators(&if_ctx, if_parent)
.map_err(ValidationError::to_owned)?,
then_,
else_,
})));
}
}
Ok(None)
}
fn compile_all_of<'a>(
ctx: &compiler::Context<'_>,
parent: &'a Map<String, Value>,
) -> Result<Option<Vec<(SchemaNode, ItemsValidators)>>, ValidationError<'a>> {
if let Some(Some(subschemas)) = parent.get("allOf").map(Value::as_array) {
let all_of_ctx = ctx.new_at_location("allOf");
let mut result = Vec::with_capacity(subschemas.len());
for (idx, subschema) in subschemas.iter().enumerate() {
if let Value::Object(parent) = subschema {
let subschema_ctx = all_of_ctx.new_at_location(idx);
result.push((
compiler::compile(&subschema_ctx, subschema_ctx.as_resource_ref(subschema))
.map_err(ValidationError::to_owned)?,
compile_items_validators(&subschema_ctx, parent)
.map_err(ValidationError::to_owned)?,
));
}
}
Ok(Some(result))
} else {
Ok(None)
}
}
fn compile_any_of<'a>(
ctx: &compiler::Context<'_>,
parent: &'a Map<String, Value>,
) -> Result<Option<Vec<(SchemaNode, ItemsValidators)>>, ValidationError<'a>> {
if let Some(Some(subschemas)) = parent.get("anyOf").map(Value::as_array) {
let any_of_ctx = ctx.new_at_location("anyOf");
let mut result = Vec::with_capacity(subschemas.len());
for (idx, subschema) in subschemas.iter().enumerate() {
if let Value::Object(parent) = subschema {
let subschema_ctx = any_of_ctx.new_at_location(idx);
result.push((
compiler::compile(&subschema_ctx, subschema_ctx.as_resource_ref(subschema))
.map_err(ValidationError::to_owned)?,
compile_items_validators(&subschema_ctx, parent)
.map_err(ValidationError::to_owned)?,
));
}
}
Ok(Some(result))
} else {
Ok(None)
}
}
fn compile_one_of<'a>(
ctx: &compiler::Context<'_>,
parent: &'a Map<String, Value>,
) -> Result<Option<Vec<(SchemaNode, ItemsValidators)>>, ValidationError<'a>> {
if let Some(Some(subschemas)) = parent.get("oneOf").map(Value::as_array) {
let one_of_ctx = ctx.new_at_location("oneOf");
let mut result = Vec::with_capacity(subschemas.len());
for (idx, subschema) in subschemas.iter().enumerate() {
if let Value::Object(parent) = subschema {
let subschema_ctx = one_of_ctx.new_at_location(idx);
result.push((
compiler::compile(&subschema_ctx, subschema_ctx.as_resource_ref(subschema))
.map_err(ValidationError::to_owned)?,
compile_items_validators(&subschema_ctx, parent)
.map_err(ValidationError::to_owned)?,
));
}
}
Ok(Some(result))
} else {
Ok(None)
}
}
pub(crate) struct UnevaluatedItemsValidator {
location: Location,
validators: ItemsValidators,
}
impl UnevaluatedItemsValidator {
pub(crate) fn compile<'a>(
ctx: &'a compiler::Context,
parent: &'a Map<String, Value>,
) -> CompilationResult<'a> {
let validators =
compile_items_validators(ctx, parent).map_err(ValidationError::to_owned)?;
Ok(Box::new(UnevaluatedItemsValidator {
location: ctx.location().join("unevaluatedItems"),
validators,
}))
}
}
impl Validate for UnevaluatedItemsValidator {
fn is_valid(&self, instance: &Value, ctx: &mut ValidationContext) -> bool {
if let Value::Array(items) = instance {
let mut indexes = vec![false; items.len()];
self.validators
.mark_evaluated_indexes(instance, &mut indexes, ctx);
for (item, is_evaluated) in items.iter().zip(indexes) {
if !is_evaluated {
if let Some(validator) = &self.validators.unevaluated {
if !validator.is_valid(item, ctx) {
return false;
}
} else {
return false;
}
}
}
}
true
}
fn validate<'i>(
&self,
instance: &'i Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> Result<(), ValidationError<'i>> {
if let Value::Array(items) = instance {
let mut indexes = vec![false; items.len()];
self.validators
.mark_evaluated_indexes(instance, &mut indexes, ctx);
let mut unevaluated = vec![];
for (item, is_evaluated) in items.iter().zip(indexes) {
if !is_evaluated {
let is_valid = if let Some(validator) = &self.validators.unevaluated {
validator.is_valid(item, ctx)
} else {
false
};
if !is_valid {
unevaluated.push(item.to_string());
}
}
}
if !unevaluated.is_empty() {
return Err(ValidationError::unevaluated_items(
self.location.clone(),
crate::paths::capture_evaluation_path(tracker, &self.location),
location.into(),
instance,
unevaluated,
));
}
}
Ok(())
}
fn evaluate(
&self,
instance: &Value,
location: &LazyLocation,
tracker: Option<&RefTracker>,
ctx: &mut ValidationContext,
) -> EvaluationResult {
if let Value::Array(items) = instance {
let mut indexes = vec![false; items.len()];
self.validators
.mark_evaluated_indexes_by_other_keywords(instance, &mut indexes, ctx);
let mut children = Vec::new();
let mut unevaluated = Vec::new();
let mut invalid = false;
for (idx, (item, is_evaluated)) in items.iter().zip(indexes.iter()).enumerate() {
if *is_evaluated {
continue;
}
if let Some(validator) = &self.validators.unevaluated {
let child =
validator.evaluate_instance(item, &location.push(idx), tracker, ctx);
if !child.valid {
invalid = true;
unevaluated.push(item.to_string());
}
children.push(child);
} else {
invalid = true;
unevaluated.push(item.to_string());
}
}
let mut errors = Vec::new();
if !unevaluated.is_empty() {
errors.push(ErrorDescription::from_validation_error(
&ValidationError::unevaluated_items(
self.location.clone(),
crate::paths::capture_evaluation_path(tracker, &self.location),
location.into(),
instance,
unevaluated,
),
));
}
if invalid {
EvaluationResult::Invalid {
errors,
children,
annotations: None,
}
} else {
EvaluationResult::Valid {
annotations: None,
children,
}
}
} else {
EvaluationResult::valid_empty()
}
}
}
pub(crate) fn compile<'a>(
ctx: &'a compiler::Context,
parent: &'a Map<String, Value>,
schema: &'a Value,
) -> Option<CompilationResult<'a>> {
match schema.as_bool() {
Some(true) => None,
_ => Some(UnevaluatedItemsValidator::compile(ctx, parent)),
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
#[test]
fn test_unevaluated_items_with_recursion() {
let schema = json!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"allOf": [
{
"$ref": "#/$defs/array_1"
}
],
"unevaluatedItems": false,
"$defs": {
"array_1": {
"type": "array",
"prefixItems": [
{
"type": "string"
},
{
"allOf": [
{
"$ref": "#/$defs/array_2"
}
],
"type": "array",
"unevaluatedItems": false
}
]
},
"array_2": {
"type": "array",
"prefixItems": [
{
"type": "number"
},
{
"allOf": [
{
"$ref": "#/$defs/array_1"
}
],
"type": "array",
"unevaluatedItems": false
}
]
}
}
});
let validator = crate::validator_for(&schema).expect("Schema should compile");
let instance = json!([
"string",
[
42,
[
"string",
[
42,
"unexpected" ]
]
]
]);
assert!(!validator.is_valid(&instance));
assert!(validator.validate(&instance).is_err());
let valid_instance = json!(["string", [42, ["string", [42]]]]);
assert!(validator.is_valid(&valid_instance));
assert!(validator.validate(&valid_instance).is_ok());
}
}