1use indexmap::{IndexMap, IndexSet};
4use serde_json::{Map, Value};
5use std::sync::Arc;
6
7use crate::parse_schema::common::compute_column_partitions;
8use crate::{topo_sort, JSONEval, LogicId, RLogic, path_utils};
9use crate::table_metadata::{ColumnMetadata, RepeatBoundMetadata, RowMetadata, TableMetadata};
10
11pub fn parse_schema(lib: &mut JSONEval) -> Result<(), String> {
12 fn walk(
14 value: &Value,
15 path: &str,
16 engine: &mut RLogic,
17 evaluations: &mut IndexMap<String, LogicId>,
18 tables: &mut IndexMap<String, Value>,
19 deps: &mut IndexMap<String, IndexSet<String>>,
20 value_fields: &mut Vec<String>,
21 layout_paths: &mut Vec<String>,
22 dependents: &mut IndexMap<String, Vec<crate::DependentItem>>,
23 options_templates: &mut Vec<(String, String, String)>,
24 subforms: &mut Vec<(String, serde_json::Map<String, Value>, Value)>,
25 fields_with_rules: &mut Vec<String>,
26 ) -> Result<(), String> {
27 match value {
28 Value::Object(map) => {
29 if let Some(evaluation) = map.get("$evaluation") {
31 let key = path.to_string();
32 let logic_value = evaluation.get("logic").unwrap_or(evaluation);
33 let logic_id = engine
34 .compile(logic_value)
35 .map_err(|e| format!("failed to compile evaluation at {key}: {e}"))?;
36 evaluations.insert(key.clone(), logic_id);
37
38 let mut refs: IndexSet<String> = engine
41 .get_referenced_vars(&logic_id)
42 .unwrap_or_default()
43 .into_iter()
44 .map(|dep| path_utils::normalize_to_json_pointer(&dep))
45 .filter(|dep| {
46 dep.matches('/').count() > 1 || dep.starts_with("/$")
50 })
51 .collect();
52 let mut extra_refs = IndexSet::new();
53 collect_refs(logic_value, &mut extra_refs);
54 if !extra_refs.is_empty() {
55 refs.extend(extra_refs.into_iter());
56 }
57
58 let refs: IndexSet<String> = refs
60 .into_iter()
61 .filter_map(|dep| {
62 if let Some(table_idx) = dep.find("/$table/") {
64 let table_path = &dep[..table_idx];
65 Some(table_path.to_string())
66 } else {
67 Some(dep)
68 }
69 })
70 .collect();
71
72 if !refs.is_empty() {
73 deps.insert(key.clone(), refs);
74 }
75 }
76
77 if let Some(table) = map.get("$table") {
79 let key = path.to_string();
80
81 let rows = table.clone();
82 let datas = map
83 .get("$datas")
84 .cloned()
85 .unwrap_or_else(|| Value::Array(vec![]));
86 let skip = map.get("$skip").cloned().unwrap_or(Value::Bool(false));
87 let clear = map.get("$clear").cloned().unwrap_or(Value::Bool(false));
88
89 let mut table_entry = Map::new();
90 table_entry.insert("rows".to_string(), rows);
91 table_entry.insert("datas".to_string(), datas);
92 table_entry.insert("skip".to_string(), skip);
93 table_entry.insert("clear".to_string(), clear);
94
95 tables.insert(key, Value::Object(table_entry));
96 }
97
98 if let Some(layout_obj) = map.get("$layout") {
100 if let Some(Value::Array(_)) = layout_obj.get("elements") {
101 let layout_elements_path = format!("{}/$layout/elements", path);
102 layout_paths.push(layout_elements_path);
103 }
104 }
105
106 if map.contains_key("rules") && !path.is_empty() && !path.starts_with("#/$") {
108 let field_path = path
111 .trim_start_matches('#')
112 .replace("/properties/", ".")
113 .trim_start_matches('/')
114 .trim_start_matches('.')
115 .to_string();
116
117 if !field_path.is_empty() && !field_path.starts_with("$") {
118 fields_with_rules.push(field_path);
119 }
120 }
121
122 if let Some(Value::String(url)) = map.get("url") {
124 if url.contains('{') && url.contains('}') {
126 let url_path = path_utils::normalize_to_json_pointer(&format!("{}/url", path));
128 let params_path = path_utils::normalize_to_json_pointer(&format!("{}/params", path));
129 options_templates.push((url_path, url.clone(), params_path));
130 }
131 }
132
133 if let Some(Value::String(type_str)) = map.get("type") {
135 if type_str == "array" {
136 if let Some(items) = map.get("items") {
137 subforms.push((path.to_string(), map.clone(), items.clone()));
139 return Ok(());
141 }
142 }
143 }
144
145 if let Some(Value::Array(dependents_arr)) = map.get("dependents") {
147 let mut dependent_items = Vec::new();
148
149 for (dep_idx, dep_item) in dependents_arr.iter().enumerate() {
150 if let Value::Object(dep_obj) = dep_item {
151 if let Some(Value::String(ref_path)) = dep_obj.get("$ref") {
152 let clear_val = if let Some(clear) = dep_obj.get("clear") {
154 if let Value::Object(clear_obj) = clear {
155 if clear_obj.contains_key("$evaluation") {
156 let clear_eval = clear_obj.get("$evaluation").unwrap();
158 let clear_key = format!("{}/dependents/{}/clear", path, dep_idx);
159 let logic_id = engine.compile(clear_eval)
160 .map_err(|e| format!("Failed to compile dependent clear at {}: {}", clear_key, e))?;
161 evaluations.insert(clear_key.clone(), logic_id);
162 Some(Value::String(clear_key))
164 } else {
165 Some(clear.clone())
166 }
167 } else {
168 Some(clear.clone())
169 }
170 } else {
171 None
172 };
173
174 let value_val = if let Some(value) = dep_obj.get("value") {
176 if let Value::Object(value_obj) = value {
177 if value_obj.contains_key("$evaluation") {
178 let value_eval = value_obj.get("$evaluation").unwrap();
180 let value_key = format!("{}/dependents/{}/value", path, dep_idx);
181 let logic_id = engine.compile(value_eval)
182 .map_err(|e| format!("Failed to compile dependent value at {}: {}", value_key, e))?;
183 evaluations.insert(value_key.clone(), logic_id);
184 Some(Value::String(value_key))
186 } else {
187 Some(value.clone())
188 }
189 } else {
190 Some(value.clone())
191 }
192 } else {
193 None
194 };
195
196 dependent_items.push(crate::DependentItem {
197 ref_path: ref_path.clone(),
198 clear: clear_val,
199 value: value_val,
200 });
201 }
202 }
203 }
204
205 if !dependent_items.is_empty() {
206 dependents.insert(path.to_string(), dependent_items);
207 }
208 }
209
210 Ok(for (key, val) in map {
212 if key == "$evaluation" || key == "dependents" {
214 continue;
215 }
216
217 let next_path = if path == "#" {
218 format!("#/{key}")
219 } else {
220 format!("{path}/{key}")
221 };
222
223 if key == "value" && !next_path.starts_with("#/$") && !next_path.contains("/$layout/") && !next_path.contains("/items/") && !next_path.contains("/options/") && !next_path.contains("/dependents/") && !next_path.contains("/rules/") {
225 value_fields.push(next_path.clone());
226 }
227
228 walk(val, &next_path, engine, evaluations, tables, deps, value_fields, layout_paths, dependents, options_templates, subforms, fields_with_rules)?;
230 })
231 }
232 Value::Array(arr) => Ok(for (index, item) in arr.iter().enumerate() {
233 let next_path = if path == "#" {
234 format!("#/{index}")
235 } else {
236 format!("{path}/{index}")
237 };
238 walk(item, &next_path, engine, evaluations, tables, deps, value_fields, layout_paths, dependents, options_templates, subforms, fields_with_rules)?;
239 }),
240 _ => Ok(()),
241 }
242 }
243
244 fn collect_refs(value: &Value, refs: &mut IndexSet<String>) {
245 match value {
246 Value::Object(map) => {
247 if let Some(path) = map.get("$ref").and_then(Value::as_str) {
248 refs.insert(path_utils::normalize_to_json_pointer(path));
249 }
250 if let Some(path) = map.get("ref").and_then(Value::as_str) {
251 refs.insert(path_utils::normalize_to_json_pointer(path));
252 }
253 if let Some(var_val) = map.get("var") {
254 match var_val {
255 Value::String(s) => {
256 refs.insert(s.clone());
257 }
258 Value::Array(arr) => {
259 if let Some(path) = arr.get(0).and_then(Value::as_str) {
260 refs.insert(path.to_string());
261 }
262 }
263 _ => {}
264 }
265 }
266 for val in map.values() {
267 collect_refs(val, refs);
268 }
269 }
270 Value::Array(arr) => {
271 for val in arr {
272 collect_refs(val, refs);
273 }
274 }
275 _ => {}
276 }
277 }
278
279 let mut evaluations = IndexMap::new();
284 let mut tables = IndexMap::new();
285 let mut dependencies = IndexMap::new();
286 let mut value_fields = Vec::new();
287 let mut layout_paths = Vec::new();
288 let mut dependents_evaluations = IndexMap::new();
289 let mut options_templates = Vec::new();
290 let mut subforms_data = Vec::new();
291
292 let engine = Arc::get_mut(&mut lib.engine)
294 .ok_or("Cannot get mutable reference to engine - JSONEval engine is shared")?;
295
296 let mut fields_with_rules = Vec::new();
297
298 walk(
299 &lib.schema,
300 "#",
301 engine,
302 &mut evaluations,
303 &mut tables,
304 &mut dependencies,
305 &mut value_fields,
306 &mut layout_paths,
307 &mut dependents_evaluations,
308 &mut options_templates,
309 &mut subforms_data,
310 &mut fields_with_rules,
311 )?;
312
313 lib.evaluations = Arc::new(evaluations);
314 lib.tables = Arc::new(tables);
315 lib.dependencies = Arc::new(dependencies);
316 lib.layout_paths = Arc::new(layout_paths);
317 lib.dependents_evaluations = Arc::new(dependents_evaluations);
318 lib.options_templates = Arc::new(options_templates);
319 lib.fields_with_rules = Arc::new(fields_with_rules);
320
321 lib.subforms = build_subforms_from_data(subforms_data, lib)?;
323
324 collect_table_dependencies(lib);
326
327 lib.sorted_evaluations = Arc::new(topo_sort::legacy::topological_sort(lib)?);
328
329 categorize_evaluations(lib);
331
332 process_value_fields(lib, value_fields);
334
335 build_table_metadata(lib)?;
337
338 Ok(())
339}
340
341fn build_subforms_from_data(
343 subforms_data: Vec<(String, serde_json::Map<String, Value>, Value)>,
344 parent: &JSONEval,
345) -> Result<IndexMap<String, Box<JSONEval>>, String> {
346 let mut subforms = IndexMap::new();
347
348 for (path, field_map, items) in subforms_data {
349 create_subform(&path, &field_map, &items, &mut subforms, parent)?;
350 }
351
352 Ok(subforms)
353}
354
355fn create_subform(
357 path: &str,
358 field_map: &serde_json::Map<String, Value>,
359 items: &Value,
360 subforms: &mut IndexMap<String, Box<JSONEval>>,
361 parent: &JSONEval,
362) -> Result<(), String> {
363 let field_key = path.trim_start_matches('#').trim_start_matches('/');
365
366 let mut subform_schema = serde_json::Map::new();
368
369 if let Some(params) = parent.schema.get("$params") {
371 subform_schema.insert("$params".to_string(), params.clone());
372 }
373
374 let mut field_obj = serde_json::Map::new();
376
377 if let Value::Object(items_map) = items {
379 for (key, value) in items_map {
380 field_obj.insert(key.clone(), value.clone());
381 }
382 }
383
384 for (key, value) in field_map {
386 if key != "items" && key != "type" {
387 field_obj.insert(key.clone(), value.clone());
388 }
389 }
390
391 field_obj.insert("type".to_string(), Value::String("object".to_string()));
393
394 subform_schema.insert(field_key.to_string(), Value::Object(field_obj));
395
396 let subform_schema_json = serde_json::to_string(&subform_schema)
398 .map_err(|e| format!("Failed to serialize subform schema: {}", e))?;
399
400 let sub_eval = crate::JSONEval::new(
401 &subform_schema_json,
402 Some(&serde_json::to_string(&parent.context).unwrap_or("{}".to_string())),
403 None, ).map_err(|e| format!("Failed to create subform for {}: {}", field_key, e))?;
405
406 subforms.insert(path.to_string(), Box::new(sub_eval));
407
408 Ok(())
409}
410
411fn collect_table_dependencies(lib: &mut JSONEval) {
413 let mut dependencies = (*lib.dependencies).clone();
415
416 for (table_key, _) in lib.tables.iter() {
417 let mut table_deps = IndexSet::new();
418
419 for (eval_key, deps) in &dependencies {
421 if eval_key.starts_with(table_key) && eval_key != table_key {
423 for dep in deps {
425 if !dep.starts_with(table_key) {
427 table_deps.insert(dep.clone());
428 }
429 }
430 }
431 }
432
433 if !table_deps.is_empty() {
435 dependencies.insert(table_key.clone(), table_deps);
436 }
437 }
438
439 lib.dependencies = Arc::new(dependencies);
441}
442
443fn categorize_evaluations(lib: &mut JSONEval) {
445 let batched_keys: IndexSet<String> = lib.sorted_evaluations
447 .iter()
448 .flatten()
449 .cloned()
450 .collect();
451
452 let mut rules_evaluations = Vec::new();
454 let mut others_evaluations = Vec::new();
455
456 for eval_key in lib.evaluations.keys() {
457 if batched_keys.contains(eval_key) {
459 continue;
460 }
461
462 if lib.tables.iter().any(|(key, _)| eval_key.starts_with(key)) {
464 continue;
465 }
466
467 if eval_key.contains("/rules/") {
469 rules_evaluations.push(eval_key.clone());
470 } else if !eval_key.contains("/dependents/") {
471 others_evaluations.push(eval_key.clone());
473 }
474 }
475
476 lib.rules_evaluations = Arc::new(rules_evaluations);
478 lib.others_evaluations = Arc::new(others_evaluations);
479}
480
481fn process_value_fields(lib: &mut JSONEval, value_fields: Vec<String>) {
483 let mut value_evaluations = Vec::new();
484
485 for path in value_fields {
486 if value_evaluations.contains(&path) {
488 continue;
489 }
490
491 if lib.tables.iter().any(|(key, _)| path.starts_with(key)) {
493 continue;
494 }
495
496 value_evaluations.push(path);
497 }
498
499 lib.value_evaluations = Arc::new(value_evaluations);
501}
502
503fn build_table_metadata(lib: &mut JSONEval) -> Result<(), String> {
505 let mut table_metadata = IndexMap::new();
506
507 for (eval_key, table) in lib.tables.iter() {
508 let metadata = compile_table_metadata(lib, eval_key, table)?;
509 table_metadata.insert(eval_key.to_string(), metadata);
510 }
511
512 lib.table_metadata = Arc::new(table_metadata);
513 Ok(())
514}
515
516fn compile_table_metadata(
518 lib: &JSONEval,
519 eval_key: &str,
520 table: &Value,
521) -> Result<TableMetadata, String> {
522 let rows = table
523 .get("rows")
524 .and_then(|v| v.as_array())
525 .ok_or("table missing rows")?;
526 let empty_datas = Vec::new();
527 let datas = table
528 .get("datas")
529 .and_then(|v| v.as_array())
530 .unwrap_or(&empty_datas);
531
532 let mut data_plans = Vec::with_capacity(datas.len());
534 for (idx, entry) in datas.iter().enumerate() {
535 let Some(name) = entry.get("name").and_then(|v| v.as_str()) else { continue };
536 let logic_path = format!("{eval_key}/$datas/{idx}/data");
537 let logic = lib.evaluations.get(&logic_path).copied();
538 let literal = entry.get("data").map(|v| Arc::new(v.clone()));
539 data_plans.push((Arc::from(name), logic, literal));
540 }
541
542 let mut row_plans = Vec::with_capacity(rows.len());
544 for (row_idx, row_val) in rows.iter().enumerate() {
545 let Some(row_obj) = row_val.as_object() else {
546 continue;
547 };
548
549 if let Some(repeat_arr) = row_obj.get("$repeat").and_then(|v| v.as_array()) {
550 if repeat_arr.len() == 3 {
551 let start_logic_path = format!("{eval_key}/$table/{row_idx}/$repeat/0");
552 let end_logic_path = format!("{eval_key}/$table/{row_idx}/$repeat/1");
553 let start_logic = lib.evaluations.get(&start_logic_path).copied();
554 let end_logic = lib.evaluations.get(&end_logic_path).copied();
555
556 let start_literal = Arc::new(repeat_arr.get(0).cloned().unwrap_or(Value::Null));
557 let end_literal = Arc::new(repeat_arr.get(1).cloned().unwrap_or(Value::Null));
558
559 if let Some(template) = repeat_arr.get(2).and_then(|v| v.as_object()) {
560 let mut columns = Vec::with_capacity(template.len());
561 for (col_name, col_val) in template {
562 let col_eval_path =
563 format!("{eval_key}/$table/{row_idx}/$repeat/2/{col_name}");
564 let logic = lib.evaluations.get(&col_eval_path).copied();
565 let literal = if logic.is_none() {
566 Some(col_val.clone())
567 } else {
568 None
569 };
570
571 let (dependencies, has_forward_ref) = if let Some(logic_id) = logic {
573 let deps = lib.engine.get_referenced_vars(&logic_id)
574 .unwrap_or_default()
575 .into_iter()
576 .filter(|v| v.starts_with('$') && v != "$iteration" && v != "$threshold")
577 .collect();
578 let has_fwd = lib.engine.has_forward_reference(&logic_id);
579 (deps, has_fwd)
580 } else {
581 (Vec::new(), false)
582 };
583
584 columns.push(ColumnMetadata::new(col_name, logic, literal, dependencies, has_forward_ref));
585 }
586
587 let (forward_cols, normal_cols) = compute_column_partitions(&columns);
589
590 row_plans.push(RowMetadata::Repeat {
591 start: RepeatBoundMetadata {
592 logic: start_logic,
593 literal: start_literal,
594 },
595 end: RepeatBoundMetadata {
596 logic: end_logic,
597 literal: end_literal,
598 },
599 columns: columns.into(),
600 forward_cols: forward_cols.into(),
601 normal_cols: normal_cols.into(),
602 });
603 continue;
604 }
605 }
606 }
607
608 let mut columns = Vec::with_capacity(row_obj.len());
610 for (col_name, col_val) in row_obj {
611 if col_name == "$repeat" {
612 continue;
613 }
614 let col_eval_path = format!("{eval_key}/$table/{row_idx}/{col_name}");
615 let logic = lib.evaluations.get(&col_eval_path).copied();
616 let literal = if logic.is_none() {
617 Some(col_val.clone())
618 } else {
619 None
620 };
621
622 let (dependencies, has_forward_ref) = if let Some(logic_id) = logic {
624 let deps = lib.engine.get_referenced_vars(&logic_id)
625 .unwrap_or_default()
626 .into_iter()
627 .filter(|v| v.starts_with('$') && v != "$iteration" && v != "$threshold")
628 .collect();
629 let has_fwd = lib.engine.has_forward_reference(&logic_id);
630 (deps, has_fwd)
631 } else {
632 (Vec::new(), false)
633 };
634
635 columns.push(ColumnMetadata::new(col_name, logic, literal, dependencies, has_forward_ref));
636 }
637 row_plans.push(RowMetadata::Static { columns: columns.into() });
638 }
639
640 let skip_logic = lib.evaluations.get(&format!("{eval_key}/$skip")).copied();
642 let skip_literal = table.get("skip").and_then(Value::as_bool).unwrap_or(false);
643 let clear_logic = lib.evaluations.get(&format!("{eval_key}/$clear")).copied();
644 let clear_literal = table.get("clear").and_then(Value::as_bool).unwrap_or(false);
645
646 Ok(TableMetadata {
647 data_plans: data_plans.into(),
648 row_plans: row_plans.into(),
649 skip_logic,
650 skip_literal,
651 clear_logic,
652 clear_literal,
653 })
654}