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 collection_refresh_paths: Vec<String> = self
118 .subforms
119 .keys()
120 .filter_map(|subform_path| {
121 let dot_path = path_utils::pointer_to_dot_notation(subform_path)
122 .replace(".properties.", ".");
123 let collection_changed = changed_paths
124 .iter()
125 .any(|path| path == &dot_path || path == &subform_field_key(subform_path));
126 collection_changed.then(|| {
127 let data_path = path_utils::schema_path_to_data_pointer(subform_path);
128 self.eval_data
129 .data()
130 .pointer(&data_path)
131 .and_then(Value::as_array)
132 .map(|items| {
133 (0..items.len())
134 .map(|idx| format!("{dot_path}.{idx}"))
135 .collect::<Vec<_>>()
136 })
137 })
138 })
139 .flatten()
140 .flatten()
141 .collect();
142
143 let extended_paths: Vec<String> = {
149 let mut paths = changed_paths.to_vec();
150 for item in &result {
151 if let Some(ref_val) = item.get("$ref").and_then(|v| v.as_str()) {
152 let s = ref_val.to_string();
153 if !paths.contains(&s) {
154 paths.push(s);
155 }
156 }
157 }
158 paths.extend(collection_refresh_paths);
159 paths
160 };
161 let subform_invalidated_tables = time_block!(" [dep] run_subform_pass", {
162 self.run_subform_pass(
163 &extended_paths,
164 changed_paths,
165 re_evaluate,
166 token,
167 &mut result,
168 )
169 })?;
170
171 if subform_invalidated_tables {
173 let _lock2 = self.eval_lock.lock().unwrap();
174 drop(_lock2);
175 self.evaluate_internal(None, token)?;
176
177 self.run_subform_pass(&[], &[], true, token, &mut result)?;
179
180 for (subform_path, _) in &self.subforms {
182 let data_ptr = path_utils::schema_path_to_data_pointer(subform_path);
183 let data_ptr_str = data_ptr.to_string();
184 let dot_path = data_ptr_str.trim_start_matches('/').replace('/', ".");
185
186 if let Some(fresh_val) = self.eval_data.get(&data_ptr_str) {
187 let mut patched = false;
188 for item in result.iter_mut() {
189 if item
190 .get("$ref")
191 .and_then(|r| r.as_str())
192 .map(|r| r == dot_path)
193 .unwrap_or(false)
194 {
195 if let Some(map) = item.as_object_mut() {
196 map.remove("clear");
197 map.insert("value".to_string(), fresh_val.clone());
198 }
199 patched = true;
200 break;
201 }
202 }
203 if !patched {
204 let mut obj = serde_json::Map::new();
205 obj.insert("$ref".to_string(), serde_json::Value::String(dot_path));
206 obj.insert("value".to_string(), fresh_val.clone());
207 result.push(serde_json::Value::Object(obj));
208 }
209 }
210 }
211 }
212 }
213
214 let deduped = {
219 let mut seen: IndexMap<String, usize> = IndexMap::new();
220 for (i, item) in result.iter().enumerate() {
221 if let Some(r) = item.get("$ref").and_then(|v| v.as_str()) {
222 seen.insert(r.to_string(), i);
223 }
224 }
225 let last_indices: IndexSet<usize> = seen.values().copied().collect();
226 let out: Vec<Value> = result
227 .into_iter()
228 .enumerate()
229 .filter(|(i, _)| last_indices.contains(i))
230 .map(|(_, item)| item)
231 .collect();
232 out
233 };
234
235 if self.eval_cache.active_item_index.is_none() {
237 let current_snapshot = self.eval_data.snapshot_data_clone();
238 self.eval_cache.main_form_snapshot = Some(current_snapshot);
239 }
240
241 Ok(Value::Array(deduped))
242 }
243
244 fn run_re_evaluate_pass(
247 &mut self,
248 token: Option<&CancellationToken>,
249 to_process: &mut Vec<(String, bool, Option<Vec<usize>>)>,
250 processed: &mut std::collections::HashMap<String, Option<std::collections::HashSet<usize>>>,
251 result: &mut Vec<Value>,
252 mut canceled_paths: Option<&mut Vec<String>>,
253 ) -> Result<(), String> {
254 self.run_schema_default_value_pass(
256 token,
257 to_process,
258 processed,
259 result,
260 canceled_paths.as_mut().map(|v| &mut **v),
261 )?;
262
263 let pre_eval_versions = if let Some(idx) = self.eval_cache.active_item_index {
269 self.eval_cache
270 .subform_caches
271 .get(&idx)
272 .map(|c| c.data_versions.clone())
273 .unwrap_or_else(|| self.eval_cache.data_versions.clone())
274 } else {
275 self.eval_cache.data_versions.clone()
276 };
277
278 self.evaluate_internal(None, token)?;
279
280 if self.collect_visible_static_defaults().is_empty() {
286 } else {
288 self.run_schema_default_value_pass(
289 token,
290 to_process,
291 processed,
292 result,
293 canceled_paths.as_mut().map(|v| &mut **v),
294 )?;
295 self.evaluate_internal(None, token)?;
296 }
297
298 let active_idx = self.eval_cache.active_item_index;
300 for eval_key in self.sorted_evaluations.iter().flatten() {
301 if eval_key.contains("/$params/") || eval_key.contains("/$") {
302 continue;
303 }
304
305 let schema_ptr = path_utils::schema_path_to_data_pointer(eval_key);
306 let data_path = schema_ptr.trim_start_matches('/').to_string();
307
308 let version_path = format!("/{}", data_path);
309 let old_ver = pre_eval_versions.get(&version_path);
310 let new_ver = if let Some(idx) = active_idx {
311 self.eval_cache
312 .subform_caches
313 .get(&idx)
314 .map(|c| c.data_versions.get(&version_path))
315 .unwrap_or_else(|| self.eval_cache.data_versions.get(&version_path))
316 } else {
317 self.eval_cache.data_versions.get(&version_path)
318 };
319
320 if new_ver > old_ver {
321 if let Some(new_val) = self.evaluated_schema.pointer(&schema_ptr) {
322 let dot_path = data_path.trim_end_matches("/value").replace('/', ".");
323 let mut obj = serde_json::Map::new();
324 obj.insert("$ref".to_string(), Value::String(dot_path));
325 let is_clear = new_val == &Value::Null || new_val.as_str() == Some("");
326 if is_clear {
327 obj.insert("clear".to_string(), Value::Bool(true));
328 } else {
329 obj.insert("value".to_string(), new_val.clone());
330 }
331 result.push(Value::Object(obj));
332 }
333 }
334 }
335
336 let mut readonly_changes = Vec::new();
338 let mut readonly_values = Vec::new();
339 for path in self.conditional_readonly_fields.iter() {
340 let normalized = path_utils::normalize_to_json_pointer(path);
341 if let Some(schema_el) = self.evaluated_schema.pointer(&normalized) {
342 self.check_readonly_for_dependents(
343 schema_el,
344 path,
345 &mut readonly_changes,
346 &mut readonly_values,
347 );
348 }
349 }
350 let had_actual_readonly_changes = !readonly_changes.is_empty();
353
354 let subform_data_paths: std::collections::HashSet<String> = self
360 .subforms
361 .keys()
362 .map(|p| {
363 path_utils::schema_path_to_data_pointer(p)
364 .replace("/value/", "/")
365 .to_string()
366 })
367 .collect();
368
369 for (path, schema_value) in readonly_changes {
370 let data_path = path_utils::schema_path_to_data_pointer(&path).replace("/value/", "/");
371
372 if subform_data_paths.contains(&data_path) {
373 if let (Value::Array(schema_items), Some(Value::Array(existing_items))) = (
375 &schema_value,
376 self.eval_data.data().pointer(&data_path).cloned().as_ref(),
377 ) {
378 let mut merged_items = existing_items.clone();
379 for (i, schema_item) in schema_items.iter().enumerate() {
380 if let (Some(existing), Value::Object(schema_map)) =
381 (merged_items.get_mut(i), schema_item)
382 {
383 if let Some(existing_map) = existing.as_object_mut() {
384 for (k, v) in schema_map {
385 if !v.is_object() {
386 existing_map.insert(k.clone(), v.clone());
387 }
388 }
389 }
390 } else if i >= merged_items.len() {
391 merged_items.push(schema_item.clone());
392 }
393 }
394 self.eval_data.set(&data_path, Value::Array(merged_items));
395 } else {
396 self.eval_data.set(&data_path, schema_value.clone());
397 }
398 self.eval_cache.bump_data_version(&data_path);
399 to_process.push((path, true, None));
400 continue;
401 }
402
403 self.eval_data.set(&data_path, schema_value.clone());
404 self.eval_cache.bump_data_version(&data_path);
405 to_process.push((path, true, None));
406 }
407 if had_actual_readonly_changes {
413 let readonly_dep_prefixes: Vec<String> = to_process
414 .iter()
415 .map(|(path, _, _)| path.trim_start_matches('#').to_string())
416 .collect();
417 let params_table_keys: Vec<String> = self
418 .table_metadata
419 .keys()
420 .filter(|key| key.starts_with("#/$params"))
421 .filter(|key| {
422 self.dependencies
423 .get(*key)
424 .map(|deps| {
425 deps.iter().any(|dep| {
426 readonly_dep_prefixes.iter().any(|readonly| {
427 dep == readonly
428 || dep
429 .strip_prefix(readonly)
430 .is_some_and(|suffix| suffix.starts_with('/'))
431 })
432 })
433 })
434 .unwrap_or(false)
435 })
436 .cloned()
437 .collect();
438
439 if !params_table_keys.is_empty() {
440 if let Some(active_idx) = self.eval_cache.active_item_index {
441 self.eval_cache
442 .invalidate_params_tables_for_item(active_idx, ¶ms_table_keys);
443 }
444 self.evaluate_internal(None, token)?;
445
446 readonly_values.clear();
450 for path in self.conditional_readonly_fields.iter() {
451 let normalized = path_utils::normalize_to_json_pointer(path);
452 if let Some(schema_el) = self.evaluated_schema.pointer(&normalized) {
453 self.check_readonly_for_dependents(
454 schema_el,
455 path,
456 &mut Vec::new(),
457 &mut readonly_values,
458 );
459 }
460 }
461 }
462 }
463
464 for (path, schema_value) in readonly_values {
465 let data_path = path_utils::schema_path_to_data_pointer(&path).replace("/value/", "/");
466 let mut obj = serde_json::Map::new();
467 obj.insert(
468 "$ref".to_string(),
469 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
470 );
471 obj.insert("$readonly".to_string(), Value::Bool(true));
472 let is_clear = schema_value == Value::Null || schema_value.as_str() == Some("");
473 if is_clear {
474 obj.insert("clear".to_string(), Value::Bool(true));
475 } else {
476 obj.insert("value".to_string(), schema_value);
477 }
478 result.push(Value::Object(obj));
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 self.resolve_layout(false)?;
503
504 let mut hidden_fields = Vec::new();
505 for path in self.conditional_hidden_fields.iter() {
506 let normalized = path_utils::normalize_to_json_pointer(path);
507 if let Some(schema_el) = self.evaluated_schema.pointer(&normalized) {
508 self.check_hidden_field(schema_el, path, &mut hidden_fields);
509 }
510 }
511 for path in self.layout_condition_hidden_refs.iter() {
512 if let Some(schema_el) = self.evaluated_schema.pointer(path) {
513 self.check_effectively_hidden_field(schema_el, path, &mut hidden_fields);
514 }
515 }
516 hidden_fields.sort();
517 hidden_fields.dedup();
518 if !hidden_fields.is_empty() {
519 Self::recursive_hide_effect(
520 &self.engine,
521 &self.evaluations,
522 &self.reffed_by,
523 &mut self.eval_data,
524 &mut self.eval_cache,
525 hidden_fields,
526 to_process,
527 result,
528 );
529 }
530 if !to_process.is_empty() {
531 Self::process_dependents_queue(
532 &self.engine,
533 &self.evaluations,
534 &mut self.eval_data,
535 &mut self.eval_cache,
536 &self.dependents_evaluations,
537 &self.dep_formula_triggers,
538 &self.evaluated_schema,
539 to_process,
540 processed,
541 result,
542 token,
543 canceled_paths.as_mut().map(|v| &mut **v),
544 )?;
545 }
546
547 Ok(())
548 }
549
550 fn collect_visible_static_defaults(&self) -> Vec<(String, Value, String)> {
552 let mut defaults = Vec::new();
553 let schema_values = self.get_schema_value_array();
554
555 if let Value::Array(values) = schema_values {
556 for item in values {
557 let Value::Object(map) = item else {
558 continue;
559 };
560 let Some(Value::String(dot_path)) = map.get("path") else {
561 continue;
562 };
563 let Some(schema_val) = map.get("value") else {
564 continue;
565 };
566
567 let schema_ptr = path_utils::dot_notation_to_schema_pointer(dot_path);
568 if let Some(Value::Object(schema_node)) = self
569 .evaluated_schema
570 .pointer(schema_ptr.trim_start_matches('#'))
571 {
572 if let Some(Value::Object(condition)) = schema_node.get("condition") {
573 if let Some(hidden_val) = condition.get("hidden") {
574 if !hidden_val.is_boolean() || hidden_val.as_bool() == Some(true) {
575 continue;
576 }
577 }
578 }
579 }
580
581 let data_path = dot_path.replace('.', "/");
582 let current_data = self
583 .eval_data
584 .data()
585 .pointer(&format!("/{}", data_path))
586 .unwrap_or(&Value::Null);
587 let is_empty = matches!(current_data, Value::Null)
588 || matches!(current_data, Value::String(s) if s.is_empty());
589 let is_schema_val_empty = matches!(schema_val, Value::Null)
590 || matches!(schema_val, Value::String(s) if s.is_empty())
591 || matches!(schema_val, Value::Object(map) if map.contains_key("$evaluation"));
592
593 if is_empty && !is_schema_val_empty && current_data != schema_val {
594 defaults.push((data_path, schema_val.clone(), dot_path.clone()));
595 }
596 }
597 }
598
599 defaults
600 }
601
602 pub(crate) fn apply_visible_static_defaults(&mut self) -> bool {
603 let defaults = self.collect_visible_static_defaults();
604 for (data_path, schema_val, _) in &defaults {
605 self.eval_data
606 .set(&format!("/{}", data_path), schema_val.clone());
607 self.eval_cache
608 .bump_data_version(&format!("/{}", data_path));
609 }
610 !defaults.is_empty()
611 }
612
613 pub(crate) fn apply_visible_static_defaults_with_dependents(
619 &mut self,
620 token: Option<&CancellationToken>,
621 ) -> Result<bool, String> {
622 if self.collect_visible_static_defaults().is_empty() {
623 return Ok(false);
624 }
625
626 let mut to_process = Vec::new();
627 let mut processed = std::collections::HashMap::new();
628 let mut result = Vec::new();
629 self.run_schema_default_value_pass(
630 token,
631 &mut to_process,
632 &mut processed,
633 &mut result,
634 None,
635 )?;
636 Ok(true)
637 }
638
639 fn run_schema_default_value_pass(
642 &mut self,
643 _token: Option<&CancellationToken>,
644 _to_process: &mut Vec<(String, bool, Option<Vec<usize>>)>,
645 _processed: &mut std::collections::HashMap<
646 String,
647 Option<std::collections::HashSet<usize>>,
648 >,
649 result: &mut Vec<Value>,
650 _canceled_paths: Option<&mut Vec<String>>,
651 ) -> Result<(), String> {
652 let mut default_value_changes = Vec::new();
653 let schema_values = self.get_schema_value_array();
654
655 if let Value::Array(values) = schema_values {
656 for item in values {
657 if let Value::Object(map) = item {
658 if let (Some(Value::String(dot_path)), Some(schema_val)) =
659 (map.get("path"), map.get("value"))
660 {
661 let schema_ptr = path_utils::dot_notation_to_schema_pointer(dot_path);
662 if let Some(Value::Object(schema_node)) = self
663 .evaluated_schema
664 .pointer(schema_ptr.trim_start_matches('#'))
665 {
666 if let Some(Value::Object(condition)) = schema_node.get("condition") {
667 if let Some(hidden_val) = condition.get("hidden") {
668 if !hidden_val.is_boolean()
670 || hidden_val.as_bool() == Some(true)
671 {
672 continue;
673 }
674 }
675 }
676 }
677
678 let data_path = dot_path.replace('.', "/");
679 let current_data = self
680 .eval_data
681 .data()
682 .pointer(&format!("/{}", data_path))
683 .unwrap_or(&Value::Null);
684
685 let is_empty = match current_data {
686 Value::Null => true,
687 Value::String(s) if s.is_empty() => true,
688 _ => false,
689 };
690
691 let is_schema_val_empty = match schema_val {
692 Value::Null => true,
693 Value::String(s) if s.is_empty() => true,
694 Value::Object(map) if map.contains_key("$evaluation") => true,
695 _ => false,
696 };
697
698 if is_empty && !is_schema_val_empty && current_data != schema_val {
699 default_value_changes.push((
700 data_path,
701 schema_val.clone(),
702 dot_path.clone(),
703 ));
704 }
705 }
706 }
707 }
708 }
709
710 for (data_path, schema_val, dot_path) in default_value_changes {
711 self.eval_data
712 .set(&format!("/{}", data_path), schema_val.clone());
713 self.eval_cache
714 .bump_data_version(&format!("/{}", data_path));
715
716 let mut change_obj = serde_json::Map::new();
717 change_obj.insert("$ref".to_string(), Value::String(dot_path));
718 let is_clear = schema_val == Value::Null || schema_val.as_str() == Some("");
719 if is_clear {
720 change_obj.insert("clear".to_string(), Value::Bool(true));
721 } else {
722 change_obj.insert("value".to_string(), schema_val);
723 }
724 result.push(Value::Object(change_obj));
725
726 }
731
732 Ok(())
733 }
734
735 fn run_subform_pass(
745 &mut self,
746 changed_paths: &[String],
747 parent_changed_paths: &[String],
748 _re_evaluate: bool,
749 token: Option<&CancellationToken>,
750 result: &mut Vec<Value>,
751 ) -> Result<bool, String> {
752 let mut any_table_invalidated = false;
753 let subform_paths: Vec<String> = self.subforms.keys().cloned().collect();
755
756 for subform_path in subform_paths {
757 let field_key = subform_field_key(&subform_path);
758 let subform_dot_path =
760 path_utils::pointer_to_dot_notation(&subform_path).replace(".properties.", ".");
761 let field_prefix = format!("{}.", field_key);
762 let subform_ptr = normalize_to_json_pointer(&subform_path);
763
764 let item_count =
766 get_value_by_pointer_without_properties(self.eval_data.data(), &subform_ptr)
767 .and_then(|v| v.as_array())
768 .map(|a| a.len())
769 .unwrap_or(0);
770
771 if item_count == 0 {
772 continue;
773 }
774
775 self.eval_cache.prune_subform_caches(item_count);
778
779 let parent_data_versions_snapshot = self.eval_cache.data_versions.clone();
784 let parent_params_versions_snapshot = self.eval_cache.params_versions.clone();
785
786 for idx in 0..item_count {
787 let prefix_dot = format!("{}.{}.", subform_dot_path, idx);
789 let prefix_bracket = format!("{}[{}].", subform_dot_path, idx);
790 let prefix_field_bracket = format!("{}[{}].", field_key, idx);
791
792 let is_collection_refresh = changed_paths
793 .iter()
794 .any(|path| path == &format!("{subform_dot_path}.{idx}"));
795 let item_changed_paths: Vec<String> = changed_paths
796 .iter()
797 .filter_map(|p| {
798 if p == &format!("{subform_dot_path}.{idx}") {
799 Some(field_key.clone())
800 } else if p.starts_with(&prefix_bracket) {
801 Some(p.replacen(&prefix_bracket, &field_prefix, 1))
802 } else if p.starts_with(&prefix_dot) {
803 Some(p.replacen(&prefix_dot, &field_prefix, 1))
804 } else if p.starts_with(&prefix_field_bracket) {
805 Some(p.replacen(&prefix_field_bracket, &field_prefix, 1))
806 } else {
807 None
808 }
809 })
810 .collect();
811
812 let item_val =
815 get_value_by_pointer_without_properties(self.eval_data.data(), &subform_ptr)
816 .and_then(|v| v.as_array())
817 .and_then(|a| a.get(idx))
818 .cloned()
819 .unwrap_or(Value::Null);
820
821 let parent_dependency_paths: Vec<String> = parent_changed_paths
826 .iter()
827 .map(|path| {
828 path_utils::dot_notation_to_schema_pointer(path)
829 .trim_start_matches('#')
830 .to_string()
831 })
832 .collect();
833 let dependent_value_paths: Vec<String> = self
834 .subforms
835 .get(&subform_path)
836 .map(|subform| {
837 subform
838 .dependencies
839 .iter()
840 .filter(|(key, deps)| {
841 !subform.table_metadata.contains_key(*key)
842 && !key.starts_with("#/$params/")
843 && key.ends_with("/value")
844 && parent_dependency_paths
845 .iter()
846 .any(|dependency| deps.contains(dependency))
847 })
848 .map(|(key, _)| key.clone())
849 .collect()
850 })
851 .unwrap_or_default();
852
853 if item_changed_paths.is_empty() && !dependent_value_paths.is_empty() {
854 let parent_cache = std::mem::take(&mut self.eval_cache);
859 let mut overlay_cache = parent_cache.clone();
860 overlay_cache.ensure_active_item_cache(idx);
861 if let Some(item_cache) = overlay_cache.subform_caches.get_mut(&idx) {
862 item_cache
866 .data_versions
867 .merge_from(&parent_data_versions_snapshot);
868 item_cache
869 .data_versions
870 .merge_from_params(&parent_params_versions_snapshot);
871 }
872 overlay_cache.set_active_item(idx);
873 let canonical_root =
874 path_utils::schema_path_to_data_pointer(&subform_path).into_owned();
875 let scope = crate::jsoneval::subform_scope::SubformScope::new(
876 &subform_path,
877 &canonical_root,
878 Some(idx),
879 );
880 let mut scoped_view = scope.evaluation_view(self.eval_data.data());
881 if let Some(view) = scoped_view.as_object_mut() {
882 view.insert(
883 "$context".to_string(),
884 self.eval_data
885 .data()
886 .get("$context")
887 .cloned()
888 .unwrap_or(Value::Null),
889 );
890 }
891 let subform = self
892 .subforms
893 .get_mut(&subform_path)
894 .expect("subform exists");
895 subform.eval_data = EvalData::new(scoped_view);
896 std::mem::swap(&mut subform.eval_cache, &mut overlay_cache);
897
898 let refresh_table_outputs = dependent_value_paths.iter().any(|source| {
903 let source = source.trim_end_matches("/value").trim_start_matches('#');
904 let affected_tables: Vec<&String> = subform
905 .table_metadata
906 .keys()
907 .filter(|table| table.starts_with("#/$params"))
908 .filter(|table| {
909 subform.dependencies.get(*table).is_some_and(|deps| {
910 deps.iter().any(|dep| dep.trim_start_matches('#') == source)
911 })
912 })
913 .collect();
914
915 !affected_tables.is_empty()
916 && subform.evaluations.iter().any(|(target, _)| {
917 target.ends_with("/value")
918 && subform.dependencies.get(target).is_some_and(|deps| {
919 deps.iter().any(|dep| {
920 affected_tables.iter().any(|table| {
921 dep.trim_start_matches('#')
922 == table.trim_start_matches('#')
923 })
924 })
925 })
926 })
927 });
928 subform.evaluate_internal(Some(&dependent_value_paths), token)?;
929
930 let mut overlay_result = Vec::new();
931 let mut overlay_queue = Vec::new();
932 let mut overlay_processed = std::collections::HashMap::new();
933 for formula_path in &dependent_value_paths {
934 let schema_path = path_utils::normalize_to_json_pointer(formula_path);
935 let data_path = path_utils::schema_path_to_data_pointer(formula_path)
936 .replace("/value", "");
937 let Some(value) = subform.evaluated_schema.pointer(&schema_path).cloned()
938 else {
939 continue;
940 };
941
942 let source_path = formula_path.trim_end_matches("/value").to_string();
947 if subform.eval_data.get(&data_path) != Some(&value) {
948 subform.eval_data.set(&data_path, value.clone());
949 subform
950 .eval_data
951 .set(&scope.canonical_path(&data_path), value.clone());
952 subform.eval_cache.bump_data_version(&data_path);
953 }
954 overlay_queue.push((source_path, true, None));
955
956 let mut change = serde_json::Map::new();
957 let field = data_path
958 .trim_start_matches('/')
959 .trim_end_matches("/value")
960 .replace('/', ".");
961 let field = field.strip_prefix(&field_prefix).unwrap_or(&field);
962 change.insert(
963 "$ref".to_string(),
964 Value::String(format!("{}.{}.{}", subform_dot_path, idx, field)),
965 );
966 if value == Value::Null || value.as_str() == Some("") {
967 change.insert("clear".to_string(), Value::Bool(true));
968 } else {
969 change.insert("value".to_string(), value);
970 }
971 result.push(Value::Object(change));
972 }
973
974 Self::process_dependents_queue(
975 &subform.engine,
976 &subform.evaluations,
977 &mut subform.eval_data,
978 &mut subform.eval_cache,
979 &subform.dependents_evaluations,
980 &subform.dep_formula_triggers,
981 &subform.evaluated_schema,
982 &mut overlay_queue,
983 &mut overlay_processed,
984 &mut overlay_result,
985 token,
986 None,
987 )?;
988
989 let local_item_path = format!("/{field_key}");
992 if let Some(local_item) = subform.eval_data.get(&local_item_path).cloned() {
993 subform
994 .eval_data
995 .set(&scope.canonical_path(&local_item_path), local_item);
996 }
997
998 if refresh_table_outputs {
999 subform.run_re_evaluate_pass(
1003 token,
1004 &mut overlay_queue,
1005 &mut overlay_processed,
1006 &mut overlay_result,
1007 None,
1008 )?;
1009 }
1010
1011 for change in overlay_result {
1012 let Some(object) = change.as_object() else {
1013 continue;
1014 };
1015 let Some(Value::String(ref_path)) = object.get("$ref") else {
1016 continue;
1017 };
1018 let local_ref = ref_path.strip_prefix(&field_prefix).unwrap_or(ref_path);
1019 let mut mapped = object.clone();
1020 mapped.insert(
1021 "$ref".to_string(),
1022 Value::String(format!("{}.{}.{}", subform_dot_path, idx, local_ref)),
1023 );
1024 result.push(Value::Object(mapped));
1025 }
1026
1027 std::mem::swap(&mut subform.eval_cache, &mut overlay_cache);
1030 self.eval_cache = parent_cache;
1031 continue;
1032 }
1033
1034 let canonical_root =
1035 path_utils::schema_path_to_data_pointer(&subform_path).into_owned();
1036 let scope = crate::jsoneval::subform_scope::SubformScope::new(
1037 &subform_path,
1038 &canonical_root,
1039 Some(idx),
1040 );
1041 let mut scoped_view = scope.evaluation_view(self.eval_data.data());
1042 if let Some(view) = scoped_view.as_object_mut() {
1043 view.insert(
1044 "$context".to_string(),
1045 self.eval_data
1046 .data()
1047 .get("$context")
1048 .cloned()
1049 .unwrap_or(Value::Null),
1050 );
1051 }
1052 let Some(subform) = self.subforms.get_mut(&subform_path) else {
1053 continue;
1054 };
1055
1056 let sub_re_evaluate = !item_changed_paths.is_empty();
1061 if !sub_re_evaluate && item_changed_paths.is_empty() {
1062 continue;
1063 }
1064
1065 self.eval_cache.ensure_active_item_cache(idx);
1067 let old_item_val = {
1068 let snapshot = self
1069 .eval_cache
1070 .subform_caches
1071 .get(&idx)
1072 .map(|c| c.item_snapshot.clone())
1073 .unwrap_or(Value::Null);
1074
1075 if snapshot == Value::Null {
1076 if let Some(main_snap) = &self.eval_cache.main_form_snapshot {
1077 get_value_by_pointer_without_properties(main_snap, &subform_ptr)
1078 .and_then(|v| v.as_array())
1079 .and_then(|a| a.get(idx))
1080 .cloned()
1081 .unwrap_or(Value::Null)
1082 } else {
1083 Value::Null
1084 }
1085 } else {
1086 snapshot
1087 }
1088 };
1089
1090 subform.eval_data = EvalData::new(scoped_view);
1091 let new_item_val = item_val.clone();
1092
1093 let mut parent_cache = std::mem::take(&mut self.eval_cache);
1095 parent_cache.ensure_active_item_cache(idx);
1096
1097 let pre_diff_item_versions = parent_cache
1100 .subform_caches
1101 .get(&idx)
1102 .map(|c| c.data_versions.clone());
1103
1104 if let Some(c) = parent_cache.subform_caches.get_mut(&idx) {
1105 c.data_versions.merge_from(&parent_data_versions_snapshot);
1109 c.data_versions
1111 .merge_from_params(&parent_params_versions_snapshot);
1112 if !is_collection_refresh {
1113 crate::jsoneval::eval_cache::diff_and_update_versions(
1114 &mut c.data_versions,
1115 &format!("/{}", field_key),
1116 &old_item_val,
1117 &new_item_val,
1118 "run_subform_pass_diff_and_update_versions",
1119 );
1120 }
1121 c.item_snapshot = new_item_val;
1124 }
1125
1126 if let (Some(ref pre), Some(c)) = (
1130 &pre_diff_item_versions,
1131 parent_cache.subform_caches.get(&idx),
1132 ) {
1133 let field_prefix_slash = format!("/{}/", field_key);
1134 let newly_bumped: Vec<String> = c
1135 .data_versions
1136 .versions()
1137 .filter(|(k, &v)| k.starts_with(&field_prefix_slash) && v > pre.get(k))
1138 .map(|(k, _)| k.to_string())
1139 .collect();
1140 if !newly_bumped.is_empty() {
1141 for k in newly_bumped {
1142 parent_cache
1143 .data_versions
1144 .bump(&k, "propagate_newly_bumped");
1145 }
1146 parent_cache.eval_generation += 1;
1147 }
1148 }
1149
1150 {
1156 let field_prefix_slash = format!("/{}/", field_key);
1157 let newly_bumped_schema_paths: Vec<String> = if let (Some(ref pre), Some(c)) = (
1158 &pre_diff_item_versions,
1159 parent_cache.subform_caches.get(&idx),
1160 ) {
1161 c.data_versions
1162 .versions()
1163 .filter(|(k, &v)| k.starts_with(&field_prefix_slash) && v > pre.get(k))
1164 .map(|(k, _)| {
1165 let sub = k.trim_start_matches(&field_prefix_slash);
1169 format!(
1170 "/{}/properties/{}",
1171 field_key,
1172 sub.replace('/', "/properties/")
1173 )
1174 })
1175 .collect()
1176 } else {
1177 Vec::new()
1178 };
1179
1180 if !newly_bumped_schema_paths.is_empty() {
1181 let params_table_keys: Vec<String> = self
1182 .table_metadata
1183 .keys()
1184 .filter(|k| k.starts_with("#/$params"))
1185 .filter(|k| {
1186 self.dependencies
1187 .get(*k)
1188 .map(|deps| {
1189 deps.iter().any(|dep| {
1190 newly_bumped_schema_paths
1191 .iter()
1192 .any(|b| dep == b || dep.starts_with(b.as_str()))
1193 })
1194 })
1195 .unwrap_or(false)
1196 })
1197 .cloned()
1198 .collect();
1199
1200 if !params_table_keys.is_empty() {
1201 parent_cache.invalidate_params_tables_for_item(idx, ¶ms_table_keys);
1202 any_table_invalidated = true;
1203 }
1204 }
1205 }
1206
1207 parent_cache.set_active_item(idx);
1208 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
1209
1210 let subform_result = time_block!(" [subform_pass] rider evaluate_dependents", {
1211 subform.evaluate_dependents(
1212 &item_changed_paths,
1213 None,
1214 None,
1215 sub_re_evaluate,
1216 token,
1217 None,
1218 false,
1219 )
1220 });
1221
1222 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
1224 parent_cache.clear_active_item();
1225
1226 if let Some(parent_item_cache) = self.eval_cache.subform_caches.get(&idx) {
1231 let snapshot = parent_item_cache.item_snapshot.clone();
1232 subform.eval_cache.ensure_active_item_cache(idx);
1233 if let Some(sub_cache) = subform.eval_cache.subform_caches.get_mut(&idx) {
1234 sub_cache.item_snapshot = snapshot;
1235 }
1236 }
1237
1238 self.eval_cache = parent_cache;
1239
1240 if let Ok(Value::Array(changes)) = subform_result {
1241 let mut had_any_change = false;
1242 for change in changes {
1243 if let Some(obj) = change.as_object() {
1244 if let Some(Value::String(ref_path)) = obj.get("$ref") {
1245 let new_ref = if ref_path.starts_with(&field_prefix) {
1247 format!(
1248 "{}.{}.{}",
1249 subform_dot_path,
1250 idx,
1251 &ref_path[field_prefix.len()..]
1252 )
1253 } else {
1254 format!("{}.{}.{}", subform_dot_path, idx, ref_path)
1255 };
1256
1257 if let Some(val) = obj.get("value") {
1263 let data_ptr = format!("/{}", new_ref.replace('.', "/"));
1264 self.eval_data.set(&data_ptr, val.clone());
1265 had_any_change = true;
1266 } else if obj.get("clear").and_then(Value::as_bool) == Some(true) {
1267 let data_ptr = format!("/{}", new_ref.replace('.', "/"));
1268 self.eval_data.set(&data_ptr, Value::Null);
1269 had_any_change = true;
1270 }
1271
1272 let mut new_obj = obj.clone();
1273 new_obj.insert("$ref".to_string(), Value::String(new_ref));
1274 result.push(Value::Object(new_obj));
1275 } else {
1276 result.push(change);
1278 }
1279 }
1280 }
1281
1282 if had_any_change {
1284 let item_path = format!("{}/{}", subform_ptr, idx);
1285 let updated_item = self
1286 .eval_data
1287 .get(&item_path)
1288 .cloned()
1289 .unwrap_or(Value::Null);
1290 if let Some(c) = self.eval_cache.subform_caches.get_mut(&idx) {
1292 c.item_snapshot = updated_item.clone();
1293 }
1294 subform.eval_cache.ensure_active_item_cache(idx);
1296 if let Some(sub_cache) = subform.eval_cache.subform_caches.get_mut(&idx) {
1297 sub_cache.item_snapshot = updated_item;
1298 }
1299 }
1300 }
1301 }
1302 }
1303 Ok(any_table_invalidated)
1304 }
1305
1306 pub(crate) fn evaluate_dependent_value_static(
1308 engine: &RLogic,
1309 evaluations: &IndexMap<String, LogicId>,
1310 eval_data: &EvalData,
1311 value: &Value,
1312 changed_field_value: &Value,
1313 changed_field_ref_value: &Value,
1314 ) -> Result<Value, String> {
1315 match value {
1316 Value::String(eval_key) => {
1318 if let Some(logic_id) = evaluations.get(eval_key) {
1319 let mut internal_context = serde_json::Map::new();
1322 internal_context.insert("$value".to_string(), changed_field_value.clone());
1323 internal_context.insert("$refValue".to_string(), changed_field_ref_value.clone());
1324 let context_value = Value::Object(internal_context);
1325
1326 let result = engine.run_with_context(logic_id, eval_data.data(), &context_value)
1327 .map_err(|e| format!("Failed to evaluate dependent logic '{}': {}", eval_key, e))?;
1328 Ok(result)
1329 } else {
1330 Ok(value.clone())
1332 }
1333 }
1334 Value::Object(map) if map.contains_key("$evaluation") => {
1337 Err("Dependent evaluation contains unparsed $evaluation - schema was not properly parsed".to_string())
1338 }
1339 _ => Ok(value.clone()),
1341 }
1342 }
1343
1344 pub(crate) fn check_readonly_for_dependents(
1346 &self,
1347 schema_element: &Value,
1348 path: &str,
1349 changes: &mut Vec<(String, Value)>,
1350 all_values: &mut Vec<(String, Value)>,
1351 ) {
1352 match schema_element {
1353 Value::Object(map) => {
1354 let mut is_disabled = false;
1356 if let Some(Value::Object(condition)) = map.get("condition") {
1357 if let Some(Value::Bool(d)) = condition.get("disabled") {
1358 is_disabled = *d;
1359 }
1360 }
1361
1362 let mut skip_readonly = false;
1364 if let Some(Value::Object(config)) = map.get("config") {
1365 if let Some(Value::Object(all)) = config.get("all") {
1366 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
1367 skip_readonly = *skip;
1368 }
1369 }
1370 }
1371
1372 if is_disabled && !skip_readonly {
1373 if let Some(schema_value) = map.get("value") {
1374 let data_path = path_utils::schema_path_to_data_pointer(path)
1375 .replace("/value/", "/");
1378
1379 let current_data = self
1380 .eval_data
1381 .data()
1382 .pointer(&data_path)
1383 .unwrap_or(&Value::Null);
1384
1385 all_values.push((path.to_string(), schema_value.clone()));
1388 if current_data != schema_value {
1389 changes.push((path.to_string(), schema_value.clone()));
1390 }
1391 }
1392 }
1393 }
1394 _ => {}
1395 }
1396 }
1397
1398 #[allow(dead_code)]
1400 pub(crate) fn collect_readonly_fixes(
1401 &self,
1402 schema_element: &Value,
1403 path: &str,
1404 changes: &mut Vec<(String, Value)>,
1405 ) {
1406 match schema_element {
1407 Value::Object(map) => {
1408 let mut is_disabled = false;
1410 if let Some(Value::Object(condition)) = map.get("condition") {
1411 if let Some(Value::Bool(d)) = condition.get("disabled") {
1412 is_disabled = *d;
1413 }
1414 }
1415
1416 let mut skip_readonly = false;
1418 if let Some(Value::Object(config)) = map.get("config") {
1419 if let Some(Value::Object(all)) = config.get("all") {
1420 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
1421 skip_readonly = *skip;
1422 }
1423 }
1424 }
1425
1426 if is_disabled && !skip_readonly {
1427 if let Some(schema_value) = map.get("value") {
1431 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1432
1433 let current_data = self
1434 .eval_data
1435 .data()
1436 .pointer(&data_path)
1437 .unwrap_or(&Value::Null);
1438
1439 if current_data != schema_value {
1440 changes.push((path.to_string(), schema_value.clone()));
1441 }
1442 }
1443 }
1444
1445 if let Some(Value::Object(props)) = map.get("properties") {
1447 for (key, val) in props {
1448 let next_path = if path == "#" {
1449 format!("#/properties/{}", key)
1450 } else {
1451 format!("{}/properties/{}", path, key)
1452 };
1453 self.collect_readonly_fixes(val, &next_path, changes);
1454 }
1455 }
1456 }
1457 _ => {}
1458 }
1459 }
1460
1461 pub(crate) fn check_hidden_field(
1463 &self,
1464 schema_element: &Value,
1465 path: &str,
1466 hidden_fields: &mut Vec<String>,
1467 ) {
1468 match schema_element {
1469 Value::Object(map) => {
1470 let mut is_hidden = false;
1472 if let Some(Value::Object(condition)) = map.get("condition") {
1473 if let Some(Value::Bool(h)) = condition.get("hidden") {
1474 is_hidden = *h;
1475 }
1476 }
1477
1478 let mut keep_hidden = false;
1480 if let Some(Value::Object(config)) = map.get("config") {
1481 if let Some(Value::Object(all)) = config.get("all") {
1482 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
1483 keep_hidden = *keep;
1484 }
1485 }
1486 }
1487
1488 if is_hidden && !keep_hidden {
1489 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1490
1491 let current_data = self
1492 .eval_data
1493 .data()
1494 .pointer(&data_path)
1495 .unwrap_or(&Value::Null);
1496
1497 if current_data != &Value::Null && current_data != "" {
1499 hidden_fields.push(path.to_string());
1500 }
1501 }
1502 }
1503 _ => {}
1504 }
1505 }
1506
1507 fn check_effectively_hidden_field(
1509 &self,
1510 schema_element: &Value,
1511 path: &str,
1512 hidden_fields: &mut Vec<String>,
1513 ) {
1514 let Value::Object(map) = schema_element else {
1515 return;
1516 };
1517
1518 let keep_hidden = map
1519 .get("config")
1520 .and_then(Value::as_object)
1521 .and_then(|config| config.get("all"))
1522 .and_then(Value::as_object)
1523 .and_then(|all| all.get("keepHiddenValue"))
1524 .and_then(Value::as_bool)
1525 .unwrap_or(false);
1526 if keep_hidden {
1527 return;
1528 }
1529
1530 let current_data = self
1531 .eval_data
1532 .data()
1533 .pointer(&path_utils::schema_path_to_data_pointer(path))
1534 .unwrap_or(&Value::Null);
1535 if current_data != &Value::Null && current_data != "" {
1536 hidden_fields.push(path.to_string());
1537 }
1538 }
1539
1540 #[allow(dead_code)]
1542 pub(crate) fn collect_hidden_fields(
1543 &self,
1544 schema_element: &Value,
1545 path: &str,
1546 hidden_fields: &mut Vec<String>,
1547 ) {
1548 match schema_element {
1549 Value::Object(map) => {
1550 let mut is_hidden = false;
1552 if let Some(Value::Object(condition)) = map.get("condition") {
1553 if let Some(Value::Bool(h)) = condition.get("hidden") {
1554 is_hidden = *h;
1555 }
1556 }
1557
1558 let mut keep_hidden = false;
1560 if let Some(Value::Object(config)) = map.get("config") {
1561 if let Some(Value::Object(all)) = config.get("all") {
1562 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
1563 keep_hidden = *keep;
1564 }
1565 }
1566 }
1567
1568 if is_hidden && !keep_hidden {
1569 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1570
1571 let current_data = self
1572 .eval_data
1573 .data()
1574 .pointer(&data_path)
1575 .unwrap_or(&Value::Null);
1576
1577 if current_data != &Value::Null && current_data != "" {
1579 hidden_fields.push(path.to_string());
1580 }
1581 }
1582
1583 for (key, val) in map {
1585 if key == "properties" {
1586 if let Value::Object(props) = val {
1587 for (p_key, p_val) in props {
1588 let next_path = if path == "#" {
1589 format!("#/properties/{}", p_key)
1590 } else {
1591 format!("{}/properties/{}", path, p_key)
1592 };
1593 self.collect_hidden_fields(p_val, &next_path, hidden_fields);
1594 }
1595 }
1596 } else if let Value::Object(_) = val {
1597 if key == "condition"
1599 || key == "config"
1600 || key == "rules"
1601 || key == "dependents"
1602 || key == "hideLayout"
1603 || key == "$layout"
1604 || key == "$params"
1605 || key == "definitions"
1606 || key == "$defs"
1607 || key.starts_with('$')
1608 {
1609 continue;
1610 }
1611
1612 let next_path = if path == "#" {
1613 format!("#/{}", key)
1614 } else {
1615 format!("{}/{}", path, key)
1616 };
1617 self.collect_hidden_fields(val, &next_path, hidden_fields);
1618 }
1619 }
1620 }
1621 _ => {}
1622 }
1623 }
1624
1625 pub(crate) fn recursive_hide_effect(
1628 engine: &RLogic,
1629 evaluations: &IndexMap<String, LogicId>,
1630 reffed_by: &IndexMap<String, Vec<String>>,
1631 eval_data: &mut EvalData,
1632 eval_cache: &mut crate::jsoneval::eval_cache::EvalCache,
1633 mut hidden_fields: Vec<String>,
1634 queue: &mut Vec<(String, bool, Option<Vec<usize>>)>,
1635 result: &mut Vec<Value>,
1636 ) {
1637 while let Some(hf) = hidden_fields.pop() {
1638 let data_path = path_utils::schema_path_to_data_pointer(&hf).into_owned();
1639
1640 eval_data.set(&data_path, Value::Null);
1642 eval_cache.bump_data_version(&data_path);
1643
1644 let mut change_obj = serde_json::Map::new();
1646 change_obj.insert(
1647 "$ref".to_string(),
1648 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
1649 );
1650 change_obj.insert("$hidden".to_string(), Value::Bool(true));
1651 change_obj.insert("clear".to_string(), Value::Bool(true));
1652 result.push(Value::Object(change_obj));
1653
1654 queue.push((hf.clone(), true, None));
1656
1657 if let Some(referencing_fields) = reffed_by.get(&data_path) {
1659 for rb in referencing_fields {
1660 let hidden_eval_key = format!("{}/condition/hidden", rb);
1664
1665 if let Some(logic_id) = evaluations.get(&hidden_eval_key) {
1666 let rb_data_path = path_utils::schema_path_to_data_pointer(rb).into_owned();
1673 let rb_value = eval_data
1674 .data()
1675 .pointer(&rb_data_path)
1676 .cloned()
1677 .unwrap_or(Value::Null);
1678
1679 if let Ok(Value::Bool(is_hidden)) = engine.run(logic_id, eval_data.data()) {
1681 if is_hidden {
1682 if !hidden_fields.contains(rb) {
1685 let has_value = rb_value != Value::Null && rb_value != "";
1686 if has_value {
1687 hidden_fields.push(rb.clone());
1688 }
1689 }
1690 }
1691 }
1692 }
1693 }
1694 }
1695 }
1696 }
1697
1698 pub(crate) fn process_dependents_queue(
1701 engine: &RLogic,
1702 evaluations: &IndexMap<String, LogicId>,
1703 eval_data: &mut EvalData,
1704 eval_cache: &mut crate::jsoneval::eval_cache::EvalCache,
1705 dependents_evaluations: &IndexMap<String, Vec<DependentItem>>,
1706 dep_formula_triggers: &IndexMap<String, Vec<(String, usize)>>,
1707 evaluated_schema: &Value,
1708 queue: &mut Vec<(String, bool, Option<Vec<usize>>)>,
1709 processed: &mut std::collections::HashMap<String, Option<std::collections::HashSet<usize>>>,
1710 result: &mut Vec<Value>,
1711 token: Option<&CancellationToken>,
1712 canceled_paths: Option<&mut Vec<String>>,
1713 ) -> Result<(), String> {
1714 while let Some((current_path, is_transitive, target_indices)) = queue.pop() {
1715 if let Some(t) = token {
1716 if t.is_cancelled() {
1717 if let Some(cp) = canceled_paths {
1718 cp.push(current_path.clone());
1719 for (path, _, _) in queue.iter() {
1720 cp.push(path.clone());
1721 }
1722 }
1723 return Err("Cancelled".to_string());
1724 }
1725 }
1726
1727 let (should_run, indices_to_run) = match processed.get(¤t_path) {
1728 Some(None) => {
1729 continue;
1731 }
1732 Some(Some(already_processed_indices)) => {
1733 if let Some(targets) = &target_indices {
1734 let new_targets: std::collections::HashSet<usize> = targets
1735 .iter()
1736 .copied()
1737 .filter(|i| !already_processed_indices.contains(i))
1738 .collect();
1739 if new_targets.is_empty() {
1740 continue;
1741 }
1742 (true, Some(new_targets))
1743 } else {
1744 (true, None)
1745 }
1746 }
1747 None => (
1748 true,
1749 target_indices.clone().map(|t| t.into_iter().collect()),
1750 ),
1751 };
1752
1753 if !should_run {
1754 continue;
1755 }
1756
1757 let new_processed_state = if let Some(targets_to_run) = &indices_to_run {
1758 match processed.get(¤t_path) {
1759 Some(Some(existing_targets)) => {
1760 let mut copy = existing_targets.clone();
1761 for t in targets_to_run {
1762 copy.insert(*t);
1763 }
1764 Some(copy)
1765 }
1766 _ => Some(targets_to_run.clone()),
1767 }
1768 } else {
1769 None
1770 };
1771 processed.insert(current_path.clone(), new_processed_state);
1772
1773 let current_data_path =
1775 path_utils::schema_path_to_data_pointer(¤t_path).into_owned();
1776 let mut current_value = eval_data
1777 .data()
1778 .pointer(¤t_data_path)
1779 .cloned()
1780 .unwrap_or(Value::Null);
1781
1782 if let Some(formula_sources) = dep_formula_triggers.get(¤t_data_path) {
1787 let mut targets_by_source: std::collections::HashMap<String, Vec<usize>> =
1788 std::collections::HashMap::new();
1789 for (source_schema_path, dep_idx) in formula_sources {
1790 let source_ptr = path_utils::dot_notation_to_schema_pointer(source_schema_path);
1791 targets_by_source
1792 .entry(source_ptr)
1793 .or_default()
1794 .push(*dep_idx);
1795 }
1796 for (source_ptr, targets) in targets_by_source {
1797 if let Some(None) = processed.get(&source_ptr) {
1799 continue;
1800 }
1801 queue.push((source_ptr, true, Some(targets)));
1802 }
1803 }
1804
1805 if let Some(dependent_items) = dependents_evaluations.get(¤t_path) {
1807 for (dep_idx, dep_item) in dependent_items.iter().enumerate() {
1808 if let Some(targets) = &indices_to_run {
1809 if !targets.contains(&dep_idx) {
1810 continue;
1811 }
1812 }
1813 let ref_path = &dep_item.ref_path;
1814 let pointer_path = path_utils::normalize_to_json_pointer(ref_path);
1815 let data_path =
1817 crate::jsoneval::path_utils::schema_path_to_data_pointer(&pointer_path)
1818 .into_owned();
1819
1820 let current_ref_value = eval_data
1821 .data()
1822 .pointer(&data_path)
1823 .cloned()
1824 .unwrap_or(Value::Null);
1825
1826 let field = evaluated_schema.pointer(&pointer_path).cloned();
1828
1829 let parent_path = if let Some(last_slash) = pointer_path.rfind("/properties") {
1831 &pointer_path[..last_slash]
1832 } else {
1833 "/"
1834 };
1835 let mut parent_field = if parent_path.is_empty() || parent_path == "/" {
1836 evaluated_schema.clone()
1837 } else {
1838 evaluated_schema
1839 .pointer(parent_path)
1840 .cloned()
1841 .unwrap_or_else(|| Value::Object(serde_json::Map::new()))
1842 };
1843
1844 if let Value::Object(ref mut map) = parent_field {
1846 map.remove("properties");
1847 map.remove("$layout");
1848 }
1849
1850 let mut change_obj = serde_json::Map::new();
1851 change_obj.insert(
1852 "$ref".to_string(),
1853 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
1854 );
1855 if let Some(f) = field {
1856 change_obj.insert("$field".to_string(), f);
1857 }
1858 change_obj.insert("$parentField".to_string(), parent_field);
1859 change_obj.insert("transitive".to_string(), Value::Bool(is_transitive));
1860
1861 if processed.contains_key(ref_path) {
1866 continue;
1867 }
1868
1869 let mut add_transitive = false;
1870 let mut add_deps = false;
1871 if let Some(clear_val) = &dep_item.clear {
1873 let should_clear = Self::evaluate_dependent_value_static(
1874 engine,
1875 evaluations,
1876 eval_data,
1877 clear_val,
1878 ¤t_value,
1879 ¤t_ref_value,
1880 )?;
1881 let clear_bool = match should_clear {
1882 Value::Bool(b) => b,
1883 _ => false,
1884 };
1885
1886 if clear_bool {
1887 if data_path == current_data_path {
1888 current_value = Value::Null;
1889 }
1890 eval_data.set(&data_path, Value::Null);
1891 eval_cache.bump_data_version(&data_path);
1892 change_obj.insert("clear".to_string(), Value::Bool(true));
1893 add_transitive = true;
1894 add_deps = true;
1895 }
1896 }
1897
1898 if let Some(value_val) = &dep_item.value {
1900 let computed_value = Self::evaluate_dependent_value_static(
1901 engine,
1902 evaluations,
1903 eval_data,
1904 value_val,
1905 ¤t_value,
1906 ¤t_ref_value,
1907 )?;
1908 let cleaned_val = clean_float_noise_scalar(computed_value);
1909
1910 let is_clear =
1911 cleaned_val == Value::Null || cleaned_val.as_str() == Some("");
1912
1913 if cleaned_val != current_ref_value && !is_clear {
1914 if data_path == current_data_path {
1915 current_value = cleaned_val.clone();
1916 }
1917 eval_data.set(&data_path, cleaned_val.clone());
1918 eval_cache.bump_data_version(&data_path);
1919 change_obj.insert("value".to_string(), cleaned_val);
1920 add_transitive = true;
1921 add_deps = true;
1922 }
1923 }
1924
1925 if add_deps {
1927 result.push(Value::Object(change_obj));
1928 }
1929
1930 if add_transitive {
1932 queue.push((ref_path.clone(), true, None));
1933 }
1934 }
1935 }
1936 }
1937 Ok(())
1938 }
1939}
1940
1941fn subform_field_key(subform_path: &str) -> String {
1948 let stripped = subform_path.trim_start_matches('#').trim_start_matches('/');
1950
1951 stripped
1953 .split('/')
1954 .filter(|seg| !seg.is_empty() && *seg != "properties")
1955 .last()
1956 .unwrap_or(stripped)
1957 .to_string()
1958}