use serde_json::{Map, Value};
use crate::schema::DefaultValue;
use super::types::{
Catalog, CatalogCompaction, CatalogFieldSpec, CheckEntry, HistoryEntry, IndexEntry, Relation, TableEntry,
TableOptions, TriggerEffect, TriggerEntry, TriggerFieldMapping, TriggerValueSource, ViewEntry,
};
fn field_document(spec: &CatalogFieldSpec) -> Value {
let mut doc = Map::new();
doc.insert("type".to_string(), Value::from(spec.spec.field_type.as_str()));
doc.insert("required".to_string(), Value::from(spec.spec.required));
doc.insert("unique".to_string(), Value::from(spec.spec.unique));
if let Some(index) = spec.spec.index {
doc.insert(
"index".to_string(),
Value::from(match index {
crate::schema::IndexKind::Exact => "exact",
crate::schema::IndexKind::Sorted => "sorted",
}),
);
}
doc.insert("fulltext".to_string(), Value::from(spec.spec.fulltext));
doc.insert("cypher".to_string(), Value::from(spec.spec.cypher));
if spec.spec.password_hash {
doc.insert("passwordHash".to_string(), Value::Bool(true));
}
if !spec.spec.addressable {
doc.insert("addressable".to_string(), Value::Bool(false));
}
if let Some(values) = &spec.spec.values {
doc.insert("values".to_string(), Value::from(values.clone()));
}
if let Some(default) = &spec.spec.default {
let value = match default {
DefaultValue::Now => Value::from("now"),
DefaultValue::Value(v) => v.clone(),
};
doc.insert("default".to_string(), value);
}
if let Some(description) = &spec.description {
doc.insert("description".to_string(), Value::from(description.clone()));
}
if let Some(deprecated) = &spec.deprecated {
doc.insert("deprecated".to_string(), Value::from(deprecated.clone()));
}
Value::Object(doc)
}
fn index_document(index: &IndexEntry) -> Value {
let mut doc = Map::new();
doc.insert("name".to_string(), Value::from(index.name.clone()));
doc.insert("fields".to_string(), Value::from(index.fields.clone()));
doc.insert("kind".to_string(), Value::from(index.kind.as_str()));
if index.unique {
doc.insert("unique".to_string(), Value::from(true));
}
if let Some(where_) = &index.where_ {
let clauses: Vec<Value> = where_
.iter()
.map(|clause| {
let mut c = Map::new();
c.insert("field".to_string(), Value::from(clause.field.clone()));
c.insert("op".to_string(), Value::from(clause.op.clone()));
c.insert("value".to_string(), clause.value.clone());
Value::Object(c)
})
.collect();
doc.insert("where".to_string(), Value::Array(clauses));
}
if let Some(expire) = index.expire_after_seconds {
doc.insert("expireAfterSeconds".to_string(), Value::from(expire));
}
Value::Object(doc)
}
fn check_document(check: &CheckEntry) -> Value {
let mut doc = Map::new();
doc.insert("name".to_string(), Value::from(check.name.clone()));
doc.insert("expr".to_string(), Value::from(check.expr.clone()));
Value::Object(doc)
}
fn history_document(entry: &HistoryEntry) -> Value {
let mut doc = Map::new();
doc.insert("revision".to_string(), Value::from(entry.revision));
doc.insert("at".to_string(), Value::from(entry.at));
if let Some(note) = &entry.note {
doc.insert("note".to_string(), Value::from(note.clone()));
}
Value::Object(doc)
}
fn options_document(options: &TableOptions) -> Value {
let mut doc = Map::new();
if let Some(sync) = options.sync {
doc.insert("sync".to_string(), Value::from(sync.as_str()));
}
if let Some(compaction) = options.compaction {
let value = match compaction {
CatalogCompaction::Disabled => Value::Bool(false),
CatalogCompaction::Enabled {
dead_bytes_ratio,
min_dead_bytes,
} => {
let mut c = Map::new();
c.insert("deadBytesRatio".to_string(), Value::from(dead_bytes_ratio));
c.insert("minDeadBytes".to_string(), Value::from(min_dead_bytes));
Value::Object(c)
}
};
doc.insert("compaction".to_string(), value);
}
if let Some(link_parsing) = options.link_parsing {
doc.insert("linkParsing".to_string(), Value::from(link_parsing.as_str()));
}
if let Some(image_dir) = &options.image_dir {
doc.insert("imageDir".to_string(), Value::from(image_dir.clone()));
}
Value::Object(doc)
}
fn table_document(entry: &TableEntry) -> Value {
let mut doc = Map::new();
doc.insert("revision".to_string(), Value::from(entry.revision));
if let Some(description) = &entry.description {
doc.insert("description".to_string(), Value::from(description.clone()));
}
doc.insert("primaryKey".to_string(), Value::from(entry.primary_key.clone()));
if let Some(partition_by) = &entry.partition_by {
doc.insert("partitionBy".to_string(), Value::from(partition_by.clone()));
}
if let Some(created_at) = entry.created_at {
doc.insert("createdAt".to_string(), Value::from(created_at));
}
if let Some(updated_at) = entry.updated_at {
doc.insert("updatedAt".to_string(), Value::from(updated_at));
}
if let Some(validation) = entry.validation {
doc.insert("validation".to_string(), Value::from(validation.as_str()));
}
if let Some(options) = &entry.options {
doc.insert("options".to_string(), options_document(options));
}
let mut fields = Map::new();
for (name, spec) in &entry.fields {
fields.insert(name.clone(), field_document(spec));
}
doc.insert("fields".to_string(), Value::Object(fields));
if let Some(indexes) = &entry.indexes {
let mut sorted = indexes.clone();
sorted.sort_by(|a, b| a.name.cmp(&b.name));
doc.insert(
"indexes".to_string(),
Value::Array(sorted.iter().map(index_document).collect()),
);
}
if let Some(checks) = &entry.checks {
let mut sorted = checks.clone();
sorted.sort_by(|a, b| a.name.cmp(&b.name));
doc.insert(
"checks".to_string(),
Value::Array(sorted.iter().map(check_document).collect()),
);
}
if let Some(history) = &entry.history {
doc.insert(
"history".to_string(),
Value::Array(history.iter().map(history_document).collect()),
);
}
Value::Object(doc)
}
fn relation_document(relation: &Relation) -> Value {
let mut doc = Map::new();
doc.insert("name".to_string(), Value::from(relation.name.clone()));
let mut from = Map::new();
from.insert("table".to_string(), Value::from(relation.from.table.clone()));
from.insert("field".to_string(), Value::from(relation.from.field.clone()));
doc.insert("from".to_string(), Value::Object(from));
let mut to = Map::new();
to.insert("table".to_string(), Value::from(relation.to.table.clone()));
to.insert("field".to_string(), Value::from(relation.to.field.clone()));
doc.insert("to".to_string(), Value::Object(to));
doc.insert("cardinality".to_string(), Value::from(relation.cardinality.as_str()));
if let Some(on_delete) = relation.on_delete {
doc.insert("onDelete".to_string(), Value::from(on_delete.as_str()));
}
if let Some(enforce) = relation.enforce {
doc.insert("enforce".to_string(), Value::from(enforce));
}
Value::Object(doc)
}
fn view_document(view: &ViewEntry) -> Value {
let mut doc = Map::new();
doc.insert("name".to_string(), Value::from(view.name.clone()));
doc.insert("query".to_string(), Value::from(view.query.clone()));
if let Some(description) = &view.description {
doc.insert("description".to_string(), Value::from(description.clone()));
}
Value::Object(doc)
}
fn trigger_value_source_document(source: &TriggerValueSource) -> Value {
let mut doc = Map::new();
match source {
TriggerValueSource::NewField(field) => {
doc.insert("kind".to_string(), Value::from("new"));
doc.insert("field".to_string(), Value::from(field.clone()));
}
TriggerValueSource::OldField(field) => {
doc.insert("kind".to_string(), Value::from("old"));
doc.insert("field".to_string(), Value::from(field.clone()));
}
TriggerValueSource::Constant(value) => {
doc.insert("kind".to_string(), Value::from("constant"));
doc.insert("value".to_string(), value.clone());
}
TriggerValueSource::Now => {
doc.insert("kind".to_string(), Value::from("now"));
}
}
Value::Object(doc)
}
fn trigger_field_mapping_document(mapping: &TriggerFieldMapping) -> Value {
let mut doc = Map::new();
doc.insert("target".to_string(), Value::from(mapping.target.clone()));
doc.insert("source".to_string(), trigger_value_source_document(&mapping.source));
Value::Object(doc)
}
fn trigger_effect_document(effect: &TriggerEffect) -> Value {
let mut doc = Map::new();
doc.insert("table".to_string(), Value::from(effect.table.clone()));
doc.insert(
"fields".to_string(),
Value::Array(effect.fields.iter().map(trigger_field_mapping_document).collect()),
);
Value::Object(doc)
}
fn trigger_document(trigger: &TriggerEntry) -> Value {
let mut doc = Map::new();
doc.insert("name".to_string(), Value::from(trigger.name.clone()));
doc.insert("table".to_string(), Value::from(trigger.table.clone()));
doc.insert("event".to_string(), Value::from(trigger.event.as_str()));
doc.insert("effect".to_string(), trigger_effect_document(&trigger.effect));
Value::Object(doc)
}
pub fn catalog_document(catalog: &Catalog) -> Value {
let mut doc = Map::new();
doc.insert("catalogVersion".to_string(), Value::from(catalog.catalog_version));
doc.insert("formatVersion".to_string(), Value::from(catalog.format_version));
doc.insert("createdAt".to_string(), Value::from(catalog.created_at));
doc.insert("updatedAt".to_string(), Value::from(catalog.updated_at));
if let Some(engine) = &catalog.engine {
doc.insert("engine".to_string(), Value::from(engine.clone()));
}
if let Some(layout) = catalog.layout {
doc.insert("layout".to_string(), Value::from(layout.as_str()));
}
if let Some(defaults) = &catalog.defaults {
doc.insert("defaults".to_string(), options_document(defaults));
}
let mut tables = Map::new();
for (name, entry) in &catalog.tables {
tables.insert(name.clone(), table_document(entry));
}
doc.insert("tables".to_string(), Value::Object(tables));
if let Some(relations) = &catalog.relations {
let mut sorted = relations.clone();
sorted.sort_by(|a, b| a.name.cmp(&b.name));
doc.insert(
"relations".to_string(),
Value::Array(sorted.iter().map(relation_document).collect()),
);
}
if let Some(views) = &catalog.views {
let mut sorted = views.clone();
sorted.sort_by(|a, b| a.name.cmp(&b.name));
doc.insert(
"views".to_string(),
Value::Array(sorted.iter().map(view_document).collect()),
);
}
if let Some(triggers) = &catalog.triggers {
let mut sorted = triggers.clone();
sorted.sort_by(|a, b| a.name.cmp(&b.name));
doc.insert(
"triggers".to_string(),
Value::Array(sorted.iter().map(trigger_document).collect()),
);
}
Value::Object(doc)
}
pub fn serialize_catalog(catalog: &Catalog) -> String {
format!(
"{}\n",
serde_json::to_string_pretty(&catalog_document(catalog)).expect("catalog document is valid JSON")
)
}