pub mod array;
pub mod base;
pub mod object;
pub mod scalar;
use serde_json::Value;
use array::{ListStrategy, TupleStrategy};
use base::{ScalarSchemaStrategy, SchemaStrategy};
use object::ObjectStrategy;
use scalar::{BooleanStrategy, NullStrategy, NumberStrategy, StringStrategy, TypelessStrategy};
use self::array::ListSchemaStrategy;
use crate::dag::{kw, Dag, Id, ObjBuf, Sym};
use crate::genson_rs::interned::{RowStates, Strat};
use crate::genson_rs::node::{DeferredStrategy, SchemaNode};
pub(crate) fn keywords(extra: &Value, dag: &mut Dag) -> ObjBuf {
match extra {
Value::Object(map) => ObjBuf(
map.iter()
.map(|(key, value)| (dag.sym(key), dag.intern_value(value)))
.collect(),
),
_ => ObjBuf::default(),
}
}
pub(crate) fn is_empty_object(extra: &Value) -> bool {
extra.as_object().is_some_and(|map| map.is_empty())
}
fn list_to_dag(extra: &Value, items: Option<Id>, dag: &mut Dag) -> Id {
if is_empty_object(extra) {
let array = dag.string(kw::ARRAY);
return match items {
Some(items) => dag.object(&[(kw::TYPE, array), (kw::ITEMS, items)]),
None => dag.object(&[(kw::TYPE, array)]),
};
}
let mut schema = keywords(extra, dag);
let array = dag.string(kw::ARRAY);
schema.insert(kw::TYPE, array);
if let Some(items) = items {
schema.insert(kw::ITEMS, items);
}
schema.build(dag)
}
#[derive(Clone, Debug, PartialEq)]
pub enum BasicSchemaStrategy {
Object(ObjectStrategy),
List(ListStrategy),
Tuple(TupleStrategy),
Null(NullStrategy),
Boolean(BooleanStrategy),
Number(NumberStrategy),
String(StringStrategy),
Typeless(TypelessStrategy),
}
impl BasicSchemaStrategy {
pub fn new_for_object(object: crate::genson_rs::JsonValue) -> Option<Self> {
if ObjectStrategy::match_object(object) {
Some(BasicSchemaStrategy::Object(ObjectStrategy::new()))
} else if <ListStrategy as ListSchemaStrategy>::match_object(object) {
Some(BasicSchemaStrategy::List(ListStrategy::new()))
} else if <TupleStrategy as ListSchemaStrategy>::match_object(object) {
Some(BasicSchemaStrategy::Tuple(TupleStrategy::new()))
} else if <NullStrategy as SchemaStrategy>::match_object(object) {
Some(BasicSchemaStrategy::Null(NullStrategy::new()))
} else if <BooleanStrategy as SchemaStrategy>::match_object(object) {
Some(BasicSchemaStrategy::Boolean(BooleanStrategy::new()))
} else if <NumberStrategy as SchemaStrategy>::match_object(object) {
Some(BasicSchemaStrategy::Number(NumberStrategy::new()))
} else if <StringStrategy as SchemaStrategy>::match_object(object) {
Some(BasicSchemaStrategy::String(StringStrategy::new()))
} else {
None
}
}
pub fn new_for_schema(schema: &Value) -> Option<Self> {
if ObjectStrategy::match_schema(schema) {
Some(BasicSchemaStrategy::Object(ObjectStrategy::new()))
} else if ListStrategy::match_schema(schema) {
Some(BasicSchemaStrategy::List(ListStrategy::new()))
} else if TupleStrategy::match_schema(schema) {
Some(BasicSchemaStrategy::Tuple(TupleStrategy::new()))
} else if <NullStrategy as SchemaStrategy>::match_schema(schema) {
Some(BasicSchemaStrategy::Null(NullStrategy::new()))
} else if <BooleanStrategy as SchemaStrategy>::match_schema(schema) {
Some(BasicSchemaStrategy::Boolean(BooleanStrategy::new()))
} else if <NumberStrategy as SchemaStrategy>::match_schema(schema) {
Some(BasicSchemaStrategy::Number(NumberStrategy::new()))
} else if <StringStrategy as SchemaStrategy>::match_schema(schema) {
Some(BasicSchemaStrategy::String(StringStrategy::new()))
} else {
None
}
}
pub fn same_kind(&self, other: &BasicSchemaStrategy) -> bool {
std::mem::discriminant(self) == std::mem::discriminant(other)
}
pub fn new_of_same_kind(&self) -> Self {
match self {
BasicSchemaStrategy::Object(_) => BasicSchemaStrategy::Object(ObjectStrategy::new()),
BasicSchemaStrategy::List(_) => BasicSchemaStrategy::List(ListStrategy::new()),
BasicSchemaStrategy::Tuple(_) => BasicSchemaStrategy::Tuple(TupleStrategy::new()),
BasicSchemaStrategy::Null(_) => BasicSchemaStrategy::Null(NullStrategy::new()),
BasicSchemaStrategy::Boolean(_) => BasicSchemaStrategy::Boolean(BooleanStrategy::new()),
BasicSchemaStrategy::Number(_) => BasicSchemaStrategy::Number(NumberStrategy::new()),
BasicSchemaStrategy::String(_) => BasicSchemaStrategy::String(StringStrategy::new()),
BasicSchemaStrategy::Typeless(_) => {
BasicSchemaStrategy::Typeless(TypelessStrategy::new())
}
}
}
pub fn bare_type(&self) -> Option<&'static str> {
let no_keywords = |keywords: &Value| keywords.as_object().is_some_and(|k| k.is_empty());
match self {
BasicSchemaStrategy::Object(strategy) => strategy.is_bare().then_some("object"),
BasicSchemaStrategy::List(_)
| BasicSchemaStrategy::Tuple(_)
| BasicSchemaStrategy::Typeless(_) => None,
BasicSchemaStrategy::Null(strategy) => {
no_keywords(strategy.get_extra_keywords()).then_some("null")
}
BasicSchemaStrategy::Boolean(strategy) => {
no_keywords(strategy.get_extra_keywords()).then_some("boolean")
}
BasicSchemaStrategy::Number(strategy) => {
no_keywords(strategy.get_extra_keywords()).then(|| strategy.number_type())
}
BasicSchemaStrategy::String(strategy) => {
no_keywords(strategy.get_extra_keywords()).then_some("string")
}
}
}
pub fn absorb(&mut self, other: BasicSchemaStrategy) {
match (self, other) {
(BasicSchemaStrategy::Object(strategy), BasicSchemaStrategy::Object(other)) => {
strategy.absorb(other)
}
(BasicSchemaStrategy::List(strategy), BasicSchemaStrategy::List(other)) => {
strategy.absorb(other)
}
(strategy, other) if strategy.same_kind(&other) && other.bare_type().is_some() => {
if let (BasicSchemaStrategy::Number(strategy), Some(number_type)) =
(strategy, other.bare_type())
{
strategy.add_number_type(number_type);
}
}
(strategy, other) => strategy.add_schema(&other.to_schema()),
}
}
pub(crate) fn same_kind_as_interned(&self, strat: Strat) -> bool {
matches!(
(self, strat),
(BasicSchemaStrategy::Object(_), Strat::Object(_))
| (BasicSchemaStrategy::List(_), Strat::List(_))
| (BasicSchemaStrategy::Null(_), Strat::Null)
| (BasicSchemaStrategy::Boolean(_), Strat::Boolean)
| (BasicSchemaStrategy::Number(_), Strat::Number(_))
| (BasicSchemaStrategy::String(_), Strat::String)
)
}
pub(crate) fn new_of_interned_kind(strat: Strat) -> Self {
match strat {
Strat::Object(_) => BasicSchemaStrategy::Object(ObjectStrategy::new()),
Strat::List(_) => BasicSchemaStrategy::List(ListStrategy::new()),
Strat::Null => BasicSchemaStrategy::Null(NullStrategy::new()),
Strat::Boolean => BasicSchemaStrategy::Boolean(BooleanStrategy::new()),
Strat::Number(_) => BasicSchemaStrategy::Number(NumberStrategy::new()),
Strat::String => BasicSchemaStrategy::String(StringStrategy::new()),
}
}
pub(crate) fn absorb_interned(&mut self, rows: &RowStates, strat: Strat) {
match (self, strat) {
(BasicSchemaStrategy::Object(strategy), Strat::Object(obj)) => {
strategy.absorb_interned(rows, obj)
}
(BasicSchemaStrategy::List(strategy), Strat::List(items)) => {
strategy.absorb_interned(rows, items)
}
(BasicSchemaStrategy::Number(strategy), Strat::Number(float)) => {
strategy.add_number_type(if float { "number" } else { "integer" })
}
_ => {}
}
}
pub(crate) fn absorb_interned_deferred<'a>(
&mut self,
rows: &'a RowStates,
strat: Strat,
deferred: &mut DeferredStrategy<'a>,
) {
match (self, strat) {
(BasicSchemaStrategy::Object(strategy), Strat::Object(obj)) => {
strategy.absorb_interned_deferred(rows, obj, deferred)
}
(BasicSchemaStrategy::List(_), Strat::List(items)) => {
if !matches!(deferred, DeferredStrategy::List(_)) {
*deferred = DeferredStrategy::List(Vec::new());
}
if let DeferredStrategy::List(states) = deferred {
states.push((rows, items));
}
}
(BasicSchemaStrategy::Number(strategy), Strat::Number(float)) => {
strategy.add_number_type(if float { "number" } else { "integer" })
}
_ => {}
}
}
pub(crate) fn absorb_deferred(&mut self, deferred: DeferredStrategy) {
match (self, deferred) {
(BasicSchemaStrategy::Object(strategy), DeferredStrategy::Object(groups)) => {
strategy.absorb_deferred(groups)
}
(BasicSchemaStrategy::List(strategy), DeferredStrategy::List(states)) => {
strategy.items_node_mut().absorb_interned_all(&states)
}
_ => {}
}
}
pub(crate) fn unintern(&mut self) {
match self {
BasicSchemaStrategy::Object(strategy) => strategy.unintern(),
BasicSchemaStrategy::List(strategy) => {
strategy.get_items_mut().for_each(SchemaNode::unintern)
}
BasicSchemaStrategy::Tuple(strategy) => {
strategy.get_items_mut().for_each(SchemaNode::unintern)
}
_ => {}
}
}
pub fn visit_properties_mut<F>(&mut self, f: &mut F)
where
F: FnMut(&str, &mut SchemaNode) -> bool,
{
match self {
BasicSchemaStrategy::Object(strategy) => strategy.visit_properties_mut(f),
BasicSchemaStrategy::List(strategy) => strategy.get_items_mut().for_each(|node| {
node.visit_properties_mut(f);
}),
BasicSchemaStrategy::Tuple(strategy) => strategy.get_items_mut().for_each(|node| {
node.visit_properties_mut(f);
}),
_ => {}
}
}
pub fn hash_schema<H: std::hash::Hasher>(&self, hasher: &mut H) {
match self {
BasicSchemaStrategy::Object(strategy) => {
hasher.write_u8(b'O');
strategy.hash_schema(hasher);
}
BasicSchemaStrategy::List(strategy) => {
hasher.write_u8(b'L');
strategy.items_node().hash_schema(hasher);
}
_ => {
hasher.write_u8(b'S');
match self.bare_type() {
Some(name) => {
hasher.write(br#"{"type":""#);
hasher.write(name.as_bytes());
hasher.write(br#""}"#);
}
None => crate::genson_rs::node::hash_value(&self.to_schema(), hasher),
}
}
}
}
pub(crate) fn to_dag(&self, dag: &mut Dag) -> Id {
let typed = |extra: &Value, js_type: Sym, dag: &mut Dag| {
if is_empty_object(extra) {
return dag.typed(js_type);
}
let mut schema = keywords(extra, dag);
let js_type = dag.string(js_type);
schema.insert(kw::TYPE, js_type);
schema.build(dag)
};
match self {
BasicSchemaStrategy::Object(strategy) => strategy.to_dag(dag),
BasicSchemaStrategy::List(strategy) => {
let items = strategy.get_items().next().map(|items| items.to_dag(dag));
list_to_dag(strategy.get_extra_keywords(), items, dag)
}
BasicSchemaStrategy::Tuple(strategy) => {
let items: Vec<Id> = strategy.get_items().map(|item| item.to_dag(dag)).collect();
let items = (!items.is_empty()).then(|| dag.array(&items));
list_to_dag(strategy.get_extra_keywords(), items, dag)
}
BasicSchemaStrategy::Null(strategy) => {
typed(strategy.get_extra_keywords(), kw::NULL, dag)
}
BasicSchemaStrategy::Boolean(strategy) => {
typed(strategy.get_extra_keywords(), kw::BOOLEAN, dag)
}
BasicSchemaStrategy::Number(strategy) => typed(
strategy.get_extra_keywords(),
match strategy.number_type() {
"integer" => kw::INTEGER,
_ => kw::NUMBER,
},
dag,
),
BasicSchemaStrategy::String(strategy) => {
typed(strategy.get_extra_keywords(), kw::STRING, dag)
}
BasicSchemaStrategy::Typeless(strategy) => {
keywords(strategy.get_extra_keywords(), dag).build(dag)
}
}
}
pub fn to_schema(&self) -> Value {
match self {
BasicSchemaStrategy::Object(strategy) => strategy.to_schema(),
BasicSchemaStrategy::List(strategy) => ListSchemaStrategy::to_schema(strategy),
BasicSchemaStrategy::Tuple(strategy) => ListSchemaStrategy::to_schema(strategy),
BasicSchemaStrategy::Null(strategy) => ScalarSchemaStrategy::to_schema(strategy),
BasicSchemaStrategy::Boolean(strategy) => ScalarSchemaStrategy::to_schema(strategy),
BasicSchemaStrategy::Number(strategy) => ScalarSchemaStrategy::to_schema(strategy),
BasicSchemaStrategy::String(strategy) => ScalarSchemaStrategy::to_schema(strategy),
BasicSchemaStrategy::Typeless(strategy) => strategy.to_schema(),
}
}
pub fn match_object(&self, object: crate::genson_rs::JsonValue) -> bool {
match self {
BasicSchemaStrategy::Object(_) => ObjectStrategy::match_object(object),
BasicSchemaStrategy::List(_) => {
<ListStrategy as ListSchemaStrategy>::match_object(object)
}
BasicSchemaStrategy::Tuple(_) => {
<TupleStrategy as ListSchemaStrategy>::match_object(object)
}
BasicSchemaStrategy::Null(_) => <NullStrategy as SchemaStrategy>::match_object(object),
BasicSchemaStrategy::Boolean(_) => {
<BooleanStrategy as SchemaStrategy>::match_object(object)
}
BasicSchemaStrategy::Number(_) => {
<NumberStrategy as SchemaStrategy>::match_object(object)
}
BasicSchemaStrategy::String(_) => {
<StringStrategy as SchemaStrategy>::match_object(object)
}
BasicSchemaStrategy::Typeless(_) => TypelessStrategy::match_object(object),
}
}
pub fn match_schema(&self, schema: &Value) -> bool {
match self {
BasicSchemaStrategy::Object(_) => ObjectStrategy::match_schema(schema),
BasicSchemaStrategy::List(_) => ListStrategy::match_schema(schema),
BasicSchemaStrategy::Tuple(_) => TupleStrategy::match_schema(schema),
BasicSchemaStrategy::Null(_) => <NullStrategy as SchemaStrategy>::match_schema(schema),
BasicSchemaStrategy::Boolean(_) => {
<BooleanStrategy as SchemaStrategy>::match_schema(schema)
}
BasicSchemaStrategy::Number(_) => {
<NumberStrategy as SchemaStrategy>::match_schema(schema)
}
BasicSchemaStrategy::String(_) => {
<StringStrategy as SchemaStrategy>::match_schema(schema)
}
BasicSchemaStrategy::Typeless(_) => TypelessStrategy::match_schema(schema),
}
}
pub fn add_schema(&mut self, schema: &Value) {
match self {
BasicSchemaStrategy::Object(strategy) => strategy.add_schema(schema),
BasicSchemaStrategy::List(strategy) => strategy.add_schema(schema),
BasicSchemaStrategy::Tuple(strategy) => strategy.add_schema(schema),
BasicSchemaStrategy::Null(strategy) => strategy.add_schema(schema),
BasicSchemaStrategy::Boolean(strategy) => strategy.add_schema(schema),
BasicSchemaStrategy::Number(strategy) => strategy.add_schema(schema),
BasicSchemaStrategy::String(strategy) => strategy.add_schema(schema),
BasicSchemaStrategy::Typeless(strategy) => strategy.add_schema(schema),
}
}
pub fn add_schemas(&mut self, schemas: &[&Value]) {
match self {
BasicSchemaStrategy::Object(strategy) => strategy.add_schemas(schemas),
BasicSchemaStrategy::List(strategy) => strategy.add_schemas(schemas),
BasicSchemaStrategy::Tuple(strategy) => strategy.add_schemas(schemas),
BasicSchemaStrategy::Boolean(strategy) => strategy.add_schemas(schemas),
BasicSchemaStrategy::Number(strategy) => strategy.add_schemas(schemas),
BasicSchemaStrategy::String(strategy) => strategy.add_schemas(schemas),
BasicSchemaStrategy::Null(strategy) => strategy.add_schemas(schemas),
BasicSchemaStrategy::Typeless(strategy) => strategy.add_schemas(schemas),
}
}
pub fn add_object(&mut self, object: crate::genson_rs::JsonValue) {
match self {
BasicSchemaStrategy::Object(strategy) => strategy.add_object(object),
BasicSchemaStrategy::List(strategy) => strategy.add_object(object),
BasicSchemaStrategy::Tuple(strategy) => strategy.add_object(object),
BasicSchemaStrategy::Null(strategy) => strategy.add_object(object),
BasicSchemaStrategy::Boolean(strategy) => strategy.add_object(object),
BasicSchemaStrategy::Number(strategy) => strategy.add_object(object),
BasicSchemaStrategy::String(strategy) => strategy.add_object(object),
BasicSchemaStrategy::Typeless(strategy) => strategy.add_object(object),
}
}
}