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;
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 _static_guard = self
37 .engine
38 .bind_static_arrays_scope(std::sync::Arc::clone(&self.static_arrays));
39 let mut structural_change_data = None;
40
41 if let Some(data_str) = data {
43 let data_value = json_parser::parse_json_str(data_str)?;
44 let context_value = if let Some(ctx) = context {
45 json_parser::parse_json_str(ctx)?
46 } else {
47 Value::Object(serde_json::Map::new())
48 };
49 let old_data = self.eval_data.snapshot_data();
50 time_block!(" [dep] data_replace_and_context", {
51 self.eval_data
52 .replace_data_and_context(data_value, context_value);
53 });
54 let new_data = self.eval_data.snapshot_data();
55 time_block!(" [dep] data_diff_versions", {
56 self.eval_cache
57 .store_snapshot_and_diff_versions(&old_data, &new_data);
58 });
59 structural_change_data = Some((old_data, new_data));
60 }
61
62 drop(_lock);
64
65 if let Some((old_data, new_data)) = structural_change_data {
69 time_block!(" [dep] invalidate_subform_structural", {
70 self.invalidate_subform_caches_on_structural_change(&old_data, &new_data);
71 });
72 }
73
74 let mut result = Vec::new();
75 let mut processed = std::collections::HashMap::new();
76 let mut to_process: Vec<(String, bool, Option<Vec<usize>>)> = changed_paths
77 .iter()
78 .map(|path| {
79 (
80 path_utils::dot_notation_to_schema_pointer(path),
81 false,
82 None,
83 )
84 })
85 .collect();
86
87 time_block!(" [dep] process_dependents_queue", {
88 Self::process_dependents_queue(
89 &self.engine,
90 &self.evaluations,
91 &mut self.eval_data,
92 &mut self.eval_cache,
93 &self.dependents_evaluations,
94 &self.dep_formula_triggers,
95 &self.evaluated_schema,
96 &mut to_process,
97 &mut processed,
98 &mut result,
99 token,
100 canceled_paths.as_mut().map(|v| &mut **v),
101 )?;
102 });
103
104 if re_evaluate {
105 time_block!(" [dep] run_re_evaluate_pass", {
106 self.run_re_evaluate_pass(
107 token,
108 &mut to_process,
109 &mut processed,
110 &mut result,
111 canceled_paths.as_mut().map(|v| &mut **v),
112 )?;
113 });
114 }
115
116 if include_subforms {
117 let collection_refresh_paths: Vec<String> = self
121 .subforms
122 .keys()
123 .filter_map(|subform_path| {
124 let dot_path = path_utils::pointer_to_dot_notation(subform_path)
125 .replace(".properties.", ".");
126 let collection_changed = changed_paths
127 .iter()
128 .any(|path| path == &dot_path || path == &subform_field_key(subform_path));
129 collection_changed.then(|| {
130 let data_path = path_utils::schema_path_to_data_pointer(subform_path);
131 self.eval_data
132 .data()
133 .pointer(&data_path)
134 .and_then(Value::as_array)
135 .map(|items| {
136 (0..items.len())
137 .map(|idx| format!("{dot_path}.{idx}"))
138 .collect::<Vec<_>>()
139 })
140 })
141 })
142 .flatten()
143 .flatten()
144 .collect();
145
146 let extended_paths: Vec<String> = {
152 let mut paths = changed_paths.to_vec();
153 for item in &result {
154 if let Some(ref_val) = item.get("$ref").and_then(|v| v.as_str()) {
155 let s = ref_val.to_string();
156 if !paths.contains(&s) {
157 paths.push(s);
158 }
159 }
160 }
161 paths.extend(collection_refresh_paths);
162 paths
163 };
164 let subform_invalidated_tables = time_block!(" [dep] run_subform_pass", {
165 self.run_subform_pass(
166 &extended_paths,
167 changed_paths,
168 re_evaluate,
169 token,
170 &mut result,
171 )
172 })?;
173
174 if subform_invalidated_tables {
176 let _lock2 = self.eval_lock.lock().unwrap();
177 drop(_lock2);
178 self.evaluate_internal(None, token)?;
179
180 self.run_subform_pass(&extended_paths, changed_paths, true, token, &mut result)?;
182
183 for (subform_path, _) in &self.subforms {
185 let data_ptr = path_utils::schema_path_to_data_pointer(subform_path);
186 let data_ptr_str = data_ptr.to_string();
187 let dot_path = data_ptr_str.trim_start_matches('/').replace('/', ".");
188
189 if let Some(fresh_val) = self.eval_data.get(&data_ptr_str) {
190 let mut patched = false;
191 for item in result.iter_mut() {
192 if item
193 .get("$ref")
194 .and_then(|r| r.as_str())
195 .map(|r| r == dot_path)
196 .unwrap_or(false)
197 {
198 if let Some(map) = item.as_object_mut() {
199 map.remove("clear");
200 map.insert("value".to_string(), fresh_val.clone());
201 }
202 patched = true;
203 break;
204 }
205 }
206 if !patched {
207 let mut obj = serde_json::Map::new();
208 obj.insert("$ref".to_string(), serde_json::Value::String(dot_path));
209 obj.insert("value".to_string(), fresh_val.clone());
210 result.push(serde_json::Value::Object(obj));
211 }
212 }
213 }
214 }
215 }
216
217 let deduped = {
222 let mut seen = std::collections::HashSet::new();
223 let mut deduped = Vec::with_capacity(result.len());
224 for item in result.into_iter().rev() {
225 if let Some(r) = item.get("$ref").and_then(|v| v.as_str()) {
226 if seen.insert(r.to_string()) {
227 deduped.push(item);
228 }
229 } else {
230 deduped.push(item);
231 }
232 }
233 deduped.reverse();
234 deduped
235 };
236
237 if self.eval_cache.active_item_index.is_none() {
239 let current_snapshot = self.eval_data.snapshot_data();
240 self.eval_cache.main_form_snapshot = Some(current_snapshot);
241 }
242
243 Ok(Value::Array(deduped))
244 }
245
246 fn run_re_evaluate_pass(
249 &mut self,
250 token: Option<&CancellationToken>,
251 to_process: &mut Vec<(String, bool, Option<Vec<usize>>)>,
252 processed: &mut std::collections::HashMap<String, Option<std::collections::HashSet<usize>>>,
253 result: &mut Vec<Value>,
254 mut canceled_paths: Option<&mut Vec<String>>,
255 ) -> Result<(), String> {
256 self.run_schema_default_value_pass(
258 token,
259 to_process,
260 processed,
261 result,
262 canceled_paths.as_mut().map(|v| &mut **v),
263 )?;
264
265 let pre_eval_versions = if let Some(idx) = self.eval_cache.active_item_index {
271 self.eval_cache
272 .subform_caches
273 .get(&idx)
274 .map(|c| c.data_versions.clone())
275 .unwrap_or_else(|| self.eval_cache.data_versions.clone())
276 } else {
277 self.eval_cache.data_versions.clone()
278 };
279
280 self.evaluate_internal(None, token)?;
281
282 if self.run_schema_default_value_pass(
288 token,
289 to_process,
290 processed,
291 result,
292 canceled_paths.as_mut().map(|v| &mut **v),
293 )? {
294 self.evaluate_internal(None, token)?;
295 }
296
297 let active_idx = self.eval_cache.active_item_index;
299 for eval_key in self.sorted_evaluations.iter().flatten() {
300 if eval_key.contains("/$params/") || eval_key.contains("/$") {
301 continue;
302 }
303
304 let schema_ptr = path_utils::schema_path_to_data_pointer(eval_key);
305 let data_path = schema_ptr.trim_start_matches('/').to_string();
306
307 let version_path = format!("/{}", data_path);
308 let old_ver = pre_eval_versions.get(&version_path);
309 let new_ver = if let Some(idx) = active_idx {
310 self.eval_cache
311 .subform_caches
312 .get(&idx)
313 .map(|c| c.data_versions.get(&version_path))
314 .unwrap_or_else(|| self.eval_cache.data_versions.get(&version_path))
315 } else {
316 self.eval_cache.data_versions.get(&version_path)
317 };
318
319 if new_ver > old_ver {
320 if let Some(new_val) = self.evaluated_schema.pointer(&schema_ptr) {
321 let dot_path = data_path.trim_end_matches("/value").replace('/', ".");
322 let mut obj = serde_json::Map::new();
323 obj.insert("$ref".to_string(), Value::String(dot_path));
324 let is_clear = new_val == &Value::Null || new_val.as_str() == Some("");
325 if is_clear {
326 obj.insert("clear".to_string(), Value::Bool(true));
327 } else {
328 obj.insert("value".to_string(), new_val.clone());
329 }
330 result.push(Value::Object(obj));
331 }
332 }
333 }
334
335 let mut readonly_changes = Vec::new();
337 let mut readonly_values = Vec::new();
338 for path in self.conditional_readonly_fields.iter() {
339 let normalized = path_utils::normalize_to_json_pointer(path);
340 if let Some(schema_el) = self.evaluated_schema.pointer(&normalized) {
341 self.check_readonly_for_dependents(
342 schema_el,
343 path,
344 &mut readonly_changes,
345 &mut readonly_values,
346 );
347 }
348 }
349 let had_actual_readonly_changes = !readonly_changes.is_empty();
352
353 let subform_data_paths: std::collections::HashSet<String> = self
359 .subforms
360 .keys()
361 .map(|p| {
362 path_utils::schema_path_to_data_pointer(p)
363 .replace("/value/", "/")
364 .to_string()
365 })
366 .collect();
367
368 for (path, schema_value) in readonly_changes {
369 let data_path = path_utils::schema_path_to_data_pointer(&path).replace("/value/", "/");
370
371 if subform_data_paths.contains(&data_path) {
372 if let (Value::Array(schema_items), Some(Value::Array(existing_items))) = (
374 &schema_value,
375 self.eval_data.data().pointer(&data_path).cloned().as_ref(),
376 ) {
377 let mut merged_items = existing_items.clone();
378 for (i, schema_item) in schema_items.iter().enumerate() {
379 if let (Some(existing), Value::Object(schema_map)) =
380 (merged_items.get_mut(i), schema_item)
381 {
382 if let Some(existing_map) = existing.as_object_mut() {
383 for (k, v) in schema_map {
384 if !v.is_object() {
385 existing_map.insert(k.clone(), v.clone());
386 }
387 }
388 }
389 } else if i >= merged_items.len() {
390 merged_items.push(schema_item.clone());
391 }
392 }
393 self.eval_data.set(&data_path, Value::Array(merged_items));
394 } else {
395 self.eval_data.set(&data_path, schema_value.clone());
396 }
397 self.eval_cache.bump_data_version(&data_path);
398 to_process.push((path, true, None));
399 continue;
400 }
401
402 self.eval_data.set(&data_path, schema_value.clone());
403 self.eval_cache.bump_data_version(&data_path);
404 to_process.push((path, true, None));
405 }
406 if had_actual_readonly_changes {
412 let readonly_dep_prefixes: Vec<String> = to_process
413 .iter()
414 .map(|(path, _, _)| path.trim_start_matches('#').to_string())
415 .collect();
416 let params_table_keys: Vec<String> = self
417 .table_metadata
418 .keys()
419 .filter(|key| key.starts_with("#/$params"))
420 .filter(|key| {
421 self.dependencies
422 .get(*key)
423 .map(|deps| {
424 deps.iter().any(|dep| {
425 readonly_dep_prefixes.iter().any(|readonly| {
426 dep == readonly
427 || dep
428 .strip_prefix(readonly)
429 .is_some_and(|suffix| suffix.starts_with('/'))
430 })
431 })
432 })
433 .unwrap_or(false)
434 })
435 .cloned()
436 .collect();
437
438 if !params_table_keys.is_empty() {
439 if let Some(active_idx) = self.eval_cache.active_item_index {
440 self.eval_cache
441 .invalidate_params_tables_for_item(active_idx, ¶ms_table_keys);
442 }
443 self.evaluate_internal(None, token)?;
444
445 readonly_values.clear();
449 for path in self.conditional_readonly_fields.iter() {
450 let normalized = path_utils::normalize_to_json_pointer(path);
451 if let Some(schema_el) = self.evaluated_schema.pointer(&normalized) {
452 self.check_readonly_for_dependents(
453 schema_el,
454 path,
455 &mut Vec::new(),
456 &mut readonly_values,
457 );
458 }
459 }
460 }
461 }
462
463 for (path, schema_value) in readonly_values {
464 let data_path = path_utils::schema_path_to_data_pointer(&path).replace("/value/", "/");
465 let mut obj = serde_json::Map::new();
466 obj.insert(
467 "$ref".to_string(),
468 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
469 );
470 obj.insert("$readonly".to_string(), Value::Bool(true));
471 let is_clear = schema_value == Value::Null || schema_value.as_str() == Some("");
472 if is_clear {
473 obj.insert("clear".to_string(), Value::Bool(true));
474 } else {
475 obj.insert("value".to_string(), schema_value);
476 }
477 result.push(Value::Object(obj));
478 }
479
480 if !to_process.is_empty() {
481 Self::process_dependents_queue(
482 &self.engine,
483 &self.evaluations,
484 &mut self.eval_data,
485 &mut self.eval_cache,
486 &self.dependents_evaluations,
487 &self.dep_formula_triggers,
488 &self.evaluated_schema,
489 to_process,
490 processed,
491 result,
492 token,
493 canceled_paths.as_mut().map(|v| &mut **v),
494 )?;
495 }
496
497 self.ensure_layout_resolved();
502
503 let mut hidden_fields = Vec::new();
504 for path in self.conditional_hidden_fields.iter() {
505 let normalized = path_utils::normalize_to_json_pointer(path);
506 if let Some(schema_el) = self.evaluated_schema.pointer(&normalized) {
507 self.check_hidden_field(schema_el, path, &mut hidden_fields);
508 }
509 }
510 let layout_condition_hidden_refs = {
511 let state = self.layout_state.read().unwrap();
512 state.layout_condition_hidden_refs.clone()
513 };
514 for path in layout_condition_hidden_refs.iter() {
515 if let Some(schema_el) = self.evaluated_schema.pointer(path) {
516 self.check_effectively_hidden_field(schema_el, path, &mut hidden_fields);
517 }
518 }
519 hidden_fields.sort();
520 hidden_fields.dedup();
521 if !hidden_fields.is_empty() {
522 Self::recursive_hide_effect(
523 &self.engine,
524 &self.evaluations,
525 &self.reffed_by,
526 &mut self.eval_data,
527 &mut self.eval_cache,
528 hidden_fields,
529 to_process,
530 result,
531 );
532 }
533 if !to_process.is_empty() {
534 Self::process_dependents_queue(
535 &self.engine,
536 &self.evaluations,
537 &mut self.eval_data,
538 &mut self.eval_cache,
539 &self.dependents_evaluations,
540 &self.dep_formula_triggers,
541 &self.evaluated_schema,
542 to_process,
543 processed,
544 result,
545 token,
546 canceled_paths.as_mut().map(|v| &mut **v),
547 )?;
548 }
549
550 Ok(())
551 }
552
553 fn collect_visible_static_defaults(&self) -> Vec<(String, Value, String)> {
555 let mut defaults = Vec::new();
556
557 for eval_key in self.value_evaluations.iter() {
558 let clean_key = eval_key.strip_prefix('#').unwrap_or(eval_key);
559
560 if clean_key.starts_with("/$params")
562 || (clean_key.ends_with("/value")
563 && (clean_key.contains("/rules/") || clean_key.contains("/options/")))
564 {
565 continue;
566 }
567
568 let schema_path = clean_key.strip_suffix("/value").unwrap_or(&clean_key);
569 if self.is_schema_effective_hidden(schema_path) {
570 continue;
571 }
572
573 let dotted_path = clean_key
574 .replace("/properties", "")
575 .replace("/value", "")
576 .trim_start_matches('/')
577 .replace('/', ".");
578
579 if dotted_path.is_empty() {
580 continue;
581 }
582
583 let schema_val = match self.resolve_static_markers_at_path(clean_key) {
584 Some(v) => crate::utils::clean_float_noise(v),
585 None => continue,
586 };
587
588 let schema_ptr = path_utils::dot_notation_to_schema_pointer(&dotted_path);
589 if let Some(Value::Object(schema_node)) = self
590 .evaluated_schema
591 .pointer(schema_ptr.trim_start_matches('#'))
592 {
593 if let Some(Value::Object(condition)) = schema_node.get("condition") {
594 if let Some(hidden_val) = condition.get("hidden") {
595 if !hidden_val.is_boolean() || hidden_val.as_bool() == Some(true) {
596 continue;
597 }
598 }
599 }
600 }
601
602 let data_path = dotted_path.replace('.', "/");
603 let current_data = self
604 .eval_data
605 .data()
606 .pointer(&format!("/{}", data_path))
607 .unwrap_or(&Value::Null);
608 let is_empty = matches!(current_data, Value::Null)
609 || matches!(current_data, Value::String(s) if s.is_empty());
610 let is_schema_val_empty = matches!(schema_val, Value::Null)
611 || matches!(schema_val, Value::String(ref s) if s.is_empty())
612 || matches!(schema_val, Value::Object(ref map) if map.contains_key("$evaluation"));
613
614 if is_empty && !is_schema_val_empty && current_data != &schema_val {
615 defaults.push((data_path, schema_val, dotted_path));
616 }
617 }
618
619 defaults
620 }
621
622 pub(crate) fn apply_visible_static_defaults(&mut self) -> bool {
623 let defaults = self.collect_visible_static_defaults();
624 for (data_path, schema_val, _) in &defaults {
625 self.eval_data
626 .set(&format!("/{}", data_path), schema_val.clone());
627 self.eval_cache
628 .bump_data_version(&format!("/{}", data_path));
629 }
630 !defaults.is_empty()
631 }
632
633 pub(crate) fn apply_visible_static_defaults_with_dependents(
639 &mut self,
640 token: Option<&CancellationToken>,
641 ) -> Result<bool, String> {
642 let mut to_process = Vec::new();
643 let mut processed = std::collections::HashMap::new();
644 let mut result = Vec::new();
645 self.run_schema_default_value_pass(
646 token,
647 &mut to_process,
648 &mut processed,
649 &mut result,
650 None,
651 )
652 }
653
654 fn run_schema_default_value_pass(
657 &mut self,
658 _token: Option<&CancellationToken>,
659 _to_process: &mut Vec<(String, bool, Option<Vec<usize>>)>,
660 _processed: &mut std::collections::HashMap<
661 String,
662 Option<std::collections::HashSet<usize>>,
663 >,
664 result: &mut Vec<Value>,
665 _canceled_paths: Option<&mut Vec<String>>,
666 ) -> Result<bool, String> {
667 let default_value_changes = self.collect_visible_static_defaults();
668 if default_value_changes.is_empty() {
669 return Ok(false);
670 }
671
672 for (data_path, schema_val, dot_path) in default_value_changes {
673 self.eval_data
674 .set(&format!("/{}", data_path), schema_val.clone());
675 self.eval_cache
676 .bump_data_version(&format!("/{}", data_path));
677
678 let mut change_obj = serde_json::Map::new();
679 change_obj.insert("$ref".to_string(), Value::String(dot_path));
680 let is_clear = schema_val == Value::Null || schema_val.as_str() == Some("");
681 if is_clear {
682 change_obj.insert("clear".to_string(), Value::Bool(true));
683 } else {
684 change_obj.insert("value".to_string(), schema_val);
685 }
686 result.push(Value::Object(change_obj));
687
688 }
693
694 Ok(true)
695 }
696
697 fn run_subform_pass(
707 &mut self,
708 changed_paths: &[String],
709 parent_changed_paths: &[String],
710 re_evaluate: bool,
711 token: Option<&CancellationToken>,
712 result: &mut Vec<Value>,
713 ) -> Result<bool, String> {
714 let mut any_table_invalidated = false;
715 let subform_paths: Vec<String> = self.subforms.keys().cloned().collect();
717 self.eval_cache.subform_roots = subform_paths
718 .iter()
719 .map(|p| format!("/{}", subform_field_key(p)))
720 .collect();
721
722 for subform_path in subform_paths {
723 let field_key = subform_field_key(&subform_path);
724 let subform_dot_path =
726 path_utils::pointer_to_dot_notation(&subform_path).replace(".properties.", ".");
727 let field_prefix = format!("{}.", field_key);
728 let subform_ptr = normalize_to_json_pointer(&subform_path);
729
730 let item_count =
732 get_value_by_pointer_without_properties(self.eval_data.data(), &subform_ptr)
733 .and_then(|v| v.as_array())
734 .map(|a| a.len())
735 .unwrap_or(0);
736
737 if item_count == 0 {
738 continue;
739 }
740
741 self.eval_cache.prune_subform_caches(item_count);
744
745 let parent_data_versions_snapshot = self.eval_cache.data_versions.clone();
750 let parent_params_versions_snapshot = self.eval_cache.params_versions.clone();
751
752 let mut parent_affected: std::collections::HashSet<String> = parent_changed_paths
757 .iter()
758 .filter(|path| {
759 !path.starts_with(&subform_dot_path)
760 && !path.starts_with(&field_prefix)
761 && !path.starts_with(&format!("{}.", field_key))
762 })
763 .map(|path| {
764 path_utils::dot_notation_to_schema_pointer(path)
765 .trim_start_matches('#')
766 .trim_start_matches('/')
767 .to_string()
768 })
769 .collect();
770
771 let mut queue: std::collections::VecDeque<String> =
774 parent_affected.iter().cloned().collect();
775 while let Some(current) = queue.pop_front() {
776 for (target, deps) in self.dependencies.iter() {
777 let clean_target = target.trim_start_matches('#').trim_start_matches('/');
778 if !parent_affected.contains(clean_target) {
779 let is_affected = deps.iter().any(|dep| {
780 let clean_dep = dep.trim_start_matches('#').trim_start_matches('/');
781 clean_dep == current
782 || (current.starts_with(clean_dep)
783 && current.as_bytes().get(clean_dep.len()) == Some(&b'/'))
784 });
785 if is_affected {
786 let target_str = clean_target.to_string();
787 parent_affected.insert(target_str.clone());
788 queue.push_back(target_str);
789 }
790 }
791 }
792 }
793
794 let dependent_value_paths: Vec<String> = self
795 .subforms
796 .get(&subform_path)
797 .map(|subform| {
798 subform
799 .dependencies
800 .iter()
801 .filter(|(key, deps)| {
802 !subform.table_metadata.contains_key(*key)
803 && !key.starts_with("#/$params/")
804 && key.ends_with("/value")
805 && deps.iter().any(|dep| {
806 let clean_dep =
807 dep.trim_start_matches('#').trim_start_matches('/');
808 parent_affected.contains(clean_dep)
809 || parent_affected.iter().any(|p| {
810 p.starts_with(clean_dep)
811 && p.as_bytes().get(clean_dep.len()) == Some(&b'/')
812 })
813 })
814 })
815 .map(|(key, _)| key.clone())
816 .collect()
817 })
818 .unwrap_or_default();
819
820 let refresh_table_outputs = self
822 .subforms
823 .get(&subform_path)
824 .map(|subform| {
825 dependent_value_paths.iter().any(|source| {
826 let source_clean =
827 source.trim_end_matches("/value").trim_start_matches('#');
828 let affected_tables: Vec<&String> = subform
829 .table_metadata
830 .keys()
831 .filter(|table| table.starts_with("#/$params"))
832 .filter(|table| {
833 subform.dependencies.get(*table).is_some_and(|deps| {
834 deps.iter()
835 .any(|dep| dep.trim_start_matches('#') == source_clean)
836 })
837 })
838 .collect();
839
840 !affected_tables.is_empty()
841 && subform.evaluations.iter().any(|(target, _)| {
842 target.ends_with("/value")
843 && subform.dependencies.get(target).is_some_and(|deps| {
844 deps.iter().any(|dep| {
845 affected_tables.iter().any(|table| {
846 dep.trim_start_matches('#')
847 == table.trim_start_matches('#')
848 })
849 })
850 })
851 })
852 })
853 })
854 .unwrap_or(false);
855
856 if let Some(subform) = self.subforms.get_mut(&subform_path) {
858 if let Some(params) = self.evaluated_schema.pointer("/$params") {
859 if let Some(sub_params) = subform.evaluated_schema.pointer_mut("/$params") {
860 *sub_params = params.clone();
861 }
862 }
863 subform.static_arrays = std::sync::Arc::clone(&self.static_arrays);
864 subform
865 .engine
866 .set_static_arrays(std::sync::Arc::clone(&subform.static_arrays));
867 }
868
869 for idx in 0..item_count {
870 let prefix_dot = format!("{}.{}.", subform_dot_path, idx);
872 let prefix_bracket = format!("{}[{}].", subform_dot_path, idx);
873 let prefix_field_bracket = format!("{}[{}].", field_key, idx);
874
875 let is_collection_refresh = changed_paths
876 .iter()
877 .any(|path| path == &format!("{subform_dot_path}.{idx}"));
878 let item_changed_paths: Vec<String> = changed_paths
879 .iter()
880 .filter_map(|p| {
881 if p == &format!("{subform_dot_path}.{idx}") {
882 Some(field_key.clone())
883 } else if p.starts_with(&prefix_bracket) {
884 Some(p.replacen(&prefix_bracket, &field_prefix, 1))
885 } else if p.starts_with(&prefix_dot) {
886 Some(p.replacen(&prefix_dot, &field_prefix, 1))
887 } else if p.starts_with(&prefix_field_bracket) {
888 Some(p.replacen(&prefix_field_bracket, &field_prefix, 1))
889 } else {
890 None
891 }
892 })
893 .collect();
894
895 let item_val =
898 get_value_by_pointer_without_properties(self.eval_data.data(), &subform_ptr)
899 .and_then(|v| v.as_array())
900 .and_then(|a| a.get(idx))
901 .cloned()
902 .unwrap_or(Value::Null);
903
904 if item_changed_paths.is_empty() && !dependent_value_paths.is_empty() {
905 let parent_cache = std::mem::take(&mut self.eval_cache);
910 let mut overlay_cache = parent_cache.clone();
911 overlay_cache.ensure_active_item_cache(idx);
912 if let Some(item_cache) = overlay_cache.subform_caches.get_mut(&idx) {
913 item_cache
917 .data_versions
918 .merge_from(&parent_data_versions_snapshot);
919 item_cache
920 .data_versions
921 .merge_from_params(&parent_params_versions_snapshot);
922 }
923 overlay_cache.set_active_item(idx);
924 let canonical_root =
925 path_utils::schema_path_to_data_pointer(&subform_path).into_owned();
926 let scope = crate::jsoneval::subform_scope::SubformScope::new(
927 &subform_path,
928 &canonical_root,
929 Some(idx),
930 );
931 let mut scoped_view = scope.evaluation_view(self.eval_data.data());
932 if let Some(view) = scoped_view.as_object_mut() {
933 view.insert(
934 "$context".to_string(),
935 self.eval_data
936 .data()
937 .get("$context")
938 .cloned()
939 .unwrap_or(Value::Null),
940 );
941 }
942 let subform = self
943 .subforms
944 .get_mut(&subform_path)
945 .expect("subform exists");
946 subform.eval_data = EvalData::new(scoped_view);
947 std::mem::swap(&mut subform.eval_cache, &mut overlay_cache);
948 subform.evaluate_internal(Some(&dependent_value_paths), token)?;
949
950 let mut overlay_result = Vec::new();
951 let mut overlay_queue = Vec::new();
952 let mut overlay_processed = std::collections::HashMap::new();
953 for formula_path in &dependent_value_paths {
954 let schema_path = path_utils::normalize_to_json_pointer(formula_path);
955 let data_path = path_utils::schema_path_to_data_pointer(formula_path)
956 .replace("/value", "");
957 let Some(value) = subform.evaluated_schema.pointer(&schema_path).cloned()
958 else {
959 continue;
960 };
961
962 let source_path = formula_path.trim_end_matches("/value").to_string();
967 if subform.eval_data.get(&data_path) != Some(&value) {
968 subform.eval_data.set(&data_path, value.clone());
969 subform
970 .eval_data
971 .set(&scope.canonical_path(&data_path), value.clone());
972 subform.eval_cache.bump_data_version(&data_path);
973 }
974 overlay_queue.push((source_path, true, None));
975
976 let mut change = serde_json::Map::new();
977 let field = data_path
978 .trim_start_matches('/')
979 .trim_end_matches("/value")
980 .replace('/', ".");
981 let field = field.strip_prefix(&field_prefix).unwrap_or(&field);
982 change.insert(
983 "$ref".to_string(),
984 Value::String(format!("{}.{}.{}", subform_dot_path, idx, field)),
985 );
986 if value == Value::Null || value.as_str() == Some("") {
987 change.insert("clear".to_string(), Value::Bool(true));
988 } else {
989 change.insert("value".to_string(), value);
990 }
991 result.push(Value::Object(change));
992 }
993
994 Self::process_dependents_queue(
995 &subform.engine,
996 &subform.evaluations,
997 &mut subform.eval_data,
998 &mut subform.eval_cache,
999 &subform.dependents_evaluations,
1000 &subform.dep_formula_triggers,
1001 &subform.evaluated_schema,
1002 &mut overlay_queue,
1003 &mut overlay_processed,
1004 &mut overlay_result,
1005 token,
1006 None,
1007 )?;
1008
1009 let local_item_path = format!("/{field_key}");
1012 if let Some(local_item) = subform.eval_data.get(&local_item_path).cloned() {
1013 subform
1014 .eval_data
1015 .set(&scope.canonical_path(&local_item_path), local_item);
1016 }
1017
1018 if refresh_table_outputs {
1019 subform.run_re_evaluate_pass(
1020 token,
1021 &mut overlay_queue,
1022 &mut overlay_processed,
1023 &mut overlay_result,
1024 None,
1025 )?;
1026 }
1027
1028 for change in overlay_result {
1029 let Some(object) = change.as_object() else {
1030 continue;
1031 };
1032 let Some(Value::String(ref_path)) = object.get("$ref") else {
1033 continue;
1034 };
1035 let local_ref = ref_path.strip_prefix(&field_prefix).unwrap_or(ref_path);
1036 let mut mapped = object.clone();
1037 mapped.insert(
1038 "$ref".to_string(),
1039 Value::String(format!("{}.{}.{}", subform_dot_path, idx, local_ref)),
1040 );
1041 result.push(Value::Object(mapped));
1042 }
1043
1044 std::mem::swap(&mut subform.eval_cache, &mut overlay_cache);
1047 self.eval_cache = parent_cache;
1048 continue;
1049 }
1050
1051 let canonical_root =
1052 path_utils::schema_path_to_data_pointer(&subform_path).into_owned();
1053 let scope = crate::jsoneval::subform_scope::SubformScope::new(
1054 &subform_path,
1055 &canonical_root,
1056 Some(idx),
1057 );
1058 let mut scoped_view = scope.evaluation_view(self.eval_data.data());
1059 if let Some(view) = scoped_view.as_object_mut() {
1060 view.insert(
1061 "$context".to_string(),
1062 self.eval_data
1063 .data()
1064 .get("$context")
1065 .cloned()
1066 .unwrap_or(Value::Null),
1067 );
1068 }
1069 let Some(subform) = self.subforms.get_mut(&subform_path) else {
1070 continue;
1071 };
1072
1073 let sub_re_evaluate = if changed_paths.is_empty() {
1078 re_evaluate
1079 } else {
1080 !item_changed_paths.is_empty()
1081 };
1082 if !sub_re_evaluate && item_changed_paths.is_empty() {
1083 continue;
1084 }
1085
1086 self.eval_cache.ensure_active_item_cache(idx);
1088 let old_item_val = {
1089 let snapshot = self
1090 .eval_cache
1091 .subform_caches
1092 .get(&idx)
1093 .map(|c| c.item_snapshot.clone())
1094 .unwrap_or(Value::Null);
1095
1096 if snapshot == Value::Null {
1097 if let Some(main_snap) = &self.eval_cache.main_form_snapshot {
1098 get_value_by_pointer_without_properties(main_snap, &subform_ptr)
1099 .and_then(|v| v.as_array())
1100 .and_then(|a| a.get(idx))
1101 .cloned()
1102 .unwrap_or(Value::Null)
1103 } else {
1104 Value::Null
1105 }
1106 } else {
1107 snapshot
1108 }
1109 };
1110
1111 subform.eval_data = EvalData::new(scoped_view);
1112 let new_item_val = item_val.clone();
1113
1114 let mut parent_cache = std::mem::take(&mut self.eval_cache);
1116 parent_cache.ensure_active_item_cache(idx);
1117
1118 let pre_diff_item_versions = parent_cache
1121 .subform_caches
1122 .get(&idx)
1123 .map(|c| c.data_versions.clone());
1124
1125 if let Some(c) = parent_cache.subform_caches.get_mut(&idx) {
1126 c.data_versions.merge_from(&parent_data_versions_snapshot);
1130 c.data_versions
1132 .merge_from_params(&parent_params_versions_snapshot);
1133 if !is_collection_refresh {
1134 crate::jsoneval::eval_cache::diff_and_update_versions(
1135 &mut c.data_versions,
1136 &format!("/{}", field_key),
1137 &old_item_val,
1138 &new_item_val,
1139 "run_subform_pass_diff_and_update_versions",
1140 );
1141 }
1142 c.item_snapshot = new_item_val;
1145 }
1146
1147 {
1153 let field_prefix_slash = format!("/{}/", field_key);
1154 let newly_bumped_schema_paths: Vec<String> = if let (Some(ref pre), Some(c)) = (
1155 &pre_diff_item_versions,
1156 parent_cache.subform_caches.get(&idx),
1157 ) {
1158 c.data_versions
1159 .versions()
1160 .filter(|(k, &v)| k.starts_with(&field_prefix_slash) && v > pre.get(k))
1161 .map(|(k, _)| {
1162 let sub = k.trim_start_matches(&field_prefix_slash);
1166 format!(
1167 "/{}/properties/{}",
1168 field_key,
1169 sub.replace('/', "/properties/")
1170 )
1171 })
1172 .collect()
1173 } else {
1174 Vec::new()
1175 };
1176
1177 if !newly_bumped_schema_paths.is_empty() {
1178 let params_table_keys: Vec<String> = self
1179 .table_metadata
1180 .keys()
1181 .filter(|k| k.starts_with("#/$params"))
1182 .filter(|k| {
1183 self.dependencies
1184 .get(*k)
1185 .map(|deps| {
1186 deps.iter().any(|dep| {
1187 let clean_dep = dep.trim_start_matches('#');
1188 newly_bumped_schema_paths.iter().any(|b| {
1189 let clean_b = b.trim_start_matches('#');
1190 clean_dep == clean_b
1191 || clean_dep.starts_with(clean_b)
1192 || b == dep
1193 || dep.starts_with(b.as_str())
1194 })
1195 })
1196 })
1197 .unwrap_or(false)
1198 })
1199 .cloned()
1200 .collect();
1201
1202 if !params_table_keys.is_empty() {
1203 parent_cache.invalidate_params_tables_for_item(idx, ¶ms_table_keys);
1204 any_table_invalidated = true;
1205 }
1206 }
1207 }
1208
1209 parent_cache.set_active_item(idx);
1210 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
1211
1212 let subform_result =
1213 time_block!(" [subform_pass] subform item evaluate_dependents", {
1214 subform.evaluate_dependents(
1215 &item_changed_paths,
1216 None,
1217 None,
1218 sub_re_evaluate,
1219 token,
1220 None,
1221 false,
1222 )
1223 });
1224 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
1226 parent_cache.clear_active_item();
1227
1228 if let Some(parent_item_cache) = self.eval_cache.subform_caches.get(&idx) {
1233 let snapshot = parent_item_cache.item_snapshot.clone();
1234 subform.eval_cache.ensure_active_item_cache(idx);
1235 if let Some(sub_cache) = subform.eval_cache.subform_caches.get_mut(&idx) {
1236 sub_cache.item_snapshot = snapshot;
1237 }
1238 }
1239
1240 self.eval_cache = parent_cache;
1241
1242 if let Ok(Value::Array(changes)) = subform_result {
1243 let mut had_any_change = false;
1244 for change in changes {
1245 if let Some(obj) = change.as_object() {
1246 if let Some(Value::String(ref_path)) = obj.get("$ref") {
1247 let new_ref = if ref_path.starts_with(&field_prefix) {
1249 format!(
1250 "{}.{}.{}",
1251 subform_dot_path,
1252 idx,
1253 &ref_path[field_prefix.len()..]
1254 )
1255 } else {
1256 format!("{}.{}.{}", subform_dot_path, idx, ref_path)
1257 };
1258
1259 if let Some(val) = obj.get("value") {
1265 let data_ptr = format!("/{}", new_ref.replace('.', "/"));
1266 self.eval_data.set(&data_ptr, val.clone());
1267 had_any_change = true;
1268 } else if obj.get("clear").and_then(Value::as_bool) == Some(true) {
1269 let data_ptr = format!("/{}", new_ref.replace('.', "/"));
1270 self.eval_data.set(&data_ptr, Value::Null);
1271 had_any_change = true;
1272 }
1273
1274 let mut new_obj = obj.clone();
1275 new_obj.insert("$ref".to_string(), Value::String(new_ref));
1276 result.push(Value::Object(new_obj));
1277 } else {
1278 result.push(change);
1280 }
1281 }
1282 }
1283
1284 if had_any_change {
1286 let item_path = format!("{}/{}", subform_ptr, idx);
1287 let updated_item = self
1288 .eval_data
1289 .get(&item_path)
1290 .cloned()
1291 .unwrap_or(Value::Null);
1292 if let Some(c) = self.eval_cache.subform_caches.get_mut(&idx) {
1294 c.item_snapshot = updated_item.clone();
1295 }
1296 subform.eval_cache.ensure_active_item_cache(idx);
1298 if let Some(sub_cache) = subform.eval_cache.subform_caches.get_mut(&idx) {
1299 sub_cache.item_snapshot = updated_item;
1300 }
1301 }
1302 }
1303 }
1304 }
1305
1306 Ok(any_table_invalidated)
1307 }
1308
1309 pub(crate) fn evaluate_dependent_value_static(
1311 engine: &RLogic,
1312 evaluations: &IndexMap<String, LogicId>,
1313 eval_data: &EvalData,
1314 value: &Value,
1315 changed_field_value: &Value,
1316 changed_field_ref_value: &Value,
1317 ) -> Result<Value, String> {
1318 match value {
1319 Value::String(eval_key) => {
1321 if let Some(logic_id) = evaluations.get(eval_key) {
1322 let mut internal_context = serde_json::Map::new();
1325 internal_context.insert("$value".to_string(), changed_field_value.clone());
1326 internal_context.insert("$refValue".to_string(), changed_field_ref_value.clone());
1327 let context_value = Value::Object(internal_context);
1328
1329 let result = engine.run_with_context(logic_id, eval_data.data(), &context_value)
1330 .map_err(|e| format!("Failed to evaluate dependent logic '{}': {}", eval_key, e))?;
1331 Ok(result)
1332 } else {
1333 Ok(value.clone())
1335 }
1336 }
1337 Value::Object(map) if map.contains_key("$evaluation") => {
1340 Err("Dependent evaluation contains unparsed $evaluation - schema was not properly parsed".to_string())
1341 }
1342 _ => Ok(value.clone()),
1344 }
1345 }
1346
1347 pub(crate) fn check_readonly_for_dependents(
1349 &self,
1350 schema_element: &Value,
1351 path: &str,
1352 changes: &mut Vec<(String, Value)>,
1353 all_values: &mut Vec<(String, Value)>,
1354 ) {
1355 match schema_element {
1356 Value::Object(map) => {
1357 let mut is_disabled = false;
1359 if let Some(Value::Object(condition)) = map.get("condition") {
1360 if let Some(Value::Bool(d)) = condition.get("disabled") {
1361 is_disabled = *d;
1362 }
1363 }
1364
1365 let mut skip_readonly = false;
1367 if let Some(Value::Object(config)) = map.get("config") {
1368 if let Some(Value::Object(all)) = config.get("all") {
1369 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
1370 skip_readonly = *skip;
1371 }
1372 }
1373 }
1374
1375 if is_disabled && !skip_readonly {
1376 if let Some(schema_value) = map.get("value") {
1377 let data_path = path_utils::schema_path_to_data_pointer(path)
1378 .replace("/value/", "/");
1381
1382 let current_data = self
1383 .eval_data
1384 .data()
1385 .pointer(&data_path)
1386 .unwrap_or(&Value::Null);
1387
1388 all_values.push((path.to_string(), schema_value.clone()));
1391 if current_data != schema_value {
1392 changes.push((path.to_string(), schema_value.clone()));
1393 }
1394 }
1395 }
1396 }
1397 _ => {}
1398 }
1399 }
1400
1401 #[allow(dead_code)]
1403 pub(crate) fn collect_readonly_fixes(
1404 &self,
1405 schema_element: &Value,
1406 path: &str,
1407 changes: &mut Vec<(String, Value)>,
1408 ) {
1409 match schema_element {
1410 Value::Object(map) => {
1411 let mut is_disabled = false;
1413 if let Some(Value::Object(condition)) = map.get("condition") {
1414 if let Some(Value::Bool(d)) = condition.get("disabled") {
1415 is_disabled = *d;
1416 }
1417 }
1418
1419 let mut skip_readonly = false;
1421 if let Some(Value::Object(config)) = map.get("config") {
1422 if let Some(Value::Object(all)) = config.get("all") {
1423 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
1424 skip_readonly = *skip;
1425 }
1426 }
1427 }
1428
1429 if is_disabled && !skip_readonly {
1430 if let Some(schema_value) = map.get("value") {
1434 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1435
1436 let current_data = self
1437 .eval_data
1438 .data()
1439 .pointer(&data_path)
1440 .unwrap_or(&Value::Null);
1441
1442 if current_data != schema_value {
1443 changes.push((path.to_string(), schema_value.clone()));
1444 }
1445 }
1446 }
1447
1448 if let Some(Value::Object(props)) = map.get("properties") {
1450 for (key, val) in props {
1451 let next_path = if path == "#" {
1452 format!("#/properties/{}", key)
1453 } else {
1454 format!("{}/properties/{}", path, key)
1455 };
1456 self.collect_readonly_fixes(val, &next_path, changes);
1457 }
1458 }
1459 }
1460 _ => {}
1461 }
1462 }
1463
1464 pub(crate) fn check_hidden_field(
1466 &self,
1467 schema_element: &Value,
1468 path: &str,
1469 hidden_fields: &mut Vec<String>,
1470 ) {
1471 match schema_element {
1472 Value::Object(map) => {
1473 let mut is_hidden = false;
1475 if let Some(Value::Object(condition)) = map.get("condition") {
1476 if let Some(Value::Bool(h)) = condition.get("hidden") {
1477 is_hidden = *h;
1478 }
1479 }
1480
1481 let mut keep_hidden = false;
1483 if let Some(Value::Object(config)) = map.get("config") {
1484 if let Some(Value::Object(all)) = config.get("all") {
1485 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
1486 keep_hidden = *keep;
1487 }
1488 }
1489 }
1490
1491 if is_hidden && !keep_hidden {
1492 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1493
1494 let current_data = self
1495 .eval_data
1496 .data()
1497 .pointer(&data_path)
1498 .unwrap_or(&Value::Null);
1499
1500 if current_data != &Value::Null && current_data != "" {
1502 hidden_fields.push(path.to_string());
1503 }
1504 }
1505 }
1506 _ => {}
1507 }
1508 }
1509
1510 fn check_effectively_hidden_field(
1512 &self,
1513 schema_element: &Value,
1514 path: &str,
1515 hidden_fields: &mut Vec<String>,
1516 ) {
1517 let Value::Object(map) = schema_element else {
1518 return;
1519 };
1520
1521 let keep_hidden = map
1522 .get("config")
1523 .and_then(Value::as_object)
1524 .and_then(|config| config.get("all"))
1525 .and_then(Value::as_object)
1526 .and_then(|all| all.get("keepHiddenValue"))
1527 .and_then(Value::as_bool)
1528 .unwrap_or(false);
1529 if keep_hidden {
1530 return;
1531 }
1532
1533 let current_data = self
1534 .eval_data
1535 .data()
1536 .pointer(&path_utils::schema_path_to_data_pointer(path))
1537 .unwrap_or(&Value::Null);
1538 if current_data != &Value::Null && current_data != "" {
1539 hidden_fields.push(path.to_string());
1540 }
1541 }
1542
1543 #[allow(dead_code)]
1545 pub(crate) fn collect_hidden_fields(
1546 &self,
1547 schema_element: &Value,
1548 path: &str,
1549 hidden_fields: &mut Vec<String>,
1550 ) {
1551 match schema_element {
1552 Value::Object(map) => {
1553 let mut is_hidden = false;
1555 if let Some(Value::Object(condition)) = map.get("condition") {
1556 if let Some(Value::Bool(h)) = condition.get("hidden") {
1557 is_hidden = *h;
1558 }
1559 }
1560
1561 let mut keep_hidden = false;
1563 if let Some(Value::Object(config)) = map.get("config") {
1564 if let Some(Value::Object(all)) = config.get("all") {
1565 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
1566 keep_hidden = *keep;
1567 }
1568 }
1569 }
1570
1571 if is_hidden && !keep_hidden {
1572 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1573
1574 let current_data = self
1575 .eval_data
1576 .data()
1577 .pointer(&data_path)
1578 .unwrap_or(&Value::Null);
1579
1580 if current_data != &Value::Null && current_data != "" {
1582 hidden_fields.push(path.to_string());
1583 }
1584 }
1585
1586 for (key, val) in map {
1588 if key == "properties" {
1589 if let Value::Object(props) = val {
1590 for (p_key, p_val) in props {
1591 let next_path = if path == "#" {
1592 format!("#/properties/{}", p_key)
1593 } else {
1594 format!("{}/properties/{}", path, p_key)
1595 };
1596 self.collect_hidden_fields(p_val, &next_path, hidden_fields);
1597 }
1598 }
1599 } else if let Value::Object(_) = val {
1600 if key == "condition"
1602 || key == "config"
1603 || key == "rules"
1604 || key == "dependents"
1605 || key == "hideLayout"
1606 || key == "$layout"
1607 || key == "$params"
1608 || key == "definitions"
1609 || key == "$defs"
1610 || key.starts_with('$')
1611 {
1612 continue;
1613 }
1614
1615 let next_path = if path == "#" {
1616 format!("#/{}", key)
1617 } else {
1618 format!("{}/{}", path, key)
1619 };
1620 self.collect_hidden_fields(val, &next_path, hidden_fields);
1621 }
1622 }
1623 }
1624 _ => {}
1625 }
1626 }
1627
1628 pub(crate) fn recursive_hide_effect(
1631 engine: &RLogic,
1632 evaluations: &IndexMap<String, LogicId>,
1633 reffed_by: &IndexMap<String, Vec<String>>,
1634 eval_data: &mut EvalData,
1635 eval_cache: &mut crate::jsoneval::eval_cache::EvalCache,
1636 mut hidden_fields: Vec<String>,
1637 queue: &mut Vec<(String, bool, Option<Vec<usize>>)>,
1638 result: &mut Vec<Value>,
1639 ) {
1640 while let Some(hf) = hidden_fields.pop() {
1641 let data_path = path_utils::schema_path_to_data_pointer(&hf).into_owned();
1642
1643 eval_data.set(&data_path, Value::Null);
1645 eval_cache.bump_data_version(&data_path);
1646
1647 let mut change_obj = serde_json::Map::new();
1649 change_obj.insert(
1650 "$ref".to_string(),
1651 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
1652 );
1653 change_obj.insert("$hidden".to_string(), Value::Bool(true));
1654 change_obj.insert("clear".to_string(), Value::Bool(true));
1655 result.push(Value::Object(change_obj));
1656
1657 queue.push((hf.clone(), true, None));
1659
1660 if let Some(referencing_fields) = reffed_by.get(&data_path) {
1662 for rb in referencing_fields {
1663 let hidden_eval_key = format!("{}/condition/hidden", rb);
1667
1668 if let Some(logic_id) = evaluations.get(&hidden_eval_key) {
1669 let rb_data_path = path_utils::schema_path_to_data_pointer(rb).into_owned();
1676 let rb_value = eval_data
1677 .data()
1678 .pointer(&rb_data_path)
1679 .cloned()
1680 .unwrap_or(Value::Null);
1681
1682 if let Ok(Value::Bool(is_hidden)) = engine.run(logic_id, eval_data.data()) {
1684 if is_hidden {
1685 if !hidden_fields.contains(rb) {
1688 let has_value = rb_value != Value::Null && rb_value != "";
1689 if has_value {
1690 hidden_fields.push(rb.clone());
1691 }
1692 }
1693 }
1694 }
1695 }
1696 }
1697 }
1698 }
1699 }
1700
1701 pub(crate) fn process_dependents_queue(
1704 engine: &RLogic,
1705 evaluations: &IndexMap<String, LogicId>,
1706 eval_data: &mut EvalData,
1707 eval_cache: &mut crate::jsoneval::eval_cache::EvalCache,
1708 dependents_evaluations: &IndexMap<String, Vec<DependentItem>>,
1709 dep_formula_triggers: &IndexMap<String, Vec<(String, usize)>>,
1710 evaluated_schema: &Value,
1711 queue: &mut Vec<(String, bool, Option<Vec<usize>>)>,
1712 processed: &mut std::collections::HashMap<String, Option<std::collections::HashSet<usize>>>,
1713 result: &mut Vec<Value>,
1714 token: Option<&CancellationToken>,
1715 canceled_paths: Option<&mut Vec<String>>,
1716 ) -> Result<(), String> {
1717 while let Some((current_path, is_transitive, target_indices)) = queue.pop() {
1718 if let Some(t) = token {
1719 if t.is_cancelled() {
1720 if let Some(cp) = canceled_paths {
1721 cp.push(current_path.clone());
1722 for (path, _, _) in queue.iter() {
1723 cp.push(path.clone());
1724 }
1725 }
1726 return Err("Cancelled".to_string());
1727 }
1728 }
1729
1730 let (should_run, indices_to_run) = match processed.get(¤t_path) {
1731 Some(None) => {
1732 continue;
1734 }
1735 Some(Some(already_processed_indices)) => {
1736 if let Some(targets) = &target_indices {
1737 let new_targets: std::collections::HashSet<usize> = targets
1738 .iter()
1739 .copied()
1740 .filter(|i| !already_processed_indices.contains(i))
1741 .collect();
1742 if new_targets.is_empty() {
1743 continue;
1744 }
1745 (true, Some(new_targets))
1746 } else {
1747 (true, None)
1748 }
1749 }
1750 None => (
1751 true,
1752 target_indices.clone().map(|t| t.into_iter().collect()),
1753 ),
1754 };
1755
1756 if !should_run {
1757 continue;
1758 }
1759
1760 let new_processed_state = if let Some(targets_to_run) = &indices_to_run {
1761 match processed.get(¤t_path) {
1762 Some(Some(existing_targets)) => {
1763 let mut copy = existing_targets.clone();
1764 for t in targets_to_run {
1765 copy.insert(*t);
1766 }
1767 Some(copy)
1768 }
1769 _ => Some(targets_to_run.clone()),
1770 }
1771 } else {
1772 None
1773 };
1774 processed.insert(current_path.clone(), new_processed_state);
1775
1776 let current_data_path =
1778 path_utils::schema_path_to_data_pointer(¤t_path).into_owned();
1779 let mut current_value = eval_data
1780 .data()
1781 .pointer(¤t_data_path)
1782 .cloned()
1783 .unwrap_or(Value::Null);
1784
1785 if target_indices.is_none() {
1790 if let Some(formula_sources) = dep_formula_triggers.get(¤t_data_path) {
1791 let mut targets_by_source: std::collections::HashMap<String, Vec<usize>> =
1792 std::collections::HashMap::new();
1793 for (source_schema_path, dep_idx) in formula_sources {
1794 let source_ptr =
1795 path_utils::dot_notation_to_schema_pointer(source_schema_path);
1796 targets_by_source
1797 .entry(source_ptr)
1798 .or_default()
1799 .push(*dep_idx);
1800 }
1801 for (source_ptr, targets) in targets_by_source {
1802 if let Some(None) = processed.get(&source_ptr) {
1804 continue;
1805 }
1806 queue.push((source_ptr, true, Some(targets)));
1807 }
1808 }
1809 }
1810
1811 if let Some(dependent_items) = dependents_evaluations.get(¤t_path) {
1813 for (dep_idx, dep_item) in dependent_items.iter().enumerate() {
1814 if let Some(targets) = &indices_to_run {
1815 if !targets.contains(&dep_idx) {
1816 continue;
1817 }
1818 }
1819 let ref_path = &dep_item.ref_path;
1820
1821 if processed.contains_key(ref_path) {
1826 continue;
1827 }
1828
1829 let pointer_path = path_utils::normalize_to_json_pointer(ref_path);
1830 let data_path =
1832 crate::jsoneval::path_utils::schema_path_to_data_pointer(&pointer_path)
1833 .into_owned();
1834
1835 let current_ref_value = eval_data
1836 .data()
1837 .pointer(&data_path)
1838 .cloned()
1839 .unwrap_or(Value::Null);
1840
1841 let mut add_transitive = false;
1842 let mut add_deps = false;
1843 let mut clear_applied = false;
1844 let mut value_to_apply = None;
1845
1846 if let Some(clear_val) = &dep_item.clear {
1848 let should_clear = Self::evaluate_dependent_value_static(
1849 engine,
1850 evaluations,
1851 eval_data,
1852 clear_val,
1853 ¤t_value,
1854 ¤t_ref_value,
1855 )?;
1856 let clear_bool = match should_clear {
1857 Value::Bool(b) => b,
1858 _ => false,
1859 };
1860
1861 if clear_bool {
1862 let was_already_null = current_ref_value == Value::Null;
1863 if data_path == current_data_path {
1864 current_value = Value::Null;
1865 }
1866 eval_data.set(&data_path, Value::Null);
1867
1868 eval_cache.bump_data_version(&data_path);
1869 clear_applied = true;
1870 if !was_already_null {
1871 add_transitive = true;
1872 }
1873 add_deps = true;
1874 }
1875 }
1876
1877 if !clear_applied {
1879 if let Some(value_val) = &dep_item.value {
1880 let computed_value = Self::evaluate_dependent_value_static(
1881 engine,
1882 evaluations,
1883 eval_data,
1884 value_val,
1885 ¤t_value,
1886 ¤t_ref_value,
1887 )?;
1888 let cleaned_val = clean_float_noise_scalar(computed_value);
1889
1890 let is_clear =
1891 cleaned_val == Value::Null || cleaned_val.as_str() == Some("");
1892
1893 if cleaned_val != current_ref_value && !is_clear {
1894 if data_path == current_data_path {
1895 current_value = cleaned_val.clone();
1896 }
1897 eval_data.set(&data_path, cleaned_val.clone());
1898 eval_cache.bump_data_version(&data_path);
1899 value_to_apply = Some(cleaned_val);
1900 add_transitive = true;
1901 add_deps = true;
1902 }
1903 }
1904 }
1905
1906 if add_deps {
1908 let field = evaluated_schema.pointer(&pointer_path).cloned();
1909
1910 let parent_path =
1912 if let Some(last_slash) = pointer_path.rfind("/properties") {
1913 &pointer_path[..last_slash]
1914 } else {
1915 "/"
1916 };
1917 let parent_field = extract_parent_field(evaluated_schema, parent_path);
1918
1919 let mut change_obj = serde_json::Map::new();
1920 change_obj.insert(
1921 "$ref".to_string(),
1922 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
1923 );
1924 if let Some(f) = field {
1925 change_obj.insert("$field".to_string(), f);
1926 }
1927 change_obj.insert("$parentField".to_string(), parent_field);
1928 change_obj.insert("transitive".to_string(), Value::Bool(is_transitive));
1929 if clear_applied {
1930 change_obj.insert("clear".to_string(), Value::Bool(true));
1931 }
1932 if let Some(val) = value_to_apply {
1933 change_obj.insert("value".to_string(), val);
1934 }
1935 result.push(Value::Object(change_obj));
1936 }
1937
1938 if add_transitive {
1940 queue.push((ref_path.clone(), true, None));
1941 }
1942 }
1943 }
1944 }
1945 Ok(())
1946 }
1947}
1948
1949pub(crate) fn subform_field_key(subform_path: &str) -> String {
1955 let stripped = subform_path.trim_start_matches('#').trim_start_matches('/');
1957
1958 stripped
1960 .split('/')
1961 .filter(|seg| !seg.is_empty() && *seg != "properties")
1962 .last()
1963 .unwrap_or(stripped)
1964 .to_string()
1965}
1966
1967fn extract_parent_field(evaluated_schema: &Value, parent_path: &str) -> Value {
1970 let node = if parent_path.is_empty() || parent_path == "/" {
1971 evaluated_schema
1972 } else {
1973 match evaluated_schema.pointer(parent_path) {
1974 Some(v) => v,
1975 None => return Value::Object(serde_json::Map::new()),
1976 }
1977 };
1978 if let Value::Object(map) = node {
1979 let mut filtered = serde_json::Map::with_capacity(map.len().saturating_sub(2));
1980 for (k, v) in map {
1981 if k != "properties" && k != "$layout" {
1982 filtered.insert(k.clone(), v.clone());
1983 }
1984 }
1985 Value::Object(filtered)
1986 } else {
1987 Value::Object(serde_json::Map::new())
1988 }
1989}