1use super::JSONEval;
2use crate::jsoneval::cancellation::CancellationToken;
3use crate::jsoneval::json_parser;
4use crate::jsoneval::path_utils;
5use crate::jsoneval::path_utils::get_value_by_pointer_without_properties;
6use crate::jsoneval::path_utils::normalize_to_json_pointer;
7use crate::jsoneval::types::DependentItem;
8use crate::rlogic::{LogicId, RLogic};
9use crate::time_block;
10use crate::utils::clean_float_noise_scalar;
11use crate::EvalData;
12
13use indexmap::{IndexMap, IndexSet};
14use serde_json::Value;
15
16impl JSONEval {
17 pub fn evaluate_dependents(
21 &mut self,
22 changed_paths: &[String],
23 data: Option<&str>,
24 context: Option<&str>,
25 re_evaluate: bool,
26 token: Option<&CancellationToken>,
27 mut canceled_paths: Option<&mut Vec<String>>,
28 include_subforms: bool,
29 ) -> Result<Value, String> {
30 if let Some(t) = token {
31 if t.is_cancelled() {
32 return Err("Cancelled".to_string());
33 }
34 }
35 let _lock = self.eval_lock.lock().unwrap();
36 let mut structural_change_data = None;
37
38 if let Some(data_str) = data {
40 let data_value = json_parser::parse_json_str(data_str)?;
41 let context_value = if let Some(ctx) = context {
42 json_parser::parse_json_str(ctx)?
43 } else {
44 Value::Object(serde_json::Map::new())
45 };
46 let old_data = self.eval_data.snapshot_data_clone();
47 time_block!(" [dep] data_replace_and_context", {
48 self.eval_data
49 .replace_data_and_context(data_value, context_value);
50 });
51 let new_data = self.eval_data.snapshot_data_clone();
52 time_block!(" [dep] data_diff_versions", {
53 self.eval_cache
54 .store_snapshot_and_diff_versions(&old_data, &new_data);
55 });
56 structural_change_data = Some((old_data, new_data));
57 }
58
59 drop(_lock);
61
62 if let Some((old_data, new_data)) = structural_change_data {
66 time_block!(" [dep] invalidate_subform_structural", {
67 self.invalidate_subform_caches_on_structural_change(&old_data, &new_data);
68 });
69 }
70
71 let mut result = Vec::new();
72 let mut processed = std::collections::HashMap::new();
73 let mut to_process: Vec<(String, bool, Option<Vec<usize>>)> = changed_paths
74 .iter()
75 .map(|path| {
76 (
77 path_utils::dot_notation_to_schema_pointer(path),
78 false,
79 None,
80 )
81 })
82 .collect();
83
84 time_block!(" [dep] process_dependents_queue", {
85 Self::process_dependents_queue(
86 &self.engine,
87 &self.evaluations,
88 &mut self.eval_data,
89 &mut self.eval_cache,
90 &self.dependents_evaluations,
91 &self.dep_formula_triggers,
92 &self.evaluated_schema,
93 &mut to_process,
94 &mut processed,
95 &mut result,
96 token,
97 canceled_paths.as_mut().map(|v| &mut **v),
98 )?;
99 });
100
101 if re_evaluate {
102 time_block!(" [dep] run_re_evaluate_pass", {
103 self.run_re_evaluate_pass(
104 token,
105 &mut to_process,
106 &mut processed,
107 &mut result,
108 canceled_paths.as_mut().map(|v| &mut **v),
109 )?;
110 });
111 }
112
113 if include_subforms {
114 let extended_paths: Vec<String> = {
120 let mut paths = changed_paths.to_vec();
121 for item in &result {
122 if let Some(ref_val) = item.get("$ref").and_then(|v| v.as_str()) {
123 let s = ref_val.to_string();
124 if !paths.contains(&s) {
125 paths.push(s);
126 }
127 }
128 }
129 paths
130 };
131 let subform_invalidated_tables = time_block!(" [dep] run_subform_pass", {
132 self.run_subform_pass(
133 &extended_paths,
134 changed_paths,
135 re_evaluate,
136 token,
137 &mut result,
138 )
139 })?;
140
141 if subform_invalidated_tables {
149 let _lock2 = self.eval_lock.lock().unwrap();
150 drop(_lock2);
151 self.evaluate_internal(None, token)?;
152
153 self.run_subform_pass(&[], &[], true, token, &mut result)?;
158
159 for (subform_path, _) in &self.subforms {
161 let data_ptr = path_utils::schema_path_to_data_pointer(subform_path);
162 let data_ptr_str = data_ptr.to_string();
163 let dot_path = data_ptr_str.trim_start_matches('/').replace('/', ".");
164
165 if let Some(fresh_val) = self.eval_data.get(&data_ptr_str) {
166 let mut patched = false;
167 for item in result.iter_mut() {
168 if item
169 .get("$ref")
170 .and_then(|r| r.as_str())
171 .map(|r| r == dot_path)
172 .unwrap_or(false)
173 {
174 if let Some(map) = item.as_object_mut() {
175 map.remove("clear");
176 map.insert("value".to_string(), fresh_val.clone());
177 }
178 patched = true;
179 break;
180 }
181 }
182 if !patched {
183 let mut obj = serde_json::Map::new();
184 obj.insert("$ref".to_string(), serde_json::Value::String(dot_path));
185 obj.insert("value".to_string(), fresh_val.clone());
186 result.push(serde_json::Value::Object(obj));
187 }
188 }
189 }
190 }
191 }
192
193 let deduped = {
198 let mut seen: IndexMap<String, usize> = IndexMap::new();
199 for (i, item) in result.iter().enumerate() {
200 if let Some(r) = item.get("$ref").and_then(|v| v.as_str()) {
201 seen.insert(r.to_string(), i);
202 }
203 }
204 let last_indices: IndexSet<usize> = seen.values().copied().collect();
205 let out: Vec<Value> = result
206 .into_iter()
207 .enumerate()
208 .filter(|(i, _)| last_indices.contains(i))
209 .map(|(_, item)| item)
210 .collect();
211 out
212 };
213
214 if self.eval_cache.active_item_index.is_none() {
222 let current_snapshot = self.eval_data.snapshot_data_clone();
223 self.eval_cache.main_form_snapshot = Some(current_snapshot);
224 }
225
226 Ok(Value::Array(deduped))
227 }
228
229 fn run_re_evaluate_pass(
232 &mut self,
233 token: Option<&CancellationToken>,
234 to_process: &mut Vec<(String, bool, Option<Vec<usize>>)>,
235 processed: &mut std::collections::HashMap<String, Option<std::collections::HashSet<usize>>>,
236 result: &mut Vec<Value>,
237 mut canceled_paths: Option<&mut Vec<String>>,
238 ) -> Result<(), String> {
239 self.run_schema_default_value_pass(
241 token,
242 to_process,
243 processed,
244 result,
245 canceled_paths.as_mut().map(|v| &mut **v),
246 )?;
247
248 let pre_eval_versions = if let Some(idx) = self.eval_cache.active_item_index {
254 self.eval_cache
255 .subform_caches
256 .get(&idx)
257 .map(|c| c.data_versions.clone())
258 .unwrap_or_else(|| self.eval_cache.data_versions.clone())
259 } else {
260 self.eval_cache.data_versions.clone()
261 };
262
263 self.evaluate_internal(None, token)?;
264
265 let active_idx = self.eval_cache.active_item_index;
267 for eval_key in self.sorted_evaluations.iter().flatten() {
268 if eval_key.contains("/$params/") || eval_key.contains("/$") {
269 continue;
270 }
271
272 let schema_ptr = path_utils::schema_path_to_data_pointer(eval_key);
273 let data_path = schema_ptr.trim_start_matches('/').to_string();
274
275 let version_path = format!("/{}", data_path);
276 let old_ver = pre_eval_versions.get(&version_path);
277 let new_ver = if let Some(idx) = active_idx {
278 self.eval_cache
279 .subform_caches
280 .get(&idx)
281 .map(|c| c.data_versions.get(&version_path))
282 .unwrap_or_else(|| self.eval_cache.data_versions.get(&version_path))
283 } else {
284 self.eval_cache.data_versions.get(&version_path)
285 };
286
287 if new_ver > old_ver {
288 if let Some(new_val) = self.evaluated_schema.pointer(&schema_ptr) {
289 let dot_path = data_path.trim_end_matches("/value").replace('/', ".");
290 let mut obj = serde_json::Map::new();
291 obj.insert("$ref".to_string(), Value::String(dot_path));
292 let is_clear = new_val == &Value::Null || new_val.as_str() == Some("");
293 if is_clear {
294 obj.insert("clear".to_string(), Value::Bool(true));
295 } else {
296 obj.insert("value".to_string(), new_val.clone());
297 }
298 result.push(Value::Object(obj));
299 }
300 }
301 }
302
303 let mut readonly_changes = Vec::new();
305 let mut readonly_values = Vec::new();
306 for path in self.conditional_readonly_fields.iter() {
307 let normalized = path_utils::normalize_to_json_pointer(path);
308 if let Some(schema_el) = self.evaluated_schema.pointer(&normalized) {
309 self.check_readonly_for_dependents(
310 schema_el,
311 path,
312 &mut readonly_changes,
313 &mut readonly_values,
314 );
315 }
316 }
317 let had_actual_readonly_changes = !readonly_changes.is_empty();
320
321 let subform_data_paths: std::collections::HashSet<String> = self
327 .subforms
328 .keys()
329 .map(|p| {
330 path_utils::schema_path_to_data_pointer(p)
331 .replace("/value/", "/")
332 .to_string()
333 })
334 .collect();
335
336 for (path, schema_value) in readonly_changes {
337 let data_path = path_utils::schema_path_to_data_pointer(&path).replace("/value/", "/");
338
339 if subform_data_paths.contains(&data_path) {
340 if let (Value::Array(schema_items), Some(Value::Array(existing_items))) = (
342 &schema_value,
343 self.eval_data.data().pointer(&data_path).cloned().as_ref(),
344 ) {
345 let mut merged_items = existing_items.clone();
346 for (i, schema_item) in schema_items.iter().enumerate() {
347 if let (Some(existing), Value::Object(schema_map)) =
348 (merged_items.get_mut(i), schema_item)
349 {
350 if let Some(existing_map) = existing.as_object_mut() {
351 for (k, v) in schema_map {
352 if !v.is_object() {
353 existing_map.insert(k.clone(), v.clone());
354 }
355 }
356 }
357 } else if i >= merged_items.len() {
358 merged_items.push(schema_item.clone());
359 }
360 }
361 self.eval_data.set(&data_path, Value::Array(merged_items));
362 } else {
363 self.eval_data.set(&data_path, schema_value.clone());
364 }
365 self.eval_cache.bump_data_version(&data_path);
366 to_process.push((path, true, None));
367 continue;
368 }
369
370 self.eval_data.set(&data_path, schema_value.clone());
371 self.eval_cache.bump_data_version(&data_path);
372 to_process.push((path, true, None));
373 }
374 if had_actual_readonly_changes {
380 let readonly_dep_prefixes: Vec<String> = to_process
381 .iter()
382 .map(|(path, _, _)| path.trim_start_matches('#').to_string())
383 .collect();
384 let params_table_keys: Vec<String> = self
385 .table_metadata
386 .keys()
387 .filter(|key| key.starts_with("#/$params"))
388 .filter(|key| {
389 self.dependencies
390 .get(*key)
391 .map(|deps| {
392 deps.iter().any(|dep| {
393 readonly_dep_prefixes.iter().any(|readonly| {
394 dep == readonly
395 || dep
396 .strip_prefix(readonly)
397 .is_some_and(|suffix| suffix.starts_with('/'))
398 })
399 })
400 })
401 .unwrap_or(false)
402 })
403 .cloned()
404 .collect();
405
406 if !params_table_keys.is_empty() {
407 if let Some(active_idx) = self.eval_cache.active_item_index {
408 self.eval_cache
409 .invalidate_params_tables_for_item(active_idx, ¶ms_table_keys);
410 }
411 self.evaluate_internal(None, token)?;
412
413 readonly_values.clear();
417 for path in self.conditional_readonly_fields.iter() {
418 let normalized = path_utils::normalize_to_json_pointer(path);
419 if let Some(schema_el) = self.evaluated_schema.pointer(&normalized) {
420 self.check_readonly_for_dependents(
421 schema_el,
422 path,
423 &mut Vec::new(),
424 &mut readonly_values,
425 );
426 }
427 }
428 }
429 }
430
431 for (path, schema_value) in readonly_values {
432 let data_path = path_utils::schema_path_to_data_pointer(&path).replace("/value/", "/");
433 let mut obj = serde_json::Map::new();
434 obj.insert(
435 "$ref".to_string(),
436 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
437 );
438 obj.insert("$readonly".to_string(), Value::Bool(true));
439 let is_clear = schema_value == Value::Null || schema_value.as_str() == Some("");
440 if is_clear {
441 obj.insert("clear".to_string(), Value::Bool(true));
442 } else {
443 obj.insert("value".to_string(), schema_value);
444 }
445 result.push(Value::Object(obj));
446 }
447
448 if !to_process.is_empty() {
449 Self::process_dependents_queue(
450 &self.engine,
451 &self.evaluations,
452 &mut self.eval_data,
453 &mut self.eval_cache,
454 &self.dependents_evaluations,
455 &self.dep_formula_triggers,
456 &self.evaluated_schema,
457 to_process,
458 processed,
459 result,
460 token,
461 canceled_paths.as_mut().map(|v| &mut **v),
462 )?;
463 }
464
465 self.resolve_layout(false)?;
470
471 let mut hidden_fields = Vec::new();
472 for path in self.conditional_hidden_fields.iter() {
473 let normalized = path_utils::normalize_to_json_pointer(path);
474 if let Some(schema_el) = self.evaluated_schema.pointer(&normalized) {
475 self.check_hidden_field(schema_el, path, &mut hidden_fields);
476 }
477 }
478 for path in self.layout_condition_hidden_refs.iter() {
479 if let Some(schema_el) = self.evaluated_schema.pointer(path) {
480 self.check_effectively_hidden_field(schema_el, path, &mut hidden_fields);
481 }
482 }
483 hidden_fields.sort();
484 hidden_fields.dedup();
485 if !hidden_fields.is_empty() {
486 Self::recursive_hide_effect(
487 &self.engine,
488 &self.evaluations,
489 &self.reffed_by,
490 &mut self.eval_data,
491 &mut self.eval_cache,
492 hidden_fields,
493 to_process,
494 result,
495 );
496 }
497 if !to_process.is_empty() {
498 Self::process_dependents_queue(
499 &self.engine,
500 &self.evaluations,
501 &mut self.eval_data,
502 &mut self.eval_cache,
503 &self.dependents_evaluations,
504 &self.dep_formula_triggers,
505 &self.evaluated_schema,
506 to_process,
507 processed,
508 result,
509 token,
510 canceled_paths.as_mut().map(|v| &mut **v),
511 )?;
512 }
513
514 Ok(())
515 }
516
517 fn collect_visible_static_defaults(&self) -> Vec<(String, Value, String)> {
519 let mut defaults = Vec::new();
520 let schema_values = self.get_schema_value_array();
521
522 if let Value::Array(values) = schema_values {
523 for item in values {
524 let Value::Object(map) = item else {
525 continue;
526 };
527 let Some(Value::String(dot_path)) = map.get("path") else {
528 continue;
529 };
530 let Some(schema_val) = map.get("value") else {
531 continue;
532 };
533
534 let schema_ptr = path_utils::dot_notation_to_schema_pointer(dot_path);
535 if let Some(Value::Object(schema_node)) = self
536 .evaluated_schema
537 .pointer(schema_ptr.trim_start_matches('#'))
538 {
539 if let Some(Value::Object(condition)) = schema_node.get("condition") {
540 if let Some(hidden_val) = condition.get("hidden") {
541 if !hidden_val.is_boolean() || hidden_val.as_bool() == Some(true) {
542 continue;
543 }
544 }
545 }
546 }
547
548 let data_path = dot_path.replace('.', "/");
549 let current_data = self
550 .eval_data
551 .data()
552 .pointer(&format!("/{}", data_path))
553 .unwrap_or(&Value::Null);
554 let is_empty = matches!(current_data, Value::Null)
555 || matches!(current_data, Value::String(s) if s.is_empty());
556 let is_schema_val_empty = matches!(schema_val, Value::Null)
557 || matches!(schema_val, Value::String(s) if s.is_empty())
558 || matches!(schema_val, Value::Object(map) if map.contains_key("$evaluation"));
559
560 if is_empty && !is_schema_val_empty && current_data != schema_val {
561 defaults.push((data_path, schema_val.clone(), dot_path.clone()));
562 }
563 }
564 }
565
566 defaults
567 }
568
569 pub(crate) fn apply_visible_static_defaults(&mut self) -> bool {
570 let defaults = self.collect_visible_static_defaults();
571 for (data_path, schema_val, _) in &defaults {
572 self.eval_data
573 .set(&format!("/{}", data_path), schema_val.clone());
574 self.eval_cache
575 .bump_data_version(&format!("/{}", data_path));
576 }
577 !defaults.is_empty()
578 }
579
580 pub(crate) fn apply_visible_static_defaults_with_dependents(
586 &mut self,
587 token: Option<&CancellationToken>,
588 ) -> Result<bool, String> {
589 if self.collect_visible_static_defaults().is_empty() {
590 return Ok(false);
591 }
592
593 let mut to_process = Vec::new();
594 let mut processed = std::collections::HashMap::new();
595 let mut result = Vec::new();
596 self.run_schema_default_value_pass(
597 token,
598 &mut to_process,
599 &mut processed,
600 &mut result,
601 None,
602 )?;
603 Ok(true)
604 }
605
606 fn run_schema_default_value_pass(
609 &mut self,
610 token: Option<&CancellationToken>,
611 to_process: &mut Vec<(String, bool, Option<Vec<usize>>)>,
612 processed: &mut std::collections::HashMap<String, Option<std::collections::HashSet<usize>>>,
613 result: &mut Vec<Value>,
614 mut canceled_paths: Option<&mut Vec<String>>,
615 ) -> Result<(), String> {
616 let mut default_value_changes = Vec::new();
617 let schema_values = self.get_schema_value_array();
618
619 if let Value::Array(values) = schema_values {
620 for item in values {
621 if let Value::Object(map) = item {
622 if let (Some(Value::String(dot_path)), Some(schema_val)) =
623 (map.get("path"), map.get("value"))
624 {
625 let schema_ptr = path_utils::dot_notation_to_schema_pointer(dot_path);
626 if let Some(Value::Object(schema_node)) = self
627 .evaluated_schema
628 .pointer(schema_ptr.trim_start_matches('#'))
629 {
630 if let Some(Value::Object(condition)) = schema_node.get("condition") {
631 if let Some(hidden_val) = condition.get("hidden") {
632 if !hidden_val.is_boolean()
634 || hidden_val.as_bool() == Some(true)
635 {
636 continue;
637 }
638 }
639 }
640 }
641
642 let data_path = dot_path.replace('.', "/");
643 let current_data = self
644 .eval_data
645 .data()
646 .pointer(&format!("/{}", data_path))
647 .unwrap_or(&Value::Null);
648
649 let is_empty = match current_data {
650 Value::Null => true,
651 Value::String(s) if s.is_empty() => true,
652 _ => false,
653 };
654
655 let is_schema_val_empty = match schema_val {
656 Value::Null => true,
657 Value::String(s) if s.is_empty() => true,
658 Value::Object(map) if map.contains_key("$evaluation") => true,
659 _ => false,
660 };
661
662 if is_empty && !is_schema_val_empty && current_data != schema_val {
663 default_value_changes.push((
664 data_path,
665 schema_val.clone(),
666 dot_path.clone(),
667 ));
668 }
669 }
670 }
671 }
672 }
673
674 let mut has_changes = false;
675 for (data_path, schema_val, dot_path) in default_value_changes {
676 self.eval_data
677 .set(&format!("/{}", data_path), schema_val.clone());
678 self.eval_cache
679 .bump_data_version(&format!("/{}", data_path));
680
681 let mut change_obj = serde_json::Map::new();
682 change_obj.insert("$ref".to_string(), Value::String(dot_path));
683 let is_clear = schema_val == Value::Null || schema_val.as_str() == Some("");
684 if is_clear {
685 change_obj.insert("clear".to_string(), Value::Bool(true));
686 } else {
687 change_obj.insert("value".to_string(), schema_val);
688 }
689 result.push(Value::Object(change_obj));
690
691 let schema_ptr = format!("#/{}", data_path.replace('/', "/properties/"));
692 to_process.push((schema_ptr, true, None));
693 has_changes = true;
694 }
695
696 if has_changes {
697 Self::process_dependents_queue(
698 &self.engine,
699 &self.evaluations,
700 &mut self.eval_data,
701 &mut self.eval_cache,
702 &self.dependents_evaluations,
703 &self.dep_formula_triggers,
704 &self.evaluated_schema,
705 to_process,
706 processed,
707 result,
708 token,
709 canceled_paths.as_mut().map(|v| &mut **v),
710 )?;
711 }
712
713 Ok(())
714 }
715
716 fn run_subform_pass(
726 &mut self,
727 changed_paths: &[String],
728 parent_changed_paths: &[String],
729 _re_evaluate: bool,
730 token: Option<&CancellationToken>,
731 result: &mut Vec<Value>,
732 ) -> Result<bool, String> {
733 let mut any_table_invalidated = false;
734 let subform_paths: Vec<String> = self.subforms.keys().cloned().collect();
736
737 for subform_path in subform_paths {
738 let field_key = subform_field_key(&subform_path);
739 let subform_dot_path =
741 path_utils::pointer_to_dot_notation(&subform_path).replace(".properties.", ".");
742 let field_prefix = format!("{}.", field_key);
743 let subform_ptr = normalize_to_json_pointer(&subform_path);
744
745 let item_count =
747 get_value_by_pointer_without_properties(self.eval_data.data(), &subform_ptr)
748 .and_then(|v| v.as_array())
749 .map(|a| a.len())
750 .unwrap_or(0);
751
752 if item_count == 0 {
753 continue;
754 }
755
756 self.eval_cache.prune_subform_caches(item_count);
759
760 let parent_data_versions_snapshot = self.eval_cache.data_versions.clone();
765 let parent_params_versions_snapshot = self.eval_cache.params_versions.clone();
766
767 for idx in 0..item_count {
768 let prefix_dot = format!("{}.{}.", subform_dot_path, idx);
770 let prefix_bracket = format!("{}[{}].", subform_dot_path, idx);
771 let prefix_field_bracket = format!("{}[{}].", field_key, idx);
772
773 let item_changed_paths: Vec<String> = changed_paths
774 .iter()
775 .filter_map(|p| {
776 if p.starts_with(&prefix_bracket) {
777 Some(p.replacen(&prefix_bracket, &field_prefix, 1))
778 } else if p.starts_with(&prefix_dot) {
779 Some(p.replacen(&prefix_dot, &field_prefix, 1))
780 } else if p.starts_with(&prefix_field_bracket) {
781 Some(p.replacen(&prefix_field_bracket, &field_prefix, 1))
782 } else {
783 None
784 }
785 })
786 .collect();
787
788 let item_val =
791 get_value_by_pointer_without_properties(self.eval_data.data(), &subform_ptr)
792 .and_then(|v| v.as_array())
793 .and_then(|a| a.get(idx))
794 .cloned()
795 .unwrap_or(Value::Null);
796
797 let parent_dependency_paths: Vec<String> = parent_changed_paths
802 .iter()
803 .map(|path| {
804 path_utils::dot_notation_to_schema_pointer(path)
805 .trim_start_matches('#')
806 .to_string()
807 })
808 .collect();
809 let dependent_value_paths: Vec<String> = self
810 .subforms
811 .get(&subform_path)
812 .map(|subform| {
813 subform
814 .dependencies
815 .iter()
816 .filter(|(key, deps)| {
817 !subform.table_metadata.contains_key(*key)
818 && !key.starts_with("#/$params/")
819 && key.ends_with("/value")
820 && parent_dependency_paths
821 .iter()
822 .any(|dependency| deps.contains(dependency))
823 })
824 .map(|(key, _)| key.clone())
825 .collect()
826 })
827 .unwrap_or_default();
828
829 if item_changed_paths.is_empty() && !dependent_value_paths.is_empty() {
830 let parent_cache = std::mem::take(&mut self.eval_cache);
835 let mut overlay_cache = parent_cache.clone();
836 overlay_cache.ensure_active_item_cache(idx);
837 if let Some(item_cache) = overlay_cache.subform_caches.get_mut(&idx) {
838 item_cache
842 .data_versions
843 .merge_from(&parent_data_versions_snapshot);
844 item_cache
845 .data_versions
846 .merge_from_params(&parent_params_versions_snapshot);
847 }
848 overlay_cache.set_active_item(idx);
849 let canonical_root =
850 path_utils::schema_path_to_data_pointer(&subform_path).into_owned();
851 let scope = crate::jsoneval::subform_scope::SubformScope::new(
852 &subform_path,
853 &canonical_root,
854 Some(idx),
855 );
856 let mut scoped_view = scope.evaluation_view(self.eval_data.data());
857 if let Some(view) = scoped_view.as_object_mut() {
858 view.insert(
859 "$context".to_string(),
860 self.eval_data
861 .data()
862 .get("$context")
863 .cloned()
864 .unwrap_or(Value::Null),
865 );
866 }
867 let subform = self
868 .subforms
869 .get_mut(&subform_path)
870 .expect("subform exists");
871 subform.eval_data = EvalData::new(scoped_view);
872 std::mem::swap(&mut subform.eval_cache, &mut overlay_cache);
873
874 let refresh_table_outputs = dependent_value_paths.iter().any(|source| {
879 let source = source.trim_end_matches("/value").trim_start_matches('#');
880 let affected_tables: Vec<&String> = subform
881 .table_metadata
882 .keys()
883 .filter(|table| table.starts_with("#/$params"))
884 .filter(|table| {
885 subform.dependencies.get(*table).is_some_and(|deps| {
886 deps.iter().any(|dep| dep.trim_start_matches('#') == source)
887 })
888 })
889 .collect();
890
891 !affected_tables.is_empty()
892 && subform.evaluations.iter().any(|(target, _)| {
893 target.ends_with("/value")
894 && subform.dependencies.get(target).is_some_and(|deps| {
895 deps.iter().any(|dep| {
896 affected_tables.iter().any(|table| {
897 dep.trim_start_matches('#')
898 == table.trim_start_matches('#')
899 })
900 })
901 })
902 })
903 });
904 subform.evaluate_internal(Some(&dependent_value_paths), token)?;
905
906 let mut overlay_result = Vec::new();
907 let mut overlay_queue = Vec::new();
908 let mut overlay_processed = std::collections::HashMap::new();
909 for formula_path in &dependent_value_paths {
910 let schema_path = path_utils::normalize_to_json_pointer(formula_path);
911 let data_path = path_utils::schema_path_to_data_pointer(formula_path)
912 .replace("/value", "");
913 let Some(value) = subform.evaluated_schema.pointer(&schema_path).cloned()
914 else {
915 continue;
916 };
917
918 let source_path = formula_path.trim_end_matches("/value").to_string();
923 if subform.eval_data.get(&data_path) != Some(&value) {
924 subform.eval_data.set(&data_path, value.clone());
925 subform
926 .eval_data
927 .set(&scope.canonical_path(&data_path), value.clone());
928 subform.eval_cache.bump_data_version(&data_path);
929 }
930 overlay_queue.push((source_path, true, None));
931
932 let mut change = serde_json::Map::new();
933 let field = data_path
934 .trim_start_matches('/')
935 .trim_end_matches("/value")
936 .replace('/', ".");
937 let field = field.strip_prefix(&field_prefix).unwrap_or(&field);
938 change.insert(
939 "$ref".to_string(),
940 Value::String(format!("{}.{}.{}", subform_dot_path, idx, field)),
941 );
942 if value == Value::Null || value.as_str() == Some("") {
943 change.insert("clear".to_string(), Value::Bool(true));
944 } else {
945 change.insert("value".to_string(), value);
946 }
947 result.push(Value::Object(change));
948 }
949
950 Self::process_dependents_queue(
951 &subform.engine,
952 &subform.evaluations,
953 &mut subform.eval_data,
954 &mut subform.eval_cache,
955 &subform.dependents_evaluations,
956 &subform.dep_formula_triggers,
957 &subform.evaluated_schema,
958 &mut overlay_queue,
959 &mut overlay_processed,
960 &mut overlay_result,
961 token,
962 None,
963 )?;
964
965 let local_item_path = format!("/{field_key}");
968 if let Some(local_item) = subform.eval_data.get(&local_item_path).cloned() {
969 subform
970 .eval_data
971 .set(&scope.canonical_path(&local_item_path), local_item);
972 }
973
974 if refresh_table_outputs {
975 subform.run_re_evaluate_pass(
979 token,
980 &mut overlay_queue,
981 &mut overlay_processed,
982 &mut overlay_result,
983 None,
984 )?;
985 }
986
987 for change in overlay_result {
988 let Some(object) = change.as_object() else {
989 continue;
990 };
991 let Some(Value::String(ref_path)) = object.get("$ref") else {
992 continue;
993 };
994 let local_ref = ref_path.strip_prefix(&field_prefix).unwrap_or(ref_path);
995 let mut mapped = object.clone();
996 mapped.insert(
997 "$ref".to_string(),
998 Value::String(format!("{}.{}.{}", subform_dot_path, idx, local_ref)),
999 );
1000 result.push(Value::Object(mapped));
1001 }
1002
1003 std::mem::swap(&mut subform.eval_cache, &mut overlay_cache);
1006 self.eval_cache = parent_cache;
1007 continue;
1008 }
1009
1010 let canonical_root =
1011 path_utils::schema_path_to_data_pointer(&subform_path).into_owned();
1012 let scope = crate::jsoneval::subform_scope::SubformScope::new(
1013 &subform_path,
1014 &canonical_root,
1015 Some(idx),
1016 );
1017 let mut scoped_view = scope.evaluation_view(self.eval_data.data());
1018 if let Some(view) = scoped_view.as_object_mut() {
1019 view.insert(
1020 "$context".to_string(),
1021 self.eval_data
1022 .data()
1023 .get("$context")
1024 .cloned()
1025 .unwrap_or(Value::Null),
1026 );
1027 }
1028 let Some(subform) = self.subforms.get_mut(&subform_path) else {
1029 continue;
1030 };
1031
1032 let sub_re_evaluate = !item_changed_paths.is_empty();
1037 if !sub_re_evaluate && item_changed_paths.is_empty() {
1038 continue;
1039 }
1040
1041 self.eval_cache.ensure_active_item_cache(idx);
1043 let old_item_val = {
1044 let snapshot = self
1045 .eval_cache
1046 .subform_caches
1047 .get(&idx)
1048 .map(|c| c.item_snapshot.clone())
1049 .unwrap_or(Value::Null);
1050
1051 if snapshot == Value::Null {
1052 if let Some(main_snap) = &self.eval_cache.main_form_snapshot {
1053 get_value_by_pointer_without_properties(main_snap, &subform_ptr)
1054 .and_then(|v| v.as_array())
1055 .and_then(|a| a.get(idx))
1056 .cloned()
1057 .unwrap_or(Value::Null)
1058 } else {
1059 Value::Null
1060 }
1061 } else {
1062 snapshot
1063 }
1064 };
1065
1066 subform.eval_data = EvalData::new(scoped_view);
1067 let new_item_val = item_val.clone();
1068
1069 let mut parent_cache = std::mem::take(&mut self.eval_cache);
1071 parent_cache.ensure_active_item_cache(idx);
1072
1073 let pre_diff_item_versions = parent_cache
1076 .subform_caches
1077 .get(&idx)
1078 .map(|c| c.data_versions.clone());
1079
1080 if let Some(c) = parent_cache.subform_caches.get_mut(&idx) {
1081 c.data_versions.merge_from(&parent_data_versions_snapshot);
1085 c.data_versions
1087 .merge_from_params(&parent_params_versions_snapshot);
1088 crate::jsoneval::eval_cache::diff_and_update_versions(
1089 &mut c.data_versions,
1090 &format!("/{}", field_key),
1091 &old_item_val,
1092 &new_item_val,
1093 "run_subform_pass_diff_and_update_versions",
1094 );
1095 c.item_snapshot = new_item_val;
1096 }
1097
1098 if let (Some(ref pre), Some(c)) = (
1102 &pre_diff_item_versions,
1103 parent_cache.subform_caches.get(&idx),
1104 ) {
1105 let field_prefix_slash = format!("/{}/", field_key);
1106 let newly_bumped: Vec<String> = c
1107 .data_versions
1108 .versions()
1109 .filter(|(k, &v)| k.starts_with(&field_prefix_slash) && v > pre.get(k))
1110 .map(|(k, _)| k.to_string())
1111 .collect();
1112 if !newly_bumped.is_empty() {
1113 for k in newly_bumped {
1114 parent_cache
1115 .data_versions
1116 .bump(&k, "propagate_newly_bumped");
1117 }
1118 parent_cache.eval_generation += 1;
1119 }
1120 }
1121
1122 {
1128 let field_prefix_slash = format!("/{}/", field_key);
1129 let newly_bumped_schema_paths: Vec<String> = if let (Some(ref pre), Some(c)) = (
1130 &pre_diff_item_versions,
1131 parent_cache.subform_caches.get(&idx),
1132 ) {
1133 c.data_versions
1134 .versions()
1135 .filter(|(k, &v)| k.starts_with(&field_prefix_slash) && v > pre.get(k))
1136 .map(|(k, _)| {
1137 let sub = k.trim_start_matches(&field_prefix_slash);
1141 format!(
1142 "/{}/properties/{}",
1143 field_key,
1144 sub.replace('/', "/properties/")
1145 )
1146 })
1147 .collect()
1148 } else {
1149 Vec::new()
1150 };
1151
1152 if !newly_bumped_schema_paths.is_empty() {
1153 let params_table_keys: Vec<String> = self
1154 .table_metadata
1155 .keys()
1156 .filter(|k| k.starts_with("#/$params"))
1157 .filter(|k| {
1158 self.dependencies
1159 .get(*k)
1160 .map(|deps| {
1161 deps.iter().any(|dep| {
1162 newly_bumped_schema_paths
1163 .iter()
1164 .any(|b| dep == b || dep.starts_with(b.as_str()))
1165 })
1166 })
1167 .unwrap_or(false)
1168 })
1169 .cloned()
1170 .collect();
1171
1172 if !params_table_keys.is_empty() {
1173 parent_cache.invalidate_params_tables_for_item(idx, ¶ms_table_keys);
1174 any_table_invalidated = true;
1175 }
1176 }
1177 }
1178
1179 parent_cache.set_active_item(idx);
1180 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
1181
1182 let subform_result = time_block!(" [subform_pass] rider evaluate_dependents", {
1183 subform.evaluate_dependents(
1184 &item_changed_paths,
1185 None,
1186 None,
1187 sub_re_evaluate,
1188 token,
1189 None,
1190 false,
1191 )
1192 });
1193
1194 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
1196 parent_cache.clear_active_item();
1197
1198 if let Some(parent_item_cache) = self.eval_cache.subform_caches.get(&idx) {
1203 let snapshot = parent_item_cache.item_snapshot.clone();
1204 subform.eval_cache.ensure_active_item_cache(idx);
1205 if let Some(sub_cache) = subform.eval_cache.subform_caches.get_mut(&idx) {
1206 sub_cache.item_snapshot = snapshot;
1207 }
1208 }
1209
1210 self.eval_cache = parent_cache;
1211
1212 if let Ok(Value::Array(changes)) = subform_result {
1213 let mut had_any_change = false;
1214 for change in changes {
1215 if let Some(obj) = change.as_object() {
1216 if let Some(Value::String(ref_path)) = obj.get("$ref") {
1217 let new_ref = if ref_path.starts_with(&field_prefix) {
1219 format!(
1220 "{}.{}.{}",
1221 subform_dot_path,
1222 idx,
1223 &ref_path[field_prefix.len()..]
1224 )
1225 } else {
1226 format!("{}.{}.{}", subform_dot_path, idx, ref_path)
1227 };
1228
1229 if let Some(val) = obj.get("value") {
1235 let data_ptr = format!("/{}", new_ref.replace('.', "/"));
1236 self.eval_data.set(&data_ptr, val.clone());
1237 had_any_change = true;
1238 } else if obj.get("clear").and_then(Value::as_bool) == Some(true) {
1239 let data_ptr = format!("/{}", new_ref.replace('.', "/"));
1240 self.eval_data.set(&data_ptr, Value::Null);
1241 had_any_change = true;
1242 }
1243
1244 let mut new_obj = obj.clone();
1245 new_obj.insert("$ref".to_string(), Value::String(new_ref));
1246 result.push(Value::Object(new_obj));
1247 } else {
1248 result.push(change);
1250 }
1251 }
1252 }
1253
1254 if had_any_change {
1262 let item_path = format!("{}/{}", subform_ptr, idx);
1263 let updated_item = self
1264 .eval_data
1265 .get(&item_path)
1266 .cloned()
1267 .unwrap_or(Value::Null);
1268 if let Some(c) = self.eval_cache.subform_caches.get_mut(&idx) {
1270 c.item_snapshot = updated_item.clone();
1271 }
1272 subform.eval_cache.ensure_active_item_cache(idx);
1275 if let Some(sub_cache) = subform.eval_cache.subform_caches.get_mut(&idx) {
1276 sub_cache.item_snapshot = updated_item;
1277 }
1278 }
1279 }
1280 }
1281 }
1282 Ok(any_table_invalidated)
1283 }
1284
1285 pub(crate) fn evaluate_dependent_value_static(
1287 engine: &RLogic,
1288 evaluations: &IndexMap<String, LogicId>,
1289 eval_data: &EvalData,
1290 value: &Value,
1291 changed_field_value: &Value,
1292 changed_field_ref_value: &Value,
1293 ) -> Result<Value, String> {
1294 match value {
1295 Value::String(eval_key) => {
1297 if let Some(logic_id) = evaluations.get(eval_key) {
1298 let mut internal_context = serde_json::Map::new();
1301 internal_context.insert("$value".to_string(), changed_field_value.clone());
1302 internal_context.insert("$refValue".to_string(), changed_field_ref_value.clone());
1303 let context_value = Value::Object(internal_context);
1304
1305 let result = engine.run_with_context(logic_id, eval_data.data(), &context_value)
1306 .map_err(|e| format!("Failed to evaluate dependent logic '{}': {}", eval_key, e))?;
1307 Ok(result)
1308 } else {
1309 Ok(value.clone())
1311 }
1312 }
1313 Value::Object(map) if map.contains_key("$evaluation") => {
1316 Err("Dependent evaluation contains unparsed $evaluation - schema was not properly parsed".to_string())
1317 }
1318 _ => Ok(value.clone()),
1320 }
1321 }
1322
1323 pub(crate) fn check_readonly_for_dependents(
1325 &self,
1326 schema_element: &Value,
1327 path: &str,
1328 changes: &mut Vec<(String, Value)>,
1329 all_values: &mut Vec<(String, Value)>,
1330 ) {
1331 match schema_element {
1332 Value::Object(map) => {
1333 let mut is_disabled = false;
1335 if let Some(Value::Object(condition)) = map.get("condition") {
1336 if let Some(Value::Bool(d)) = condition.get("disabled") {
1337 is_disabled = *d;
1338 }
1339 }
1340
1341 let mut skip_readonly = false;
1343 if let Some(Value::Object(config)) = map.get("config") {
1344 if let Some(Value::Object(all)) = config.get("all") {
1345 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
1346 skip_readonly = *skip;
1347 }
1348 }
1349 }
1350
1351 if is_disabled && !skip_readonly {
1352 if let Some(schema_value) = map.get("value") {
1353 let data_path = path_utils::schema_path_to_data_pointer(path)
1354 .replace("/value/", "/");
1357
1358 let current_data = self
1359 .eval_data
1360 .data()
1361 .pointer(&data_path)
1362 .unwrap_or(&Value::Null);
1363
1364 all_values.push((path.to_string(), schema_value.clone()));
1367 if current_data != schema_value {
1368 changes.push((path.to_string(), schema_value.clone()));
1369 }
1370 }
1371 }
1372 }
1373 _ => {}
1374 }
1375 }
1376
1377 #[allow(dead_code)]
1379 pub(crate) fn collect_readonly_fixes(
1380 &self,
1381 schema_element: &Value,
1382 path: &str,
1383 changes: &mut Vec<(String, Value)>,
1384 ) {
1385 match schema_element {
1386 Value::Object(map) => {
1387 let mut is_disabled = false;
1389 if let Some(Value::Object(condition)) = map.get("condition") {
1390 if let Some(Value::Bool(d)) = condition.get("disabled") {
1391 is_disabled = *d;
1392 }
1393 }
1394
1395 let mut skip_readonly = false;
1397 if let Some(Value::Object(config)) = map.get("config") {
1398 if let Some(Value::Object(all)) = config.get("all") {
1399 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
1400 skip_readonly = *skip;
1401 }
1402 }
1403 }
1404
1405 if is_disabled && !skip_readonly {
1406 if let Some(schema_value) = map.get("value") {
1410 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1411
1412 let current_data = self
1413 .eval_data
1414 .data()
1415 .pointer(&data_path)
1416 .unwrap_or(&Value::Null);
1417
1418 if current_data != schema_value {
1419 changes.push((path.to_string(), schema_value.clone()));
1420 }
1421 }
1422 }
1423
1424 if let Some(Value::Object(props)) = map.get("properties") {
1426 for (key, val) in props {
1427 let next_path = if path == "#" {
1428 format!("#/properties/{}", key)
1429 } else {
1430 format!("{}/properties/{}", path, key)
1431 };
1432 self.collect_readonly_fixes(val, &next_path, changes);
1433 }
1434 }
1435 }
1436 _ => {}
1437 }
1438 }
1439
1440 pub(crate) fn check_hidden_field(
1442 &self,
1443 schema_element: &Value,
1444 path: &str,
1445 hidden_fields: &mut Vec<String>,
1446 ) {
1447 match schema_element {
1448 Value::Object(map) => {
1449 let mut is_hidden = false;
1451 if let Some(Value::Object(condition)) = map.get("condition") {
1452 if let Some(Value::Bool(h)) = condition.get("hidden") {
1453 is_hidden = *h;
1454 }
1455 }
1456
1457 let mut keep_hidden = false;
1459 if let Some(Value::Object(config)) = map.get("config") {
1460 if let Some(Value::Object(all)) = config.get("all") {
1461 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
1462 keep_hidden = *keep;
1463 }
1464 }
1465 }
1466
1467 if is_hidden && !keep_hidden {
1468 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1469
1470 let current_data = self
1471 .eval_data
1472 .data()
1473 .pointer(&data_path)
1474 .unwrap_or(&Value::Null);
1475
1476 if current_data != &Value::Null && current_data != "" {
1478 hidden_fields.push(path.to_string());
1479 }
1480 }
1481 }
1482 _ => {}
1483 }
1484 }
1485
1486 fn check_effectively_hidden_field(
1488 &self,
1489 schema_element: &Value,
1490 path: &str,
1491 hidden_fields: &mut Vec<String>,
1492 ) {
1493 let Value::Object(map) = schema_element else {
1494 return;
1495 };
1496
1497 let keep_hidden = map
1498 .get("config")
1499 .and_then(Value::as_object)
1500 .and_then(|config| config.get("all"))
1501 .and_then(Value::as_object)
1502 .and_then(|all| all.get("keepHiddenValue"))
1503 .and_then(Value::as_bool)
1504 .unwrap_or(false);
1505 if keep_hidden {
1506 return;
1507 }
1508
1509 let current_data = self
1510 .eval_data
1511 .data()
1512 .pointer(&path_utils::schema_path_to_data_pointer(path))
1513 .unwrap_or(&Value::Null);
1514 if current_data != &Value::Null && current_data != "" {
1515 hidden_fields.push(path.to_string());
1516 }
1517 }
1518
1519 #[allow(dead_code)]
1521 pub(crate) fn collect_hidden_fields(
1522 &self,
1523 schema_element: &Value,
1524 path: &str,
1525 hidden_fields: &mut Vec<String>,
1526 ) {
1527 match schema_element {
1528 Value::Object(map) => {
1529 let mut is_hidden = false;
1531 if let Some(Value::Object(condition)) = map.get("condition") {
1532 if let Some(Value::Bool(h)) = condition.get("hidden") {
1533 is_hidden = *h;
1534 }
1535 }
1536
1537 let mut keep_hidden = false;
1539 if let Some(Value::Object(config)) = map.get("config") {
1540 if let Some(Value::Object(all)) = config.get("all") {
1541 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
1542 keep_hidden = *keep;
1543 }
1544 }
1545 }
1546
1547 if is_hidden && !keep_hidden {
1548 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1549
1550 let current_data = self
1551 .eval_data
1552 .data()
1553 .pointer(&data_path)
1554 .unwrap_or(&Value::Null);
1555
1556 if current_data != &Value::Null && current_data != "" {
1558 hidden_fields.push(path.to_string());
1559 }
1560 }
1561
1562 for (key, val) in map {
1564 if key == "properties" {
1565 if let Value::Object(props) = val {
1566 for (p_key, p_val) in props {
1567 let next_path = if path == "#" {
1568 format!("#/properties/{}", p_key)
1569 } else {
1570 format!("{}/properties/{}", path, p_key)
1571 };
1572 self.collect_hidden_fields(p_val, &next_path, hidden_fields);
1573 }
1574 }
1575 } else if let Value::Object(_) = val {
1576 if key == "condition"
1578 || key == "config"
1579 || key == "rules"
1580 || key == "dependents"
1581 || key == "hideLayout"
1582 || key == "$layout"
1583 || key == "$params"
1584 || key == "definitions"
1585 || key == "$defs"
1586 || key.starts_with('$')
1587 {
1588 continue;
1589 }
1590
1591 let next_path = if path == "#" {
1592 format!("#/{}", key)
1593 } else {
1594 format!("{}/{}", path, key)
1595 };
1596 self.collect_hidden_fields(val, &next_path, hidden_fields);
1597 }
1598 }
1599 }
1600 _ => {}
1601 }
1602 }
1603
1604 pub(crate) fn recursive_hide_effect(
1607 engine: &RLogic,
1608 evaluations: &IndexMap<String, LogicId>,
1609 reffed_by: &IndexMap<String, Vec<String>>,
1610 eval_data: &mut EvalData,
1611 eval_cache: &mut crate::jsoneval::eval_cache::EvalCache,
1612 mut hidden_fields: Vec<String>,
1613 queue: &mut Vec<(String, bool, Option<Vec<usize>>)>,
1614 result: &mut Vec<Value>,
1615 ) {
1616 while let Some(hf) = hidden_fields.pop() {
1617 let data_path = path_utils::schema_path_to_data_pointer(&hf).into_owned();
1618
1619 eval_data.set(&data_path, Value::Null);
1621 eval_cache.bump_data_version(&data_path);
1622
1623 let mut change_obj = serde_json::Map::new();
1625 change_obj.insert(
1626 "$ref".to_string(),
1627 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
1628 );
1629 change_obj.insert("$hidden".to_string(), Value::Bool(true));
1630 change_obj.insert("clear".to_string(), Value::Bool(true));
1631 result.push(Value::Object(change_obj));
1632
1633 queue.push((hf.clone(), true, None));
1635
1636 if let Some(referencing_fields) = reffed_by.get(&data_path) {
1638 for rb in referencing_fields {
1639 let hidden_eval_key = format!("{}/condition/hidden", rb);
1643
1644 if let Some(logic_id) = evaluations.get(&hidden_eval_key) {
1645 let rb_data_path = path_utils::schema_path_to_data_pointer(rb).into_owned();
1652 let rb_value = eval_data
1653 .data()
1654 .pointer(&rb_data_path)
1655 .cloned()
1656 .unwrap_or(Value::Null);
1657
1658 if let Ok(Value::Bool(is_hidden)) = engine.run(logic_id, eval_data.data()) {
1660 if is_hidden {
1661 if !hidden_fields.contains(rb) {
1664 let has_value = rb_value != Value::Null && rb_value != "";
1665 if has_value {
1666 hidden_fields.push(rb.clone());
1667 }
1668 }
1669 }
1670 }
1671 }
1672 }
1673 }
1674 }
1675 }
1676
1677 pub(crate) fn process_dependents_queue(
1680 engine: &RLogic,
1681 evaluations: &IndexMap<String, LogicId>,
1682 eval_data: &mut EvalData,
1683 eval_cache: &mut crate::jsoneval::eval_cache::EvalCache,
1684 dependents_evaluations: &IndexMap<String, Vec<DependentItem>>,
1685 dep_formula_triggers: &IndexMap<String, Vec<(String, usize)>>,
1686 evaluated_schema: &Value,
1687 queue: &mut Vec<(String, bool, Option<Vec<usize>>)>,
1688 processed: &mut std::collections::HashMap<String, Option<std::collections::HashSet<usize>>>,
1689 result: &mut Vec<Value>,
1690 token: Option<&CancellationToken>,
1691 canceled_paths: Option<&mut Vec<String>>,
1692 ) -> Result<(), String> {
1693 while let Some((current_path, is_transitive, target_indices)) = queue.pop() {
1694 if let Some(t) = token {
1695 if t.is_cancelled() {
1696 if let Some(cp) = canceled_paths {
1697 cp.push(current_path.clone());
1698 for (path, _, _) in queue.iter() {
1699 cp.push(path.clone());
1700 }
1701 }
1702 return Err("Cancelled".to_string());
1703 }
1704 }
1705
1706 let (should_run, indices_to_run) = match processed.get(¤t_path) {
1707 Some(None) => {
1708 continue;
1710 }
1711 Some(Some(already_processed_indices)) => {
1712 if let Some(targets) = &target_indices {
1713 let new_targets: std::collections::HashSet<usize> = targets
1714 .iter()
1715 .copied()
1716 .filter(|i| !already_processed_indices.contains(i))
1717 .collect();
1718 if new_targets.is_empty() {
1719 continue;
1720 }
1721 (true, Some(new_targets))
1722 } else {
1723 (true, None)
1724 }
1725 }
1726 None => (
1727 true,
1728 target_indices.clone().map(|t| t.into_iter().collect()),
1729 ),
1730 };
1731
1732 if !should_run {
1733 continue;
1734 }
1735
1736 let new_processed_state = if let Some(targets_to_run) = &indices_to_run {
1737 match processed.get(¤t_path) {
1738 Some(Some(existing_targets)) => {
1739 let mut copy = existing_targets.clone();
1740 for t in targets_to_run {
1741 copy.insert(*t);
1742 }
1743 Some(copy)
1744 }
1745 _ => Some(targets_to_run.clone()),
1746 }
1747 } else {
1748 None
1749 };
1750 processed.insert(current_path.clone(), new_processed_state);
1751
1752 let current_data_path =
1754 path_utils::schema_path_to_data_pointer(¤t_path).into_owned();
1755 let mut current_value = eval_data
1756 .data()
1757 .pointer(¤t_data_path)
1758 .cloned()
1759 .unwrap_or(Value::Null);
1760
1761 if let Some(formula_sources) = dep_formula_triggers.get(¤t_data_path) {
1766 let mut targets_by_source: std::collections::HashMap<String, Vec<usize>> =
1767 std::collections::HashMap::new();
1768 for (source_schema_path, dep_idx) in formula_sources {
1769 let source_ptr = path_utils::dot_notation_to_schema_pointer(source_schema_path);
1770 targets_by_source
1771 .entry(source_ptr)
1772 .or_default()
1773 .push(*dep_idx);
1774 }
1775 for (source_ptr, targets) in targets_by_source {
1776 if let Some(None) = processed.get(&source_ptr) {
1778 continue;
1779 }
1780 queue.push((source_ptr, true, Some(targets)));
1781 }
1782 }
1783
1784 if let Some(dependent_items) = dependents_evaluations.get(¤t_path) {
1786 for (dep_idx, dep_item) in dependent_items.iter().enumerate() {
1787 if let Some(targets) = &indices_to_run {
1788 if !targets.contains(&dep_idx) {
1789 continue;
1790 }
1791 }
1792 let ref_path = &dep_item.ref_path;
1793 let pointer_path = path_utils::normalize_to_json_pointer(ref_path);
1794 let data_path =
1796 crate::jsoneval::path_utils::schema_path_to_data_pointer(&pointer_path)
1797 .into_owned();
1798
1799 let current_ref_value = eval_data
1800 .data()
1801 .pointer(&data_path)
1802 .cloned()
1803 .unwrap_or(Value::Null);
1804
1805 let field = evaluated_schema.pointer(&pointer_path).cloned();
1807
1808 let parent_path = if let Some(last_slash) = pointer_path.rfind("/properties") {
1810 &pointer_path[..last_slash]
1811 } else {
1812 "/"
1813 };
1814 let mut parent_field = if parent_path.is_empty() || parent_path == "/" {
1815 evaluated_schema.clone()
1816 } else {
1817 evaluated_schema
1818 .pointer(parent_path)
1819 .cloned()
1820 .unwrap_or_else(|| Value::Object(serde_json::Map::new()))
1821 };
1822
1823 if let Value::Object(ref mut map) = parent_field {
1825 map.remove("properties");
1826 map.remove("$layout");
1827 }
1828
1829 let mut change_obj = serde_json::Map::new();
1830 change_obj.insert(
1831 "$ref".to_string(),
1832 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
1833 );
1834 if let Some(f) = field {
1835 change_obj.insert("$field".to_string(), f);
1836 }
1837 change_obj.insert("$parentField".to_string(), parent_field);
1838 change_obj.insert("transitive".to_string(), Value::Bool(is_transitive));
1839
1840 if processed.contains_key(ref_path) {
1845 continue;
1846 }
1847
1848 let mut add_transitive = false;
1849 let mut add_deps = false;
1850 if let Some(clear_val) = &dep_item.clear {
1852 let should_clear = Self::evaluate_dependent_value_static(
1853 engine,
1854 evaluations,
1855 eval_data,
1856 clear_val,
1857 ¤t_value,
1858 ¤t_ref_value,
1859 )?;
1860 let clear_bool = match should_clear {
1861 Value::Bool(b) => b,
1862 _ => false,
1863 };
1864
1865 if clear_bool {
1866 if data_path == current_data_path {
1867 current_value = Value::Null;
1868 }
1869 eval_data.set(&data_path, Value::Null);
1870 eval_cache.bump_data_version(&data_path);
1871 change_obj.insert("clear".to_string(), Value::Bool(true));
1872 add_transitive = true;
1873 add_deps = true;
1874 }
1875 }
1876
1877 if let Some(value_val) = &dep_item.value {
1879 let computed_value = Self::evaluate_dependent_value_static(
1880 engine,
1881 evaluations,
1882 eval_data,
1883 value_val,
1884 ¤t_value,
1885 ¤t_ref_value,
1886 )?;
1887 let cleaned_val = clean_float_noise_scalar(computed_value);
1888
1889 let is_clear =
1890 cleaned_val == Value::Null || cleaned_val.as_str() == Some("");
1891
1892 if cleaned_val != current_ref_value && !is_clear {
1893 if data_path == current_data_path {
1894 current_value = cleaned_val.clone();
1895 }
1896 eval_data.set(&data_path, cleaned_val.clone());
1897 eval_cache.bump_data_version(&data_path);
1898 change_obj.insert("value".to_string(), cleaned_val);
1899 add_transitive = true;
1900 add_deps = true;
1901 }
1902 }
1903
1904 if add_deps {
1906 result.push(Value::Object(change_obj));
1907 }
1908
1909 if add_transitive {
1911 queue.push((ref_path.clone(), true, None));
1912 }
1913 }
1914 }
1915 }
1916 Ok(())
1917 }
1918}
1919
1920fn subform_field_key(subform_path: &str) -> String {
1927 let stripped = subform_path.trim_start_matches('#').trim_start_matches('/');
1929
1930 stripped
1932 .split('/')
1933 .filter(|seg| !seg.is_empty() && *seg != "properties")
1934 .last()
1935 .unwrap_or(stripped)
1936 .to_string()
1937}