use core::panic;
use std::borrow::Cow;
use crate::genson_rs::strategy::base::SchemaStrategy;
use crate::genson_rs::strategy::scalar::TypelessStrategy;
use crate::genson_rs::strategy::BasicSchemaStrategy;
use ordermap::OrderMap;
use serde_json::{json, Value};
const PAR_MERGE_MIN: usize = 8;
#[derive(Debug, PartialEq)]
pub struct SchemaNode {
active_strategies: Vec<BasicSchemaStrategy>,
}
#[derive(Clone)]
pub enum DataType<'a> {
Schema(&'a Value),
Object(&'a simd_json::BorrowedValue<'a>),
SchemaNode(&'a SchemaNode),
}
impl Default for SchemaNode {
fn default() -> Self {
Self::new()
}
}
impl SchemaNode {
pub fn new() -> Self {
SchemaNode {
active_strategies: vec![],
}
}
pub fn is_empty(&self) -> bool {
self.active_strategies.is_empty()
}
pub fn add_schema(&mut self, data: DataType) -> &mut Self {
let schema: Cow<Value> = match data {
DataType::SchemaNode(node) => Cow::Owned(node.to_schema()),
DataType::Schema(schema) => Cow::Borrowed(schema),
_ => panic!("Invalid schema type"),
};
if !SchemaNode::needs_splitting(&schema) {
let active_strategy = self.get_or_create_strategy_for_schema(&schema);
SchemaNode::add_schema_or_object_to_strategy(
active_strategy,
DataType::Schema(&schema),
);
return self;
}
for subschema in SchemaNode::get_subschemas(&schema) {
let active_strategy = self.get_or_create_strategy_for_schema(&subschema);
SchemaNode::add_schema_or_object_to_strategy(
active_strategy,
DataType::Schema(&subschema),
);
}
self
}
fn needs_splitting(schema: &Value) -> bool {
match schema {
Value::Object(obj) => {
matches!(obj.get("anyOf"), Some(Value::Array(_)))
|| matches!(obj.get("type"), Some(Value::Array(_)))
}
_ => false,
}
}
pub fn add_schemas_par(&mut self, schemas: &[&Value]) {
if schemas.len() >= PAR_MERGE_MIN
&& (self.add_object_schemas_par(schemas) || self.add_list_schemas_par(schemas))
{
return;
}
for schema in schemas {
self.add_schema(DataType::Schema(schema));
}
}
fn add_list_schemas_par(&mut self, schemas: &[&Value]) -> bool {
let plain_lists = schemas.iter().all(|schema| {
schema["type"] == "array"
&& schema["items"].is_object()
&& !SchemaNode::needs_splitting(schema)
});
if !plain_lists {
return false;
}
match self.active_strategies.as_slice() {
[] => {
self.get_or_create_strategy_for_schema(schemas[0]);
}
[BasicSchemaStrategy::List(_)] => {}
_ => return false,
}
match self.active_strategies.first_mut() {
Some(BasicSchemaStrategy::List(strategy)) => {
let items: Vec<&Value> = schemas.iter().map(|schema| &schema["items"]).collect();
strategy.add_item_schemas_par(&items);
true
}
_ => false,
}
}
pub fn add_object_schemas_par(&mut self, schemas: &[&Value]) -> bool {
if schemas.is_empty() {
return true;
}
let plain_objects = schemas
.iter()
.all(|schema| schema["type"] == "object" && !SchemaNode::needs_splitting(schema));
if !plain_objects {
return false;
}
match self.active_strategies.as_slice() {
[] => {
self.get_or_create_strategy_for_schema(schemas[0]);
}
[BasicSchemaStrategy::Object(_)] => {}
_ => return false,
}
match self.active_strategies.first_mut() {
Some(BasicSchemaStrategy::Object(strategy)) => {
strategy.add_schemas_par(schemas);
true
}
_ => false,
}
}
pub fn add_schemas(&mut self, schemas: &[Value]) -> &mut Self {
let mut all_subschemas: Vec<Cow<Value>> = Vec::new();
let mut schema_groups: OrderMap<usize, Vec<usize>> = OrderMap::new();
for schema in schemas {
for subschema in SchemaNode::get_subschemas(schema) {
let strategy_idx =
if let Some(idx) = self.get_strategy_for_kind(DataType::Schema(&subschema)) {
idx
} else {
if let Some(_strategy) =
self.create_strategy_for_kind(DataType::Schema(&subschema))
{
self.active_strategies.len() - 1
} else {
continue; }
};
let subschema_idx = all_subschemas.len();
all_subschemas.push(subschema);
schema_groups
.entry(strategy_idx)
.or_default()
.push(subschema_idx);
}
}
for (strategy_idx, subschema_indices) in schema_groups {
if let Some(strategy) = self.active_strategies.get_mut(strategy_idx) {
let schema_refs: Vec<&Value> = subschema_indices
.iter()
.map(|&idx| all_subschemas[idx].as_ref())
.collect();
strategy.add_schemas(&schema_refs);
}
}
self
}
fn get_subschemas(schema: &Value) -> Vec<Cow<'_, Value>> {
if let Value::Object(obj) = schema {
if let Some(Value::Array(anyof)) = obj.get("anyOf") {
return anyof.iter().flat_map(SchemaNode::get_subschemas).collect();
} else if let Some(Value::Array(types)) = obj.get("type") {
return types
.iter()
.map(|t| {
let mut new_schema = obj.clone();
new_schema["type"] = t.clone();
Cow::Owned(Value::Object(new_schema))
})
.collect();
} else {
return vec![Cow::Borrowed(schema)];
}
}
vec![Cow::Borrowed(schema)]
}
pub fn add_object(&mut self, data: DataType) -> &mut Self {
let object = match data {
DataType::Object(obj) => obj,
_ => panic!("Invalid object type"),
};
let active_strategy = self.get_or_create_strategy_for_object(object);
SchemaNode::add_schema_or_object_to_strategy(active_strategy, DataType::Object(object));
self
}
pub fn to_schema(&self) -> Value {
let mut scalar_schemas: Vec<Value> = vec![];
let mut generated_schemas: Vec<Value> = vec![];
self.active_strategies.iter().for_each(|strategy| {
let generated_schema: Value = strategy.to_schema();
if let Value::Object(ref schema) = generated_schema {
if schema.keys().len() == 1 && schema.contains_key("type") {
scalar_schemas.push(generated_schema);
} else {
generated_schemas.push(generated_schema);
}
} else {
panic!("Invalid schema type for strategy {:?}", strategy);
}
});
if scalar_schemas.len() == 1 {
generated_schemas.push(scalar_schemas.swap_remove(0));
} else if !scalar_schemas.is_empty() {
let mut scalar_type_list: Vec<&str> = scalar_schemas
.iter()
.map(|schema| schema["type"].as_str().unwrap())
.collect();
scalar_type_list.sort();
scalar_type_list.dedup();
if scalar_type_list.len() == 1 {
generated_schemas.push(scalar_schemas.swap_remove(0));
} else {
generated_schemas.push(json!({"type": scalar_type_list}));
}
}
if generated_schemas.len() == 1 {
generated_schemas.swap_remove(0)
} else if !generated_schemas.is_empty() {
json!({"anyOf": generated_schemas})
} else {
json!({})
}
}
fn get_or_create_strategy_for_object(
&mut self,
object: &simd_json::BorrowedValue,
) -> &mut BasicSchemaStrategy {
if let Some(idx) = self.get_strategy_for_kind(DataType::Object(object)) {
return &mut self.active_strategies[idx];
}
if let Some(strategy) = self.create_strategy_for_kind(DataType::Object(object)) {
return strategy;
}
panic!("Could not find matching schema type for object: {object}")
}
fn get_or_create_strategy_for_schema(&mut self, schema: &Value) -> &mut BasicSchemaStrategy {
if let Some(idx) = self.get_strategy_for_kind(DataType::Schema(schema)) {
return &mut self.active_strategies[idx];
}
if let Some(strategy) = self.create_strategy_for_kind(DataType::Schema(schema)) {
return strategy;
}
panic!("Could not find matching schema type for schema: {schema}")
}
fn get_strategy_for_kind(&self, schema_or_object: DataType) -> Option<usize> {
self.active_strategies.iter().position(|strategy| {
SchemaNode::strategy_does_match_schema_or_object(strategy, &schema_or_object)
})
}
fn create_strategy_for_kind(
&mut self,
schema_or_object: DataType,
) -> Option<&mut BasicSchemaStrategy> {
if let Some(mut strategy) =
SchemaNode::create_strategy_for_schema_or_object(&schema_or_object)
{
if let Some(BasicSchemaStrategy::Typeless(typeless)) = self.active_strategies.last() {
SchemaNode::add_schema_or_object_to_strategy(
&mut strategy,
DataType::Schema(&typeless.to_schema()),
);
self.active_strategies.pop();
}
self.active_strategies.push(strategy);
return Some(self.active_strategies.last_mut().unwrap());
}
else if let DataType::Schema(schema) = schema_or_object {
if TypelessStrategy::match_schema(schema) {
if self.active_strategies.is_empty() {
self.active_strategies
.push(BasicSchemaStrategy::Typeless(TypelessStrategy::new()));
}
let first_strategy = self.active_strategies.first_mut().unwrap();
return Some(first_strategy);
}
}
None
}
fn strategy_does_match_schema_or_object(
strategy: &BasicSchemaStrategy,
schema_or_object: &DataType,
) -> bool {
match schema_or_object {
DataType::Object(obj) => strategy.match_object(obj),
DataType::Schema(schema) => strategy.match_schema(schema),
_ => false,
}
}
fn create_strategy_for_schema_or_object(
schema_or_object: &DataType,
) -> Option<BasicSchemaStrategy> {
match schema_or_object {
DataType::Object(obj) => BasicSchemaStrategy::new_for_object(obj),
DataType::Schema(schema) => BasicSchemaStrategy::new_for_schema(schema),
_ => None,
}
}
fn add_schema_or_object_to_strategy(
strategy: &mut BasicSchemaStrategy,
schema_or_object: DataType,
) {
match schema_or_object {
DataType::Object(obj) => strategy.add_object(obj),
DataType::Schema(schema) => strategy.add_schema(schema),
_ => (),
}
}
}