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 parent_dependency_paths: Vec<String> = parent_changed_paths
750 .iter()
751 .map(|path| {
752 path_utils::dot_notation_to_schema_pointer(path)
753 .trim_start_matches('#')
754 .to_string()
755 })
756 .collect();
757 let dependent_value_paths: Vec<String> = self
758 .subforms
759 .get(&subform_path)
760 .map(|subform| {
761 subform
762 .dependencies
763 .iter()
764 .filter(|(key, deps)| {
765 !subform.table_metadata.contains_key(*key)
766 && !key.starts_with("#/$params/")
767 && key.ends_with("/value")
768 && parent_dependency_paths
769 .iter()
770 .any(|dependency| deps.contains(dependency))
771 })
772 .map(|(key, _)| key.clone())
773 .collect()
774 })
775 .unwrap_or_default();
776
777 for idx in 0..item_count {
778 let prefix_dot = format!("{}.{}.", subform_dot_path, idx);
780 let prefix_bracket = format!("{}[{}].", subform_dot_path, idx);
781 let prefix_field_bracket = format!("{}[{}].", field_key, idx);
782
783 let is_collection_refresh = changed_paths
784 .iter()
785 .any(|path| path == &format!("{subform_dot_path}.{idx}"));
786 let item_changed_paths: Vec<String> = changed_paths
787 .iter()
788 .filter_map(|p| {
789 if p == &format!("{subform_dot_path}.{idx}") {
790 Some(field_key.clone())
791 } else if p.starts_with(&prefix_bracket) {
792 Some(p.replacen(&prefix_bracket, &field_prefix, 1))
793 } else if p.starts_with(&prefix_dot) {
794 Some(p.replacen(&prefix_dot, &field_prefix, 1))
795 } else if p.starts_with(&prefix_field_bracket) {
796 Some(p.replacen(&prefix_field_bracket, &field_prefix, 1))
797 } else {
798 None
799 }
800 })
801 .collect();
802
803 let item_val =
806 get_value_by_pointer_without_properties(self.eval_data.data(), &subform_ptr)
807 .and_then(|v| v.as_array())
808 .and_then(|a| a.get(idx))
809 .cloned()
810 .unwrap_or(Value::Null);
811
812 if item_changed_paths.is_empty() && !dependent_value_paths.is_empty() {
813 let parent_cache = std::mem::take(&mut self.eval_cache);
818 let mut overlay_cache = parent_cache.clone();
819 overlay_cache.ensure_active_item_cache(idx);
820 if let Some(item_cache) = overlay_cache.subform_caches.get_mut(&idx) {
821 item_cache
825 .data_versions
826 .merge_from(&parent_data_versions_snapshot);
827 item_cache
828 .data_versions
829 .merge_from_params(&parent_params_versions_snapshot);
830 }
831 overlay_cache.set_active_item(idx);
832 let canonical_root =
833 path_utils::schema_path_to_data_pointer(&subform_path).into_owned();
834 let scope = crate::jsoneval::subform_scope::SubformScope::new(
835 &subform_path,
836 &canonical_root,
837 Some(idx),
838 );
839 let mut scoped_view = scope.evaluation_view(self.eval_data.data());
840 if let Some(view) = scoped_view.as_object_mut() {
841 view.insert(
842 "$context".to_string(),
843 self.eval_data
844 .data()
845 .get("$context")
846 .cloned()
847 .unwrap_or(Value::Null),
848 );
849 }
850 let subform = self
851 .subforms
852 .get_mut(&subform_path)
853 .expect("subform exists");
854 subform.eval_data = EvalData::new(scoped_view);
855 std::mem::swap(&mut subform.eval_cache, &mut overlay_cache);
856
857 let refresh_table_outputs = dependent_value_paths.iter().any(|source| {
862 let source = source.trim_end_matches("/value").trim_start_matches('#');
863 let affected_tables: Vec<&String> = subform
864 .table_metadata
865 .keys()
866 .filter(|table| table.starts_with("#/$params"))
867 .filter(|table| {
868 subform.dependencies.get(*table).is_some_and(|deps| {
869 deps.iter().any(|dep| dep.trim_start_matches('#') == source)
870 })
871 })
872 .collect();
873
874 !affected_tables.is_empty()
875 && subform.evaluations.iter().any(|(target, _)| {
876 target.ends_with("/value")
877 && subform.dependencies.get(target).is_some_and(|deps| {
878 deps.iter().any(|dep| {
879 affected_tables.iter().any(|table| {
880 dep.trim_start_matches('#')
881 == table.trim_start_matches('#')
882 })
883 })
884 })
885 })
886 });
887 subform.evaluate_internal(Some(&dependent_value_paths), token)?;
888
889 let mut overlay_result = Vec::new();
890 let mut overlay_queue = Vec::new();
891 let mut overlay_processed = std::collections::HashMap::new();
892 for formula_path in &dependent_value_paths {
893 let schema_path = path_utils::normalize_to_json_pointer(formula_path);
894 let data_path = path_utils::schema_path_to_data_pointer(formula_path)
895 .replace("/value", "");
896 let Some(value) = subform.evaluated_schema.pointer(&schema_path).cloned()
897 else {
898 continue;
899 };
900
901 let source_path = formula_path.trim_end_matches("/value").to_string();
906 if subform.eval_data.get(&data_path) != Some(&value) {
907 subform.eval_data.set(&data_path, value.clone());
908 subform
909 .eval_data
910 .set(&scope.canonical_path(&data_path), value.clone());
911 subform.eval_cache.bump_data_version(&data_path);
912 }
913 overlay_queue.push((source_path, true, None));
914
915 let mut change = serde_json::Map::new();
916 let field = data_path
917 .trim_start_matches('/')
918 .trim_end_matches("/value")
919 .replace('/', ".");
920 let field = field.strip_prefix(&field_prefix).unwrap_or(&field);
921 change.insert(
922 "$ref".to_string(),
923 Value::String(format!("{}.{}.{}", subform_dot_path, idx, field)),
924 );
925 if value == Value::Null || value.as_str() == Some("") {
926 change.insert("clear".to_string(), Value::Bool(true));
927 } else {
928 change.insert("value".to_string(), value);
929 }
930 result.push(Value::Object(change));
931 }
932
933 Self::process_dependents_queue(
934 &subform.engine,
935 &subform.evaluations,
936 &mut subform.eval_data,
937 &mut subform.eval_cache,
938 &subform.dependents_evaluations,
939 &subform.dep_formula_triggers,
940 &subform.evaluated_schema,
941 &mut overlay_queue,
942 &mut overlay_processed,
943 &mut overlay_result,
944 token,
945 None,
946 )?;
947
948 let local_item_path = format!("/{field_key}");
951 if let Some(local_item) = subform.eval_data.get(&local_item_path).cloned() {
952 subform
953 .eval_data
954 .set(&scope.canonical_path(&local_item_path), local_item);
955 }
956
957 if refresh_table_outputs {
958 subform.run_re_evaluate_pass(
962 token,
963 &mut overlay_queue,
964 &mut overlay_processed,
965 &mut overlay_result,
966 None,
967 )?;
968 }
969
970 for change in overlay_result {
971 let Some(object) = change.as_object() else {
972 continue;
973 };
974 let Some(Value::String(ref_path)) = object.get("$ref") else {
975 continue;
976 };
977 let local_ref = ref_path.strip_prefix(&field_prefix).unwrap_or(ref_path);
978 let mut mapped = object.clone();
979 mapped.insert(
980 "$ref".to_string(),
981 Value::String(format!("{}.{}.{}", subform_dot_path, idx, local_ref)),
982 );
983 result.push(Value::Object(mapped));
984 }
985
986 std::mem::swap(&mut subform.eval_cache, &mut overlay_cache);
989 self.eval_cache = parent_cache;
990 continue;
991 }
992
993 let canonical_root =
994 path_utils::schema_path_to_data_pointer(&subform_path).into_owned();
995 let scope = crate::jsoneval::subform_scope::SubformScope::new(
996 &subform_path,
997 &canonical_root,
998 Some(idx),
999 );
1000 let mut scoped_view = scope.evaluation_view(self.eval_data.data());
1001 if let Some(view) = scoped_view.as_object_mut() {
1002 view.insert(
1003 "$context".to_string(),
1004 self.eval_data
1005 .data()
1006 .get("$context")
1007 .cloned()
1008 .unwrap_or(Value::Null),
1009 );
1010 }
1011 let Some(subform) = self.subforms.get_mut(&subform_path) else {
1012 continue;
1013 };
1014
1015 let sub_re_evaluate = !item_changed_paths.is_empty();
1020 if !sub_re_evaluate && item_changed_paths.is_empty() {
1021 continue;
1022 }
1023
1024 self.eval_cache.ensure_active_item_cache(idx);
1026 let old_item_val = {
1027 let snapshot = self
1028 .eval_cache
1029 .subform_caches
1030 .get(&idx)
1031 .map(|c| c.item_snapshot.clone())
1032 .unwrap_or(Value::Null);
1033
1034 if snapshot == Value::Null {
1035 if let Some(main_snap) = &self.eval_cache.main_form_snapshot {
1036 get_value_by_pointer_without_properties(main_snap, &subform_ptr)
1037 .and_then(|v| v.as_array())
1038 .and_then(|a| a.get(idx))
1039 .cloned()
1040 .unwrap_or(Value::Null)
1041 } else {
1042 Value::Null
1043 }
1044 } else {
1045 snapshot
1046 }
1047 };
1048
1049 subform.eval_data = EvalData::new(scoped_view);
1050 let new_item_val = item_val.clone();
1051
1052 let mut parent_cache = std::mem::take(&mut self.eval_cache);
1054 parent_cache.ensure_active_item_cache(idx);
1055
1056 let pre_diff_item_versions = parent_cache
1059 .subform_caches
1060 .get(&idx)
1061 .map(|c| c.data_versions.clone());
1062
1063 if let Some(c) = parent_cache.subform_caches.get_mut(&idx) {
1064 c.data_versions.merge_from(&parent_data_versions_snapshot);
1068 c.data_versions
1070 .merge_from_params(&parent_params_versions_snapshot);
1071 if !is_collection_refresh {
1072 crate::jsoneval::eval_cache::diff_and_update_versions(
1073 &mut c.data_versions,
1074 &format!("/{}", field_key),
1075 &old_item_val,
1076 &new_item_val,
1077 "run_subform_pass_diff_and_update_versions",
1078 );
1079 }
1080 c.item_snapshot = new_item_val;
1083 }
1084
1085 if let (Some(ref pre), Some(c)) = (
1089 &pre_diff_item_versions,
1090 parent_cache.subform_caches.get(&idx),
1091 ) {
1092 let field_prefix_slash = format!("/{}/", field_key);
1093 let newly_bumped: Vec<String> = c
1094 .data_versions
1095 .versions()
1096 .filter(|(k, &v)| k.starts_with(&field_prefix_slash) && v > pre.get(k))
1097 .map(|(k, _)| k.to_string())
1098 .collect();
1099 if !newly_bumped.is_empty() {
1100 for k in newly_bumped {
1101 parent_cache
1102 .data_versions
1103 .bump(&k, "propagate_newly_bumped");
1104 }
1105 parent_cache.eval_generation += 1;
1106 }
1107 }
1108
1109 {
1115 let field_prefix_slash = format!("/{}/", field_key);
1116 let newly_bumped_schema_paths: Vec<String> = if let (Some(ref pre), Some(c)) = (
1117 &pre_diff_item_versions,
1118 parent_cache.subform_caches.get(&idx),
1119 ) {
1120 c.data_versions
1121 .versions()
1122 .filter(|(k, &v)| k.starts_with(&field_prefix_slash) && v > pre.get(k))
1123 .map(|(k, _)| {
1124 let sub = k.trim_start_matches(&field_prefix_slash);
1128 format!(
1129 "/{}/properties/{}",
1130 field_key,
1131 sub.replace('/', "/properties/")
1132 )
1133 })
1134 .collect()
1135 } else {
1136 Vec::new()
1137 };
1138
1139 if !newly_bumped_schema_paths.is_empty() {
1140 let params_table_keys: Vec<String> = self
1141 .table_metadata
1142 .keys()
1143 .filter(|k| k.starts_with("#/$params"))
1144 .filter(|k| {
1145 self.dependencies
1146 .get(*k)
1147 .map(|deps| {
1148 deps.iter().any(|dep| {
1149 newly_bumped_schema_paths
1150 .iter()
1151 .any(|b| dep == b || dep.starts_with(b.as_str()))
1152 })
1153 })
1154 .unwrap_or(false)
1155 })
1156 .cloned()
1157 .collect();
1158
1159 if !params_table_keys.is_empty() {
1160 parent_cache.invalidate_params_tables_for_item(idx, ¶ms_table_keys);
1161 any_table_invalidated = true;
1162 }
1163 }
1164 }
1165
1166 parent_cache.set_active_item(idx);
1167 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
1168
1169 let subform_result = time_block!(" [subform_pass] rider evaluate_dependents", {
1170 subform.evaluate_dependents(
1171 &item_changed_paths,
1172 None,
1173 None,
1174 sub_re_evaluate,
1175 token,
1176 None,
1177 false,
1178 )
1179 });
1180
1181 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
1183 parent_cache.clear_active_item();
1184
1185 if let Some(parent_item_cache) = self.eval_cache.subform_caches.get(&idx) {
1190 let snapshot = parent_item_cache.item_snapshot.clone();
1191 subform.eval_cache.ensure_active_item_cache(idx);
1192 if let Some(sub_cache) = subform.eval_cache.subform_caches.get_mut(&idx) {
1193 sub_cache.item_snapshot = snapshot;
1194 }
1195 }
1196
1197 self.eval_cache = parent_cache;
1198
1199 if let Ok(Value::Array(changes)) = subform_result {
1200 let mut had_any_change = false;
1201 for change in changes {
1202 if let Some(obj) = change.as_object() {
1203 if let Some(Value::String(ref_path)) = obj.get("$ref") {
1204 let new_ref = if ref_path.starts_with(&field_prefix) {
1206 format!(
1207 "{}.{}.{}",
1208 subform_dot_path,
1209 idx,
1210 &ref_path[field_prefix.len()..]
1211 )
1212 } else {
1213 format!("{}.{}.{}", subform_dot_path, idx, ref_path)
1214 };
1215
1216 if let Some(val) = obj.get("value") {
1222 let data_ptr = format!("/{}", new_ref.replace('.', "/"));
1223 self.eval_data.set(&data_ptr, val.clone());
1224 had_any_change = true;
1225 } else if obj.get("clear").and_then(Value::as_bool) == Some(true) {
1226 let data_ptr = format!("/{}", new_ref.replace('.', "/"));
1227 self.eval_data.set(&data_ptr, Value::Null);
1228 had_any_change = true;
1229 }
1230
1231 let mut new_obj = obj.clone();
1232 new_obj.insert("$ref".to_string(), Value::String(new_ref));
1233 result.push(Value::Object(new_obj));
1234 } else {
1235 result.push(change);
1237 }
1238 }
1239 }
1240
1241 if had_any_change {
1243 let item_path = format!("{}/{}", subform_ptr, idx);
1244 let updated_item = self
1245 .eval_data
1246 .get(&item_path)
1247 .cloned()
1248 .unwrap_or(Value::Null);
1249 if let Some(c) = self.eval_cache.subform_caches.get_mut(&idx) {
1251 c.item_snapshot = updated_item.clone();
1252 }
1253 subform.eval_cache.ensure_active_item_cache(idx);
1255 if let Some(sub_cache) = subform.eval_cache.subform_caches.get_mut(&idx) {
1256 sub_cache.item_snapshot = updated_item;
1257 }
1258 }
1259 }
1260 }
1261 }
1262 Ok(any_table_invalidated)
1263 }
1264
1265 pub(crate) fn evaluate_dependent_value_static(
1267 engine: &RLogic,
1268 evaluations: &IndexMap<String, LogicId>,
1269 eval_data: &EvalData,
1270 value: &Value,
1271 changed_field_value: &Value,
1272 changed_field_ref_value: &Value,
1273 ) -> Result<Value, String> {
1274 match value {
1275 Value::String(eval_key) => {
1277 if let Some(logic_id) = evaluations.get(eval_key) {
1278 let mut internal_context = serde_json::Map::new();
1281 internal_context.insert("$value".to_string(), changed_field_value.clone());
1282 internal_context.insert("$refValue".to_string(), changed_field_ref_value.clone());
1283 let context_value = Value::Object(internal_context);
1284
1285 let result = engine.run_with_context(logic_id, eval_data.data(), &context_value)
1286 .map_err(|e| format!("Failed to evaluate dependent logic '{}': {}", eval_key, e))?;
1287 Ok(result)
1288 } else {
1289 Ok(value.clone())
1291 }
1292 }
1293 Value::Object(map) if map.contains_key("$evaluation") => {
1296 Err("Dependent evaluation contains unparsed $evaluation - schema was not properly parsed".to_string())
1297 }
1298 _ => Ok(value.clone()),
1300 }
1301 }
1302
1303 pub(crate) fn check_readonly_for_dependents(
1305 &self,
1306 schema_element: &Value,
1307 path: &str,
1308 changes: &mut Vec<(String, Value)>,
1309 all_values: &mut Vec<(String, Value)>,
1310 ) {
1311 match schema_element {
1312 Value::Object(map) => {
1313 let mut is_disabled = false;
1315 if let Some(Value::Object(condition)) = map.get("condition") {
1316 if let Some(Value::Bool(d)) = condition.get("disabled") {
1317 is_disabled = *d;
1318 }
1319 }
1320
1321 let mut skip_readonly = false;
1323 if let Some(Value::Object(config)) = map.get("config") {
1324 if let Some(Value::Object(all)) = config.get("all") {
1325 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
1326 skip_readonly = *skip;
1327 }
1328 }
1329 }
1330
1331 if is_disabled && !skip_readonly {
1332 if let Some(schema_value) = map.get("value") {
1333 let data_path = path_utils::schema_path_to_data_pointer(path)
1334 .replace("/value/", "/");
1337
1338 let current_data = self
1339 .eval_data
1340 .data()
1341 .pointer(&data_path)
1342 .unwrap_or(&Value::Null);
1343
1344 all_values.push((path.to_string(), schema_value.clone()));
1347 if current_data != schema_value {
1348 changes.push((path.to_string(), schema_value.clone()));
1349 }
1350 }
1351 }
1352 }
1353 _ => {}
1354 }
1355 }
1356
1357 #[allow(dead_code)]
1359 pub(crate) fn collect_readonly_fixes(
1360 &self,
1361 schema_element: &Value,
1362 path: &str,
1363 changes: &mut Vec<(String, Value)>,
1364 ) {
1365 match schema_element {
1366 Value::Object(map) => {
1367 let mut is_disabled = false;
1369 if let Some(Value::Object(condition)) = map.get("condition") {
1370 if let Some(Value::Bool(d)) = condition.get("disabled") {
1371 is_disabled = *d;
1372 }
1373 }
1374
1375 let mut skip_readonly = false;
1377 if let Some(Value::Object(config)) = map.get("config") {
1378 if let Some(Value::Object(all)) = config.get("all") {
1379 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
1380 skip_readonly = *skip;
1381 }
1382 }
1383 }
1384
1385 if is_disabled && !skip_readonly {
1386 if let Some(schema_value) = map.get("value") {
1390 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1391
1392 let current_data = self
1393 .eval_data
1394 .data()
1395 .pointer(&data_path)
1396 .unwrap_or(&Value::Null);
1397
1398 if current_data != schema_value {
1399 changes.push((path.to_string(), schema_value.clone()));
1400 }
1401 }
1402 }
1403
1404 if let Some(Value::Object(props)) = map.get("properties") {
1406 for (key, val) in props {
1407 let next_path = if path == "#" {
1408 format!("#/properties/{}", key)
1409 } else {
1410 format!("{}/properties/{}", path, key)
1411 };
1412 self.collect_readonly_fixes(val, &next_path, changes);
1413 }
1414 }
1415 }
1416 _ => {}
1417 }
1418 }
1419
1420 pub(crate) fn check_hidden_field(
1422 &self,
1423 schema_element: &Value,
1424 path: &str,
1425 hidden_fields: &mut Vec<String>,
1426 ) {
1427 match schema_element {
1428 Value::Object(map) => {
1429 let mut is_hidden = false;
1431 if let Some(Value::Object(condition)) = map.get("condition") {
1432 if let Some(Value::Bool(h)) = condition.get("hidden") {
1433 is_hidden = *h;
1434 }
1435 }
1436
1437 let mut keep_hidden = false;
1439 if let Some(Value::Object(config)) = map.get("config") {
1440 if let Some(Value::Object(all)) = config.get("all") {
1441 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
1442 keep_hidden = *keep;
1443 }
1444 }
1445 }
1446
1447 if is_hidden && !keep_hidden {
1448 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1449
1450 let current_data = self
1451 .eval_data
1452 .data()
1453 .pointer(&data_path)
1454 .unwrap_or(&Value::Null);
1455
1456 if current_data != &Value::Null && current_data != "" {
1458 hidden_fields.push(path.to_string());
1459 }
1460 }
1461 }
1462 _ => {}
1463 }
1464 }
1465
1466 fn check_effectively_hidden_field(
1468 &self,
1469 schema_element: &Value,
1470 path: &str,
1471 hidden_fields: &mut Vec<String>,
1472 ) {
1473 let Value::Object(map) = schema_element else {
1474 return;
1475 };
1476
1477 let keep_hidden = map
1478 .get("config")
1479 .and_then(Value::as_object)
1480 .and_then(|config| config.get("all"))
1481 .and_then(Value::as_object)
1482 .and_then(|all| all.get("keepHiddenValue"))
1483 .and_then(Value::as_bool)
1484 .unwrap_or(false);
1485 if keep_hidden {
1486 return;
1487 }
1488
1489 let current_data = self
1490 .eval_data
1491 .data()
1492 .pointer(&path_utils::schema_path_to_data_pointer(path))
1493 .unwrap_or(&Value::Null);
1494 if current_data != &Value::Null && current_data != "" {
1495 hidden_fields.push(path.to_string());
1496 }
1497 }
1498
1499 #[allow(dead_code)]
1501 pub(crate) fn collect_hidden_fields(
1502 &self,
1503 schema_element: &Value,
1504 path: &str,
1505 hidden_fields: &mut Vec<String>,
1506 ) {
1507 match schema_element {
1508 Value::Object(map) => {
1509 let mut is_hidden = false;
1511 if let Some(Value::Object(condition)) = map.get("condition") {
1512 if let Some(Value::Bool(h)) = condition.get("hidden") {
1513 is_hidden = *h;
1514 }
1515 }
1516
1517 let mut keep_hidden = false;
1519 if let Some(Value::Object(config)) = map.get("config") {
1520 if let Some(Value::Object(all)) = config.get("all") {
1521 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
1522 keep_hidden = *keep;
1523 }
1524 }
1525 }
1526
1527 if is_hidden && !keep_hidden {
1528 let data_path = path_utils::schema_path_to_data_pointer(path).into_owned();
1529
1530 let current_data = self
1531 .eval_data
1532 .data()
1533 .pointer(&data_path)
1534 .unwrap_or(&Value::Null);
1535
1536 if current_data != &Value::Null && current_data != "" {
1538 hidden_fields.push(path.to_string());
1539 }
1540 }
1541
1542 for (key, val) in map {
1544 if key == "properties" {
1545 if let Value::Object(props) = val {
1546 for (p_key, p_val) in props {
1547 let next_path = if path == "#" {
1548 format!("#/properties/{}", p_key)
1549 } else {
1550 format!("{}/properties/{}", path, p_key)
1551 };
1552 self.collect_hidden_fields(p_val, &next_path, hidden_fields);
1553 }
1554 }
1555 } else if let Value::Object(_) = val {
1556 if key == "condition"
1558 || key == "config"
1559 || key == "rules"
1560 || key == "dependents"
1561 || key == "hideLayout"
1562 || key == "$layout"
1563 || key == "$params"
1564 || key == "definitions"
1565 || key == "$defs"
1566 || key.starts_with('$')
1567 {
1568 continue;
1569 }
1570
1571 let next_path = if path == "#" {
1572 format!("#/{}", key)
1573 } else {
1574 format!("{}/{}", path, key)
1575 };
1576 self.collect_hidden_fields(val, &next_path, hidden_fields);
1577 }
1578 }
1579 }
1580 _ => {}
1581 }
1582 }
1583
1584 pub(crate) fn recursive_hide_effect(
1587 engine: &RLogic,
1588 evaluations: &IndexMap<String, LogicId>,
1589 reffed_by: &IndexMap<String, Vec<String>>,
1590 eval_data: &mut EvalData,
1591 eval_cache: &mut crate::jsoneval::eval_cache::EvalCache,
1592 mut hidden_fields: Vec<String>,
1593 queue: &mut Vec<(String, bool, Option<Vec<usize>>)>,
1594 result: &mut Vec<Value>,
1595 ) {
1596 while let Some(hf) = hidden_fields.pop() {
1597 let data_path = path_utils::schema_path_to_data_pointer(&hf).into_owned();
1598
1599 eval_data.set(&data_path, Value::Null);
1601 eval_cache.bump_data_version(&data_path);
1602
1603 let mut change_obj = serde_json::Map::new();
1605 change_obj.insert(
1606 "$ref".to_string(),
1607 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
1608 );
1609 change_obj.insert("$hidden".to_string(), Value::Bool(true));
1610 change_obj.insert("clear".to_string(), Value::Bool(true));
1611 result.push(Value::Object(change_obj));
1612
1613 queue.push((hf.clone(), true, None));
1615
1616 if let Some(referencing_fields) = reffed_by.get(&data_path) {
1618 for rb in referencing_fields {
1619 let hidden_eval_key = format!("{}/condition/hidden", rb);
1623
1624 if let Some(logic_id) = evaluations.get(&hidden_eval_key) {
1625 let rb_data_path = path_utils::schema_path_to_data_pointer(rb).into_owned();
1632 let rb_value = eval_data
1633 .data()
1634 .pointer(&rb_data_path)
1635 .cloned()
1636 .unwrap_or(Value::Null);
1637
1638 if let Ok(Value::Bool(is_hidden)) = engine.run(logic_id, eval_data.data()) {
1640 if is_hidden {
1641 if !hidden_fields.contains(rb) {
1644 let has_value = rb_value != Value::Null && rb_value != "";
1645 if has_value {
1646 hidden_fields.push(rb.clone());
1647 }
1648 }
1649 }
1650 }
1651 }
1652 }
1653 }
1654 }
1655 }
1656
1657 pub(crate) fn process_dependents_queue(
1660 engine: &RLogic,
1661 evaluations: &IndexMap<String, LogicId>,
1662 eval_data: &mut EvalData,
1663 eval_cache: &mut crate::jsoneval::eval_cache::EvalCache,
1664 dependents_evaluations: &IndexMap<String, Vec<DependentItem>>,
1665 dep_formula_triggers: &IndexMap<String, Vec<(String, usize)>>,
1666 evaluated_schema: &Value,
1667 queue: &mut Vec<(String, bool, Option<Vec<usize>>)>,
1668 processed: &mut std::collections::HashMap<String, Option<std::collections::HashSet<usize>>>,
1669 result: &mut Vec<Value>,
1670 token: Option<&CancellationToken>,
1671 canceled_paths: Option<&mut Vec<String>>,
1672 ) -> Result<(), String> {
1673 while let Some((current_path, is_transitive, target_indices)) = queue.pop() {
1674 if let Some(t) = token {
1675 if t.is_cancelled() {
1676 if let Some(cp) = canceled_paths {
1677 cp.push(current_path.clone());
1678 for (path, _, _) in queue.iter() {
1679 cp.push(path.clone());
1680 }
1681 }
1682 return Err("Cancelled".to_string());
1683 }
1684 }
1685
1686 let (should_run, indices_to_run) = match processed.get(¤t_path) {
1687 Some(None) => {
1688 continue;
1690 }
1691 Some(Some(already_processed_indices)) => {
1692 if let Some(targets) = &target_indices {
1693 let new_targets: std::collections::HashSet<usize> = targets
1694 .iter()
1695 .copied()
1696 .filter(|i| !already_processed_indices.contains(i))
1697 .collect();
1698 if new_targets.is_empty() {
1699 continue;
1700 }
1701 (true, Some(new_targets))
1702 } else {
1703 (true, None)
1704 }
1705 }
1706 None => (
1707 true,
1708 target_indices.clone().map(|t| t.into_iter().collect()),
1709 ),
1710 };
1711
1712 if !should_run {
1713 continue;
1714 }
1715
1716 let new_processed_state = if let Some(targets_to_run) = &indices_to_run {
1717 match processed.get(¤t_path) {
1718 Some(Some(existing_targets)) => {
1719 let mut copy = existing_targets.clone();
1720 for t in targets_to_run {
1721 copy.insert(*t);
1722 }
1723 Some(copy)
1724 }
1725 _ => Some(targets_to_run.clone()),
1726 }
1727 } else {
1728 None
1729 };
1730 processed.insert(current_path.clone(), new_processed_state);
1731
1732 let current_data_path =
1734 path_utils::schema_path_to_data_pointer(¤t_path).into_owned();
1735 let mut current_value = eval_data
1736 .data()
1737 .pointer(¤t_data_path)
1738 .cloned()
1739 .unwrap_or(Value::Null);
1740
1741 if let Some(formula_sources) = dep_formula_triggers.get(¤t_data_path) {
1746 let mut targets_by_source: std::collections::HashMap<String, Vec<usize>> =
1747 std::collections::HashMap::new();
1748 for (source_schema_path, dep_idx) in formula_sources {
1749 let source_ptr = path_utils::dot_notation_to_schema_pointer(source_schema_path);
1750 targets_by_source
1751 .entry(source_ptr)
1752 .or_default()
1753 .push(*dep_idx);
1754 }
1755 for (source_ptr, targets) in targets_by_source {
1756 if let Some(None) = processed.get(&source_ptr) {
1758 continue;
1759 }
1760 queue.push((source_ptr, true, Some(targets)));
1761 }
1762 }
1763
1764 if let Some(dependent_items) = dependents_evaluations.get(¤t_path) {
1766 for (dep_idx, dep_item) in dependent_items.iter().enumerate() {
1767 if let Some(targets) = &indices_to_run {
1768 if !targets.contains(&dep_idx) {
1769 continue;
1770 }
1771 }
1772 let ref_path = &dep_item.ref_path;
1773
1774 if processed.contains_key(ref_path) {
1779 continue;
1780 }
1781
1782 let pointer_path = path_utils::normalize_to_json_pointer(ref_path);
1783 let data_path =
1785 crate::jsoneval::path_utils::schema_path_to_data_pointer(&pointer_path)
1786 .into_owned();
1787
1788 let current_ref_value = eval_data
1789 .data()
1790 .pointer(&data_path)
1791 .cloned()
1792 .unwrap_or(Value::Null);
1793
1794 let mut add_transitive = false;
1795 let mut add_deps = false;
1796 let mut clear_applied = false;
1797 let mut value_to_apply = None;
1798
1799 if let Some(clear_val) = &dep_item.clear {
1801 let should_clear = Self::evaluate_dependent_value_static(
1802 engine,
1803 evaluations,
1804 eval_data,
1805 clear_val,
1806 ¤t_value,
1807 ¤t_ref_value,
1808 )?;
1809 let clear_bool = match should_clear {
1810 Value::Bool(b) => b,
1811 _ => false,
1812 };
1813
1814 if clear_bool {
1815 if data_path == current_data_path {
1816 current_value = Value::Null;
1817 }
1818 eval_data.set(&data_path, Value::Null);
1819 eval_cache.bump_data_version(&data_path);
1820 clear_applied = true;
1821 add_transitive = true;
1822 add_deps = true;
1823 }
1824 }
1825
1826 if let Some(value_val) = &dep_item.value {
1828 let computed_value = Self::evaluate_dependent_value_static(
1829 engine,
1830 evaluations,
1831 eval_data,
1832 value_val,
1833 ¤t_value,
1834 ¤t_ref_value,
1835 )?;
1836 let cleaned_val = clean_float_noise_scalar(computed_value);
1837
1838 let is_clear =
1839 cleaned_val == Value::Null || cleaned_val.as_str() == Some("");
1840
1841 if cleaned_val != current_ref_value && !is_clear {
1842 if data_path == current_data_path {
1843 current_value = cleaned_val.clone();
1844 }
1845 eval_data.set(&data_path, cleaned_val.clone());
1846 eval_cache.bump_data_version(&data_path);
1847 value_to_apply = Some(cleaned_val);
1848 add_transitive = true;
1849 add_deps = true;
1850 }
1851 }
1852
1853 if add_deps {
1855 let field = evaluated_schema.pointer(&pointer_path).cloned();
1856
1857 let parent_path = if let Some(last_slash) = pointer_path.rfind("/properties") {
1859 &pointer_path[..last_slash]
1860 } else {
1861 "/"
1862 };
1863 let parent_field = extract_parent_field(evaluated_schema, parent_path);
1864
1865 let mut change_obj = serde_json::Map::new();
1866 change_obj.insert(
1867 "$ref".to_string(),
1868 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
1869 );
1870 if let Some(f) = field {
1871 change_obj.insert("$field".to_string(), f);
1872 }
1873 change_obj.insert("$parentField".to_string(), parent_field);
1874 change_obj.insert("transitive".to_string(), Value::Bool(is_transitive));
1875 if clear_applied {
1876 change_obj.insert("clear".to_string(), Value::Bool(true));
1877 }
1878 if let Some(val) = value_to_apply {
1879 change_obj.insert("value".to_string(), val);
1880 }
1881 result.push(Value::Object(change_obj));
1882 }
1883
1884 if add_transitive {
1886 queue.push((ref_path.clone(), true, None));
1887 }
1888 }
1889 }
1890 }
1891 Ok(())
1892 }
1893}
1894
1895fn subform_field_key(subform_path: &str) -> String {
1902 let stripped = subform_path.trim_start_matches('#').trim_start_matches('/');
1904
1905 stripped
1907 .split('/')
1908 .filter(|seg| !seg.is_empty() && *seg != "properties")
1909 .last()
1910 .unwrap_or(stripped)
1911 .to_string()
1912}
1913
1914fn extract_parent_field(evaluated_schema: &Value, parent_path: &str) -> Value {
1917 let node = if parent_path.is_empty() || parent_path == "/" {
1918 evaluated_schema
1919 } else {
1920 match evaluated_schema.pointer(parent_path) {
1921 Some(v) => v,
1922 None => return Value::Object(serde_json::Map::new()),
1923 }
1924 };
1925 if let Value::Object(map) = node {
1926 let mut filtered = serde_json::Map::with_capacity(map.len().saturating_sub(2));
1927 for (k, v) in map {
1928 if k != "properties" && k != "$layout" {
1929 filtered.insert(k.clone(), v.clone());
1930 }
1931 }
1932 Value::Object(filtered)
1933 } else {
1934 Value::Object(serde_json::Map::new())
1935 }
1936}