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, LogicId, RLogic, path_utils};
9use crate::table_metadata::{ColumnMetadata, RepeatBoundMetadata, RowMetadata, TableMetadata};
10use crate::ParsedSchema;
11
12pub fn parse_schema_into(parsed: &mut ParsedSchema) -> Result<(), String> {
13 fn walk(
15 value: &Value,
16 path: &str,
17 engine: &mut RLogic,
18 evaluations: &mut IndexMap<String, LogicId>,
19 tables: &mut IndexMap<String, Value>,
20 deps: &mut IndexMap<String, IndexSet<String>>,
21 value_fields: &mut Vec<String>,
22 layout_paths: &mut Vec<String>,
23 dependents: &mut IndexMap<String, Vec<crate::DependentItem>>,
24 options_templates: &mut Vec<(String, String, String)>,
25 subforms: &mut Vec<(String, serde_json::Map<String, Value>, Value)>,
26 fields_with_rules: &mut Vec<String>,
27 ) -> Result<(), String> {
28 match value {
29 Value::Object(map) => {
30 if let Some(evaluation) = map.get("$evaluation") {
32 let key = path.to_string();
33 let logic_value = evaluation.get("logic").unwrap_or(evaluation);
34 let logic_id = engine
35 .compile(logic_value)
36 .map_err(|e| format!("failed to compile evaluation at {key}: {e}"))?;
37 evaluations.insert(key.clone(), logic_id);
38
39 let mut refs: IndexSet<String> = engine
42 .get_referenced_vars(&logic_id)
43 .unwrap_or_default()
44 .into_iter()
45 .map(|dep| path_utils::normalize_to_json_pointer(&dep))
46 .filter(|dep| {
47 dep.matches('/').count() > 1 || dep.starts_with("/$")
51 })
52 .collect();
53 let mut extra_refs = IndexSet::new();
54 collect_refs(logic_value, &mut extra_refs);
55 if !extra_refs.is_empty() {
56 refs.extend(extra_refs.into_iter());
57 }
58
59 let refs: IndexSet<String> = refs
61 .into_iter()
62 .filter_map(|dep| {
63 if let Some(table_idx) = dep.find("/$table/") {
65 let table_path = &dep[..table_idx];
66 Some(table_path.to_string())
67 } else {
68 Some(dep)
69 }
70 })
71 .collect();
72
73 if !refs.is_empty() {
74 deps.insert(key.clone(), refs);
75 }
76 }
77
78 if let Some(table) = map.get("$table") {
80 let key = path.to_string();
81
82 let rows = table.clone();
83 let datas = map
84 .get("$datas")
85 .cloned()
86 .unwrap_or_else(|| Value::Array(vec![]));
87 let skip = map.get("$skip").cloned().unwrap_or(Value::Bool(false));
88 let clear = map.get("$clear").cloned().unwrap_or(Value::Bool(false));
89
90 let mut table_entry = Map::new();
91 table_entry.insert("rows".to_string(), rows);
92 table_entry.insert("datas".to_string(), datas);
93 table_entry.insert("skip".to_string(), skip);
94 table_entry.insert("clear".to_string(), clear);
95
96 tables.insert(key, Value::Object(table_entry));
97 }
98
99 if let Some(layout_obj) = map.get("$layout") {
101 if let Some(Value::Array(_)) = layout_obj.get("elements") {
102 let layout_elements_path = format!("{}/$layout/elements", path);
103 layout_paths.push(layout_elements_path);
104 }
105 }
106
107 if map.contains_key("rules") && !path.is_empty() && !path.starts_with("#/$") {
109 let field_path = path
112 .trim_start_matches('#')
113 .replace("/properties/", ".")
114 .trim_start_matches('/')
115 .trim_start_matches('.')
116 .to_string();
117
118 if !field_path.is_empty() && !field_path.starts_with("$") {
119 fields_with_rules.push(field_path);
120 }
121 }
122
123 if let Some(Value::String(url)) = map.get("url") {
125 if url.contains('{') && url.contains('}') {
127 let url_path = path_utils::normalize_to_json_pointer(&format!("{}/url", path));
129 let params_path = path_utils::normalize_to_json_pointer(&format!("{}/params", path));
130 options_templates.push((url_path, url.clone(), params_path));
131 }
132 }
133
134 if let Some(Value::String(type_str)) = map.get("type") {
136 if type_str == "array" {
137 if let Some(items) = map.get("items") {
138 subforms.push((path.to_string(), map.clone(), items.clone()));
140 return Ok(());
142 }
143 }
144 }
145
146 if let Some(Value::Array(dependents_arr)) = map.get("dependents") {
148 let mut dependent_items = Vec::new();
149
150 for (dep_idx, dep_item) in dependents_arr.iter().enumerate() {
151 if let Value::Object(dep_obj) = dep_item {
152 if let Some(Value::String(ref_path)) = dep_obj.get("$ref") {
153 let clear_val = if let Some(clear) = dep_obj.get("clear") {
155 if let Value::Object(clear_obj) = clear {
156 if clear_obj.contains_key("$evaluation") {
157 let clear_eval = clear_obj.get("$evaluation").unwrap();
159 let clear_key = format!("{}/dependents/{}/clear", path, dep_idx);
160 let logic_id = engine.compile(clear_eval)
161 .map_err(|e| format!("Failed to compile dependent clear at {}: {}", clear_key, e))?;
162 evaluations.insert(clear_key.clone(), logic_id);
163 Some(Value::String(clear_key))
165 } else {
166 Some(clear.clone())
167 }
168 } else {
169 Some(clear.clone())
170 }
171 } else {
172 None
173 };
174
175 let value_val = if let Some(value) = dep_obj.get("value") {
177 if let Value::Object(value_obj) = value {
178 if value_obj.contains_key("$evaluation") {
179 let value_eval = value_obj.get("$evaluation").unwrap();
181 let value_key = format!("{}/dependents/{}/value", path, dep_idx);
182 let logic_id = engine.compile(value_eval)
183 .map_err(|e| format!("Failed to compile dependent value at {}: {}", value_key, e))?;
184 evaluations.insert(value_key.clone(), logic_id);
185 Some(Value::String(value_key))
187 } else {
188 Some(value.clone())
189 }
190 } else {
191 Some(value.clone())
192 }
193 } else {
194 None
195 };
196
197 dependent_items.push(crate::DependentItem {
198 ref_path: ref_path.clone(),
199 clear: clear_val,
200 value: value_val,
201 });
202 }
203 }
204 }
205
206 if !dependent_items.is_empty() {
207 dependents.insert(path.to_string(), dependent_items);
208 }
209 }
210
211 Ok(for (key, val) in map {
213 if key == "$evaluation" || key == "dependents" {
215 continue;
216 }
217
218 let next_path = if path == "#" {
219 format!("#/{key}")
220 } else {
221 format!("{path}/{key}")
222 };
223
224 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/") {
226 value_fields.push(next_path.clone());
227 }
228
229 walk(val, &next_path, engine, evaluations, tables, deps, value_fields, layout_paths, dependents, options_templates, subforms, fields_with_rules)?;
231 })
232 }
233 Value::Array(arr) => Ok(for (index, item) in arr.iter().enumerate() {
234 let next_path = if path == "#" {
235 format!("#/{index}")
236 } else {
237 format!("{path}/{index}")
238 };
239 walk(item, &next_path, engine, evaluations, tables, deps, value_fields, layout_paths, dependents, options_templates, subforms, fields_with_rules)?;
240 }),
241 _ => Ok(()),
242 }
243 }
244
245 fn collect_refs(value: &Value, refs: &mut IndexSet<String>) {
246 match value {
247 Value::Object(map) => {
248 if let Some(path) = map.get("$ref").and_then(Value::as_str) {
249 refs.insert(path_utils::normalize_to_json_pointer(path));
250 }
251 if let Some(path) = map.get("ref").and_then(Value::as_str) {
252 refs.insert(path_utils::normalize_to_json_pointer(path));
253 }
254 if let Some(var_val) = map.get("var") {
255 match var_val {
256 Value::String(s) => {
257 refs.insert(s.clone());
258 }
259 Value::Array(arr) => {
260 if let Some(path) = arr.get(0).and_then(Value::as_str) {
261 refs.insert(path.to_string());
262 }
263 }
264 _ => {}
265 }
266 }
267 for val in map.values() {
268 collect_refs(val, refs);
269 }
270 }
271 Value::Array(arr) => {
272 for val in arr {
273 collect_refs(val, refs);
274 }
275 }
276 _ => {}
277 }
278 }
279
280 let mut evaluations = IndexMap::new();
282 let mut tables = IndexMap::new();
283 let mut dependencies = IndexMap::new();
284 let mut value_fields = Vec::new();
285 let mut layout_paths = Vec::new();
286 let mut dependents_evaluations = IndexMap::new();
287 let mut options_templates = Vec::new();
288 let mut subforms_data = Vec::new();
289
290 let engine = Arc::get_mut(&mut parsed.engine)
292 .ok_or("Cannot get mutable reference to engine - ParsedSchema is shared")?;
293
294 let mut fields_with_rules = Vec::new();
295
296 walk(
297 &parsed.schema,
298 "#",
299 engine,
300 &mut evaluations,
301 &mut tables,
302 &mut dependencies,
303 &mut value_fields,
304 &mut layout_paths,
305 &mut dependents_evaluations,
306 &mut options_templates,
307 &mut subforms_data,
308 &mut fields_with_rules,
309 )?;
310
311 parsed.evaluations = Arc::new(evaluations);
312 parsed.tables = Arc::new(tables);
313 parsed.dependencies = Arc::new(dependencies);
314 parsed.layout_paths = Arc::new(layout_paths);
315 parsed.dependents_evaluations = Arc::new(dependents_evaluations);
316 parsed.options_templates = Arc::new(options_templates);
317 parsed.fields_with_rules = Arc::new(fields_with_rules);
318
319 parsed.subforms = build_subforms_from_data_parsed(subforms_data, parsed)?;
321
322 collect_table_dependencies_parsed(parsed);
324
325 parsed.sorted_evaluations = Arc::new(topo_sort::parsed::topological_sort_parsed(parsed)?);
326
327 categorize_evaluations_parsed(parsed);
329
330 process_value_fields_parsed(parsed, value_fields);
332
333 build_table_metadata_parsed(parsed)?;
335
336 Ok(())
337}
338
339fn build_subforms_from_data_parsed(
343 subforms_data: Vec<(String, serde_json::Map<String, Value>, Value)>,
344 parsed: &ParsedSchema,
345) -> Result<IndexMap<String, Arc<ParsedSchema>>, String> {
346 let mut subforms = IndexMap::new();
347
348 for (path, field_map, items) in subforms_data {
349 create_subform_parsed(&path, &field_map, &items, &mut subforms, parsed)?;
350 }
351
352 Ok(subforms)
353}
354
355fn create_subform_parsed(
360 path: &str,
361 field_map: &serde_json::Map<String, Value>,
362 items: &Value,
363 subforms: &mut IndexMap<String, Arc<ParsedSchema>>,
364 parsed: &ParsedSchema,
365) -> Result<(), String> {
366 let field_key = path.trim_start_matches('#').trim_start_matches('/');
368
369 let mut subform_schema = serde_json::Map::new();
371
372 if let Some(params) = parsed.schema.get("$params") {
374 subform_schema.insert("$params".to_string(), params.clone());
375 }
376
377 let mut field_obj = serde_json::Map::new();
379
380 if let Value::Object(items_map) = items {
382 for (key, value) in items_map {
383 field_obj.insert(key.clone(), value.clone());
384 }
385 }
386
387 for (key, value) in field_map {
389 if key != "items" && key != "type" {
390 field_obj.insert(key.clone(), value.clone());
391 }
392 }
393
394 field_obj.insert("type".to_string(), Value::String("object".to_string()));
396
397 subform_schema.insert(field_key.to_string(), Value::Object(field_obj));
398
399 let subform_schema_value = Value::Object(subform_schema);
402 let subform_parsed = ParsedSchema::parse_value(subform_schema_value)
403 .map_err(|e| format!("Failed to parse subform schema for {}: {}", field_key, e))?;
404
405 subforms.insert(path.to_string(), Arc::new(subform_parsed));
406
407 Ok(())
408}
409
410fn collect_table_dependencies_parsed(parsed: &mut ParsedSchema) {
412 let table_keys: Vec<String> = parsed.tables.keys().cloned().collect();
413
414 let mut dependencies = (*parsed.dependencies).clone();
416
417 for table_key in table_keys {
418 let mut table_deps = IndexSet::new();
419
420 for (eval_key, deps) in &dependencies {
422 if eval_key.starts_with(&table_key) && eval_key != &table_key {
424 for dep in deps {
426 if !dep.starts_with(&table_key) {
428 table_deps.insert(dep.clone());
429 }
430 }
431 }
432 }
433
434 if !table_deps.is_empty() {
436 dependencies.insert(table_key.clone(), table_deps);
437 }
438 }
439
440 parsed.dependencies = Arc::new(dependencies);
442}
443
444fn categorize_evaluations_parsed(parsed: &mut ParsedSchema) {
446 let batched_keys: IndexSet<String> = parsed.sorted_evaluations
448 .iter()
449 .flatten()
450 .cloned()
451 .collect();
452
453 let mut rules_evaluations = Vec::new();
454 let mut others_evaluations = Vec::new();
455
456 for eval_key in parsed.evaluations.keys() {
458 if batched_keys.contains(eval_key) {
460 continue;
461 }
462
463 if parsed.tables.iter().any(|(key, _)| eval_key.starts_with(key)) {
465 continue;
466 }
467
468 if eval_key.contains("/rules/") {
470 rules_evaluations.push(eval_key.clone());
471 } else if !eval_key.contains("/dependents/") {
472 others_evaluations.push(eval_key.clone());
474 }
475 }
476
477 parsed.rules_evaluations = Arc::new(rules_evaluations);
479 parsed.others_evaluations = Arc::new(others_evaluations);
480}
481
482fn process_value_fields_parsed(parsed: &mut ParsedSchema, value_fields: Vec<String>) {
484 let mut value_evaluations = Vec::new();
485
486 for path in value_fields {
487 if value_evaluations.contains(&path) {
489 continue;
490 }
491
492 if parsed.tables.iter().any(|(key, _)| path.starts_with(key)) {
494 continue;
495 }
496
497 value_evaluations.push(path);
498 }
499
500 parsed.value_evaluations = Arc::new(value_evaluations);
502}
503
504fn build_table_metadata_parsed(parsed: &mut ParsedSchema) -> Result<(), String> {
506 let mut table_metadata = IndexMap::new();
507
508 for (eval_key, table) in parsed.tables.iter() {
509 let metadata = compile_table_metadata_parsed(parsed, eval_key, table)?;
510 table_metadata.insert(eval_key.to_string(), metadata);
511 }
512
513 parsed.table_metadata = Arc::new(table_metadata);
514 Ok(())
515}
516
517fn compile_table_metadata_parsed(
519 parsed: &ParsedSchema,
520 eval_key: &str,
521 table: &Value,
522) -> Result<TableMetadata, String> {
523 let rows = table
524 .get("rows")
525 .and_then(|v| v.as_array())
526 .ok_or("table missing rows")?;
527 let empty_datas = Vec::new();
528 let datas = table
529 .get("datas")
530 .and_then(|v| v.as_array())
531 .unwrap_or(&empty_datas);
532
533 let mut data_plans = Vec::with_capacity(datas.len());
535 for (idx, entry) in datas.iter().enumerate() {
536 let Some(name) = entry.get("name").and_then(|v| v.as_str()) else { continue };
537 let logic_path = format!("{eval_key}/$datas/{idx}/data");
538 let logic = parsed.evaluations.get(&logic_path).copied();
539 let literal = entry.get("data").map(|v| Arc::new(v.clone()));
540 data_plans.push((Arc::from(name), logic, literal));
541 }
542
543 let mut row_plans = Vec::with_capacity(rows.len());
545 for (row_idx, row_val) in rows.iter().enumerate() {
546 let Some(row_obj) = row_val.as_object() else {
547 continue;
548 };
549
550 if let Some(repeat_arr) = row_obj.get("$repeat").and_then(|v| v.as_array()) {
551 if repeat_arr.len() == 3 {
552 let start_logic_path = format!("{eval_key}/$table/{row_idx}/$repeat/0");
553 let end_logic_path = format!("{eval_key}/$table/{row_idx}/$repeat/1");
554 let start_logic = parsed.evaluations.get(&start_logic_path).copied();
555 let end_logic = parsed.evaluations.get(&end_logic_path).copied();
556
557 let start_literal = Arc::new(repeat_arr.get(0).cloned().unwrap_or(Value::Null));
558 let end_literal = Arc::new(repeat_arr.get(1).cloned().unwrap_or(Value::Null));
559
560 if let Some(template) = repeat_arr.get(2).and_then(|v| v.as_object()) {
561 let mut columns = Vec::with_capacity(template.len());
562 for (col_name, col_val) in template {
563 let col_eval_path =
564 format!("{eval_key}/$table/{row_idx}/$repeat/2/{col_name}");
565 let logic = parsed.evaluations.get(&col_eval_path).copied();
566 let literal = if logic.is_none() {
567 Some(col_val.clone())
568 } else {
569 None
570 };
571
572 let (dependencies, has_forward_ref) = if let Some(logic_id) = logic {
574 let deps = parsed.engine.get_referenced_vars(&logic_id)
575 .unwrap_or_default()
576 .into_iter()
577 .filter(|v| v.starts_with('$') && v != "$iteration" && v != "$threshold")
578 .collect();
579 let has_fwd = parsed.engine.has_forward_reference(&logic_id);
580 (deps, has_fwd)
581 } else {
582 (Vec::new(), false)
583 };
584
585 columns.push(ColumnMetadata::new(col_name, logic, literal, dependencies, has_forward_ref));
586 }
587
588 let (forward_cols, normal_cols) = compute_column_partitions(&columns);
590
591 row_plans.push(RowMetadata::Repeat {
592 start: RepeatBoundMetadata {
593 logic: start_logic,
594 literal: start_literal,
595 },
596 end: RepeatBoundMetadata {
597 logic: end_logic,
598 literal: end_literal,
599 },
600 columns: columns.into(),
601 forward_cols: forward_cols.into(),
602 normal_cols: normal_cols.into(),
603 });
604 continue;
605 }
606 }
607 }
608
609 let mut columns = Vec::with_capacity(row_obj.len());
611 for (col_name, col_val) in row_obj {
612 if col_name == "$repeat" {
613 continue;
614 }
615 let col_eval_path = format!("{eval_key}/$table/{row_idx}/{col_name}");
616 let logic = parsed.evaluations.get(&col_eval_path).copied();
617 let literal = if logic.is_none() {
618 Some(col_val.clone())
619 } else {
620 None
621 };
622
623 let (dependencies, has_forward_ref) = if let Some(logic_id) = logic {
625 let deps = parsed.engine.get_referenced_vars(&logic_id)
626 .unwrap_or_default()
627 .into_iter()
628 .filter(|v| v.starts_with('$') && v != "$iteration" && v != "$threshold")
629 .collect();
630 let has_fwd = parsed.engine.has_forward_reference(&logic_id);
631 (deps, has_fwd)
632 } else {
633 (Vec::new(), false)
634 };
635
636 columns.push(ColumnMetadata::new(col_name, logic, literal, dependencies, has_forward_ref));
637 }
638 row_plans.push(RowMetadata::Static { columns: columns.into() });
639 }
640
641 let skip_logic = parsed.evaluations.get(&format!("{eval_key}/$skip")).copied();
643 let skip_literal = table.get("skip").and_then(Value::as_bool).unwrap_or(false);
644 let clear_logic = parsed.evaluations.get(&format!("{eval_key}/$clear")).copied();
645 let clear_literal = table.get("clear").and_then(Value::as_bool).unwrap_or(false);
646
647 Ok(TableMetadata {
648 data_plans: data_plans.into(),
649 row_plans: row_plans.into(),
650 skip_logic,
651 skip_literal,
652 clear_logic,
653 clear_literal,
654 })
655}