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