use crate::SchemaNode;
use json_schema_ast::{NodeId, PatternSupport, SchemaNodeKind};
use serde_json::Value;
use std::collections::{HashMap, HashSet};
#[derive(Default)]
pub(crate) struct SubschemaCheckContext {
active_pairs: HashMap<(NodeId, NodeId), usize>,
pub(super) acceptance_deviations: HashMap<NodeId, AcceptanceDeviation>,
productive_depth: usize,
pub(super) assume_subset_omits_undeclared_properties: bool,
}
impl SubschemaCheckContext {
pub(super) fn for_emitted_values() -> Self {
Self {
assume_subset_omits_undeclared_properties: true,
..Self::default()
}
}
pub(super) fn with_productive_frame<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
self.productive_depth += 1;
let result = f(self);
self.productive_depth -= 1;
result
}
pub(super) fn recursion_reentry_is_guarded(&self, key: (NodeId, NodeId)) -> Option<bool> {
self.active_pairs
.get(&key)
.map(|active_depth| self.productive_depth > *active_depth)
}
pub(super) fn with_recursion_pair<T>(
&mut self,
key: (NodeId, NodeId),
f: impl FnOnce(&mut Self) -> T,
) -> T {
self.active_pairs.insert(key, self.productive_depth);
let result = f(self);
self.active_pairs.remove(&key);
result
}
pub(super) fn superset_contains_value(&mut self, sup: &SchemaNode, value: &Value) -> bool {
!self.schema_may_over_accept_values(sup) && sup.accepts_value(value)
}
pub(super) fn superset_contains_value_set(
&mut self,
sup: &SchemaNode,
values: &[Value],
) -> bool {
values
.iter()
.all(|value| self.superset_contains_value(sup, value))
}
pub(super) fn schema_definitely_rejects_value(
&mut self,
schema: &SchemaNode,
value: &Value,
) -> bool {
!schema_acceptance_deviation_cached(schema, &mut self.acceptance_deviations)
.may_under_accept
&& !schema.accepts_value(value)
}
pub(super) fn finite_upper_bound_fits_target(
&mut self,
sub: &SchemaNode,
sup: &SchemaNode,
values: &[Value],
) -> bool {
values.iter().all(|value| {
self.schema_definitely_rejects_value(sub, value)
|| self.superset_contains_value(sup, value)
})
}
fn schema_may_over_accept_values(&mut self, schema: &SchemaNode) -> bool {
schema_acceptance_deviation_cached(schema, &mut self.acceptance_deviations).may_over_accept
}
}
pub(super) fn schema_may_under_accept_values(schema: &SchemaNode) -> bool {
schema_acceptance_deviation(schema).may_under_accept
}
#[derive(Debug, Clone, Copy, Default)]
pub(super) struct AcceptanceDeviation {
may_under_accept: bool,
may_over_accept: bool,
}
impl AcceptanceDeviation {
const NONE: Self = Self {
may_under_accept: false,
may_over_accept: false,
};
const UNDER: Self = Self {
may_under_accept: true,
may_over_accept: false,
};
const BOTH: Self = Self {
may_under_accept: true,
may_over_accept: true,
};
fn combine(self, other: Self) -> Self {
Self {
may_under_accept: self.may_under_accept || other.may_under_accept,
may_over_accept: self.may_over_accept || other.may_over_accept,
}
}
fn inverted(self) -> Self {
Self {
may_under_accept: self.may_over_accept,
may_over_accept: self.may_under_accept,
}
}
fn is_exact(self) -> bool {
!self.may_under_accept && !self.may_over_accept
}
}
fn schema_acceptance_deviation(schema: &SchemaNode) -> AcceptanceDeviation {
schema_acceptance_deviation_cached(schema, &mut HashMap::new())
}
fn schema_acceptance_deviation_cached(
schema: &SchemaNode,
memo: &mut HashMap<NodeId, AcceptanceDeviation>,
) -> AcceptanceDeviation {
schema_acceptance_deviation_with_state(schema, memo, &mut HashSet::new())
}
fn schema_acceptance_deviation_with_state(
schema: &SchemaNode,
memo: &mut HashMap<NodeId, AcceptanceDeviation>,
active: &mut HashSet<NodeId>,
) -> AcceptanceDeviation {
if let Some(deviation) = memo.get(&schema.id()) {
return *deviation;
}
if !active.insert(schema.id()) {
return AcceptanceDeviation::UNDER;
}
let deviation =
match schema.kind() {
SchemaNodeKind::String {
pattern: Some(pattern),
..
} if pattern.support() == PatternSupport::Unsupported => AcceptanceDeviation::UNDER,
SchemaNodeKind::Object {
properties,
pattern_properties,
additional,
property_names,
..
} => {
let mut child_deviation = AcceptanceDeviation::NONE;
for property in properties.values() {
child_deviation = child_deviation.combine(
schema_acceptance_deviation_with_state(property, memo, active),
);
}
for property in pattern_properties.values() {
child_deviation = child_deviation.combine(
schema_acceptance_deviation_with_state(&property.schema, memo, active),
);
}
child_deviation = child_deviation.combine(schema_acceptance_deviation_with_state(
additional, memo, active,
));
child_deviation = child_deviation.combine(schema_acceptance_deviation_with_state(
property_names,
memo,
active,
));
if pattern_properties
.values()
.any(|property| property.pattern.support() == PatternSupport::Unsupported)
{
child_deviation.combine(AcceptanceDeviation::BOTH)
} else {
child_deviation
}
}
SchemaNodeKind::Array {
prefix_items,
items,
contains,
..
} => {
let mut item_deviation = AcceptanceDeviation::NONE;
for item in prefix_items {
item_deviation = item_deviation
.combine(schema_acceptance_deviation_with_state(item, memo, active));
}
item_deviation = item_deviation
.combine(schema_acceptance_deviation_with_state(items, memo, active));
match contains {
None => item_deviation,
Some(contains) => {
let contains_deviation =
schema_acceptance_deviation_with_state(&contains.schema, memo, active);
let mut contains_effect = AcceptanceDeviation::NONE;
if contains.count().min() > 0 {
contains_effect.may_under_accept = contains_deviation.may_under_accept;
contains_effect.may_over_accept = contains_deviation.may_over_accept;
}
if contains.count().max().is_some() {
contains_effect.may_under_accept |= contains_deviation.may_over_accept;
contains_effect.may_over_accept |= contains_deviation.may_under_accept;
}
item_deviation.combine(contains_effect)
}
}
}
SchemaNodeKind::AllOf(children) | SchemaNodeKind::AnyOf(children) => children
.iter()
.map(|child| schema_acceptance_deviation_with_state(child, memo, active))
.fold(AcceptanceDeviation::NONE, AcceptanceDeviation::combine),
SchemaNodeKind::OneOf(children) => {
let child_deviation = children
.iter()
.map(|child| schema_acceptance_deviation_with_state(child, memo, active))
.fold(AcceptanceDeviation::NONE, AcceptanceDeviation::combine);
if child_deviation.is_exact() {
AcceptanceDeviation::NONE
} else {
AcceptanceDeviation::BOTH
}
}
SchemaNodeKind::Not(child) => {
schema_acceptance_deviation_with_state(child, memo, active).inverted()
}
SchemaNodeKind::IfThenElse {
if_schema,
then_schema,
else_schema,
} => {
let if_deviation = schema_acceptance_deviation_with_state(if_schema, memo, active);
let branch_deviation =
then_schema
.as_ref()
.map(|schema| schema_acceptance_deviation_with_state(schema, memo, active))
.into_iter()
.chain(else_schema.as_ref().map(|schema| {
schema_acceptance_deviation_with_state(schema, memo, active)
}))
.fold(AcceptanceDeviation::NONE, AcceptanceDeviation::combine);
if if_deviation.is_exact() {
branch_deviation
} else {
branch_deviation.combine(AcceptanceDeviation::BOTH)
}
}
_ => AcceptanceDeviation::NONE,
};
active.remove(&schema.id());
memo.insert(schema.id(), deviation);
deviation
}