Skip to main content

hayson_engine/catalog/
serialize.rs

1use serde_json::{Map, Value};
2
3use crate::schema::DefaultValue;
4
5use super::types::{
6    Catalog, CatalogCompaction, CatalogFieldSpec, CheckEntry, HistoryEntry, IndexEntry, Relation, TableEntry,
7    TableOptions, TriggerEffect, TriggerEntry, TriggerFieldMapping, TriggerValueSource, ViewEntry,
8};
9
10fn field_document(spec: &CatalogFieldSpec) -> Value {
11    let mut doc = Map::new();
12    doc.insert("type".to_string(), Value::from(spec.spec.field_type.as_str()));
13    doc.insert("required".to_string(), Value::from(spec.spec.required));
14    doc.insert("unique".to_string(), Value::from(spec.spec.unique));
15    if let Some(index) = spec.spec.index {
16        doc.insert(
17            "index".to_string(),
18            Value::from(match index {
19                crate::schema::IndexKind::Exact => "exact",
20                crate::schema::IndexKind::Sorted => "sorted",
21            }),
22        );
23    }
24    doc.insert("fulltext".to_string(), Value::from(spec.spec.fulltext));
25    doc.insert("cypher".to_string(), Value::from(spec.spec.cypher));
26    // NOTE: only when set -- rendering the default would rewrite every existing catalog.
27    if spec.spec.password_hash {
28        doc.insert("passwordHash".to_string(), Value::Bool(true));
29    }
30    // NOTE: only when opted out -- rendering the default would rewrite every existing catalog.
31    if !spec.spec.addressable {
32        doc.insert("addressable".to_string(), Value::Bool(false));
33    }
34    if let Some(values) = &spec.spec.values {
35        doc.insert("values".to_string(), Value::from(values.clone()));
36    }
37    if let Some(default) = &spec.spec.default {
38        let value = match default {
39            DefaultValue::Now => Value::from("now"),
40            DefaultValue::Value(v) => v.clone(),
41        };
42        doc.insert("default".to_string(), value);
43    }
44    if let Some(description) = &spec.description {
45        doc.insert("description".to_string(), Value::from(description.clone()));
46    }
47    if let Some(deprecated) = &spec.deprecated {
48        doc.insert("deprecated".to_string(), Value::from(deprecated.clone()));
49    }
50    Value::Object(doc)
51}
52
53fn index_document(index: &IndexEntry) -> Value {
54    let mut doc = Map::new();
55    doc.insert("name".to_string(), Value::from(index.name.clone()));
56    doc.insert("fields".to_string(), Value::from(index.fields.clone()));
57    doc.insert("kind".to_string(), Value::from(index.kind.as_str()));
58    if index.unique {
59        doc.insert("unique".to_string(), Value::from(true));
60    }
61    if let Some(where_) = &index.where_ {
62        let clauses: Vec<Value> = where_
63            .iter()
64            .map(|clause| {
65                let mut c = Map::new();
66                c.insert("field".to_string(), Value::from(clause.field.clone()));
67                c.insert("op".to_string(), Value::from(clause.op.clone()));
68                c.insert("value".to_string(), clause.value.clone());
69                Value::Object(c)
70            })
71            .collect();
72        doc.insert("where".to_string(), Value::Array(clauses));
73    }
74    if let Some(expire) = index.expire_after_seconds {
75        doc.insert("expireAfterSeconds".to_string(), Value::from(expire));
76    }
77    Value::Object(doc)
78}
79
80fn check_document(check: &CheckEntry) -> Value {
81    let mut doc = Map::new();
82    doc.insert("name".to_string(), Value::from(check.name.clone()));
83    doc.insert("expr".to_string(), Value::from(check.expr.clone()));
84    Value::Object(doc)
85}
86
87fn history_document(entry: &HistoryEntry) -> Value {
88    let mut doc = Map::new();
89    doc.insert("revision".to_string(), Value::from(entry.revision));
90    doc.insert("at".to_string(), Value::from(entry.at));
91    if let Some(note) = &entry.note {
92        doc.insert("note".to_string(), Value::from(note.clone()));
93    }
94    Value::Object(doc)
95}
96
97fn options_document(options: &TableOptions) -> Value {
98    let mut doc = Map::new();
99    if let Some(sync) = options.sync {
100        doc.insert("sync".to_string(), Value::from(sync.as_str()));
101    }
102    if let Some(compaction) = options.compaction {
103        let value = match compaction {
104            CatalogCompaction::Disabled => Value::Bool(false),
105            CatalogCompaction::Enabled {
106                dead_bytes_ratio,
107                min_dead_bytes,
108            } => {
109                let mut c = Map::new();
110                c.insert("deadBytesRatio".to_string(), Value::from(dead_bytes_ratio));
111                c.insert("minDeadBytes".to_string(), Value::from(min_dead_bytes));
112                Value::Object(c)
113            }
114        };
115        doc.insert("compaction".to_string(), value);
116    }
117    if let Some(link_parsing) = options.link_parsing {
118        doc.insert("linkParsing".to_string(), Value::from(link_parsing.as_str()));
119    }
120    if let Some(image_dir) = &options.image_dir {
121        doc.insert("imageDir".to_string(), Value::from(image_dir.clone()));
122    }
123    Value::Object(doc)
124}
125
126fn table_document(entry: &TableEntry) -> Value {
127    let mut doc = Map::new();
128    doc.insert("revision".to_string(), Value::from(entry.revision));
129    if let Some(description) = &entry.description {
130        doc.insert("description".to_string(), Value::from(description.clone()));
131    }
132    doc.insert("primaryKey".to_string(), Value::from(entry.primary_key.clone()));
133    if let Some(partition_by) = &entry.partition_by {
134        doc.insert("partitionBy".to_string(), Value::from(partition_by.clone()));
135    }
136    if let Some(created_at) = entry.created_at {
137        doc.insert("createdAt".to_string(), Value::from(created_at));
138    }
139    if let Some(updated_at) = entry.updated_at {
140        doc.insert("updatedAt".to_string(), Value::from(updated_at));
141    }
142    if let Some(validation) = entry.validation {
143        doc.insert("validation".to_string(), Value::from(validation.as_str()));
144    }
145    if let Some(options) = &entry.options {
146        doc.insert("options".to_string(), options_document(options));
147    }
148    let mut fields = Map::new();
149    for (name, spec) in &entry.fields {
150        fields.insert(name.clone(), field_document(spec));
151    }
152    doc.insert("fields".to_string(), Value::Object(fields));
153    if let Some(indexes) = &entry.indexes {
154        let mut sorted = indexes.clone();
155        sorted.sort_by(|a, b| a.name.cmp(&b.name));
156        doc.insert(
157            "indexes".to_string(),
158            Value::Array(sorted.iter().map(index_document).collect()),
159        );
160    }
161    if let Some(checks) = &entry.checks {
162        let mut sorted = checks.clone();
163        sorted.sort_by(|a, b| a.name.cmp(&b.name));
164        doc.insert(
165            "checks".to_string(),
166            Value::Array(sorted.iter().map(check_document).collect()),
167        );
168    }
169    if let Some(history) = &entry.history {
170        doc.insert(
171            "history".to_string(),
172            Value::Array(history.iter().map(history_document).collect()),
173        );
174    }
175    Value::Object(doc)
176}
177
178fn relation_document(relation: &Relation) -> Value {
179    let mut doc = Map::new();
180    doc.insert("name".to_string(), Value::from(relation.name.clone()));
181    let mut from = Map::new();
182    from.insert("table".to_string(), Value::from(relation.from.table.clone()));
183    from.insert("field".to_string(), Value::from(relation.from.field.clone()));
184    doc.insert("from".to_string(), Value::Object(from));
185    let mut to = Map::new();
186    to.insert("table".to_string(), Value::from(relation.to.table.clone()));
187    to.insert("field".to_string(), Value::from(relation.to.field.clone()));
188    doc.insert("to".to_string(), Value::Object(to));
189    doc.insert("cardinality".to_string(), Value::from(relation.cardinality.as_str()));
190    if let Some(on_delete) = relation.on_delete {
191        doc.insert("onDelete".to_string(), Value::from(on_delete.as_str()));
192    }
193    if let Some(enforce) = relation.enforce {
194        doc.insert("enforce".to_string(), Value::from(enforce));
195    }
196    Value::Object(doc)
197}
198
199fn view_document(view: &ViewEntry) -> Value {
200    let mut doc = Map::new();
201    doc.insert("name".to_string(), Value::from(view.name.clone()));
202    doc.insert("query".to_string(), Value::from(view.query.clone()));
203    if let Some(description) = &view.description {
204        doc.insert("description".to_string(), Value::from(description.clone()));
205    }
206    Value::Object(doc)
207}
208
209fn trigger_value_source_document(source: &TriggerValueSource) -> Value {
210    let mut doc = Map::new();
211    match source {
212        TriggerValueSource::NewField(field) => {
213            doc.insert("kind".to_string(), Value::from("new"));
214            doc.insert("field".to_string(), Value::from(field.clone()));
215        }
216        TriggerValueSource::OldField(field) => {
217            doc.insert("kind".to_string(), Value::from("old"));
218            doc.insert("field".to_string(), Value::from(field.clone()));
219        }
220        TriggerValueSource::Constant(value) => {
221            doc.insert("kind".to_string(), Value::from("constant"));
222            doc.insert("value".to_string(), value.clone());
223        }
224        TriggerValueSource::Now => {
225            doc.insert("kind".to_string(), Value::from("now"));
226        }
227    }
228    Value::Object(doc)
229}
230
231fn trigger_field_mapping_document(mapping: &TriggerFieldMapping) -> Value {
232    let mut doc = Map::new();
233    doc.insert("target".to_string(), Value::from(mapping.target.clone()));
234    doc.insert("source".to_string(), trigger_value_source_document(&mapping.source));
235    Value::Object(doc)
236}
237
238fn trigger_effect_document(effect: &TriggerEffect) -> Value {
239    let mut doc = Map::new();
240    doc.insert("table".to_string(), Value::from(effect.table.clone()));
241    doc.insert(
242        "fields".to_string(),
243        Value::Array(effect.fields.iter().map(trigger_field_mapping_document).collect()),
244    );
245    Value::Object(doc)
246}
247
248fn trigger_document(trigger: &TriggerEntry) -> Value {
249    let mut doc = Map::new();
250    doc.insert("name".to_string(), Value::from(trigger.name.clone()));
251    doc.insert("table".to_string(), Value::from(trigger.table.clone()));
252    doc.insert("event".to_string(), Value::from(trigger.event.as_str()));
253    doc.insert("effect".to_string(), trigger_effect_document(&trigger.effect));
254    Value::Object(doc)
255}
256
257/// Builds the ordered document `serialize_catalog` renders to bytes — sections and known fields in
258/// the fixed order of the format (`docs/format.md`, „hayson.json w formacie 2"), `tables` and
259/// `fields` sorted alphabetically by key, `indexes`/`checks`/`relations`/`views` sorted by `name`,
260/// and `history` left in the order it arrived.
261pub fn catalog_document(catalog: &Catalog) -> Value {
262    let mut doc = Map::new();
263    doc.insert("catalogVersion".to_string(), Value::from(catalog.catalog_version));
264    doc.insert("formatVersion".to_string(), Value::from(catalog.format_version));
265    doc.insert("createdAt".to_string(), Value::from(catalog.created_at));
266    doc.insert("updatedAt".to_string(), Value::from(catalog.updated_at));
267    if let Some(engine) = &catalog.engine {
268        doc.insert("engine".to_string(), Value::from(engine.clone()));
269    }
270    if let Some(layout) = catalog.layout {
271        doc.insert("layout".to_string(), Value::from(layout.as_str()));
272    }
273    if let Some(defaults) = &catalog.defaults {
274        doc.insert("defaults".to_string(), options_document(defaults));
275    }
276    let mut tables = Map::new();
277    for (name, entry) in &catalog.tables {
278        tables.insert(name.clone(), table_document(entry));
279    }
280    doc.insert("tables".to_string(), Value::Object(tables));
281    if let Some(relations) = &catalog.relations {
282        let mut sorted = relations.clone();
283        sorted.sort_by(|a, b| a.name.cmp(&b.name));
284        doc.insert(
285            "relations".to_string(),
286            Value::Array(sorted.iter().map(relation_document).collect()),
287        );
288    }
289    if let Some(views) = &catalog.views {
290        let mut sorted = views.clone();
291        sorted.sort_by(|a, b| a.name.cmp(&b.name));
292        doc.insert(
293            "views".to_string(),
294            Value::Array(sorted.iter().map(view_document).collect()),
295        );
296    }
297    if let Some(triggers) = &catalog.triggers {
298        let mut sorted = triggers.clone();
299        sorted.sort_by(|a, b| a.name.cmp(&b.name));
300        doc.insert(
301            "triggers".to_string(),
302            Value::Array(sorted.iter().map(trigger_document).collect()),
303        );
304    }
305    Value::Object(doc)
306}
307
308pub fn serialize_catalog(catalog: &Catalog) -> String {
309    format!(
310        "{}\n",
311        serde_json::to_string_pretty(&catalog_document(catalog)).expect("catalog document is valid JSON")
312    )
313}