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