use std::collections::BTreeMap;
use serde_json::{Number, Value};
use crate::{
canonical::{
ir::{
ArrayLeaf, BoundCardinality, BoundInteger, BoundNumber, BoundRational, CanonicalJson,
Divisors, IntegerLeaf, NumberLeaf, ObjectLeaf, SchemaKind, StringLeaf,
},
CanonicalSchema,
},
JsonType, JsonTypeSet,
};
pub use crate::canonical::ir::CanonicalKind;
impl CanonicalKind {
#[must_use]
pub fn as_str(self) -> &'static str {
self.into()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum CanonicalView {
MultiType(JsonTypeSet),
TypedGroup(TypedGroupView),
String(StringView),
Integer(IntegerView),
Number(NumberView),
Array(ArrayView),
Object(ObjectView),
Const(Value),
Enum(Vec<Value>),
Not(Box<CanonicalSchema>),
AllOf(Vec<CanonicalSchema>),
AnyOf(Vec<CanonicalSchema>),
OneOf(Vec<CanonicalSchema>),
Reference(String),
True,
False,
Raw(Value),
}
#[derive(Debug, Clone, PartialEq)]
pub struct TypedGroupView {
pub ty: JsonType,
pub body: CanonicalSchema,
}
#[derive(Debug, Clone, PartialEq)]
pub struct StringView {
pub min_length: Option<Number>,
pub max_length: Option<Number>,
pub patterns: Vec<String>,
pub formats: Vec<String>,
pub content_media_types: Vec<String>,
pub content_encodings: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct NumberView {
pub minimum: Option<Number>,
pub exclusive_minimum: bool,
pub maximum: Option<Number>,
pub exclusive_maximum: bool,
pub multiple_of: Vec<Number>,
}
#[allow(clippy::struct_field_names)]
#[derive(Debug, Clone, PartialEq)]
pub struct ArrayView {
pub min_items: Option<Number>,
pub max_items: Option<Number>,
pub unique_items: bool,
pub prefix_items: Vec<CanonicalSchema>,
pub items: Option<CanonicalSchema>,
pub contains: Vec<ContainsView>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ContainsView {
pub schema: CanonicalSchema,
pub min_contains: Option<Number>,
pub max_contains: Option<Number>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ObjectView {
pub min_properties: Option<Number>,
pub max_properties: Option<Number>,
pub required: Vec<String>,
pub property_names: Option<CanonicalSchema>,
pub properties: BTreeMap<String, CanonicalSchema>,
pub pattern_properties: BTreeMap<String, CanonicalSchema>,
pub additional_properties: Option<CanonicalSchema>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct IntegerView {
pub minimum: Option<Number>,
pub maximum: Option<Number>,
pub multiple_of: Vec<Number>,
}
impl CanonicalSchema {
#[must_use]
pub fn view(&self) -> CanonicalView {
match self.schema_kind() {
SchemaKind::MultiType(set) => CanonicalView::MultiType(*set),
SchemaKind::TypedGroup { ty, body } => CanonicalView::TypedGroup(TypedGroupView {
ty: *ty,
body: self.wrap_child(body),
}),
SchemaKind::String(leaf) => CanonicalView::String(string_view(leaf.get())),
SchemaKind::Integer(bounds) => CanonicalView::Integer(integer_view(bounds.get())),
SchemaKind::Number(leaf) => CanonicalView::Number(number_view(leaf.get())),
SchemaKind::Array(leaf) => CanonicalView::Array(array_view(
leaf.get(),
leaf.get()
.prefix
.iter()
.map(|schema| self.wrap_child(schema))
.collect(),
leaf.get()
.items
.as_ref()
.map(|items| self.wrap_child(items)),
leaf.get()
.contains
.iter()
.map(|facet| ContainsView {
schema: self.wrap_child(&facet.schema),
min_contains: facet.minimum.as_ref().map(BoundCardinality::to_number),
max_contains: facet.maximum.as_ref().map(BoundCardinality::to_number),
})
.collect(),
)),
SchemaKind::Object(leaf) => CanonicalView::Object(object_view(
leaf.get(),
leaf.get()
.property_names
.as_ref()
.map(|names| self.wrap_child(names)),
leaf.get()
.properties
.iter()
.map(|(key, schema)| (key.to_string(), self.wrap_child(schema)))
.collect(),
leaf.get()
.pattern_properties
.iter()
.map(|(pattern, schema)| (pattern.to_string(), self.wrap_child(schema)))
.collect(),
leaf.get()
.additional
.as_ref()
.map(|shield| self.wrap_child(shield)),
)),
SchemaKind::Const(value) => CanonicalView::Const(value.to_value()),
SchemaKind::Enum(values) => CanonicalView::Enum(
values
.as_slice()
.iter()
.map(CanonicalJson::to_value)
.collect(),
),
SchemaKind::Not(schema) => CanonicalView::Not(Box::new(self.wrap_child(schema))),
SchemaKind::AllOf(branches) => CanonicalView::AllOf(
branches
.as_slice()
.iter()
.map(|branch| self.wrap_child(branch))
.collect(),
),
SchemaKind::AnyOf(branches) => CanonicalView::AnyOf(
branches
.as_slice()
.iter()
.map(|branch| self.wrap_child(branch))
.collect(),
),
SchemaKind::OneOf(branches) => CanonicalView::OneOf(
branches
.iter()
.map(|branch| self.wrap_child(branch))
.collect(),
),
SchemaKind::Reference(uri) => CanonicalView::Reference(uri.to_string()),
SchemaKind::True => CanonicalView::True,
SchemaKind::False => CanonicalView::False,
SchemaKind::Raw(_) => CanonicalView::Raw(self.to_json_schema()),
}
}
#[must_use]
pub fn kind(&self) -> CanonicalKind {
self.schema_kind().into()
}
}
fn number_view(leaf: &NumberLeaf) -> NumberView {
NumberView {
minimum: leaf.minimum.as_ref().map(BoundNumber::to_number),
exclusive_minimum: leaf.minimum.as_ref().is_some_and(|b| !b.is_inclusive()),
maximum: leaf.maximum.as_ref().map(BoundNumber::to_number),
exclusive_maximum: leaf.maximum.as_ref().is_some_and(|b| !b.is_inclusive()),
multiple_of: divisor_numbers(&leaf.multiple_of),
}
}
fn integer_view(leaf: &IntegerLeaf) -> IntegerView {
IntegerView {
minimum: leaf.bounds.minimum.as_ref().map(BoundInteger::to_number),
maximum: leaf.bounds.maximum.as_ref().map(BoundInteger::to_number),
multiple_of: divisor_numbers(&leaf.multiple_of),
}
}
fn array_view(
leaf: &ArrayLeaf,
prefix_items: Vec<CanonicalSchema>,
items: Option<CanonicalSchema>,
contains: Vec<ContainsView>,
) -> ArrayView {
ArrayView {
prefix_items,
items,
contains,
min_items: leaf
.lengths
.minimum
.as_ref()
.map(BoundCardinality::to_number),
max_items: leaf
.lengths
.maximum
.as_ref()
.map(BoundCardinality::to_number),
unique_items: leaf.unique,
}
}
fn object_view(
leaf: &ObjectLeaf,
property_names: Option<CanonicalSchema>,
properties: BTreeMap<String, CanonicalSchema>,
pattern_properties: BTreeMap<String, CanonicalSchema>,
additional_properties: Option<CanonicalSchema>,
) -> ObjectView {
ObjectView {
min_properties: leaf.sizes.minimum.as_ref().map(BoundCardinality::to_number),
max_properties: leaf.sizes.maximum.as_ref().map(BoundCardinality::to_number),
required: leaf.required.iter().map(ToString::to_string).collect(),
property_names,
properties,
pattern_properties,
additional_properties,
}
}
fn string_view(leaf: &StringLeaf) -> StringView {
StringView {
min_length: leaf
.lengths
.minimum
.as_ref()
.map(BoundCardinality::to_number),
max_length: leaf
.lengths
.maximum
.as_ref()
.map(BoundCardinality::to_number),
patterns: leaf.patterns.iter().map(ToString::to_string).collect(),
formats: leaf.formats.iter().map(ToString::to_string).collect(),
content_media_types: leaf
.content_media_types
.iter()
.map(ToString::to_string)
.collect(),
content_encodings: leaf
.content_encodings
.iter()
.map(ToString::to_string)
.collect(),
}
}
fn divisor_numbers(divisors: &Divisors) -> Vec<Number> {
divisors
.as_slice()
.iter()
.map(BoundRational::to_number)
.collect()
}