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 for (path, schema_value) in readonly_values {
375 let data_path = path_utils::schema_path_to_data_pointer(&path).replace("/value/", "/");
376 let mut obj = serde_json::Map::new();
377 obj.insert(
378 "$ref".to_string(),
379 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
380 );
381 obj.insert("$readonly".to_string(), Value::Bool(true));
382 let is_clear = schema_value == Value::Null || schema_value.as_str() == Some("");
383 if is_clear {
384 obj.insert("clear".to_string(), Value::Bool(true));
385 } else {
386 obj.insert("value".to_string(), schema_value);
387 }
388 result.push(Value::Object(obj));
389 }
390
391 if had_actual_readonly_changes {
397 let readonly_dep_prefixes: Vec<String> = to_process
398 .iter()
399 .map(|(path, _, _)| path.trim_start_matches('#').to_string())
400 .collect();
401 let params_table_keys: Vec<String> = self
402 .table_metadata
403 .keys()
404 .filter(|key| key.starts_with("#/$params"))
405 .filter(|key| {
406 self.dependencies
407 .get(*key)
408 .map(|deps| {
409 deps.iter().any(|dep| {
410 readonly_dep_prefixes.iter().any(|readonly| {
411 dep == readonly
412 || dep
413 .strip_prefix(readonly)
414 .is_some_and(|suffix| suffix.starts_with('/'))
415 })
416 })
417 })
418 .unwrap_or(false)
419 })
420 .cloned()
421 .collect();
422
423 if !params_table_keys.is_empty() {
424 if let Some(active_idx) = self.eval_cache.active_item_index {
425 self.eval_cache
426 .invalidate_params_tables_for_item(active_idx, ¶ms_table_keys);
427 }
428 self.evaluate_internal(None, token)?;
429 }
430 }
431
432 if !to_process.is_empty() {
433 Self::process_dependents_queue(
434 &self.engine,
435 &self.evaluations,
436 &mut self.eval_data,
437 &mut self.eval_cache,
438 &self.dependents_evaluations,
439 &self.dep_formula_triggers,
440 &self.evaluated_schema,
441 to_process,
442 processed,
443 result,
444 token,
445 canceled_paths.as_mut().map(|v| &mut **v),
446 )?;
447 }
448
449 self.resolve_layout(false)?;
454
455 let mut hidden_fields = Vec::new();
456 for path in self.conditional_hidden_fields.iter() {
457 let normalized = path_utils::normalize_to_json_pointer(path);
458 if let Some(schema_el) = self.evaluated_schema.pointer(&normalized) {
459 self.check_hidden_field(schema_el, path, &mut hidden_fields);
460 }
461 }
462 for path in self.layout_condition_hidden_refs.iter() {
463 if let Some(schema_el) = self.evaluated_schema.pointer(path) {
464 self.check_effectively_hidden_field(schema_el, path, &mut hidden_fields);
465 }
466 }
467 hidden_fields.sort();
468 hidden_fields.dedup();
469 if !hidden_fields.is_empty() {
470 Self::recursive_hide_effect(
471 &self.engine,
472 &self.evaluations,
473 &self.reffed_by,
474 &mut self.eval_data,
475 &mut self.eval_cache,
476 hidden_fields,
477 to_process,
478 result,
479 );
480 }
481 if !to_process.is_empty() {
482 Self::process_dependents_queue(
483 &self.engine,
484 &self.evaluations,
485 &mut self.eval_data,
486 &mut self.eval_cache,
487 &self.dependents_evaluations,
488 &self.dep_formula_triggers,
489 &self.evaluated_schema,
490 to_process,
491 processed,
492 result,
493 token,
494 canceled_paths.as_mut().map(|v| &mut **v),
495 )?;
496 }
497
498 Ok(())
499 }
500
501 fn collect_visible_static_defaults(&self) -> Vec<(String, Value, String)> {
503 let mut defaults = Vec::new();
504 let schema_values = self.get_schema_value_array();
505
506 if let Value::Array(values) = schema_values {
507 for item in values {
508 let Value::Object(map) = item else {
509 continue;
510 };
511 let Some(Value::String(dot_path)) = map.get("path") else {
512 continue;
513 };
514 let Some(schema_val) = map.get("value") else {
515 continue;
516 };
517
518 let schema_ptr = path_utils::dot_notation_to_schema_pointer(dot_path);
519 if let Some(Value::Object(schema_node)) = self
520 .evaluated_schema
521 .pointer(schema_ptr.trim_start_matches('#'))
522 {
523 if let Some(Value::Object(condition)) = schema_node.get("condition") {
524 if let Some(hidden_val) = condition.get("hidden") {
525 if !hidden_val.is_boolean() || hidden_val.as_bool() == Some(true) {
526 continue;
527 }
528 }
529 }
530 }
531
532 let data_path = dot_path.replace('.', "/");
533 let current_data = self
534 .eval_data
535 .data()
536 .pointer(&format!("/{}", data_path))
537 .unwrap_or(&Value::Null);
538 let is_empty = matches!(current_data, Value::Null)
539 || matches!(current_data, Value::String(s) if s.is_empty());
540 let is_schema_val_empty = matches!(schema_val, Value::Null)
541 || matches!(schema_val, Value::String(s) if s.is_empty())
542 || matches!(schema_val, Value::Object(map) if map.contains_key("$evaluation"));
543
544 if is_empty && !is_schema_val_empty && current_data != schema_val {
545 defaults.push((data_path, schema_val.clone(), dot_path.clone()));
546 }
547 }
548 }
549
550 defaults
551 }
552
553 pub(crate) fn apply_visible_static_defaults(&mut self) -> bool {
554 let defaults = self.collect_visible_static_defaults();
555 for (data_path, schema_val, _) in &defaults {
556 self.eval_data
557 .set(&format!("/{}", data_path), schema_val.clone());
558 self.eval_cache
559 .bump_data_version(&format!("/{}", data_path));
560 }
561 !defaults.is_empty()
562 }
563
564 fn run_schema_default_value_pass(
567 &mut self,
568 token: Option<&CancellationToken>,
569 to_process: &mut Vec<(String, bool, Option<Vec<usize>>)>,
570 processed: &mut std::collections::HashMap<String, Option<std::collections::HashSet<usize>>>,
571 result: &mut Vec<Value>,
572 mut canceled_paths: Option<&mut Vec<String>>,
573 ) -> Result<(), String> {
574 let mut default_value_changes = Vec::new();
575 let schema_values = self.get_schema_value_array();
576
577 if let Value::Array(values) = schema_values {
578 for item in values {
579 if let Value::Object(map) = item {
580 if let (Some(Value::String(dot_path)), Some(schema_val)) =
581 (map.get("path"), map.get("value"))
582 {
583 let schema_ptr = path_utils::dot_notation_to_schema_pointer(dot_path);
584 if let Some(Value::Object(schema_node)) = self
585 .evaluated_schema
586 .pointer(schema_ptr.trim_start_matches('#'))
587 {
588 if let Some(Value::Object(condition)) = schema_node.get("condition") {
589 if let Some(hidden_val) = condition.get("hidden") {
590 if !hidden_val.is_boolean()
592 || hidden_val.as_bool() == Some(true)
593 {
594 continue;
595 }
596 }
597 }
598 }
599
600 let data_path = dot_path.replace('.', "/");
601 let current_data = self
602 .eval_data
603 .data()
604 .pointer(&format!("/{}", data_path))
605 .unwrap_or(&Value::Null);
606
607 let is_empty = match current_data {
608 Value::Null => true,
609 Value::String(s) if s.is_empty() => true,
610 _ => false,
611 };
612
613 let is_schema_val_empty = match schema_val {
614 Value::Null => true,
615 Value::String(s) if s.is_empty() => true,
616 Value::Object(map) if map.contains_key("$evaluation") => true,
617 _ => false,
618 };
619
620 if is_empty && !is_schema_val_empty && current_data != schema_val {
621 default_value_changes.push((
622 data_path,
623 schema_val.clone(),
624 dot_path.clone(),
625 ));
626 }
627 }
628 }
629 }
630 }
631
632 let mut has_changes = false;
633 for (data_path, schema_val, dot_path) in default_value_changes {
634 self.eval_data
635 .set(&format!("/{}", data_path), schema_val.clone());
636 self.eval_cache
637 .bump_data_version(&format!("/{}", data_path));
638
639 let mut change_obj = serde_json::Map::new();
640 change_obj.insert("$ref".to_string(), Value::String(dot_path));
641 let is_clear = schema_val == Value::Null || schema_val.as_str() == Some("");
642 if is_clear {
643 change_obj.insert("clear".to_string(), Value::Bool(true));
644 } else {
645 change_obj.insert("value".to_string(), schema_val);
646 }
647 result.push(Value::Object(change_obj));
648
649 let schema_ptr = format!("#/{}", data_path.replace('/', "/properties/"));
650 to_process.push((schema_ptr, true, None));
651 has_changes = true;
652 }
653
654 if has_changes {
655 Self::process_dependents_queue(
656 &self.engine,
657 &self.evaluations,
658 &mut self.eval_data,
659 &mut self.eval_cache,
660 &self.dependents_evaluations,
661 &self.dep_formula_triggers,
662 &self.evaluated_schema,
663 to_process,
664 processed,
665 result,
666 token,
667 canceled_paths.as_mut().map(|v| &mut **v),
668 )?;
669 }
670
671 Ok(())
672 }
673
674 fn run_subform_pass(
684 &mut self,
685 changed_paths: &[String],
686 parent_changed_paths: &[String],
687 _re_evaluate: bool,
688 token: Option<&CancellationToken>,
689 result: &mut Vec<Value>,
690 ) -> Result<bool, String> {
691 let mut any_table_invalidated = false;
692 let subform_paths: Vec<String> = self.subforms.keys().cloned().collect();
694
695 for subform_path in subform_paths {
696 let field_key = subform_field_key(&subform_path);
697 let subform_dot_path =
699 path_utils::pointer_to_dot_notation(&subform_path).replace(".properties.", ".");
700 let field_prefix = format!("{}.", field_key);
701 let subform_ptr = normalize_to_json_pointer(&subform_path);
702
703 let item_count =
705 get_value_by_pointer_without_properties(self.eval_data.data(), &subform_ptr)
706 .and_then(|v| v.as_array())
707 .map(|a| a.len())
708 .unwrap_or(0);
709
710 if item_count == 0 {
711 continue;
712 }
713
714 self.eval_cache.prune_subform_caches(item_count);
717
718 let parent_data_versions_snapshot = self.eval_cache.data_versions.clone();
723 let parent_params_versions_snapshot = self.eval_cache.params_versions.clone();
724
725 for idx in 0..item_count {
726 let prefix_dot = format!("{}.{}.", subform_dot_path, idx);
728 let prefix_bracket = format!("{}[{}].", subform_dot_path, idx);
729 let prefix_field_bracket = format!("{}[{}].", field_key, idx);
730
731 let item_changed_paths: Vec<String> = changed_paths
732 .iter()
733 .filter_map(|p| {
734 if p.starts_with(&prefix_bracket) {
735 Some(p.replacen(&prefix_bracket, &field_prefix, 1))
736 } else if p.starts_with(&prefix_dot) {
737 Some(p.replacen(&prefix_dot, &field_prefix, 1))
738 } else if p.starts_with(&prefix_field_bracket) {
739 Some(p.replacen(&prefix_field_bracket, &field_prefix, 1))
740 } else {
741 None
742 }
743 })
744 .collect();
745
746 let item_val =
749 get_value_by_pointer_without_properties(self.eval_data.data(), &subform_ptr)
750 .and_then(|v| v.as_array())
751 .and_then(|a| a.get(idx))
752 .cloned()
753 .unwrap_or(Value::Null);
754
755 let merged_data = {
759 let parent = self.eval_data.data();
760 let mut map = serde_json::Map::new();
761 if let Value::Object(parent_map) = parent {
762 for (k, v) in parent_map {
763 if k == &field_key {
764 continue;
766 }
767 if !v.is_array() {
770 map.insert(k.clone(), v.clone());
771 }
772 }
773 }
774 map.insert(field_key.clone(), item_val.clone());
775 Value::Object(map)
776 };
777
778 let parent_dependency_paths: Vec<String> = parent_changed_paths
783 .iter()
784 .map(|path| {
785 path_utils::dot_notation_to_schema_pointer(path)
786 .trim_start_matches('#')
787 .to_string()
788 })
789 .collect();
790 let dependent_value_paths: Vec<String> = self
791 .subforms
792 .get(&subform_path)
793 .map(|subform| {
794 subform
795 .dependencies
796 .iter()
797 .filter(|(key, deps)| {
798 !subform.table_metadata.contains_key(*key)
799 && !key.starts_with("#/$params/")
800 && key.ends_with("/value")
801 && parent_dependency_paths
802 .iter()
803 .any(|dependency| deps.contains(dependency))
804 })
805 .map(|(key, _)| key.clone())
806 .collect()
807 })
808 .unwrap_or_default();
809
810 if item_changed_paths.is_empty() && !dependent_value_paths.is_empty() {
811 let mut parent_cache = std::mem::take(&mut self.eval_cache);
812 parent_cache.set_active_item(idx);
813 let subform = self
814 .subforms
815 .get_mut(&subform_path)
816 .expect("subform exists");
817 subform.eval_data.replace_data_and_context(
818 merged_data,
819 self.eval_data
820 .data()
821 .get("$context")
822 .cloned()
823 .unwrap_or(Value::Null),
824 );
825 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
826 subform.evaluate_internal(Some(&dependent_value_paths), token)?;
827
828 for formula_path in &dependent_value_paths {
829 let schema_path = path_utils::normalize_to_json_pointer(formula_path);
830 let data_path = path_utils::schema_path_to_data_pointer(formula_path);
831 let Some(value) = subform.evaluated_schema.pointer(&schema_path).cloned()
832 else {
833 continue;
834 };
835 let mut change = serde_json::Map::new();
836 let field = data_path
837 .trim_start_matches('/')
838 .trim_end_matches("/value")
839 .replace('/', ".");
840 let field = field.strip_prefix(&field_prefix).unwrap_or(&field);
841 change.insert(
842 "$ref".to_string(),
843 Value::String(format!("{}.{}.{}", subform_dot_path, idx, field)),
844 );
845 if value == Value::Null || value.as_str() == Some("") {
846 change.insert("clear".to_string(), Value::Bool(true));
847 } else {
848 change.insert("value".to_string(), value);
849 }
850 result.push(Value::Object(change));
851 }
852 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
853 parent_cache.clear_active_item();
854 self.eval_cache = parent_cache;
855 continue;
856 }
857
858 let Some(subform) = self.subforms.get_mut(&subform_path) else {
859 continue;
860 };
861
862 let sub_re_evaluate = !item_changed_paths.is_empty();
867 if !sub_re_evaluate && item_changed_paths.is_empty() {
868 continue;
869 }
870
871 self.eval_cache.ensure_active_item_cache(idx);
873 let old_item_val = {
874 let snapshot = self
875 .eval_cache
876 .subform_caches
877 .get(&idx)
878 .map(|c| c.item_snapshot.clone())
879 .unwrap_or(Value::Null);
880
881 if snapshot == Value::Null {
882 if let Some(main_snap) = &self.eval_cache.main_form_snapshot {
883 get_value_by_pointer_without_properties(main_snap, &subform_ptr)
884 .and_then(|v| v.as_array())
885 .and_then(|a| a.get(idx))
886 .cloned()
887 .unwrap_or(Value::Null)
888 } else {
889 Value::Null
890 }
891 } else {
892 snapshot
893 }
894 };
895
896 subform.eval_data.replace_data_and_context(
897 merged_data,
898 self.eval_data
899 .data()
900 .get("$context")
901 .cloned()
902 .unwrap_or(Value::Null),
903 );
904 let new_item_val = subform
905 .eval_data
906 .data()
907 .get(&field_key)
908 .cloned()
909 .unwrap_or(Value::Null);
910
911 let mut parent_cache = std::mem::take(&mut self.eval_cache);
913 parent_cache.ensure_active_item_cache(idx);
914
915 let pre_diff_item_versions = parent_cache
918 .subform_caches
919 .get(&idx)
920 .map(|c| c.data_versions.clone());
921
922 if let Some(c) = parent_cache.subform_caches.get_mut(&idx) {
923 c.data_versions.merge_from(&parent_data_versions_snapshot);
927 c.data_versions
929 .merge_from_params(&parent_params_versions_snapshot);
930 crate::jsoneval::eval_cache::diff_and_update_versions(
931 &mut c.data_versions,
932 &format!("/{}", field_key),
933 &old_item_val,
934 &new_item_val,
935 "run_subform_pass_diff_and_update_versions",
936 );
937 c.item_snapshot = new_item_val;
938 }
939
940 if let (Some(ref pre), Some(c)) = (
944 &pre_diff_item_versions,
945 parent_cache.subform_caches.get(&idx),
946 ) {
947 let field_prefix_slash = format!("/{}/", field_key);
948 let newly_bumped: Vec<String> = c
949 .data_versions
950 .versions()
951 .filter(|(k, &v)| k.starts_with(&field_prefix_slash) && v > pre.get(k))
952 .map(|(k, _)| k.to_string())
953 .collect();
954 if !newly_bumped.is_empty() {
955 for k in newly_bumped {
956 parent_cache
957 .data_versions
958 .bump(&k, "propagate_newly_bumped");
959 }
960 parent_cache.eval_generation += 1;
961 }
962 }
963
964 {
970 let field_prefix_slash = format!("/{}/", field_key);
971 let newly_bumped_schema_paths: Vec<String> = if let (Some(ref pre), Some(c)) = (
972 &pre_diff_item_versions,
973 parent_cache.subform_caches.get(&idx),
974 ) {
975 c.data_versions
976 .versions()
977 .filter(|(k, &v)| k.starts_with(&field_prefix_slash) && v > pre.get(k))
978 .map(|(k, _)| {
979 let sub = k.trim_start_matches(&field_prefix_slash);
983 format!(
984 "/{}/properties/{}",
985 field_key,
986 sub.replace('/', "/properties/")
987 )
988 })
989 .collect()
990 } else {
991 Vec::new()
992 };
993
994 if !newly_bumped_schema_paths.is_empty() {
995 let params_table_keys: Vec<String> = self
996 .table_metadata
997 .keys()
998 .filter(|k| k.starts_with("#/$params"))
999 .filter(|k| {
1000 self.dependencies
1001 .get(*k)
1002 .map(|deps| {
1003 deps.iter().any(|dep| {
1004 newly_bumped_schema_paths
1005 .iter()
1006 .any(|b| dep == b || dep.starts_with(b.as_str()))
1007 })
1008 })
1009 .unwrap_or(false)
1010 })
1011 .cloned()
1012 .collect();
1013
1014 if !params_table_keys.is_empty() {
1015 parent_cache.invalidate_params_tables_for_item(idx, ¶ms_table_keys);
1016 any_table_invalidated = true;
1017 }
1018 }
1019 }
1020
1021 parent_cache.set_active_item(idx);
1022 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
1023
1024 let subform_result = time_block!(" [subform_pass] rider evaluate_dependents", {
1025 subform.evaluate_dependents(
1026 &item_changed_paths,
1027 None,
1028 None,
1029 sub_re_evaluate,
1030 token,
1031 None,
1032 false,
1033 )
1034 });
1035
1036 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
1038 parent_cache.clear_active_item();
1039
1040 if let Some(parent_item_cache) = self.eval_cache.subform_caches.get(&idx) {
1045 let snapshot = parent_item_cache.item_snapshot.clone();
1046 subform.eval_cache.ensure_active_item_cache(idx);
1047 if let Some(sub_cache) = subform.eval_cache.subform_caches.get_mut(&idx) {
1048 sub_cache.item_snapshot = snapshot;
1049 }
1050 }
1051
1052 self.eval_cache = parent_cache;
1053
1054 if let Ok(Value::Array(changes)) = subform_result {
1055 let mut had_any_change = false;
1056 for change in changes {
1057 if let Some(obj) = change.as_object() {
1058 if let Some(Value::String(ref_path)) = obj.get("$ref") {
1059 let new_ref = if ref_path.starts_with(&field_prefix) {
1061 format!(
1062 "{}.{}.{}",
1063 subform_dot_path,
1064 idx,
1065 &ref_path[field_prefix.len()..]
1066 )
1067 } else {
1068 format!("{}.{}.{}", subform_dot_path, idx, ref_path)
1069 };
1070
1071 if let Some(val) = obj.get("value") {
1077 let data_ptr = format!("/{}", new_ref.replace('.', "/"));
1078 self.eval_data.set(&data_ptr, val.clone());
1079 had_any_change = true;
1080 } else if obj.get("clear").and_then(Value::as_bool) == Some(true) {
1081 let data_ptr = format!("/{}", new_ref.replace('.', "/"));
1082 self.eval_data.set(&data_ptr, Value::Null);
1083 had_any_change = true;
1084 }
1085
1086 let mut new_obj = obj.clone();
1087 new_obj.insert("$ref".to_string(), Value::String(new_ref));
1088 result.push(Value::Object(new_obj));
1089 } else {
1090 result.push(change);
1092 }
1093 }
1094 }
1095
1096 if had_any_change {
1104 let item_path = format!("{}/{}", subform_ptr, idx);
1105 let updated_item = self
1106 .eval_data
1107 .get(&item_path)
1108 .cloned()
1109 .unwrap_or(Value::Null);
1110 if let Some(c) = self.eval_cache.subform_caches.get_mut(&idx) {
1112 c.item_snapshot = updated_item.clone();
1113 }
1114 subform.eval_cache.ensure_active_item_cache(idx);
1117 if let Some(sub_cache) = subform.eval_cache.subform_caches.get_mut(&idx) {
1118 sub_cache.item_snapshot = updated_item;
1119 }
1120 }
1121 }
1122 }
1123 }
1124 Ok(any_table_invalidated)
1125 }
1126
1127 pub(crate) fn evaluate_dependent_value_static(
1129 engine: &RLogic,
1130 evaluations: &IndexMap<String, LogicId>,
1131 eval_data: &EvalData,
1132 value: &Value,
1133 changed_field_value: &Value,
1134 changed_field_ref_value: &Value,
1135 ) -> Result<Value, String> {
1136 match value {
1137 Value::String(eval_key) => {
1139 if let Some(logic_id) = evaluations.get(eval_key) {
1140 let mut internal_context = serde_json::Map::new();
1143 internal_context.insert("$value".to_string(), changed_field_value.clone());
1144 internal_context.insert("$refValue".to_string(), changed_field_ref_value.clone());
1145 let context_value = Value::Object(internal_context);
1146
1147 let result = engine.run_with_context(logic_id, eval_data.data(), &context_value)
1148 .map_err(|e| format!("Failed to evaluate dependent logic '{}': {}", eval_key, e))?;
1149 Ok(result)
1150 } else {
1151 Ok(value.clone())
1153 }
1154 }
1155 Value::Object(map) if map.contains_key("$evaluation") => {
1158 Err("Dependent evaluation contains unparsed $evaluation - schema was not properly parsed".to_string())
1159 }
1160 _ => Ok(value.clone()),
1162 }
1163 }
1164
1165 pub(crate) fn check_readonly_for_dependents(
1167 &self,
1168 schema_element: &Value,
1169 path: &str,
1170 changes: &mut Vec<(String, Value)>,
1171 all_values: &mut Vec<(String, Value)>,
1172 ) {
1173 match schema_element {
1174 Value::Object(map) => {
1175 let mut is_disabled = false;
1177 if let Some(Value::Object(condition)) = map.get("condition") {
1178 if let Some(Value::Bool(d)) = condition.get("disabled") {
1179 is_disabled = *d;
1180 }
1181 }
1182
1183 let mut skip_readonly = false;
1185 if let Some(Value::Object(config)) = map.get("config") {
1186 if let Some(Value::Object(all)) = config.get("all") {
1187 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
1188 skip_readonly = *skip;
1189 }
1190 }
1191 }
1192
1193 if is_disabled && !skip_readonly {
1194 if let Some(schema_value) = map.get("value") {
1195 let data_path = path_utils::schema_path_to_data_pointer(path)
1196 .replace("/value/", "/");
1199
1200 let current_data = self
1201 .eval_data
1202 .data()
1203 .pointer(&data_path)
1204 .unwrap_or(&Value::Null);
1205
1206 all_values.push((path.to_string(), schema_value.clone()));
1209 if current_data != schema_value {
1210 changes.push((path.to_string(), schema_value.clone()));
1211 }
1212 }
1213 }
1214 }
1215 _ => {}
1216 }
1217 }
1218
1219 #[allow(dead_code)]
1221 pub(crate) fn collect_readonly_fixes(
1222 &self,
1223 schema_element: &Value,
1224 path: &str,
1225 changes: &mut Vec<(String, Value)>,
1226 ) {
1227 match schema_element {
1228 Value::Object(map) => {
1229 let mut is_disabled = false;
1231 if let Some(Value::Object(condition)) = map.get("condition") {
1232 if let Some(Value::Bool(d)) = condition.get("disabled") {
1233 is_disabled = *d;
1234 }
1235 }
1236
1237 let mut skip_readonly = false;
1239 if let Some(Value::Object(config)) = map.get("config") {
1240 if let Some(Value::Object(all)) = config.get("all") {
1241 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
1242 skip_readonly = *skip;
1243 }
1244 }
1245 }
1246
1247 if is_disabled && !skip_readonly {
1248 if let Some(schema_value) = map.get("value") {
1252 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1253
1254 let current_data = self
1255 .eval_data
1256 .data()
1257 .pointer(&data_path)
1258 .unwrap_or(&Value::Null);
1259
1260 if current_data != schema_value {
1261 changes.push((path.to_string(), schema_value.clone()));
1262 }
1263 }
1264 }
1265
1266 if let Some(Value::Object(props)) = map.get("properties") {
1268 for (key, val) in props {
1269 let next_path = if path == "#" {
1270 format!("#/properties/{}", key)
1271 } else {
1272 format!("{}/properties/{}", path, key)
1273 };
1274 self.collect_readonly_fixes(val, &next_path, changes);
1275 }
1276 }
1277 }
1278 _ => {}
1279 }
1280 }
1281
1282 pub(crate) fn check_hidden_field(
1284 &self,
1285 schema_element: &Value,
1286 path: &str,
1287 hidden_fields: &mut Vec<String>,
1288 ) {
1289 match schema_element {
1290 Value::Object(map) => {
1291 let mut is_hidden = false;
1293 if let Some(Value::Object(condition)) = map.get("condition") {
1294 if let Some(Value::Bool(h)) = condition.get("hidden") {
1295 is_hidden = *h;
1296 }
1297 }
1298
1299 let mut keep_hidden = false;
1301 if let Some(Value::Object(config)) = map.get("config") {
1302 if let Some(Value::Object(all)) = config.get("all") {
1303 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
1304 keep_hidden = *keep;
1305 }
1306 }
1307 }
1308
1309 if is_hidden && !keep_hidden {
1310 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1311
1312 let current_data = self
1313 .eval_data
1314 .data()
1315 .pointer(&data_path)
1316 .unwrap_or(&Value::Null);
1317
1318 if current_data != &Value::Null && current_data != "" {
1320 hidden_fields.push(path.to_string());
1321 }
1322 }
1323 }
1324 _ => {}
1325 }
1326 }
1327
1328 fn check_effectively_hidden_field(
1330 &self,
1331 schema_element: &Value,
1332 path: &str,
1333 hidden_fields: &mut Vec<String>,
1334 ) {
1335 let Value::Object(map) = schema_element else {
1336 return;
1337 };
1338
1339 let keep_hidden = map
1340 .get("config")
1341 .and_then(Value::as_object)
1342 .and_then(|config| config.get("all"))
1343 .and_then(Value::as_object)
1344 .and_then(|all| all.get("keepHiddenValue"))
1345 .and_then(Value::as_bool)
1346 .unwrap_or(false);
1347 if keep_hidden {
1348 return;
1349 }
1350
1351 let current_data = self
1352 .eval_data
1353 .data()
1354 .pointer(&path_utils::schema_path_to_data_pointer(path))
1355 .unwrap_or(&Value::Null);
1356 if current_data != &Value::Null && current_data != "" {
1357 hidden_fields.push(path.to_string());
1358 }
1359 }
1360
1361 #[allow(dead_code)]
1363 pub(crate) fn collect_hidden_fields(
1364 &self,
1365 schema_element: &Value,
1366 path: &str,
1367 hidden_fields: &mut Vec<String>,
1368 ) {
1369 match schema_element {
1370 Value::Object(map) => {
1371 let mut is_hidden = false;
1373 if let Some(Value::Object(condition)) = map.get("condition") {
1374 if let Some(Value::Bool(h)) = condition.get("hidden") {
1375 is_hidden = *h;
1376 }
1377 }
1378
1379 let mut keep_hidden = false;
1381 if let Some(Value::Object(config)) = map.get("config") {
1382 if let Some(Value::Object(all)) = config.get("all") {
1383 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
1384 keep_hidden = *keep;
1385 }
1386 }
1387 }
1388
1389 if is_hidden && !keep_hidden {
1390 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1391
1392 let current_data = self
1393 .eval_data
1394 .data()
1395 .pointer(&data_path)
1396 .unwrap_or(&Value::Null);
1397
1398 if current_data != &Value::Null && current_data != "" {
1400 hidden_fields.push(path.to_string());
1401 }
1402 }
1403
1404 for (key, val) in map {
1406 if key == "properties" {
1407 if let Value::Object(props) = val {
1408 for (p_key, p_val) in props {
1409 let next_path = if path == "#" {
1410 format!("#/properties/{}", p_key)
1411 } else {
1412 format!("{}/properties/{}", path, p_key)
1413 };
1414 self.collect_hidden_fields(p_val, &next_path, hidden_fields);
1415 }
1416 }
1417 } else if let Value::Object(_) = val {
1418 if key == "condition"
1420 || key == "config"
1421 || key == "rules"
1422 || key == "dependents"
1423 || key == "hideLayout"
1424 || key == "$layout"
1425 || key == "$params"
1426 || key == "definitions"
1427 || key == "$defs"
1428 || key.starts_with('$')
1429 {
1430 continue;
1431 }
1432
1433 let next_path = if path == "#" {
1434 format!("#/{}", key)
1435 } else {
1436 format!("{}/{}", path, key)
1437 };
1438 self.collect_hidden_fields(val, &next_path, hidden_fields);
1439 }
1440 }
1441 }
1442 _ => {}
1443 }
1444 }
1445
1446 pub(crate) fn recursive_hide_effect(
1449 engine: &RLogic,
1450 evaluations: &IndexMap<String, LogicId>,
1451 reffed_by: &IndexMap<String, Vec<String>>,
1452 eval_data: &mut EvalData,
1453 eval_cache: &mut crate::jsoneval::eval_cache::EvalCache,
1454 mut hidden_fields: Vec<String>,
1455 queue: &mut Vec<(String, bool, Option<Vec<usize>>)>,
1456 result: &mut Vec<Value>,
1457 ) {
1458 while let Some(hf) = hidden_fields.pop() {
1459 let data_path = path_utils::schema_path_to_data_pointer(&hf).into_owned();
1460
1461 eval_data.set(&data_path, Value::Null);
1463 eval_cache.bump_data_version(&data_path);
1464
1465 let mut change_obj = serde_json::Map::new();
1467 change_obj.insert(
1468 "$ref".to_string(),
1469 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
1470 );
1471 change_obj.insert("$hidden".to_string(), Value::Bool(true));
1472 change_obj.insert("clear".to_string(), Value::Bool(true));
1473 result.push(Value::Object(change_obj));
1474
1475 queue.push((hf.clone(), true, None));
1477
1478 if let Some(referencing_fields) = reffed_by.get(&data_path) {
1480 for rb in referencing_fields {
1481 let hidden_eval_key = format!("{}/condition/hidden", rb);
1485
1486 if let Some(logic_id) = evaluations.get(&hidden_eval_key) {
1487 let rb_data_path = path_utils::schema_path_to_data_pointer(rb).into_owned();
1494 let rb_value = eval_data
1495 .data()
1496 .pointer(&rb_data_path)
1497 .cloned()
1498 .unwrap_or(Value::Null);
1499
1500 if let Ok(Value::Bool(is_hidden)) = engine.run(logic_id, eval_data.data()) {
1502 if is_hidden {
1503 if !hidden_fields.contains(rb) {
1506 let has_value = rb_value != Value::Null && rb_value != "";
1507 if has_value {
1508 hidden_fields.push(rb.clone());
1509 }
1510 }
1511 }
1512 }
1513 }
1514 }
1515 }
1516 }
1517 }
1518
1519 pub(crate) fn process_dependents_queue(
1522 engine: &RLogic,
1523 evaluations: &IndexMap<String, LogicId>,
1524 eval_data: &mut EvalData,
1525 eval_cache: &mut crate::jsoneval::eval_cache::EvalCache,
1526 dependents_evaluations: &IndexMap<String, Vec<DependentItem>>,
1527 dep_formula_triggers: &IndexMap<String, Vec<(String, usize)>>,
1528 evaluated_schema: &Value,
1529 queue: &mut Vec<(String, bool, Option<Vec<usize>>)>,
1530 processed: &mut std::collections::HashMap<String, Option<std::collections::HashSet<usize>>>,
1531 result: &mut Vec<Value>,
1532 token: Option<&CancellationToken>,
1533 canceled_paths: Option<&mut Vec<String>>,
1534 ) -> Result<(), String> {
1535 while let Some((current_path, is_transitive, target_indices)) = queue.pop() {
1536 if let Some(t) = token {
1537 if t.is_cancelled() {
1538 if let Some(cp) = canceled_paths {
1539 cp.push(current_path.clone());
1540 for (path, _, _) in queue.iter() {
1541 cp.push(path.clone());
1542 }
1543 }
1544 return Err("Cancelled".to_string());
1545 }
1546 }
1547
1548 let (should_run, indices_to_run) = match processed.get(¤t_path) {
1549 Some(None) => {
1550 continue;
1552 }
1553 Some(Some(already_processed_indices)) => {
1554 if let Some(targets) = &target_indices {
1555 let new_targets: std::collections::HashSet<usize> = targets
1556 .iter()
1557 .copied()
1558 .filter(|i| !already_processed_indices.contains(i))
1559 .collect();
1560 if new_targets.is_empty() {
1561 continue;
1562 }
1563 (true, Some(new_targets))
1564 } else {
1565 (true, None)
1566 }
1567 }
1568 None => (
1569 true,
1570 target_indices.clone().map(|t| t.into_iter().collect()),
1571 ),
1572 };
1573
1574 if !should_run {
1575 continue;
1576 }
1577
1578 let new_processed_state = if let Some(targets_to_run) = &indices_to_run {
1579 match processed.get(¤t_path) {
1580 Some(Some(existing_targets)) => {
1581 let mut copy = existing_targets.clone();
1582 for t in targets_to_run {
1583 copy.insert(*t);
1584 }
1585 Some(copy)
1586 }
1587 _ => Some(targets_to_run.clone()),
1588 }
1589 } else {
1590 None
1591 };
1592 processed.insert(current_path.clone(), new_processed_state);
1593
1594 let current_data_path =
1596 path_utils::schema_path_to_data_pointer(¤t_path).into_owned();
1597 let mut current_value = eval_data
1598 .data()
1599 .pointer(¤t_data_path)
1600 .cloned()
1601 .unwrap_or(Value::Null);
1602
1603 if let Some(formula_sources) = dep_formula_triggers.get(¤t_data_path) {
1608 let mut targets_by_source: std::collections::HashMap<String, Vec<usize>> =
1609 std::collections::HashMap::new();
1610 for (source_schema_path, dep_idx) in formula_sources {
1611 let source_ptr = path_utils::dot_notation_to_schema_pointer(source_schema_path);
1612 targets_by_source
1613 .entry(source_ptr)
1614 .or_default()
1615 .push(*dep_idx);
1616 }
1617 for (source_ptr, targets) in targets_by_source {
1618 if let Some(None) = processed.get(&source_ptr) {
1620 continue;
1621 }
1622 queue.push((source_ptr, true, Some(targets)));
1623 }
1624 }
1625
1626 if let Some(dependent_items) = dependents_evaluations.get(¤t_path) {
1628 for (dep_idx, dep_item) in dependent_items.iter().enumerate() {
1629 if let Some(targets) = &indices_to_run {
1630 if !targets.contains(&dep_idx) {
1631 continue;
1632 }
1633 }
1634 let ref_path = &dep_item.ref_path;
1635 let pointer_path = path_utils::normalize_to_json_pointer(ref_path);
1636 let data_path =
1638 crate::jsoneval::path_utils::schema_path_to_data_pointer(&pointer_path)
1639 .into_owned();
1640
1641 let current_ref_value = eval_data
1642 .data()
1643 .pointer(&data_path)
1644 .cloned()
1645 .unwrap_or(Value::Null);
1646
1647 let field = evaluated_schema.pointer(&pointer_path).cloned();
1649
1650 let parent_path = if let Some(last_slash) = pointer_path.rfind("/properties") {
1652 &pointer_path[..last_slash]
1653 } else {
1654 "/"
1655 };
1656 let mut parent_field = if parent_path.is_empty() || parent_path == "/" {
1657 evaluated_schema.clone()
1658 } else {
1659 evaluated_schema
1660 .pointer(parent_path)
1661 .cloned()
1662 .unwrap_or_else(|| Value::Object(serde_json::Map::new()))
1663 };
1664
1665 if let Value::Object(ref mut map) = parent_field {
1667 map.remove("properties");
1668 map.remove("$layout");
1669 }
1670
1671 let mut change_obj = serde_json::Map::new();
1672 change_obj.insert(
1673 "$ref".to_string(),
1674 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
1675 );
1676 if let Some(f) = field {
1677 change_obj.insert("$field".to_string(), f);
1678 }
1679 change_obj.insert("$parentField".to_string(), parent_field);
1680 change_obj.insert("transitive".to_string(), Value::Bool(is_transitive));
1681
1682 if processed.contains_key(ref_path) {
1687 continue;
1688 }
1689
1690 let mut add_transitive = false;
1691 let mut add_deps = false;
1692 if let Some(clear_val) = &dep_item.clear {
1694 let should_clear = Self::evaluate_dependent_value_static(
1695 engine,
1696 evaluations,
1697 eval_data,
1698 clear_val,
1699 ¤t_value,
1700 ¤t_ref_value,
1701 )?;
1702 let clear_bool = match should_clear {
1703 Value::Bool(b) => b,
1704 _ => false,
1705 };
1706
1707 if clear_bool {
1708 if data_path == current_data_path {
1709 current_value = Value::Null;
1710 }
1711 eval_data.set(&data_path, Value::Null);
1712 eval_cache.bump_data_version(&data_path);
1713 change_obj.insert("clear".to_string(), Value::Bool(true));
1714 add_transitive = true;
1715 add_deps = true;
1716 }
1717 }
1718
1719 if let Some(value_val) = &dep_item.value {
1721 let computed_value = Self::evaluate_dependent_value_static(
1722 engine,
1723 evaluations,
1724 eval_data,
1725 value_val,
1726 ¤t_value,
1727 ¤t_ref_value,
1728 )?;
1729 let cleaned_val = clean_float_noise_scalar(computed_value);
1730
1731 let is_clear =
1732 cleaned_val == Value::Null || cleaned_val.as_str() == Some("");
1733
1734 if cleaned_val != current_ref_value && !is_clear {
1735 if data_path == current_data_path {
1736 current_value = cleaned_val.clone();
1737 }
1738 eval_data.set(&data_path, cleaned_val.clone());
1739 eval_cache.bump_data_version(&data_path);
1740 change_obj.insert("value".to_string(), cleaned_val);
1741 add_transitive = true;
1742 add_deps = true;
1743 }
1744 }
1745
1746 if add_deps {
1748 result.push(Value::Object(change_obj));
1749 }
1750
1751 if add_transitive {
1753 queue.push((ref_path.clone(), true, None));
1754 }
1755 }
1756 }
1757 }
1758 Ok(())
1759 }
1760}
1761
1762fn subform_field_key(subform_path: &str) -> String {
1769 let stripped = subform_path.trim_start_matches('#').trim_start_matches('/');
1771
1772 stripped
1774 .split('/')
1775 .filter(|seg| !seg.is_empty() && *seg != "properties")
1776 .last()
1777 .unwrap_or(stripped)
1778 .to_string()
1779}