use std::{collections::BTreeMap, sync::Arc};
use ahash::AHashSet;
use referencing::Draft;
use serde_json::Value;
use crate::{
canonical::{
algebra,
context::CanonicalizationContext,
ir::{
canonicalize_value_set, type_set_schema, typed_group, ArrayLeaf, BoundCardinality,
BoundNumber, BoundRational, CanonicalJson, ContainsFacet, Divisors, IntegerLeaf,
LengthBounds, NumberLeaf, ObjectLeaf, Schema, SchemaKind, Side, StringLeaf,
},
negate, CanonicalizationError,
},
JsonType, JsonTypeSet,
};
pub(crate) fn parse(
value: &Value,
ctx: &CanonicalizationContext,
) -> Result<Option<Schema>, CanonicalizationError> {
let mut scan = DocumentScan::default();
let parsed = parse_schema(value, ctx, true, &mut scan)?;
if scan.additional_schema && scan.pattern_properties {
return Ok(None);
}
Ok(parsed)
}
#[derive(Default)]
struct DocumentScan {
additional_schema: bool,
pattern_properties: bool,
}
fn parse_schema(
value: &Value,
ctx: &CanonicalizationContext,
is_root: bool,
scan: &mut DocumentScan,
) -> Result<Option<Schema>, CanonicalizationError> {
let map = match value {
Value::Bool(true) => return Ok(Some(Schema::new(SchemaKind::True))),
Value::Bool(false) => return Ok(Some(Schema::new(SchemaKind::False))),
Value::Object(map) => map,
Value::Null | Value::Number(_) | Value::String(_) | Value::Array(_) => return Ok(None),
};
let mut type_set = None;
let mut enum_values = None;
let mut const_value = None;
let mut min_length: Option<BoundCardinality> = None;
let mut max_length: Option<BoundCardinality> = None;
let mut unique_items = false;
let mut min_items: Option<BoundCardinality> = None;
let mut max_items: Option<BoundCardinality> = None;
let mut items: Option<Schema> = None;
let mut contains_schema: Option<Schema> = None;
let mut min_contains: Option<BoundCardinality> = None;
let mut max_contains: Option<BoundCardinality> = None;
let mut item_prefix: Option<Vec<Schema>> = None;
let mut additional_items: Option<&Value> = None;
let mut required: Vec<Arc<str>> = Vec::new();
let mut property_names: Option<Schema> = None;
let mut properties: BTreeMap<Arc<str>, Schema> = BTreeMap::new();
let mut pattern_properties: BTreeMap<Arc<str>, Schema> = BTreeMap::new();
let mut forbid_unmatched_keys = false;
let mut additional_schema: Option<Schema> = None;
let mut min_properties: Option<BoundCardinality> = None;
let mut max_properties: Option<BoundCardinality> = None;
let mut patterns: Vec<Arc<str>> = Vec::new();
let mut formats: Vec<Arc<str>> = Vec::new();
let mut content_media_types: Vec<Arc<str>> = Vec::new();
let mut content_encodings: Vec<Arc<str>> = Vec::new();
let mut multiple_of = Divisors::default();
let mut real_minimum: Option<BoundNumber> = None;
let mut real_maximum: Option<BoundNumber> = None;
let mut draft4_exclusive_minimum = false;
let mut draft4_exclusive_maximum = false;
let mut if_schema: Option<Schema> = None;
let mut then_schema: Option<Schema> = None;
let mut else_schema: Option<Schema> = None;
let mut conjuncts: Vec<Schema> = Vec::new();
for (key, entry) in map {
match (key.as_str(), entry) {
("$schema", _) if !is_root => return Ok(None),
("$schema", Value::String(uri)) => {
if matches!(Draft::from_schema_uri(uri), Draft::Unknown) {
return Ok(None);
}
}
("allOf", Value::Array(branches)) => {
for branch in branches {
match parse_schema(branch, ctx, false, scan)? {
Some(schema) => conjuncts.push(schema),
None => return Ok(None),
}
}
}
("anyOf", Value::Array(items)) => {
let mut branches = Vec::new();
for branch in items {
match parse_schema(branch, ctx, false, scan)? {
Some(schema) => branches.push(schema),
None => return Ok(None),
}
}
conjuncts.push(algebra::union(branches, ctx));
}
("oneOf", Value::Array(items)) => {
let mut branches = Vec::new();
for branch in items {
match parse_schema(branch, ctx, false, scan)? {
Some(schema) => branches.push(schema),
None => return Ok(None),
}
}
let overlaps = pairwise_overlaps(&branches, ctx);
if overlaps.is_empty() {
conjuncts.push(algebra::union(branches, ctx));
} else {
match exactly_one_of(branches, overlaps, ctx) {
Some(schema) => conjuncts.push(schema),
None => return Ok(None),
}
}
}
("type", value) => match parse_type_set(value) {
Some(set) => type_set = Some(set),
None => return Ok(None),
},
("enum", Value::Array(values)) if ctx.draft().is_known_keyword("enum") => {
if !values.iter().all(finite_value_spelling_is_exact) {
return Ok(None);
}
enum_values = Some(values);
}
("const", value) if ctx.draft().is_known_keyword("const") => {
if !finite_value_spelling_is_exact(value) {
return Ok(None);
}
const_value = Some(value);
}
("minLength", Value::Number(number)) if ctx.draft().is_known_keyword("minLength") => {
match BoundCardinality::from_number(number) {
Some(bound) => min_length = Some(bound),
None => return Ok(None),
}
}
("maxLength", Value::Number(number)) if ctx.draft().is_known_keyword("maxLength") => {
match BoundCardinality::from_number(number) {
Some(bound) => max_length = Some(bound),
None => return Ok(None),
}
}
("uniqueItems", Value::Bool(flag)) if ctx.draft().is_known_keyword("uniqueItems") => {
unique_items = *flag;
}
("minItems", Value::Number(number)) if ctx.draft().is_known_keyword("minItems") => {
match BoundCardinality::from_number(number) {
Some(bound) => min_items = Some(bound),
None => return Ok(None),
}
}
("maxItems", Value::Number(number)) if ctx.draft().is_known_keyword("maxItems") => {
match BoundCardinality::from_number(number) {
Some(bound) => max_items = Some(bound),
None => return Ok(None),
}
}
("items", value @ (Value::Object(_) | Value::Bool(_)))
if ctx.draft().is_known_keyword("items") =>
{
match parse_schema(value, ctx, false, scan)? {
Some(schema) => items = Some(schema),
None => return Ok(None),
}
}
("prefixItems", Value::Array(schemas))
if ctx.draft().is_known_keyword("prefixItems") =>
{
match parse_prefix(schemas, ctx, scan)? {
Some(prefix) => item_prefix = Some(prefix),
None => return Ok(None),
}
}
("items", Value::Array(schemas))
if matches!(
ctx.draft(),
Draft::Draft4 | Draft::Draft6 | Draft::Draft7 | Draft::Draft201909
) =>
{
match parse_prefix(schemas, ctx, scan)? {
Some(prefix) => item_prefix = Some(prefix),
None => return Ok(None),
}
}
("additionalItems", value @ (Value::Object(_) | Value::Bool(_)))
if matches!(
ctx.draft(),
Draft::Draft4 | Draft::Draft6 | Draft::Draft7 | Draft::Draft201909
) =>
{
additional_items = Some(value);
}
("contains", value @ (Value::Object(_) | Value::Bool(_)))
if ctx.draft().is_known_keyword("contains") =>
{
match parse_schema(value, ctx, false, scan)? {
Some(schema) => contains_schema = Some(schema),
None => return Ok(None),
}
}
("minContains", Value::Number(number))
if ctx.draft().is_known_keyword("minContains") =>
{
match BoundCardinality::from_number(number) {
Some(bound) => min_contains = Some(bound),
None => return Ok(None),
}
}
("maxContains", Value::Number(number))
if ctx.draft().is_known_keyword("maxContains") =>
{
match BoundCardinality::from_number(number) {
Some(bound) => max_contains = Some(bound),
None => return Ok(None),
}
}
("required", Value::Array(names))
if ctx.draft().is_known_keyword("required")
&& names.iter().all(Value::is_string) =>
{
required.extend(names.iter().filter_map(Value::as_str).map(Arc::from));
}
("properties", Value::Object(entries))
if ctx.draft().is_known_keyword("properties") =>
{
for (key, value) in entries {
match parse_schema(value, ctx, false, scan)? {
Some(schema) => {
properties.insert(Arc::from(key.as_str()), schema);
}
None => return Ok(None),
}
}
}
("patternProperties", Value::Object(entries))
if ctx.draft().is_known_keyword("patternProperties") =>
{
scan.pattern_properties = true;
for (pattern, value) in entries {
let pattern: Arc<str> = Arc::from(pattern.as_str());
if ctx.compile_regex(&pattern).is_none() {
return Err(CanonicalizationError::InvalidPattern {
pattern: pattern.to_string(),
});
}
match parse_schema(value, ctx, false, scan)? {
Some(schema) => {
pattern_properties.insert(pattern, schema);
}
None => return Ok(None),
}
}
}
("propertyNames", value) if ctx.draft().is_known_keyword("propertyNames") => {
match parse_schema(value, ctx, false, scan)? {
Some(schema) => property_names = Some(schema),
None => return Ok(None),
}
}
("additionalProperties", value @ (Value::Object(_) | Value::Bool(_)))
if ctx.draft().is_known_keyword("additionalProperties") =>
{
match parse_schema(value, ctx, false, scan)? {
Some(schema) if matches!(schema.kind(), SchemaKind::True) => {}
Some(schema) if matches!(schema.kind(), SchemaKind::False) => {
forbid_unmatched_keys = true;
}
Some(schema) => {
scan.additional_schema = true;
additional_schema = Some(schema);
}
None => return Ok(None),
}
}
("minProperties", Value::Number(number))
if ctx.draft().is_known_keyword("minProperties") =>
{
match BoundCardinality::from_number(number) {
Some(bound) => min_properties = Some(bound),
None => return Ok(None),
}
}
("maxProperties", Value::Number(number))
if ctx.draft().is_known_keyword("maxProperties") =>
{
match BoundCardinality::from_number(number) {
Some(bound) => max_properties = Some(bound),
None => return Ok(None),
}
}
("pattern", Value::String(text)) if ctx.draft().is_known_keyword("pattern") => {
let pattern: Arc<str> = Arc::from(text.as_str());
if ctx.compile_regex(&pattern).is_none() {
return Err(CanonicalizationError::InvalidPattern {
pattern: pattern.to_string(),
});
}
if !pattern.is_empty() {
patterns.push(pattern);
}
}
("format", Value::String(name)) if ctx.draft().is_known_keyword("format") => {
if ctx.validate_formats() {
formats.push(Arc::from(name.as_str()));
}
}
("contentEncoding" | "contentMediaType" | "contentSchema", _)
if matches!(
ctx.draft(),
Draft::Draft201909 | Draft::Draft202012 | Draft::Unknown
) => {}
("contentMediaType", Value::String(name))
if matches!(ctx.draft(), Draft::Draft6 | Draft::Draft7)
&& !map.contains_key("contentEncoding") =>
{
content_media_types.push(Arc::from(name.as_str()));
}
("contentEncoding", Value::String(name))
if matches!(ctx.draft(), Draft::Draft6 | Draft::Draft7)
&& !map.contains_key("contentMediaType") =>
{
content_encodings.push(Arc::from(name.as_str()));
}
("multipleOf", Value::Number(number)) if ctx.draft().is_known_keyword("multipleOf") => {
match BoundRational::new(number) {
Some(step) => multiple_of = Divisors::one(step),
None => return Ok(None),
}
}
("minimum", Value::Number(number)) if ctx.draft().is_known_keyword("minimum") => {
real_minimum = tighter_real(real_minimum, number, true, Side::Lower);
}
("maximum", Value::Number(number)) if ctx.draft().is_known_keyword("maximum") => {
real_maximum = tighter_real(real_maximum, number, true, Side::Upper);
}
("exclusiveMinimum", Value::Number(number))
if !matches!(ctx.draft(), Draft::Draft4)
&& ctx.draft().is_known_keyword("exclusiveMinimum") =>
{
real_minimum = tighter_real(real_minimum, number, false, Side::Lower);
}
("exclusiveMaximum", Value::Number(number))
if !matches!(ctx.draft(), Draft::Draft4)
&& ctx.draft().is_known_keyword("exclusiveMaximum") =>
{
real_maximum = tighter_real(real_maximum, number, false, Side::Upper);
}
("exclusiveMinimum", Value::Bool(flag)) if matches!(ctx.draft(), Draft::Draft4) => {
draft4_exclusive_minimum = *flag;
}
("exclusiveMaximum", Value::Bool(flag)) if matches!(ctx.draft(), Draft::Draft4) => {
draft4_exclusive_maximum = *flag;
}
("if", value) if ctx.draft().is_known_keyword("if") => {
match parse_schema(value, ctx, false, scan)? {
Some(schema) => if_schema = Some(schema),
None => return Ok(None),
}
}
("then", value) if ctx.draft().is_known_keyword("then") => {
match parse_schema(value, ctx, false, scan)? {
Some(schema) => then_schema = Some(schema),
None => return Ok(None),
}
}
("else", value) if ctx.draft().is_known_keyword("else") => {
match parse_schema(value, ctx, false, scan)? {
Some(schema) => else_schema = Some(schema),
None => return Ok(None),
}
}
("dependencies", Value::Object(entries)) => {
for (key, entry) in entries {
match entry {
Value::Array(names) if names.iter().all(Value::is_string) => {
conjuncts.push(required_dependency(key, names, ctx));
}
value @ (Value::Object(_) | Value::Bool(_)) => {
match parse_schema(value, ctx, false, scan)? {
Some(schema) => {
conjuncts.push(schema_dependency(key, schema, ctx));
}
None => return Ok(None),
}
}
Value::Null | Value::Number(_) | Value::String(_) | Value::Array(_) => {
return Ok(None)
}
}
}
}
("dependentRequired", Value::Object(entries))
if ctx.draft().is_known_keyword("dependentRequired") =>
{
for (key, entry) in entries {
match entry {
Value::Array(names) if names.iter().all(Value::is_string) => {
conjuncts.push(required_dependency(key, names, ctx));
}
Value::Null
| Value::Bool(_)
| Value::Number(_)
| Value::String(_)
| Value::Array(_)
| Value::Object(_) => return Ok(None),
}
}
}
("dependentSchemas", Value::Object(entries))
if ctx.draft().is_known_keyword("dependentSchemas") =>
{
for (key, entry) in entries {
match entry {
value @ (Value::Object(_) | Value::Bool(_)) => {
match parse_schema(value, ctx, false, scan)? {
Some(schema) => {
conjuncts.push(schema_dependency(key, schema, ctx));
}
None => return Ok(None),
}
}
Value::Null | Value::Number(_) | Value::String(_) | Value::Array(_) => {
return Ok(None)
}
}
}
}
("not", value) if ctx.draft().is_known_keyword("not") => {
match parse_schema(value, ctx, false, scan)? {
Some(child) => match negate::negate(&child, ctx) {
Some(complement) => conjuncts.push(complement),
None => return Ok(None),
},
None => return Ok(None),
}
}
(other, _) if ctx.draft().is_known_keyword(other) => return Ok(None),
_ => {}
}
}
if draft4_exclusive_minimum {
real_minimum = real_minimum.map(BoundNumber::excluded);
}
if draft4_exclusive_maximum {
real_maximum = real_maximum.map(BoundNumber::excluded);
}
if matches!(ctx.draft(), Draft::Draft4)
&& (enum_values.is_some() || const_value.is_some())
&& type_set.is_some_and(|set| {
set.contains(JsonType::Integer) && set != JsonTypeSet::from(JsonType::Integer)
})
{
return Ok(None);
}
if min_length.as_ref().is_some_and(BoundCardinality::is_zero) {
min_length = None;
}
if min_length.is_some()
|| max_length.is_some()
|| !patterns.is_empty()
|| !formats.is_empty()
|| !content_media_types.is_empty()
|| !content_encodings.is_empty()
{
patterns.sort();
patterns.dedup();
formats.sort();
formats.dedup();
content_media_types.sort();
content_media_types.dedup();
content_encodings.sort();
content_encodings.dedup();
let leaf = StringLeaf {
lengths: LengthBounds {
minimum: min_length,
maximum: max_length,
},
patterns,
formats,
content_media_types,
content_encodings,
};
conjuncts.push(string_facet_schema(leaf, ctx));
}
if min_items.as_ref().is_some_and(BoundCardinality::is_zero) {
min_items = None;
}
let (prefix, tail) = match item_prefix {
Some(prefix)
if matches!(
ctx.draft(),
Draft::Draft4 | Draft::Draft6 | Draft::Draft7 | Draft::Draft201909
) =>
{
let tail = match additional_items {
Some(value) => match parse_schema(value, ctx, false, scan)? {
Some(schema) => Some(schema),
None => return Ok(None),
},
None => None,
};
(prefix, tail)
}
Some(prefix) => (prefix, items),
None => (Vec::new(), items),
};
let contains: Vec<ContainsFacet> = contains_schema
.map(|schema| ContainsFacet {
schema,
minimum: min_contains,
maximum: max_contains,
})
.into_iter()
.collect();
if min_items.is_some()
|| max_items.is_some()
|| unique_items
|| !prefix.is_empty()
|| tail.is_some()
|| !contains.is_empty()
{
conjuncts.push(array_facet_schema(
ArrayLeaf {
lengths: LengthBounds {
minimum: min_items,
maximum: max_items,
},
unique: unique_items,
prefix,
items: tail,
contains,
},
ctx,
));
}
if min_properties
.as_ref()
.is_some_and(BoundCardinality::is_zero)
{
min_properties = None;
}
if forbid_unmatched_keys
&& matches!(ctx.draft(), Draft::Draft4)
&& !pattern_properties.is_empty()
{
return Ok(None);
}
if forbid_unmatched_keys {
let mut allowed: Vec<Schema> =
Vec::with_capacity(properties.len() + pattern_properties.len());
for key in properties.keys() {
allowed.push(Schema::new(SchemaKind::Const(CanonicalJson::from_value(
&Value::String(key.to_string()),
))));
}
for pattern in pattern_properties.keys() {
allowed.push(algebra::string_leaf(
StringLeaf {
lengths: LengthBounds::default(),
patterns: vec![Arc::clone(pattern)],
formats: Vec::new(),
content_media_types: Vec::new(),
content_encodings: Vec::new(),
},
ctx,
));
}
let allowed = algebra::union(allowed, ctx);
property_names = Some(match property_names.take() {
Some(names) => algebra::intersect(names, allowed, ctx),
None => allowed,
});
}
if min_properties.is_some()
|| max_properties.is_some()
|| !required.is_empty()
|| property_names.is_some()
|| !properties.is_empty()
|| !pattern_properties.is_empty()
|| additional_schema.is_some()
{
required.sort();
conjuncts.push(object_facet_schema(
ObjectLeaf {
sizes: LengthBounds {
minimum: min_properties,
maximum: max_properties,
},
required,
property_names,
properties,
pattern_properties,
additional: additional_schema,
},
ctx,
));
}
if real_minimum.is_some() || real_maximum.is_some() || !multiple_of.is_empty() {
let leaf = NumberLeaf {
minimum: real_minimum,
maximum: real_maximum,
multiple_of,
};
let Some(bounds) = algebra::integer_bounds_within(&leaf) else {
return Ok(None);
};
if type_set == Some(JsonTypeSet::from(JsonType::Integer)) {
conjuncts.push(algebra::integer_leaf(
IntegerLeaf {
bounds,
multiple_of: leaf.multiple_of,
},
ctx,
));
} else {
conjuncts.push(number_facet_schema(leaf, ctx));
}
}
match (if_schema, then_schema, else_schema) {
(None, _, _) | (Some(_), None, None) => {}
(Some(condition), Some(then), None) => match negate::negate(&condition, ctx) {
Some(complement) => conjuncts.push(algebra::union(vec![complement, then], ctx)),
None => return Ok(None),
},
(Some(condition), None, Some(else_branch)) => {
conjuncts.push(algebra::union(vec![condition, else_branch], ctx));
}
(Some(condition), Some(then), Some(else_branch)) => match negate::negate(&condition, ctx) {
Some(complement) => {
let holds = algebra::intersect(condition, then, ctx);
let fails = algebra::intersect(complement, else_branch, ctx);
conjuncts.push(algebra::union(vec![holds, fails], ctx));
}
None => return Ok(None),
},
}
let base = match (type_set, admitted_values(enum_values, const_value)) {
(None, None) => Schema::new(SchemaKind::True),
(Some(set), None) => type_set_schema(set),
(None, Some(values)) => canonicalize_value_set(values),
(Some(set), Some(values)) => restrict_values_to_types(values, set, ctx),
};
Ok(Some(
conjuncts.into_iter().fold(base, |result, conjunct| {
algebra::intersect(result, conjunct, ctx)
}),
))
}
fn required_dependency(key: &str, names: &[Value], ctx: &CanonicalizationContext) -> Schema {
let mut required: Vec<Arc<str>> = names
.iter()
.filter_map(Value::as_str)
.map(Arc::from)
.collect();
required.push(Arc::from(key));
required.sort();
required.dedup();
dependency_conjunct(key, object_with_required(required, ctx), ctx)
}
fn schema_dependency(key: &str, schema: Schema, ctx: &CanonicalizationContext) -> Schema {
let present = object_with_required(vec![Arc::from(key)], ctx);
dependency_conjunct(key, algebra::intersect(present, schema, ctx), ctx)
}
fn dependency_conjunct(key: &str, consequent: Schema, ctx: &CanonicalizationContext) -> Schema {
let vacuous = type_set_schema(JsonTypeSet::all().remove(JsonType::Object));
let absent = algebra::object_leaf(
ObjectLeaf {
sizes: LengthBounds::default(),
required: Vec::new(),
property_names: None,
properties: BTreeMap::from([(Arc::from(key), Schema::new(SchemaKind::False))]),
pattern_properties: BTreeMap::new(),
additional: None,
},
ctx,
);
algebra::union(vec![vacuous, absent, consequent], ctx)
}
fn object_with_required(required: Vec<Arc<str>>, ctx: &CanonicalizationContext) -> Schema {
algebra::object_leaf(
ObjectLeaf {
sizes: LengthBounds::default(),
required,
property_names: None,
properties: BTreeMap::new(),
pattern_properties: BTreeMap::new(),
additional: None,
},
ctx,
)
}
fn exactly_one_of(
branches: Vec<Schema>,
overlaps: Vec<Schema>,
ctx: &CanonicalizationContext,
) -> Option<Schema> {
let mut result = algebra::union(branches, ctx);
for overlap in overlaps {
result = algebra::intersect(result, negate::negate(&overlap, ctx)?, ctx);
}
Some(result)
}
fn pairwise_overlaps(branches: &[Schema], ctx: &CanonicalizationContext) -> Vec<Schema> {
let mut seen: AHashSet<&CanonicalJson> = AHashSet::new();
let mut shared: Vec<CanonicalJson> = Vec::new();
let mut finite: Vec<&Schema> = Vec::new();
let mut structural: Vec<&Schema> = Vec::new();
for branch in branches {
match branch.kind() {
SchemaKind::Const(value) => {
if !seen.insert(value) {
shared.push(value.clone());
}
finite.push(branch);
}
SchemaKind::Enum(values) => {
for value in values.as_slice() {
if !seen.insert(value) {
shared.push(value.clone());
}
}
finite.push(branch);
}
SchemaKind::MultiType(_)
| SchemaKind::TypedGroup { .. }
| SchemaKind::String(_)
| SchemaKind::Integer(_)
| SchemaKind::Number(_)
| SchemaKind::Array(_)
| SchemaKind::Object(_)
| SchemaKind::AnyOf(_)
| SchemaKind::True
| SchemaKind::False
| SchemaKind::Raw(_) => structural.push(branch),
}
}
let mut overlaps = Vec::new();
if !shared.is_empty() {
overlaps.push(canonicalize_value_set(shared));
}
for (index, left) in structural.iter().enumerate() {
for right in structural[index + 1..].iter().chain(&finite) {
let intersection = algebra::intersect((*left).clone(), (*right).clone(), ctx);
if !matches!(intersection.kind(), SchemaKind::False) {
overlaps.push(intersection);
}
}
}
overlaps
}
fn admitted_values(
enum_values: Option<&Vec<Value>>,
const_value: Option<&Value>,
) -> Option<Vec<CanonicalJson>> {
let mut values: Option<Vec<CanonicalJson>> =
enum_values.map(|entries| entries.iter().map(CanonicalJson::from_value).collect());
if let Some(constant) = const_value {
let constant = CanonicalJson::from_value(constant);
values = Some(match values {
Some(members) => members
.into_iter()
.filter(|value| *value == constant)
.collect(),
None => vec![constant],
});
}
values
}
pub(crate) fn restrict_values_to_types(
values: Vec<CanonicalJson>,
set: JsonTypeSet,
ctx: &CanonicalizationContext,
) -> Schema {
let cover = SchemaKind::semantic_cover(set);
let filtered: Vec<CanonicalJson> = values
.into_iter()
.filter(|value| cover.contains(value.json_type()))
.collect();
if !keeps_draft4_integer_guard(set, ctx.draft()) {
return canonicalize_value_set(filtered);
}
let (integers, others): (Vec<_>, Vec<_>) = filtered
.into_iter()
.partition(|value| value.json_type() == JsonType::Integer);
let mut branches = Vec::new();
let integer_set = canonicalize_value_set(integers);
if !matches!(integer_set.kind(), SchemaKind::False) {
branches.push(typed_group(JsonType::Integer, integer_set));
}
let other_set = canonicalize_value_set(others);
if !matches!(other_set.kind(), SchemaKind::False) {
branches.push(other_set);
}
algebra::union(branches, ctx)
}
#[cfg(feature = "arbitrary-precision")]
fn finite_value_spelling_is_exact(value: &Value) -> bool {
match value {
Value::Number(number) => {
let canonical = crate::canonical::json::canonical_number(number.as_str());
let text = canonical.as_deref().unwrap_or(number.as_str());
!text.bytes().any(|byte| matches!(byte, b'e' | b'E'))
}
Value::Array(items) => items.iter().all(finite_value_spelling_is_exact),
Value::Object(map) => map.values().all(finite_value_spelling_is_exact),
Value::Null | Value::Bool(_) | Value::String(_) => true,
}
}
#[cfg(not(feature = "arbitrary-precision"))]
fn finite_value_spelling_is_exact(_value: &Value) -> bool {
true
}
fn parse_type_set(value: &Value) -> Option<JsonTypeSet> {
match value {
Value::String(name) => Some(JsonTypeSet::from(name.parse::<JsonType>().ok()?)),
Value::Array(names) => names.iter().try_fold(JsonTypeSet::empty(), |set, name| {
Some(set.insert(name.as_str()?.parse::<JsonType>().ok()?))
}),
Value::Null | Value::Bool(_) | Value::Number(_) | Value::Object(_) => None,
}
}
fn tighter_real(
current: Option<BoundNumber>,
limit: &serde_json::Number,
inclusive: bool,
side: Side,
) -> Option<BoundNumber> {
let bound = BoundNumber::new(limit, inclusive);
match current {
Some(current) if current.is_tighter_than(&bound, side) => Some(current),
_ => Some(bound),
}
}
fn number_facet_schema(leaf: NumberLeaf, ctx: &CanonicalizationContext) -> Schema {
let non_number = Schema::new(SchemaKind::MultiType(
JsonTypeSet::all()
.remove(JsonType::Number)
.remove(JsonType::Integer),
));
algebra::union(vec![non_number, algebra::number_leaf(leaf, ctx)], ctx)
}
fn string_facet_schema(leaf: StringLeaf, ctx: &CanonicalizationContext) -> Schema {
let non_string = Schema::new(SchemaKind::MultiType(
JsonTypeSet::all().remove(JsonType::String),
));
algebra::union(vec![non_string, algebra::string_leaf(leaf, ctx)], ctx)
}
fn parse_prefix(
schemas: &[Value],
ctx: &CanonicalizationContext,
scan: &mut DocumentScan,
) -> Result<Option<Vec<Schema>>, CanonicalizationError> {
let mut prefix = Vec::with_capacity(schemas.len());
for schema in schemas {
match parse_schema(schema, ctx, false, scan)? {
Some(schema) => prefix.push(schema),
None => return Ok(None),
}
}
Ok(Some(prefix))
}
fn array_facet_schema(leaf: ArrayLeaf, ctx: &CanonicalizationContext) -> Schema {
let non_array = Schema::new(SchemaKind::MultiType(
JsonTypeSet::all().remove(JsonType::Array),
));
algebra::union(vec![non_array, algebra::array_leaf(leaf, ctx)], ctx)
}
fn object_facet_schema(leaf: ObjectLeaf, ctx: &CanonicalizationContext) -> Schema {
let non_object = Schema::new(SchemaKind::MultiType(
JsonTypeSet::all().remove(JsonType::Object),
));
algebra::union(vec![non_object, algebra::object_leaf(leaf, ctx)], ctx)
}
fn keeps_draft4_integer_guard(set: JsonTypeSet, draft: Draft) -> bool {
matches!(draft, Draft::Draft4)
&& set.contains(JsonType::Integer)
&& !set.contains(JsonType::Number)
}