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 merged_data = {
801 let parent = self.eval_data.data();
802 let mut map = serde_json::Map::new();
803 if let Value::Object(parent_map) = parent {
804 for (k, v) in parent_map {
805 if k == &field_key {
806 continue;
808 }
809 if !v.is_array() {
812 map.insert(k.clone(), v.clone());
813 }
814 }
815 }
816 map.insert(field_key.clone(), item_val.clone());
817 Value::Object(map)
818 };
819
820 let parent_dependency_paths: Vec<String> = parent_changed_paths
825 .iter()
826 .map(|path| {
827 path_utils::dot_notation_to_schema_pointer(path)
828 .trim_start_matches('#')
829 .to_string()
830 })
831 .collect();
832 let dependent_value_paths: Vec<String> = self
833 .subforms
834 .get(&subform_path)
835 .map(|subform| {
836 subform
837 .dependencies
838 .iter()
839 .filter(|(key, deps)| {
840 !subform.table_metadata.contains_key(*key)
841 && !key.starts_with("#/$params/")
842 && key.ends_with("/value")
843 && parent_dependency_paths
844 .iter()
845 .any(|dependency| deps.contains(dependency))
846 })
847 .map(|(key, _)| key.clone())
848 .collect()
849 })
850 .unwrap_or_default();
851
852 if item_changed_paths.is_empty() && !dependent_value_paths.is_empty() {
853 let parent_cache = std::mem::take(&mut self.eval_cache);
858 let mut overlay_cache = parent_cache.clone();
859 overlay_cache.ensure_active_item_cache(idx);
860 if let Some(item_cache) = overlay_cache.subform_caches.get_mut(&idx) {
861 item_cache
865 .data_versions
866 .merge_from(&parent_data_versions_snapshot);
867 item_cache
868 .data_versions
869 .merge_from_params(&parent_params_versions_snapshot);
870 }
871 overlay_cache.set_active_item(idx);
872 let subform = self
873 .subforms
874 .get_mut(&subform_path)
875 .expect("subform exists");
876 subform.eval_data.replace_data_and_context(
877 merged_data,
878 self.eval_data
879 .data()
880 .get("$context")
881 .cloned()
882 .unwrap_or(Value::Null),
883 );
884 std::mem::swap(&mut subform.eval_cache, &mut overlay_cache);
885 subform.evaluate_internal(Some(&dependent_value_paths), token)?;
886
887 let mut overlay_result = Vec::new();
888 let mut overlay_queue = Vec::new();
889 let mut overlay_processed = std::collections::HashMap::new();
890 for formula_path in &dependent_value_paths {
891 let schema_path = path_utils::normalize_to_json_pointer(formula_path);
892 let data_path = path_utils::schema_path_to_data_pointer(formula_path)
893 .replace("/value", "");
894 let Some(value) = subform.evaluated_schema.pointer(&schema_path).cloned()
895 else {
896 continue;
897 };
898
899 let source_path = formula_path.trim_end_matches("/value").to_string();
903 subform.eval_data.set(&data_path, value.clone());
904 overlay_queue.push((source_path, true, None));
905
906 let mut change = serde_json::Map::new();
907 let field = data_path
908 .trim_start_matches('/')
909 .trim_end_matches("/value")
910 .replace('/', ".");
911 let field = field.strip_prefix(&field_prefix).unwrap_or(&field);
912 change.insert(
913 "$ref".to_string(),
914 Value::String(format!("{}.{}.{}", subform_dot_path, idx, field)),
915 );
916 if value == Value::Null || value.as_str() == Some("") {
917 change.insert("clear".to_string(), Value::Bool(true));
918 } else {
919 change.insert("value".to_string(), value);
920 }
921 result.push(Value::Object(change));
922 }
923
924 Self::process_dependents_queue(
925 &subform.engine,
926 &subform.evaluations,
927 &mut subform.eval_data,
928 &mut subform.eval_cache,
929 &subform.dependents_evaluations,
930 &subform.dep_formula_triggers,
931 &subform.evaluated_schema,
932 &mut overlay_queue,
933 &mut overlay_processed,
934 &mut overlay_result,
935 token,
936 None,
937 )?;
938
939 for change in overlay_result {
940 let Some(object) = change.as_object() else {
941 continue;
942 };
943 let Some(Value::String(ref_path)) = object.get("$ref") else {
944 continue;
945 };
946 let local_ref = ref_path.strip_prefix(&field_prefix).unwrap_or(ref_path);
947 let mut mapped = object.clone();
948 mapped.insert(
949 "$ref".to_string(),
950 Value::String(format!("{}.{}.{}", subform_dot_path, idx, local_ref)),
951 );
952 result.push(Value::Object(mapped));
953 }
954
955 std::mem::swap(&mut subform.eval_cache, &mut overlay_cache);
958 self.eval_cache = parent_cache;
959 continue;
960 }
961
962 let Some(subform) = self.subforms.get_mut(&subform_path) else {
963 continue;
964 };
965
966 let sub_re_evaluate = !item_changed_paths.is_empty();
971 if !sub_re_evaluate && item_changed_paths.is_empty() {
972 continue;
973 }
974
975 self.eval_cache.ensure_active_item_cache(idx);
977 let old_item_val = {
978 let snapshot = self
979 .eval_cache
980 .subform_caches
981 .get(&idx)
982 .map(|c| c.item_snapshot.clone())
983 .unwrap_or(Value::Null);
984
985 if snapshot == Value::Null {
986 if let Some(main_snap) = &self.eval_cache.main_form_snapshot {
987 get_value_by_pointer_without_properties(main_snap, &subform_ptr)
988 .and_then(|v| v.as_array())
989 .and_then(|a| a.get(idx))
990 .cloned()
991 .unwrap_or(Value::Null)
992 } else {
993 Value::Null
994 }
995 } else {
996 snapshot
997 }
998 };
999
1000 subform.eval_data.replace_data_and_context(
1001 merged_data,
1002 self.eval_data
1003 .data()
1004 .get("$context")
1005 .cloned()
1006 .unwrap_or(Value::Null),
1007 );
1008 let new_item_val = subform
1009 .eval_data
1010 .data()
1011 .get(&field_key)
1012 .cloned()
1013 .unwrap_or(Value::Null);
1014
1015 let mut parent_cache = std::mem::take(&mut self.eval_cache);
1017 parent_cache.ensure_active_item_cache(idx);
1018
1019 let pre_diff_item_versions = parent_cache
1022 .subform_caches
1023 .get(&idx)
1024 .map(|c| c.data_versions.clone());
1025
1026 if let Some(c) = parent_cache.subform_caches.get_mut(&idx) {
1027 c.data_versions.merge_from(&parent_data_versions_snapshot);
1031 c.data_versions
1033 .merge_from_params(&parent_params_versions_snapshot);
1034 crate::jsoneval::eval_cache::diff_and_update_versions(
1035 &mut c.data_versions,
1036 &format!("/{}", field_key),
1037 &old_item_val,
1038 &new_item_val,
1039 "run_subform_pass_diff_and_update_versions",
1040 );
1041 c.item_snapshot = new_item_val;
1042 }
1043
1044 if let (Some(ref pre), Some(c)) = (
1048 &pre_diff_item_versions,
1049 parent_cache.subform_caches.get(&idx),
1050 ) {
1051 let field_prefix_slash = format!("/{}/", field_key);
1052 let newly_bumped: Vec<String> = c
1053 .data_versions
1054 .versions()
1055 .filter(|(k, &v)| k.starts_with(&field_prefix_slash) && v > pre.get(k))
1056 .map(|(k, _)| k.to_string())
1057 .collect();
1058 if !newly_bumped.is_empty() {
1059 for k in newly_bumped {
1060 parent_cache
1061 .data_versions
1062 .bump(&k, "propagate_newly_bumped");
1063 }
1064 parent_cache.eval_generation += 1;
1065 }
1066 }
1067
1068 {
1074 let field_prefix_slash = format!("/{}/", field_key);
1075 let newly_bumped_schema_paths: Vec<String> = if let (Some(ref pre), Some(c)) = (
1076 &pre_diff_item_versions,
1077 parent_cache.subform_caches.get(&idx),
1078 ) {
1079 c.data_versions
1080 .versions()
1081 .filter(|(k, &v)| k.starts_with(&field_prefix_slash) && v > pre.get(k))
1082 .map(|(k, _)| {
1083 let sub = k.trim_start_matches(&field_prefix_slash);
1087 format!(
1088 "/{}/properties/{}",
1089 field_key,
1090 sub.replace('/', "/properties/")
1091 )
1092 })
1093 .collect()
1094 } else {
1095 Vec::new()
1096 };
1097
1098 if !newly_bumped_schema_paths.is_empty() {
1099 let params_table_keys: Vec<String> = self
1100 .table_metadata
1101 .keys()
1102 .filter(|k| k.starts_with("#/$params"))
1103 .filter(|k| {
1104 self.dependencies
1105 .get(*k)
1106 .map(|deps| {
1107 deps.iter().any(|dep| {
1108 newly_bumped_schema_paths
1109 .iter()
1110 .any(|b| dep == b || dep.starts_with(b.as_str()))
1111 })
1112 })
1113 .unwrap_or(false)
1114 })
1115 .cloned()
1116 .collect();
1117
1118 if !params_table_keys.is_empty() {
1119 parent_cache.invalidate_params_tables_for_item(idx, ¶ms_table_keys);
1120 any_table_invalidated = true;
1121 }
1122 }
1123 }
1124
1125 parent_cache.set_active_item(idx);
1126 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
1127
1128 let subform_result = time_block!(" [subform_pass] rider evaluate_dependents", {
1129 subform.evaluate_dependents(
1130 &item_changed_paths,
1131 None,
1132 None,
1133 sub_re_evaluate,
1134 token,
1135 None,
1136 false,
1137 )
1138 });
1139
1140 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
1142 parent_cache.clear_active_item();
1143
1144 if let Some(parent_item_cache) = self.eval_cache.subform_caches.get(&idx) {
1149 let snapshot = parent_item_cache.item_snapshot.clone();
1150 subform.eval_cache.ensure_active_item_cache(idx);
1151 if let Some(sub_cache) = subform.eval_cache.subform_caches.get_mut(&idx) {
1152 sub_cache.item_snapshot = snapshot;
1153 }
1154 }
1155
1156 self.eval_cache = parent_cache;
1157
1158 if let Ok(Value::Array(changes)) = subform_result {
1159 let mut had_any_change = false;
1160 for change in changes {
1161 if let Some(obj) = change.as_object() {
1162 if let Some(Value::String(ref_path)) = obj.get("$ref") {
1163 let new_ref = if ref_path.starts_with(&field_prefix) {
1165 format!(
1166 "{}.{}.{}",
1167 subform_dot_path,
1168 idx,
1169 &ref_path[field_prefix.len()..]
1170 )
1171 } else {
1172 format!("{}.{}.{}", subform_dot_path, idx, ref_path)
1173 };
1174
1175 if let Some(val) = obj.get("value") {
1181 let data_ptr = format!("/{}", new_ref.replace('.', "/"));
1182 self.eval_data.set(&data_ptr, val.clone());
1183 had_any_change = true;
1184 } else if obj.get("clear").and_then(Value::as_bool) == Some(true) {
1185 let data_ptr = format!("/{}", new_ref.replace('.', "/"));
1186 self.eval_data.set(&data_ptr, Value::Null);
1187 had_any_change = true;
1188 }
1189
1190 let mut new_obj = obj.clone();
1191 new_obj.insert("$ref".to_string(), Value::String(new_ref));
1192 result.push(Value::Object(new_obj));
1193 } else {
1194 result.push(change);
1196 }
1197 }
1198 }
1199
1200 if had_any_change {
1208 let item_path = format!("{}/{}", subform_ptr, idx);
1209 let updated_item = self
1210 .eval_data
1211 .get(&item_path)
1212 .cloned()
1213 .unwrap_or(Value::Null);
1214 if let Some(c) = self.eval_cache.subform_caches.get_mut(&idx) {
1216 c.item_snapshot = updated_item.clone();
1217 }
1218 subform.eval_cache.ensure_active_item_cache(idx);
1221 if let Some(sub_cache) = subform.eval_cache.subform_caches.get_mut(&idx) {
1222 sub_cache.item_snapshot = updated_item;
1223 }
1224 }
1225 }
1226 }
1227 }
1228 Ok(any_table_invalidated)
1229 }
1230
1231 pub(crate) fn evaluate_dependent_value_static(
1233 engine: &RLogic,
1234 evaluations: &IndexMap<String, LogicId>,
1235 eval_data: &EvalData,
1236 value: &Value,
1237 changed_field_value: &Value,
1238 changed_field_ref_value: &Value,
1239 ) -> Result<Value, String> {
1240 match value {
1241 Value::String(eval_key) => {
1243 if let Some(logic_id) = evaluations.get(eval_key) {
1244 let mut internal_context = serde_json::Map::new();
1247 internal_context.insert("$value".to_string(), changed_field_value.clone());
1248 internal_context.insert("$refValue".to_string(), changed_field_ref_value.clone());
1249 let context_value = Value::Object(internal_context);
1250
1251 let result = engine.run_with_context(logic_id, eval_data.data(), &context_value)
1252 .map_err(|e| format!("Failed to evaluate dependent logic '{}': {}", eval_key, e))?;
1253 Ok(result)
1254 } else {
1255 Ok(value.clone())
1257 }
1258 }
1259 Value::Object(map) if map.contains_key("$evaluation") => {
1262 Err("Dependent evaluation contains unparsed $evaluation - schema was not properly parsed".to_string())
1263 }
1264 _ => Ok(value.clone()),
1266 }
1267 }
1268
1269 pub(crate) fn check_readonly_for_dependents(
1271 &self,
1272 schema_element: &Value,
1273 path: &str,
1274 changes: &mut Vec<(String, Value)>,
1275 all_values: &mut Vec<(String, Value)>,
1276 ) {
1277 match schema_element {
1278 Value::Object(map) => {
1279 let mut is_disabled = false;
1281 if let Some(Value::Object(condition)) = map.get("condition") {
1282 if let Some(Value::Bool(d)) = condition.get("disabled") {
1283 is_disabled = *d;
1284 }
1285 }
1286
1287 let mut skip_readonly = false;
1289 if let Some(Value::Object(config)) = map.get("config") {
1290 if let Some(Value::Object(all)) = config.get("all") {
1291 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
1292 skip_readonly = *skip;
1293 }
1294 }
1295 }
1296
1297 if is_disabled && !skip_readonly {
1298 if let Some(schema_value) = map.get("value") {
1299 let data_path = path_utils::schema_path_to_data_pointer(path)
1300 .replace("/value/", "/");
1303
1304 let current_data = self
1305 .eval_data
1306 .data()
1307 .pointer(&data_path)
1308 .unwrap_or(&Value::Null);
1309
1310 all_values.push((path.to_string(), schema_value.clone()));
1313 if current_data != schema_value {
1314 changes.push((path.to_string(), schema_value.clone()));
1315 }
1316 }
1317 }
1318 }
1319 _ => {}
1320 }
1321 }
1322
1323 #[allow(dead_code)]
1325 pub(crate) fn collect_readonly_fixes(
1326 &self,
1327 schema_element: &Value,
1328 path: &str,
1329 changes: &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") {
1356 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1357
1358 let current_data = self
1359 .eval_data
1360 .data()
1361 .pointer(&data_path)
1362 .unwrap_or(&Value::Null);
1363
1364 if current_data != schema_value {
1365 changes.push((path.to_string(), schema_value.clone()));
1366 }
1367 }
1368 }
1369
1370 if let Some(Value::Object(props)) = map.get("properties") {
1372 for (key, val) in props {
1373 let next_path = if path == "#" {
1374 format!("#/properties/{}", key)
1375 } else {
1376 format!("{}/properties/{}", path, key)
1377 };
1378 self.collect_readonly_fixes(val, &next_path, changes);
1379 }
1380 }
1381 }
1382 _ => {}
1383 }
1384 }
1385
1386 pub(crate) fn check_hidden_field(
1388 &self,
1389 schema_element: &Value,
1390 path: &str,
1391 hidden_fields: &mut Vec<String>,
1392 ) {
1393 match schema_element {
1394 Value::Object(map) => {
1395 let mut is_hidden = false;
1397 if let Some(Value::Object(condition)) = map.get("condition") {
1398 if let Some(Value::Bool(h)) = condition.get("hidden") {
1399 is_hidden = *h;
1400 }
1401 }
1402
1403 let mut keep_hidden = false;
1405 if let Some(Value::Object(config)) = map.get("config") {
1406 if let Some(Value::Object(all)) = config.get("all") {
1407 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
1408 keep_hidden = *keep;
1409 }
1410 }
1411 }
1412
1413 if is_hidden && !keep_hidden {
1414 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1415
1416 let current_data = self
1417 .eval_data
1418 .data()
1419 .pointer(&data_path)
1420 .unwrap_or(&Value::Null);
1421
1422 if current_data != &Value::Null && current_data != "" {
1424 hidden_fields.push(path.to_string());
1425 }
1426 }
1427 }
1428 _ => {}
1429 }
1430 }
1431
1432 fn check_effectively_hidden_field(
1434 &self,
1435 schema_element: &Value,
1436 path: &str,
1437 hidden_fields: &mut Vec<String>,
1438 ) {
1439 let Value::Object(map) = schema_element else {
1440 return;
1441 };
1442
1443 let keep_hidden = map
1444 .get("config")
1445 .and_then(Value::as_object)
1446 .and_then(|config| config.get("all"))
1447 .and_then(Value::as_object)
1448 .and_then(|all| all.get("keepHiddenValue"))
1449 .and_then(Value::as_bool)
1450 .unwrap_or(false);
1451 if keep_hidden {
1452 return;
1453 }
1454
1455 let current_data = self
1456 .eval_data
1457 .data()
1458 .pointer(&path_utils::schema_path_to_data_pointer(path))
1459 .unwrap_or(&Value::Null);
1460 if current_data != &Value::Null && current_data != "" {
1461 hidden_fields.push(path.to_string());
1462 }
1463 }
1464
1465 #[allow(dead_code)]
1467 pub(crate) fn collect_hidden_fields(
1468 &self,
1469 schema_element: &Value,
1470 path: &str,
1471 hidden_fields: &mut Vec<String>,
1472 ) {
1473 match schema_element {
1474 Value::Object(map) => {
1475 let mut is_hidden = false;
1477 if let Some(Value::Object(condition)) = map.get("condition") {
1478 if let Some(Value::Bool(h)) = condition.get("hidden") {
1479 is_hidden = *h;
1480 }
1481 }
1482
1483 let mut keep_hidden = false;
1485 if let Some(Value::Object(config)) = map.get("config") {
1486 if let Some(Value::Object(all)) = config.get("all") {
1487 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
1488 keep_hidden = *keep;
1489 }
1490 }
1491 }
1492
1493 if is_hidden && !keep_hidden {
1494 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1495
1496 let current_data = self
1497 .eval_data
1498 .data()
1499 .pointer(&data_path)
1500 .unwrap_or(&Value::Null);
1501
1502 if current_data != &Value::Null && current_data != "" {
1504 hidden_fields.push(path.to_string());
1505 }
1506 }
1507
1508 for (key, val) in map {
1510 if key == "properties" {
1511 if let Value::Object(props) = val {
1512 for (p_key, p_val) in props {
1513 let next_path = if path == "#" {
1514 format!("#/properties/{}", p_key)
1515 } else {
1516 format!("{}/properties/{}", path, p_key)
1517 };
1518 self.collect_hidden_fields(p_val, &next_path, hidden_fields);
1519 }
1520 }
1521 } else if let Value::Object(_) = val {
1522 if key == "condition"
1524 || key == "config"
1525 || key == "rules"
1526 || key == "dependents"
1527 || key == "hideLayout"
1528 || key == "$layout"
1529 || key == "$params"
1530 || key == "definitions"
1531 || key == "$defs"
1532 || key.starts_with('$')
1533 {
1534 continue;
1535 }
1536
1537 let next_path = if path == "#" {
1538 format!("#/{}", key)
1539 } else {
1540 format!("{}/{}", path, key)
1541 };
1542 self.collect_hidden_fields(val, &next_path, hidden_fields);
1543 }
1544 }
1545 }
1546 _ => {}
1547 }
1548 }
1549
1550 pub(crate) fn recursive_hide_effect(
1553 engine: &RLogic,
1554 evaluations: &IndexMap<String, LogicId>,
1555 reffed_by: &IndexMap<String, Vec<String>>,
1556 eval_data: &mut EvalData,
1557 eval_cache: &mut crate::jsoneval::eval_cache::EvalCache,
1558 mut hidden_fields: Vec<String>,
1559 queue: &mut Vec<(String, bool, Option<Vec<usize>>)>,
1560 result: &mut Vec<Value>,
1561 ) {
1562 while let Some(hf) = hidden_fields.pop() {
1563 let data_path = path_utils::schema_path_to_data_pointer(&hf).into_owned();
1564
1565 eval_data.set(&data_path, Value::Null);
1567 eval_cache.bump_data_version(&data_path);
1568
1569 let mut change_obj = serde_json::Map::new();
1571 change_obj.insert(
1572 "$ref".to_string(),
1573 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
1574 );
1575 change_obj.insert("$hidden".to_string(), Value::Bool(true));
1576 change_obj.insert("clear".to_string(), Value::Bool(true));
1577 result.push(Value::Object(change_obj));
1578
1579 queue.push((hf.clone(), true, None));
1581
1582 if let Some(referencing_fields) = reffed_by.get(&data_path) {
1584 for rb in referencing_fields {
1585 let hidden_eval_key = format!("{}/condition/hidden", rb);
1589
1590 if let Some(logic_id) = evaluations.get(&hidden_eval_key) {
1591 let rb_data_path = path_utils::schema_path_to_data_pointer(rb).into_owned();
1598 let rb_value = eval_data
1599 .data()
1600 .pointer(&rb_data_path)
1601 .cloned()
1602 .unwrap_or(Value::Null);
1603
1604 if let Ok(Value::Bool(is_hidden)) = engine.run(logic_id, eval_data.data()) {
1606 if is_hidden {
1607 if !hidden_fields.contains(rb) {
1610 let has_value = rb_value != Value::Null && rb_value != "";
1611 if has_value {
1612 hidden_fields.push(rb.clone());
1613 }
1614 }
1615 }
1616 }
1617 }
1618 }
1619 }
1620 }
1621 }
1622
1623 pub(crate) fn process_dependents_queue(
1626 engine: &RLogic,
1627 evaluations: &IndexMap<String, LogicId>,
1628 eval_data: &mut EvalData,
1629 eval_cache: &mut crate::jsoneval::eval_cache::EvalCache,
1630 dependents_evaluations: &IndexMap<String, Vec<DependentItem>>,
1631 dep_formula_triggers: &IndexMap<String, Vec<(String, usize)>>,
1632 evaluated_schema: &Value,
1633 queue: &mut Vec<(String, bool, Option<Vec<usize>>)>,
1634 processed: &mut std::collections::HashMap<String, Option<std::collections::HashSet<usize>>>,
1635 result: &mut Vec<Value>,
1636 token: Option<&CancellationToken>,
1637 canceled_paths: Option<&mut Vec<String>>,
1638 ) -> Result<(), String> {
1639 while let Some((current_path, is_transitive, target_indices)) = queue.pop() {
1640 if let Some(t) = token {
1641 if t.is_cancelled() {
1642 if let Some(cp) = canceled_paths {
1643 cp.push(current_path.clone());
1644 for (path, _, _) in queue.iter() {
1645 cp.push(path.clone());
1646 }
1647 }
1648 return Err("Cancelled".to_string());
1649 }
1650 }
1651
1652 let (should_run, indices_to_run) = match processed.get(¤t_path) {
1653 Some(None) => {
1654 continue;
1656 }
1657 Some(Some(already_processed_indices)) => {
1658 if let Some(targets) = &target_indices {
1659 let new_targets: std::collections::HashSet<usize> = targets
1660 .iter()
1661 .copied()
1662 .filter(|i| !already_processed_indices.contains(i))
1663 .collect();
1664 if new_targets.is_empty() {
1665 continue;
1666 }
1667 (true, Some(new_targets))
1668 } else {
1669 (true, None)
1670 }
1671 }
1672 None => (
1673 true,
1674 target_indices.clone().map(|t| t.into_iter().collect()),
1675 ),
1676 };
1677
1678 if !should_run {
1679 continue;
1680 }
1681
1682 let new_processed_state = if let Some(targets_to_run) = &indices_to_run {
1683 match processed.get(¤t_path) {
1684 Some(Some(existing_targets)) => {
1685 let mut copy = existing_targets.clone();
1686 for t in targets_to_run {
1687 copy.insert(*t);
1688 }
1689 Some(copy)
1690 }
1691 _ => Some(targets_to_run.clone()),
1692 }
1693 } else {
1694 None
1695 };
1696 processed.insert(current_path.clone(), new_processed_state);
1697
1698 let current_data_path =
1700 path_utils::schema_path_to_data_pointer(¤t_path).into_owned();
1701 let mut current_value = eval_data
1702 .data()
1703 .pointer(¤t_data_path)
1704 .cloned()
1705 .unwrap_or(Value::Null);
1706
1707 if let Some(formula_sources) = dep_formula_triggers.get(¤t_data_path) {
1712 let mut targets_by_source: std::collections::HashMap<String, Vec<usize>> =
1713 std::collections::HashMap::new();
1714 for (source_schema_path, dep_idx) in formula_sources {
1715 let source_ptr = path_utils::dot_notation_to_schema_pointer(source_schema_path);
1716 targets_by_source
1717 .entry(source_ptr)
1718 .or_default()
1719 .push(*dep_idx);
1720 }
1721 for (source_ptr, targets) in targets_by_source {
1722 if let Some(None) = processed.get(&source_ptr) {
1724 continue;
1725 }
1726 queue.push((source_ptr, true, Some(targets)));
1727 }
1728 }
1729
1730 if let Some(dependent_items) = dependents_evaluations.get(¤t_path) {
1732 for (dep_idx, dep_item) in dependent_items.iter().enumerate() {
1733 if let Some(targets) = &indices_to_run {
1734 if !targets.contains(&dep_idx) {
1735 continue;
1736 }
1737 }
1738 let ref_path = &dep_item.ref_path;
1739 let pointer_path = path_utils::normalize_to_json_pointer(ref_path);
1740 let data_path =
1742 crate::jsoneval::path_utils::schema_path_to_data_pointer(&pointer_path)
1743 .into_owned();
1744
1745 let current_ref_value = eval_data
1746 .data()
1747 .pointer(&data_path)
1748 .cloned()
1749 .unwrap_or(Value::Null);
1750
1751 let field = evaluated_schema.pointer(&pointer_path).cloned();
1753
1754 let parent_path = if let Some(last_slash) = pointer_path.rfind("/properties") {
1756 &pointer_path[..last_slash]
1757 } else {
1758 "/"
1759 };
1760 let mut parent_field = if parent_path.is_empty() || parent_path == "/" {
1761 evaluated_schema.clone()
1762 } else {
1763 evaluated_schema
1764 .pointer(parent_path)
1765 .cloned()
1766 .unwrap_or_else(|| Value::Object(serde_json::Map::new()))
1767 };
1768
1769 if let Value::Object(ref mut map) = parent_field {
1771 map.remove("properties");
1772 map.remove("$layout");
1773 }
1774
1775 let mut change_obj = serde_json::Map::new();
1776 change_obj.insert(
1777 "$ref".to_string(),
1778 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
1779 );
1780 if let Some(f) = field {
1781 change_obj.insert("$field".to_string(), f);
1782 }
1783 change_obj.insert("$parentField".to_string(), parent_field);
1784 change_obj.insert("transitive".to_string(), Value::Bool(is_transitive));
1785
1786 if processed.contains_key(ref_path) {
1791 continue;
1792 }
1793
1794 let mut add_transitive = false;
1795 let mut add_deps = false;
1796 if let Some(clear_val) = &dep_item.clear {
1798 let should_clear = Self::evaluate_dependent_value_static(
1799 engine,
1800 evaluations,
1801 eval_data,
1802 clear_val,
1803 ¤t_value,
1804 ¤t_ref_value,
1805 )?;
1806 let clear_bool = match should_clear {
1807 Value::Bool(b) => b,
1808 _ => false,
1809 };
1810
1811 if clear_bool {
1812 if data_path == current_data_path {
1813 current_value = Value::Null;
1814 }
1815 eval_data.set(&data_path, Value::Null);
1816 eval_cache.bump_data_version(&data_path);
1817 change_obj.insert("clear".to_string(), Value::Bool(true));
1818 add_transitive = true;
1819 add_deps = true;
1820 }
1821 }
1822
1823 if let Some(value_val) = &dep_item.value {
1825 let computed_value = Self::evaluate_dependent_value_static(
1826 engine,
1827 evaluations,
1828 eval_data,
1829 value_val,
1830 ¤t_value,
1831 ¤t_ref_value,
1832 )?;
1833 let cleaned_val = clean_float_noise_scalar(computed_value);
1834
1835 let is_clear =
1836 cleaned_val == Value::Null || cleaned_val.as_str() == Some("");
1837
1838 if cleaned_val != current_ref_value && !is_clear {
1839 if data_path == current_data_path {
1840 current_value = cleaned_val.clone();
1841 }
1842 eval_data.set(&data_path, cleaned_val.clone());
1843 eval_cache.bump_data_version(&data_path);
1844 change_obj.insert("value".to_string(), cleaned_val);
1845 add_transitive = true;
1846 add_deps = true;
1847 }
1848 }
1849
1850 if add_deps {
1852 result.push(Value::Object(change_obj));
1853 }
1854
1855 if add_transitive {
1857 queue.push((ref_path.clone(), true, None));
1858 }
1859 }
1860 }
1861 }
1862 Ok(())
1863 }
1864}
1865
1866fn subform_field_key(subform_path: &str) -> String {
1873 let stripped = subform_path.trim_start_matches('#').trim_start_matches('/');
1875
1876 stripped
1878 .split('/')
1879 .filter(|seg| !seg.is_empty() && *seg != "properties")
1880 .last()
1881 .unwrap_or(stripped)
1882 .to_string()
1883}