use std::{
cmp::Ordering,
collections::BTreeMap,
hash::{Hash, Hasher},
sync::Arc,
};
use referencing::Draft;
use serde_json::Value;
use crate::{
canonical::{
algebra,
context::CanonicalizationContext,
emit,
error::OperandMismatch,
ir::{Schema, SchemaKind, UncheckableFacet, Verdict},
negate, oracle, parse, CanonicalizationError,
},
options::PatternEngineOptions,
};
pub(crate) type DefinitionMap = BTreeMap<Arc<str>, Schema>;
#[derive(Clone, Debug)]
pub struct CanonicalSchema {
inner: Schema,
draft: Draft,
pattern_options: PatternEngineOptions,
validate_formats: bool,
definitions: Arc<DefinitionMap>,
}
impl PartialEq for CanonicalSchema {
fn eq(&self, other: &Self) -> bool {
self.validate_formats == other.validate_formats
&& self.draft == other.draft
&& self.inner == other.inner
&& (Arc::ptr_eq(&self.definitions, &other.definitions)
|| self.definitions == other.definitions)
}
}
impl Eq for CanonicalSchema {}
impl PartialOrd for CanonicalSchema {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for CanonicalSchema {
fn cmp(&self, other: &Self) -> Ordering {
self.inner
.cmp(&other.inner)
.then_with(|| self.draft.cmp(&other.draft))
.then_with(|| self.validate_formats.cmp(&other.validate_formats))
.then_with(|| self.definitions.cmp(&other.definitions))
}
}
impl Hash for CanonicalSchema {
fn hash<H: Hasher>(&self, state: &mut H) {
self.inner.hash(state);
self.draft.hash(state);
self.validate_formats.hash(state);
}
}
impl CanonicalSchema {
pub(crate) fn new(
inner: Schema,
draft: Draft,
pattern_options: PatternEngineOptions,
validate_formats: bool,
definitions: Arc<DefinitionMap>,
) -> Self {
Self {
inner,
draft,
pattern_options,
validate_formats,
definitions,
}
}
#[must_use]
pub fn to_json_schema(&self) -> Value {
emit::to_json_schema(&self.inner, self.draft, &self.definitions)
}
#[must_use]
pub fn is_satisfiable(&self) -> bool {
!matches!(self.schema_kind(), SchemaKind::False)
}
#[must_use]
pub(crate) fn schema_kind(&self) -> &SchemaKind {
self.inner.kind()
}
#[must_use]
pub fn draft(&self) -> Draft {
self.draft
}
pub(crate) fn wrap_child(&self, child: &Schema) -> Self {
Self::new(
child.clone(),
self.draft,
self.pattern_options,
self.validate_formats,
Arc::clone(&self.definitions),
)
}
#[must_use]
pub fn definition(&self, uri: &str) -> Option<CanonicalSchema> {
self.definitions.get(uri).map(|body| self.wrap_child(body))
}
#[must_use]
pub fn definitions(&self) -> impl ExactSizeIterator<Item = (String, CanonicalSchema)> + '_ {
self.definitions
.iter()
.map(|(uri, body)| (uri.to_string(), self.wrap_child(body)))
}
pub fn intersect(&self, other: &Self) -> Result<Self, CanonicalizationError> {
self.check_operands(other)?;
let definitions = self.merged_definitions(other)?;
let context =
CanonicalizationContext::new(self.draft, self.pattern_options, self.validate_formats);
let inner = algebra::intersect(self.inner.clone(), other.inner.clone(), &context);
if context.saw_unspellable_meet() {
return Err(CanonicalizationError::UnmodeledOperand);
}
Ok(Self::new(
inner,
self.draft,
self.pattern_options,
self.validate_formats,
definitions,
))
}
pub fn is_subset_of(&self, other: &Self) -> Result<Option<bool>, CanonicalizationError> {
self.check_operands(other)?;
self.merged_definitions(other)?;
let context =
CanonicalizationContext::new(self.draft, self.pattern_options, self.validate_formats);
if oracle::covers(&self.inner, &other.inner, &context) == Verdict::Admits {
return Ok(Some(true));
}
let Some(values) = self.schema_kind().finite_values() else {
return Ok(None);
};
let refuted = values.iter().any(|value| {
algebra::admits_value(
&other.inner,
value.as_value(),
UncheckableFacet::Undecided,
&context,
) == Verdict::Rejects
});
Ok(refuted.then_some(false))
}
#[must_use]
pub fn negate(&self) -> Option<Self> {
let context =
CanonicalizationContext::new(self.draft, self.pattern_options, self.validate_formats);
let inner = negate::negate_with_definitions(&self.inner, &self.definitions, &context)?;
let mut definitions = (*self.definitions).clone();
parse::prune_unreachable_definitions(&inner, &mut definitions);
Some(Self::new(
inner,
self.draft,
self.pattern_options,
self.validate_formats,
Arc::new(definitions),
))
}
fn check_operands(&self, other: &Self) -> Result<(), CanonicalizationError> {
if matches!(self.schema_kind(), SchemaKind::Raw(_))
|| matches!(other.schema_kind(), SchemaKind::Raw(_))
{
return Err(CanonicalizationError::UnmodeledOperand);
}
let mismatch = if self.draft != other.draft {
OperandMismatch::Drafts {
left: self.draft,
right: other.draft,
}
} else if self.validate_formats != other.validate_formats {
OperandMismatch::FormatAssertions
} else if self.pattern_options != other.pattern_options {
OperandMismatch::PatternEngine
} else {
return Ok(());
};
Err(CanonicalizationError::IncompatibleOperands(mismatch))
}
fn merged_definitions(
&self,
other: &Self,
) -> Result<Arc<DefinitionMap>, CanonicalizationError> {
if Arc::ptr_eq(&self.definitions, &other.definitions) || other.definitions.is_empty() {
return Ok(Arc::clone(&self.definitions));
}
if self.definitions.is_empty() {
return Ok(Arc::clone(&other.definitions));
}
Err(CanonicalizationError::IncompatibleOperands(
OperandMismatch::Definitions,
))
}
}