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