use serde_json::{json, Value};
use std::hash::Hasher;
use crate::dag::{kw, Dag, Id, ObjBuf};
use crate::genson_rs::interned::{RowStates, StateId};
use crate::genson_rs::node::{DataType, Deferred, SchemaNode};
const DEFAULT_SCHEMA_URI: &str = "http://json-schema.org/schema#";
const NULL_SCHEMA_URI: &str = "NULL";
pub struct SchemaBuilder {
schema_uri: Option<String>,
root_node: SchemaNode,
}
impl SchemaBuilder {
pub fn new(schema_uri: Option<&str>) -> Self {
let root_node: SchemaNode = SchemaNode::new();
if let Some(uri) = schema_uri {
if uri == "AUTO" {
SchemaBuilder {
schema_uri: None,
root_node,
}
} else {
SchemaBuilder {
schema_uri: Some(uri.to_string()),
root_node,
}
}
} else {
SchemaBuilder {
schema_uri: Some(NULL_SCHEMA_URI.to_string()),
root_node,
}
}
}
pub fn add_schema(&mut self, mut schema: Value) {
self.add_schema_mut(&mut schema);
}
pub fn add_schema_mut(&mut self, schema: &mut Value) {
if let Value::Object(schema_obj) = schema {
if schema_obj.contains_key("$schema") && self.schema_uri.is_none() {
self.schema_uri = Some(schema_obj["$schema"].as_str().unwrap().to_string());
schema_obj.shift_remove("$schema");
}
self.root_node.add_schema(DataType::Schema(schema));
} else {
panic!("Invalid schema type - must be a valid JSON object")
}
}
pub fn add_schemas_mut(&mut self, schemas: &mut [Value]) {
for schema in schemas.iter_mut() {
let Value::Object(schema_obj) = schema else {
panic!("Invalid schema type - must be a valid JSON object")
};
if schema_obj.contains_key("$schema") && self.schema_uri.is_none() {
self.schema_uri = Some(schema_obj["$schema"].as_str().unwrap().to_string());
schema_obj.shift_remove("$schema");
}
}
let refs: Vec<&Value> = schemas.iter().collect();
if !self.root_node.add_object_schemas_par(&refs) {
for schema in refs {
self.root_node.add_schema(DataType::Schema(schema));
}
}
}
pub fn absorb(&mut self, other: SchemaBuilder) {
if self.root_node.is_empty() && other.schema_uri.as_deref() == Some(NULL_SCHEMA_URI) {
self.root_node = other.root_node;
} else {
self.add_builder(other);
}
}
pub fn add_builder(&mut self, other: SchemaBuilder) {
match other.emitted_schema_uri() {
None => self.root_node.absorb(other.root_node),
Some(uri) if self.schema_uri.is_none() => {
self.schema_uri = Some(uri.to_string());
self.root_node.absorb(other.root_node);
}
Some(_) => self.add_schema(other.to_schema()),
}
}
pub fn add_builders(&mut self, others: Vec<SchemaBuilder>) {
let mut objects = Vec::with_capacity(others.len());
let mut kept_uri = None;
for other in others {
if !other.root_node.is_object() {
self.root_node.absorb_all(std::mem::take(&mut objects));
self.add_builder(other);
continue;
}
match (other.emitted_schema_uri(), &self.schema_uri) {
(Some(uri), None) => self.schema_uri = Some(uri.to_string()),
(Some(uri), Some(_)) => kept_uri = kept_uri.or(Some(uri.to_string())),
(None, _) => {}
}
objects.push(other.root_node);
}
self.root_node.absorb_all(objects);
if let Some(uri) = kept_uri {
self.root_node.add_object_keyword("$schema", uri.into());
}
}
pub(crate) fn add_interned_row_chunks<'a>(
&mut self,
chunks: impl IntoIterator<Item = Vec<(&'a RowStates, StateId)>>,
row_uri: Option<&str>,
) {
let row_uri = SchemaBuilder::new(row_uri)
.emitted_schema_uri()
.map(str::to_string);
let mut deferred = Deferred::default();
for chunk in chunks {
let mut kept_uri = None;
for (states, state) in chunk {
match (&row_uri, &self.schema_uri) {
(Some(uri), None) => self.schema_uri = Some(uri.clone()),
(Some(uri), Some(_)) => kept_uri = kept_uri.or(Some(uri.clone())),
(None, _) => {}
}
self.root_node
.absorb_interned_deferred(states, state, &mut deferred);
}
if let Some(uri) = kept_uri {
self.root_node.add_object_keyword("$schema", uri.into());
}
}
self.root_node.absorb_deferred(deferred);
}
pub(crate) fn root_node_mut(&mut self) -> &mut SchemaNode {
&mut self.root_node
}
pub fn schema_hash(&self) -> u64 {
let mut hasher = xxhash_rust::xxh64::Xxh64::new(0);
hasher.write(self.emitted_schema_uri().unwrap_or_default().as_bytes());
hasher.write_u8(0xff);
self.root_node.hash_schema(&mut hasher);
hasher.finish()
}
fn emitted_schema_uri(&self) -> Option<&str> {
match self.schema_uri.as_deref() {
Some(NULL_SCHEMA_URI) => None,
Some(uri) => Some(uri),
None => Some(DEFAULT_SCHEMA_URI),
}
}
pub fn add_schemas(&mut self, schemas: &[Value]) {
let schemas: Vec<&Value> = schemas.iter().collect();
self.root_node.add_schemas(&schemas);
}
pub fn add_schema_node(&mut self, node: SchemaNode) {
self.root_node.add_schema(DataType::SchemaNode(&node));
}
pub fn add_object(&mut self, object: super::JsonValue) {
self.root_node.add_object(DataType::Object(object));
}
pub fn to_schema(&self) -> Value {
let mut base_schema = self.get_base_schema();
let base_schema_map = base_schema.as_object_mut().unwrap();
let Value::Object(node_schema_map) = self.root_node.to_schema() else {
panic!("node schema must be an object")
};
for (key, value) in node_schema_map {
base_schema_map.insert(key, value);
}
base_schema
}
pub(crate) fn to_dag(&self, dag: &mut Dag) -> Id {
let mut schema = ObjBuf::default();
match self.schema_uri.as_deref() {
Some(NULL_SCHEMA_URI) => {}
uri => {
let uri = dag.str(uri.unwrap_or(DEFAULT_SCHEMA_URI));
schema.insert(kw::SCHEMA, uri);
}
}
let node = self.root_node.to_dag(dag);
let Some(node) = dag.as_object(node) else {
panic!("node schema must be an object")
};
for (key, value) in node.iter() {
schema.insert(key, value);
}
schema.build(dag)
}
fn get_base_schema(&self) -> Value {
if let Some(uri) = &self.schema_uri {
if uri == NULL_SCHEMA_URI {
json!({})
} else {
json!({"$schema": uri})
}
} else {
json!({"$schema": DEFAULT_SCHEMA_URI})
}
}
pub fn to_json(&self) -> String {
let schema = self.to_schema();
schema.to_string()
}
}