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 mut parent_cache = std::mem::take(&mut self.eval_cache);
854 parent_cache.set_active_item(idx);
855 let subform = self
856 .subforms
857 .get_mut(&subform_path)
858 .expect("subform exists");
859 subform.eval_data.replace_data_and_context(
860 merged_data,
861 self.eval_data
862 .data()
863 .get("$context")
864 .cloned()
865 .unwrap_or(Value::Null),
866 );
867 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
868 subform.evaluate_internal(Some(&dependent_value_paths), token)?;
869
870 for formula_path in &dependent_value_paths {
871 let schema_path = path_utils::normalize_to_json_pointer(formula_path);
872 let data_path = path_utils::schema_path_to_data_pointer(formula_path);
873 let Some(value) = subform.evaluated_schema.pointer(&schema_path).cloned()
874 else {
875 continue;
876 };
877 let mut change = serde_json::Map::new();
878 let field = data_path
879 .trim_start_matches('/')
880 .trim_end_matches("/value")
881 .replace('/', ".");
882 let field = field.strip_prefix(&field_prefix).unwrap_or(&field);
883 change.insert(
884 "$ref".to_string(),
885 Value::String(format!("{}.{}.{}", subform_dot_path, idx, field)),
886 );
887 if value == Value::Null || value.as_str() == Some("") {
888 change.insert("clear".to_string(), Value::Bool(true));
889 } else {
890 change.insert("value".to_string(), value);
891 }
892 result.push(Value::Object(change));
893 }
894 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
895 parent_cache.clear_active_item();
896 self.eval_cache = parent_cache;
897 continue;
898 }
899
900 let Some(subform) = self.subforms.get_mut(&subform_path) else {
901 continue;
902 };
903
904 let sub_re_evaluate = !item_changed_paths.is_empty();
909 if !sub_re_evaluate && item_changed_paths.is_empty() {
910 continue;
911 }
912
913 self.eval_cache.ensure_active_item_cache(idx);
915 let old_item_val = {
916 let snapshot = self
917 .eval_cache
918 .subform_caches
919 .get(&idx)
920 .map(|c| c.item_snapshot.clone())
921 .unwrap_or(Value::Null);
922
923 if snapshot == Value::Null {
924 if let Some(main_snap) = &self.eval_cache.main_form_snapshot {
925 get_value_by_pointer_without_properties(main_snap, &subform_ptr)
926 .and_then(|v| v.as_array())
927 .and_then(|a| a.get(idx))
928 .cloned()
929 .unwrap_or(Value::Null)
930 } else {
931 Value::Null
932 }
933 } else {
934 snapshot
935 }
936 };
937
938 subform.eval_data.replace_data_and_context(
939 merged_data,
940 self.eval_data
941 .data()
942 .get("$context")
943 .cloned()
944 .unwrap_or(Value::Null),
945 );
946 let new_item_val = subform
947 .eval_data
948 .data()
949 .get(&field_key)
950 .cloned()
951 .unwrap_or(Value::Null);
952
953 let mut parent_cache = std::mem::take(&mut self.eval_cache);
955 parent_cache.ensure_active_item_cache(idx);
956
957 let pre_diff_item_versions = parent_cache
960 .subform_caches
961 .get(&idx)
962 .map(|c| c.data_versions.clone());
963
964 if let Some(c) = parent_cache.subform_caches.get_mut(&idx) {
965 c.data_versions.merge_from(&parent_data_versions_snapshot);
969 c.data_versions
971 .merge_from_params(&parent_params_versions_snapshot);
972 crate::jsoneval::eval_cache::diff_and_update_versions(
973 &mut c.data_versions,
974 &format!("/{}", field_key),
975 &old_item_val,
976 &new_item_val,
977 "run_subform_pass_diff_and_update_versions",
978 );
979 c.item_snapshot = new_item_val;
980 }
981
982 if let (Some(ref pre), Some(c)) = (
986 &pre_diff_item_versions,
987 parent_cache.subform_caches.get(&idx),
988 ) {
989 let field_prefix_slash = format!("/{}/", field_key);
990 let newly_bumped: Vec<String> = c
991 .data_versions
992 .versions()
993 .filter(|(k, &v)| k.starts_with(&field_prefix_slash) && v > pre.get(k))
994 .map(|(k, _)| k.to_string())
995 .collect();
996 if !newly_bumped.is_empty() {
997 for k in newly_bumped {
998 parent_cache
999 .data_versions
1000 .bump(&k, "propagate_newly_bumped");
1001 }
1002 parent_cache.eval_generation += 1;
1003 }
1004 }
1005
1006 {
1012 let field_prefix_slash = format!("/{}/", field_key);
1013 let newly_bumped_schema_paths: Vec<String> = if let (Some(ref pre), Some(c)) = (
1014 &pre_diff_item_versions,
1015 parent_cache.subform_caches.get(&idx),
1016 ) {
1017 c.data_versions
1018 .versions()
1019 .filter(|(k, &v)| k.starts_with(&field_prefix_slash) && v > pre.get(k))
1020 .map(|(k, _)| {
1021 let sub = k.trim_start_matches(&field_prefix_slash);
1025 format!(
1026 "/{}/properties/{}",
1027 field_key,
1028 sub.replace('/', "/properties/")
1029 )
1030 })
1031 .collect()
1032 } else {
1033 Vec::new()
1034 };
1035
1036 if !newly_bumped_schema_paths.is_empty() {
1037 let params_table_keys: Vec<String> = self
1038 .table_metadata
1039 .keys()
1040 .filter(|k| k.starts_with("#/$params"))
1041 .filter(|k| {
1042 self.dependencies
1043 .get(*k)
1044 .map(|deps| {
1045 deps.iter().any(|dep| {
1046 newly_bumped_schema_paths
1047 .iter()
1048 .any(|b| dep == b || dep.starts_with(b.as_str()))
1049 })
1050 })
1051 .unwrap_or(false)
1052 })
1053 .cloned()
1054 .collect();
1055
1056 if !params_table_keys.is_empty() {
1057 parent_cache.invalidate_params_tables_for_item(idx, ¶ms_table_keys);
1058 any_table_invalidated = true;
1059 }
1060 }
1061 }
1062
1063 parent_cache.set_active_item(idx);
1064 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
1065
1066 let subform_result = time_block!(" [subform_pass] rider evaluate_dependents", {
1067 subform.evaluate_dependents(
1068 &item_changed_paths,
1069 None,
1070 None,
1071 sub_re_evaluate,
1072 token,
1073 None,
1074 false,
1075 )
1076 });
1077
1078 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
1080 parent_cache.clear_active_item();
1081
1082 if let Some(parent_item_cache) = self.eval_cache.subform_caches.get(&idx) {
1087 let snapshot = parent_item_cache.item_snapshot.clone();
1088 subform.eval_cache.ensure_active_item_cache(idx);
1089 if let Some(sub_cache) = subform.eval_cache.subform_caches.get_mut(&idx) {
1090 sub_cache.item_snapshot = snapshot;
1091 }
1092 }
1093
1094 self.eval_cache = parent_cache;
1095
1096 if let Ok(Value::Array(changes)) = subform_result {
1097 let mut had_any_change = false;
1098 for change in changes {
1099 if let Some(obj) = change.as_object() {
1100 if let Some(Value::String(ref_path)) = obj.get("$ref") {
1101 let new_ref = if ref_path.starts_with(&field_prefix) {
1103 format!(
1104 "{}.{}.{}",
1105 subform_dot_path,
1106 idx,
1107 &ref_path[field_prefix.len()..]
1108 )
1109 } else {
1110 format!("{}.{}.{}", subform_dot_path, idx, ref_path)
1111 };
1112
1113 if let Some(val) = obj.get("value") {
1119 let data_ptr = format!("/{}", new_ref.replace('.', "/"));
1120 self.eval_data.set(&data_ptr, val.clone());
1121 had_any_change = true;
1122 } else if obj.get("clear").and_then(Value::as_bool) == Some(true) {
1123 let data_ptr = format!("/{}", new_ref.replace('.', "/"));
1124 self.eval_data.set(&data_ptr, Value::Null);
1125 had_any_change = true;
1126 }
1127
1128 let mut new_obj = obj.clone();
1129 new_obj.insert("$ref".to_string(), Value::String(new_ref));
1130 result.push(Value::Object(new_obj));
1131 } else {
1132 result.push(change);
1134 }
1135 }
1136 }
1137
1138 if had_any_change {
1146 let item_path = format!("{}/{}", subform_ptr, idx);
1147 let updated_item = self
1148 .eval_data
1149 .get(&item_path)
1150 .cloned()
1151 .unwrap_or(Value::Null);
1152 if let Some(c) = self.eval_cache.subform_caches.get_mut(&idx) {
1154 c.item_snapshot = updated_item.clone();
1155 }
1156 subform.eval_cache.ensure_active_item_cache(idx);
1159 if let Some(sub_cache) = subform.eval_cache.subform_caches.get_mut(&idx) {
1160 sub_cache.item_snapshot = updated_item;
1161 }
1162 }
1163 }
1164 }
1165 }
1166 Ok(any_table_invalidated)
1167 }
1168
1169 pub(crate) fn evaluate_dependent_value_static(
1171 engine: &RLogic,
1172 evaluations: &IndexMap<String, LogicId>,
1173 eval_data: &EvalData,
1174 value: &Value,
1175 changed_field_value: &Value,
1176 changed_field_ref_value: &Value,
1177 ) -> Result<Value, String> {
1178 match value {
1179 Value::String(eval_key) => {
1181 if let Some(logic_id) = evaluations.get(eval_key) {
1182 let mut internal_context = serde_json::Map::new();
1185 internal_context.insert("$value".to_string(), changed_field_value.clone());
1186 internal_context.insert("$refValue".to_string(), changed_field_ref_value.clone());
1187 let context_value = Value::Object(internal_context);
1188
1189 let result = engine.run_with_context(logic_id, eval_data.data(), &context_value)
1190 .map_err(|e| format!("Failed to evaluate dependent logic '{}': {}", eval_key, e))?;
1191 Ok(result)
1192 } else {
1193 Ok(value.clone())
1195 }
1196 }
1197 Value::Object(map) if map.contains_key("$evaluation") => {
1200 Err("Dependent evaluation contains unparsed $evaluation - schema was not properly parsed".to_string())
1201 }
1202 _ => Ok(value.clone()),
1204 }
1205 }
1206
1207 pub(crate) fn check_readonly_for_dependents(
1209 &self,
1210 schema_element: &Value,
1211 path: &str,
1212 changes: &mut Vec<(String, Value)>,
1213 all_values: &mut Vec<(String, Value)>,
1214 ) {
1215 match schema_element {
1216 Value::Object(map) => {
1217 let mut is_disabled = false;
1219 if let Some(Value::Object(condition)) = map.get("condition") {
1220 if let Some(Value::Bool(d)) = condition.get("disabled") {
1221 is_disabled = *d;
1222 }
1223 }
1224
1225 let mut skip_readonly = false;
1227 if let Some(Value::Object(config)) = map.get("config") {
1228 if let Some(Value::Object(all)) = config.get("all") {
1229 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
1230 skip_readonly = *skip;
1231 }
1232 }
1233 }
1234
1235 if is_disabled && !skip_readonly {
1236 if let Some(schema_value) = map.get("value") {
1237 let data_path = path_utils::schema_path_to_data_pointer(path)
1238 .replace("/value/", "/");
1241
1242 let current_data = self
1243 .eval_data
1244 .data()
1245 .pointer(&data_path)
1246 .unwrap_or(&Value::Null);
1247
1248 all_values.push((path.to_string(), schema_value.clone()));
1251 if current_data != schema_value {
1252 changes.push((path.to_string(), schema_value.clone()));
1253 }
1254 }
1255 }
1256 }
1257 _ => {}
1258 }
1259 }
1260
1261 #[allow(dead_code)]
1263 pub(crate) fn collect_readonly_fixes(
1264 &self,
1265 schema_element: &Value,
1266 path: &str,
1267 changes: &mut Vec<(String, Value)>,
1268 ) {
1269 match schema_element {
1270 Value::Object(map) => {
1271 let mut is_disabled = false;
1273 if let Some(Value::Object(condition)) = map.get("condition") {
1274 if let Some(Value::Bool(d)) = condition.get("disabled") {
1275 is_disabled = *d;
1276 }
1277 }
1278
1279 let mut skip_readonly = false;
1281 if let Some(Value::Object(config)) = map.get("config") {
1282 if let Some(Value::Object(all)) = config.get("all") {
1283 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
1284 skip_readonly = *skip;
1285 }
1286 }
1287 }
1288
1289 if is_disabled && !skip_readonly {
1290 if let Some(schema_value) = map.get("value") {
1294 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1295
1296 let current_data = self
1297 .eval_data
1298 .data()
1299 .pointer(&data_path)
1300 .unwrap_or(&Value::Null);
1301
1302 if current_data != schema_value {
1303 changes.push((path.to_string(), schema_value.clone()));
1304 }
1305 }
1306 }
1307
1308 if let Some(Value::Object(props)) = map.get("properties") {
1310 for (key, val) in props {
1311 let next_path = if path == "#" {
1312 format!("#/properties/{}", key)
1313 } else {
1314 format!("{}/properties/{}", path, key)
1315 };
1316 self.collect_readonly_fixes(val, &next_path, changes);
1317 }
1318 }
1319 }
1320 _ => {}
1321 }
1322 }
1323
1324 pub(crate) fn check_hidden_field(
1326 &self,
1327 schema_element: &Value,
1328 path: &str,
1329 hidden_fields: &mut Vec<String>,
1330 ) {
1331 match schema_element {
1332 Value::Object(map) => {
1333 let mut is_hidden = false;
1335 if let Some(Value::Object(condition)) = map.get("condition") {
1336 if let Some(Value::Bool(h)) = condition.get("hidden") {
1337 is_hidden = *h;
1338 }
1339 }
1340
1341 let mut keep_hidden = 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(keep)) = all.get("keepHiddenValue") {
1346 keep_hidden = *keep;
1347 }
1348 }
1349 }
1350
1351 if is_hidden && !keep_hidden {
1352 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1353
1354 let current_data = self
1355 .eval_data
1356 .data()
1357 .pointer(&data_path)
1358 .unwrap_or(&Value::Null);
1359
1360 if current_data != &Value::Null && current_data != "" {
1362 hidden_fields.push(path.to_string());
1363 }
1364 }
1365 }
1366 _ => {}
1367 }
1368 }
1369
1370 fn check_effectively_hidden_field(
1372 &self,
1373 schema_element: &Value,
1374 path: &str,
1375 hidden_fields: &mut Vec<String>,
1376 ) {
1377 let Value::Object(map) = schema_element else {
1378 return;
1379 };
1380
1381 let keep_hidden = map
1382 .get("config")
1383 .and_then(Value::as_object)
1384 .and_then(|config| config.get("all"))
1385 .and_then(Value::as_object)
1386 .and_then(|all| all.get("keepHiddenValue"))
1387 .and_then(Value::as_bool)
1388 .unwrap_or(false);
1389 if keep_hidden {
1390 return;
1391 }
1392
1393 let current_data = self
1394 .eval_data
1395 .data()
1396 .pointer(&path_utils::schema_path_to_data_pointer(path))
1397 .unwrap_or(&Value::Null);
1398 if current_data != &Value::Null && current_data != "" {
1399 hidden_fields.push(path.to_string());
1400 }
1401 }
1402
1403 #[allow(dead_code)]
1405 pub(crate) fn collect_hidden_fields(
1406 &self,
1407 schema_element: &Value,
1408 path: &str,
1409 hidden_fields: &mut Vec<String>,
1410 ) {
1411 match schema_element {
1412 Value::Object(map) => {
1413 let mut is_hidden = false;
1415 if let Some(Value::Object(condition)) = map.get("condition") {
1416 if let Some(Value::Bool(h)) = condition.get("hidden") {
1417 is_hidden = *h;
1418 }
1419 }
1420
1421 let mut keep_hidden = false;
1423 if let Some(Value::Object(config)) = map.get("config") {
1424 if let Some(Value::Object(all)) = config.get("all") {
1425 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
1426 keep_hidden = *keep;
1427 }
1428 }
1429 }
1430
1431 if is_hidden && !keep_hidden {
1432 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1433
1434 let current_data = self
1435 .eval_data
1436 .data()
1437 .pointer(&data_path)
1438 .unwrap_or(&Value::Null);
1439
1440 if current_data != &Value::Null && current_data != "" {
1442 hidden_fields.push(path.to_string());
1443 }
1444 }
1445
1446 for (key, val) in map {
1448 if key == "properties" {
1449 if let Value::Object(props) = val {
1450 for (p_key, p_val) in props {
1451 let next_path = if path == "#" {
1452 format!("#/properties/{}", p_key)
1453 } else {
1454 format!("{}/properties/{}", path, p_key)
1455 };
1456 self.collect_hidden_fields(p_val, &next_path, hidden_fields);
1457 }
1458 }
1459 } else if let Value::Object(_) = val {
1460 if key == "condition"
1462 || key == "config"
1463 || key == "rules"
1464 || key == "dependents"
1465 || key == "hideLayout"
1466 || key == "$layout"
1467 || key == "$params"
1468 || key == "definitions"
1469 || key == "$defs"
1470 || key.starts_with('$')
1471 {
1472 continue;
1473 }
1474
1475 let next_path = if path == "#" {
1476 format!("#/{}", key)
1477 } else {
1478 format!("{}/{}", path, key)
1479 };
1480 self.collect_hidden_fields(val, &next_path, hidden_fields);
1481 }
1482 }
1483 }
1484 _ => {}
1485 }
1486 }
1487
1488 pub(crate) fn recursive_hide_effect(
1491 engine: &RLogic,
1492 evaluations: &IndexMap<String, LogicId>,
1493 reffed_by: &IndexMap<String, Vec<String>>,
1494 eval_data: &mut EvalData,
1495 eval_cache: &mut crate::jsoneval::eval_cache::EvalCache,
1496 mut hidden_fields: Vec<String>,
1497 queue: &mut Vec<(String, bool, Option<Vec<usize>>)>,
1498 result: &mut Vec<Value>,
1499 ) {
1500 while let Some(hf) = hidden_fields.pop() {
1501 let data_path = path_utils::schema_path_to_data_pointer(&hf).into_owned();
1502
1503 eval_data.set(&data_path, Value::Null);
1505 eval_cache.bump_data_version(&data_path);
1506
1507 let mut change_obj = serde_json::Map::new();
1509 change_obj.insert(
1510 "$ref".to_string(),
1511 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
1512 );
1513 change_obj.insert("$hidden".to_string(), Value::Bool(true));
1514 change_obj.insert("clear".to_string(), Value::Bool(true));
1515 result.push(Value::Object(change_obj));
1516
1517 queue.push((hf.clone(), true, None));
1519
1520 if let Some(referencing_fields) = reffed_by.get(&data_path) {
1522 for rb in referencing_fields {
1523 let hidden_eval_key = format!("{}/condition/hidden", rb);
1527
1528 if let Some(logic_id) = evaluations.get(&hidden_eval_key) {
1529 let rb_data_path = path_utils::schema_path_to_data_pointer(rb).into_owned();
1536 let rb_value = eval_data
1537 .data()
1538 .pointer(&rb_data_path)
1539 .cloned()
1540 .unwrap_or(Value::Null);
1541
1542 if let Ok(Value::Bool(is_hidden)) = engine.run(logic_id, eval_data.data()) {
1544 if is_hidden {
1545 if !hidden_fields.contains(rb) {
1548 let has_value = rb_value != Value::Null && rb_value != "";
1549 if has_value {
1550 hidden_fields.push(rb.clone());
1551 }
1552 }
1553 }
1554 }
1555 }
1556 }
1557 }
1558 }
1559 }
1560
1561 pub(crate) fn process_dependents_queue(
1564 engine: &RLogic,
1565 evaluations: &IndexMap<String, LogicId>,
1566 eval_data: &mut EvalData,
1567 eval_cache: &mut crate::jsoneval::eval_cache::EvalCache,
1568 dependents_evaluations: &IndexMap<String, Vec<DependentItem>>,
1569 dep_formula_triggers: &IndexMap<String, Vec<(String, usize)>>,
1570 evaluated_schema: &Value,
1571 queue: &mut Vec<(String, bool, Option<Vec<usize>>)>,
1572 processed: &mut std::collections::HashMap<String, Option<std::collections::HashSet<usize>>>,
1573 result: &mut Vec<Value>,
1574 token: Option<&CancellationToken>,
1575 canceled_paths: Option<&mut Vec<String>>,
1576 ) -> Result<(), String> {
1577 while let Some((current_path, is_transitive, target_indices)) = queue.pop() {
1578 if let Some(t) = token {
1579 if t.is_cancelled() {
1580 if let Some(cp) = canceled_paths {
1581 cp.push(current_path.clone());
1582 for (path, _, _) in queue.iter() {
1583 cp.push(path.clone());
1584 }
1585 }
1586 return Err("Cancelled".to_string());
1587 }
1588 }
1589
1590 let (should_run, indices_to_run) = match processed.get(¤t_path) {
1591 Some(None) => {
1592 continue;
1594 }
1595 Some(Some(already_processed_indices)) => {
1596 if let Some(targets) = &target_indices {
1597 let new_targets: std::collections::HashSet<usize> = targets
1598 .iter()
1599 .copied()
1600 .filter(|i| !already_processed_indices.contains(i))
1601 .collect();
1602 if new_targets.is_empty() {
1603 continue;
1604 }
1605 (true, Some(new_targets))
1606 } else {
1607 (true, None)
1608 }
1609 }
1610 None => (
1611 true,
1612 target_indices.clone().map(|t| t.into_iter().collect()),
1613 ),
1614 };
1615
1616 if !should_run {
1617 continue;
1618 }
1619
1620 let new_processed_state = if let Some(targets_to_run) = &indices_to_run {
1621 match processed.get(¤t_path) {
1622 Some(Some(existing_targets)) => {
1623 let mut copy = existing_targets.clone();
1624 for t in targets_to_run {
1625 copy.insert(*t);
1626 }
1627 Some(copy)
1628 }
1629 _ => Some(targets_to_run.clone()),
1630 }
1631 } else {
1632 None
1633 };
1634 processed.insert(current_path.clone(), new_processed_state);
1635
1636 let current_data_path =
1638 path_utils::schema_path_to_data_pointer(¤t_path).into_owned();
1639 let mut current_value = eval_data
1640 .data()
1641 .pointer(¤t_data_path)
1642 .cloned()
1643 .unwrap_or(Value::Null);
1644
1645 if let Some(formula_sources) = dep_formula_triggers.get(¤t_data_path) {
1650 let mut targets_by_source: std::collections::HashMap<String, Vec<usize>> =
1651 std::collections::HashMap::new();
1652 for (source_schema_path, dep_idx) in formula_sources {
1653 let source_ptr = path_utils::dot_notation_to_schema_pointer(source_schema_path);
1654 targets_by_source
1655 .entry(source_ptr)
1656 .or_default()
1657 .push(*dep_idx);
1658 }
1659 for (source_ptr, targets) in targets_by_source {
1660 if let Some(None) = processed.get(&source_ptr) {
1662 continue;
1663 }
1664 queue.push((source_ptr, true, Some(targets)));
1665 }
1666 }
1667
1668 if let Some(dependent_items) = dependents_evaluations.get(¤t_path) {
1670 for (dep_idx, dep_item) in dependent_items.iter().enumerate() {
1671 if let Some(targets) = &indices_to_run {
1672 if !targets.contains(&dep_idx) {
1673 continue;
1674 }
1675 }
1676 let ref_path = &dep_item.ref_path;
1677 let pointer_path = path_utils::normalize_to_json_pointer(ref_path);
1678 let data_path =
1680 crate::jsoneval::path_utils::schema_path_to_data_pointer(&pointer_path)
1681 .into_owned();
1682
1683 let current_ref_value = eval_data
1684 .data()
1685 .pointer(&data_path)
1686 .cloned()
1687 .unwrap_or(Value::Null);
1688
1689 let field = evaluated_schema.pointer(&pointer_path).cloned();
1691
1692 let parent_path = if let Some(last_slash) = pointer_path.rfind("/properties") {
1694 &pointer_path[..last_slash]
1695 } else {
1696 "/"
1697 };
1698 let mut parent_field = if parent_path.is_empty() || parent_path == "/" {
1699 evaluated_schema.clone()
1700 } else {
1701 evaluated_schema
1702 .pointer(parent_path)
1703 .cloned()
1704 .unwrap_or_else(|| Value::Object(serde_json::Map::new()))
1705 };
1706
1707 if let Value::Object(ref mut map) = parent_field {
1709 map.remove("properties");
1710 map.remove("$layout");
1711 }
1712
1713 let mut change_obj = serde_json::Map::new();
1714 change_obj.insert(
1715 "$ref".to_string(),
1716 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
1717 );
1718 if let Some(f) = field {
1719 change_obj.insert("$field".to_string(), f);
1720 }
1721 change_obj.insert("$parentField".to_string(), parent_field);
1722 change_obj.insert("transitive".to_string(), Value::Bool(is_transitive));
1723
1724 if processed.contains_key(ref_path) {
1729 continue;
1730 }
1731
1732 let mut add_transitive = false;
1733 let mut add_deps = false;
1734 if let Some(clear_val) = &dep_item.clear {
1736 let should_clear = Self::evaluate_dependent_value_static(
1737 engine,
1738 evaluations,
1739 eval_data,
1740 clear_val,
1741 ¤t_value,
1742 ¤t_ref_value,
1743 )?;
1744 let clear_bool = match should_clear {
1745 Value::Bool(b) => b,
1746 _ => false,
1747 };
1748
1749 if clear_bool {
1750 if data_path == current_data_path {
1751 current_value = Value::Null;
1752 }
1753 eval_data.set(&data_path, Value::Null);
1754 eval_cache.bump_data_version(&data_path);
1755 change_obj.insert("clear".to_string(), Value::Bool(true));
1756 add_transitive = true;
1757 add_deps = true;
1758 }
1759 }
1760
1761 if let Some(value_val) = &dep_item.value {
1763 let computed_value = Self::evaluate_dependent_value_static(
1764 engine,
1765 evaluations,
1766 eval_data,
1767 value_val,
1768 ¤t_value,
1769 ¤t_ref_value,
1770 )?;
1771 let cleaned_val = clean_float_noise_scalar(computed_value);
1772
1773 let is_clear =
1774 cleaned_val == Value::Null || cleaned_val.as_str() == Some("");
1775
1776 if cleaned_val != current_ref_value && !is_clear {
1777 if data_path == current_data_path {
1778 current_value = cleaned_val.clone();
1779 }
1780 eval_data.set(&data_path, cleaned_val.clone());
1781 eval_cache.bump_data_version(&data_path);
1782 change_obj.insert("value".to_string(), cleaned_val);
1783 add_transitive = true;
1784 add_deps = true;
1785 }
1786 }
1787
1788 if add_deps {
1790 result.push(Value::Object(change_obj));
1791 }
1792
1793 if add_transitive {
1795 queue.push((ref_path.clone(), true, None));
1796 }
1797 }
1798 }
1799 }
1800 Ok(())
1801 }
1802}
1803
1804fn subform_field_key(subform_path: &str) -> String {
1811 let stripped = subform_path.trim_start_matches('#').trim_start_matches('/');
1813
1814 stripped
1816 .split('/')
1817 .filter(|seg| !seg.is_empty() && *seg != "properties")
1818 .last()
1819 .unwrap_or(stripped)
1820 .to_string()
1821}