1use super::JSONEval;
2use crate::jsoneval::cancellation::CancellationToken;
3use crate::jsoneval::json_parser;
4use crate::jsoneval::path_utils;
5use crate::jsoneval::path_utils::get_value_by_pointer_without_properties;
6use crate::jsoneval::path_utils::normalize_to_json_pointer;
7use crate::jsoneval::types::DependentItem;
8use crate::rlogic::{LogicId, RLogic};
9use crate::time_block;
10use crate::utils::clean_float_noise_scalar;
11use crate::EvalData;
12
13use indexmap::{IndexMap, IndexSet};
14use serde_json::Value;
15
16impl JSONEval {
17 pub fn evaluate_dependents(
21 &mut self,
22 changed_paths: &[String],
23 data: Option<&str>,
24 context: Option<&str>,
25 re_evaluate: bool,
26 token: Option<&CancellationToken>,
27 mut canceled_paths: Option<&mut Vec<String>>,
28 include_subforms: bool,
29 ) -> Result<Value, String> {
30 if let Some(t) = token {
31 if t.is_cancelled() {
32 return Err("Cancelled".to_string());
33 }
34 }
35 let _lock = self.eval_lock.lock().unwrap();
36 let mut structural_change_data = None;
37
38 if let Some(data_str) = data {
40 let data_value = json_parser::parse_json_str(data_str)?;
41 let context_value = if let Some(ctx) = context {
42 json_parser::parse_json_str(ctx)?
43 } else {
44 Value::Object(serde_json::Map::new())
45 };
46 let old_data = self.eval_data.snapshot_data_clone();
47 time_block!(" [dep] data_replace_and_context", {
48 self.eval_data
49 .replace_data_and_context(data_value, context_value);
50 });
51 let new_data = self.eval_data.snapshot_data_clone();
52 time_block!(" [dep] data_diff_versions", {
53 self.eval_cache
54 .store_snapshot_and_diff_versions(&old_data, &new_data);
55 });
56 structural_change_data = Some((old_data, new_data));
57 }
58
59 drop(_lock);
61
62 if let Some((old_data, new_data)) = structural_change_data {
66 time_block!(" [dep] invalidate_subform_structural", {
67 self.invalidate_subform_caches_on_structural_change(&old_data, &new_data);
68 });
69 }
70
71 let mut result = Vec::new();
72 let mut processed = std::collections::HashMap::new();
73 let mut to_process: Vec<(String, bool, Option<Vec<usize>>)> = changed_paths
74 .iter()
75 .map(|path| {
76 (
77 path_utils::dot_notation_to_schema_pointer(path),
78 false,
79 None,
80 )
81 })
82 .collect();
83
84 time_block!(" [dep] process_dependents_queue", {
85 Self::process_dependents_queue(
86 &self.engine,
87 &self.evaluations,
88 &mut self.eval_data,
89 &mut self.eval_cache,
90 &self.dependents_evaluations,
91 &self.dep_formula_triggers,
92 &self.evaluated_schema,
93 &mut to_process,
94 &mut processed,
95 &mut result,
96 token,
97 canceled_paths.as_mut().map(|v| &mut **v),
98 )?;
99 });
100
101 if re_evaluate {
102 time_block!(" [dep] run_re_evaluate_pass", {
103 self.run_re_evaluate_pass(
104 token,
105 &mut to_process,
106 &mut processed,
107 &mut result,
108 canceled_paths.as_mut().map(|v| &mut **v),
109 )?;
110 });
111 }
112
113 if include_subforms {
114 let extended_paths: Vec<String> = {
120 let mut paths = changed_paths.to_vec();
121 for item in &result {
122 if let Some(ref_val) = item.get("$ref").and_then(|v| v.as_str()) {
123 let s = ref_val.to_string();
124 if !paths.contains(&s) {
125 paths.push(s);
126 }
127 }
128 }
129 paths
130 };
131 time_block!(" [dep] run_subform_pass", {
132 self.run_subform_pass(&extended_paths, re_evaluate, token, &mut result)?;
133 });
134 }
135
136 let deduped = {
141 let mut seen: IndexMap<String, usize> = IndexMap::new();
142 for (i, item) in result.iter().enumerate() {
143 if let Some(r) = item.get("$ref").and_then(|v| v.as_str()) {
144 seen.insert(r.to_string(), i);
145 }
146 }
147 let last_indices: IndexSet<usize> = seen.values().copied().collect();
148 let out: Vec<Value> = result
149 .into_iter()
150 .enumerate()
151 .filter(|(i, _)| last_indices.contains(i))
152 .map(|(_, item)| item)
153 .collect();
154 out
155 };
156
157 if self.eval_cache.active_item_index.is_none() {
165 let current_snapshot = self.eval_data.snapshot_data_clone();
166 self.eval_cache.main_form_snapshot = Some(current_snapshot);
167 }
168
169 Ok(Value::Array(deduped))
170 }
171
172 fn run_re_evaluate_pass(
175 &mut self,
176 token: Option<&CancellationToken>,
177 to_process: &mut Vec<(String, bool, Option<Vec<usize>>)>,
178 processed: &mut std::collections::HashMap<String, Option<std::collections::HashSet<usize>>>,
179 result: &mut Vec<Value>,
180 mut canceled_paths: Option<&mut Vec<String>>,
181 ) -> Result<(), String> {
182 self.run_schema_default_value_pass(
184 token,
185 to_process,
186 processed,
187 result,
188 canceled_paths.as_mut().map(|v| &mut **v),
189 )?;
190
191 let pre_eval_versions = if let Some(idx) = self.eval_cache.active_item_index {
197 self.eval_cache
198 .subform_caches
199 .get(&idx)
200 .map(|c| c.data_versions.clone())
201 .unwrap_or_else(|| self.eval_cache.data_versions.clone())
202 } else {
203 self.eval_cache.data_versions.clone()
204 };
205
206 self.evaluate_internal(None, token)?;
207
208 self.run_schema_default_value_pass(
210 token,
211 to_process,
212 processed,
213 result,
214 canceled_paths.as_mut().map(|v| &mut **v),
215 )?;
216
217 let active_idx = self.eval_cache.active_item_index;
219 for eval_key in self.sorted_evaluations.iter().flatten() {
220 if eval_key.contains("/$params/") || eval_key.contains("/$") {
221 continue;
222 }
223
224 let schema_ptr = path_utils::normalize_to_json_pointer(eval_key);
225 let data_path = schema_ptr
226 .replace("/properties/", "/")
227 .trim_start_matches('#')
228 .trim_start_matches('/')
229 .to_string();
230
231 let version_path = format!("/{}", data_path);
232 let old_ver = pre_eval_versions.get(&version_path);
233 let new_ver = if let Some(idx) = active_idx {
234 self.eval_cache
235 .subform_caches
236 .get(&idx)
237 .map(|c| c.data_versions.get(&version_path))
238 .unwrap_or_else(|| self.eval_cache.data_versions.get(&version_path))
239 } else {
240 self.eval_cache.data_versions.get(&version_path)
241 };
242
243 if new_ver > old_ver {
244 if let Some(new_val) = self.evaluated_schema.pointer(&schema_ptr) {
245 let dot_path = data_path.trim_end_matches("/value").replace('/', ".");
246 let mut obj = serde_json::Map::new();
247 obj.insert("$ref".to_string(), Value::String(dot_path));
248 let is_clear = new_val == &Value::Null || new_val.as_str() == Some("");
249 if is_clear {
250 obj.insert("clear".to_string(), Value::Bool(true));
251 } else {
252 obj.insert("value".to_string(), new_val.clone());
253 }
254 result.push(Value::Object(obj));
255 }
256 }
257 }
258
259 let _lock = self.eval_lock.lock().unwrap();
261
262 let mut readonly_changes = Vec::new();
264 let mut readonly_values = Vec::new();
265 for path in self.conditional_readonly_fields.iter() {
266 let normalized = path_utils::normalize_to_json_pointer(path);
267 if let Some(schema_el) = self.evaluated_schema.pointer(&normalized) {
268 self.check_readonly_for_dependents(
269 schema_el,
270 path,
271 &mut readonly_changes,
272 &mut readonly_values,
273 );
274 }
275 }
276 let had_actual_readonly_changes = !readonly_changes.is_empty();
279 for (path, schema_value) in readonly_changes {
280 let data_path = path_utils::normalize_to_json_pointer(&path)
281 .replace("/properties/", "/")
282 .trim_start_matches('#')
283 .to_string();
284 self.eval_data.set(&data_path, schema_value.clone());
285 self.eval_cache.bump_data_version(&data_path);
286 to_process.push((path, true, None));
287 }
288 for (path, schema_value) in readonly_values {
289 let data_path = path_utils::normalize_to_json_pointer(&path)
290 .replace("/properties/", "/")
291 .trim_start_matches('#')
292 .to_string();
293 let mut obj = serde_json::Map::new();
294 obj.insert(
295 "$ref".to_string(),
296 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
297 );
298 obj.insert("$readonly".to_string(), Value::Bool(true));
299 let is_clear = schema_value == Value::Null || schema_value.as_str() == Some("");
300 if is_clear {
301 obj.insert("clear".to_string(), Value::Bool(true));
302 } else {
303 obj.insert("value".to_string(), schema_value);
304 }
305 result.push(Value::Object(obj));
306 }
307
308 if had_actual_readonly_changes {
314 if let Some(active_idx) = self.eval_cache.active_item_index {
315 let readonly_dep_prefixes: Vec<String> =
320 to_process.iter().map(|(path, _, _)| path.clone()).collect();
321
322 let params_table_keys: Vec<String> = self
323 .table_metadata
324 .keys()
325 .filter(|k| {
326 if !k.starts_with("#/$params") {
327 return false;
328 }
329 if let Some(deps) = self.dependencies.get(*k) {
331 deps.iter().any(|dep| {
332 readonly_dep_prefixes
333 .iter()
334 .any(|ro| dep == ro || dep.starts_with(ro.as_str()))
335 })
336 } else {
337 false
338 }
339 })
340 .cloned()
341 .collect();
342
343 if !params_table_keys.is_empty() {
344 self.eval_cache
345 .invalidate_params_tables_for_item(active_idx, ¶ms_table_keys);
346 drop(_lock);
347 self.evaluate_internal(None, token)?;
348 }
349 }
350 }
351
352 if !to_process.is_empty() {
353 Self::process_dependents_queue(
354 &self.engine,
355 &self.evaluations,
356 &mut self.eval_data,
357 &mut self.eval_cache,
358 &self.dependents_evaluations,
359 &self.dep_formula_triggers,
360 &self.evaluated_schema,
361 to_process,
362 processed,
363 result,
364 token,
365 canceled_paths.as_mut().map(|v| &mut **v),
366 )?;
367 }
368
369 let mut hidden_fields = Vec::new();
371 for path in self.conditional_hidden_fields.iter() {
372 let normalized = path_utils::normalize_to_json_pointer(path);
373 if let Some(schema_el) = self.evaluated_schema.pointer(&normalized) {
374 self.check_hidden_field(schema_el, path, &mut hidden_fields);
375 }
376 }
377 if !hidden_fields.is_empty() {
378 Self::recursive_hide_effect(
379 &self.engine,
380 &self.evaluations,
381 &self.reffed_by,
382 &mut self.eval_data,
383 &mut self.eval_cache,
384 hidden_fields,
385 to_process,
386 result,
387 );
388 }
389 if !to_process.is_empty() {
390 Self::process_dependents_queue(
391 &self.engine,
392 &self.evaluations,
393 &mut self.eval_data,
394 &mut self.eval_cache,
395 &self.dependents_evaluations,
396 &self.dep_formula_triggers,
397 &self.evaluated_schema,
398 to_process,
399 processed,
400 result,
401 token,
402 canceled_paths.as_mut().map(|v| &mut **v),
403 )?;
404 }
405
406 Ok(())
407 }
408
409 fn run_schema_default_value_pass(
412 &mut self,
413 token: Option<&CancellationToken>,
414 to_process: &mut Vec<(String, bool, Option<Vec<usize>>)>,
415 processed: &mut std::collections::HashMap<String, Option<std::collections::HashSet<usize>>>,
416 result: &mut Vec<Value>,
417 mut canceled_paths: Option<&mut Vec<String>>,
418 ) -> Result<(), String> {
419 let mut default_value_changes = Vec::new();
420 let schema_values = self.get_schema_value_array();
421
422 if let Value::Array(values) = schema_values {
423 for item in values {
424 if let Value::Object(map) = item {
425 if let (Some(Value::String(dot_path)), Some(schema_val)) =
426 (map.get("path"), map.get("value"))
427 {
428 let schema_ptr = path_utils::dot_notation_to_schema_pointer(dot_path);
429 if let Some(Value::Object(schema_node)) = self
430 .evaluated_schema
431 .pointer(schema_ptr.trim_start_matches('#'))
432 {
433 if let Some(Value::Object(condition)) = schema_node.get("condition") {
434 if let Some(hidden_val) = condition.get("hidden") {
435 if !hidden_val.is_boolean()
437 || hidden_val.as_bool() == Some(true)
438 {
439 continue;
440 }
441 }
442 }
443 }
444
445 let data_path = dot_path.replace('.', "/");
446 let current_data = self
447 .eval_data
448 .data()
449 .pointer(&format!("/{}", data_path))
450 .unwrap_or(&Value::Null);
451
452 let is_empty = match current_data {
453 Value::Null => true,
454 Value::String(s) if s.is_empty() => true,
455 _ => false,
456 };
457
458 let is_schema_val_empty = match schema_val {
459 Value::Null => true,
460 Value::String(s) if s.is_empty() => true,
461 Value::Object(map) if map.contains_key("$evaluation") => true,
462 _ => false,
463 };
464
465 if is_empty && !is_schema_val_empty && current_data != schema_val {
466 default_value_changes.push((
467 data_path,
468 schema_val.clone(),
469 dot_path.clone(),
470 ));
471 }
472 }
473 }
474 }
475 }
476
477 let mut has_changes = false;
478 for (data_path, schema_val, dot_path) in default_value_changes {
479 self.eval_data
480 .set(&format!("/{}", data_path), schema_val.clone());
481 self.eval_cache
482 .bump_data_version(&format!("/{}", data_path));
483
484 let mut change_obj = serde_json::Map::new();
485 change_obj.insert("$ref".to_string(), Value::String(dot_path));
486 let is_clear = schema_val == Value::Null || schema_val.as_str() == Some("");
487 if is_clear {
488 change_obj.insert("clear".to_string(), Value::Bool(true));
489 } else {
490 change_obj.insert("value".to_string(), schema_val);
491 }
492 result.push(Value::Object(change_obj));
493
494 let schema_ptr = format!("#/{}", data_path.replace('/', "/properties/"));
495 to_process.push((schema_ptr, true, None));
496 has_changes = true;
497 }
498
499 if has_changes {
500 Self::process_dependents_queue(
501 &self.engine,
502 &self.evaluations,
503 &mut self.eval_data,
504 &mut self.eval_cache,
505 &self.dependents_evaluations,
506 &self.dep_formula_triggers,
507 &self.evaluated_schema,
508 to_process,
509 processed,
510 result,
511 token,
512 canceled_paths.as_mut().map(|v| &mut **v),
513 )?;
514 }
515
516 Ok(())
517 }
518
519 fn run_subform_pass(
529 &mut self,
530 changed_paths: &[String],
531 re_evaluate: bool,
532 token: Option<&CancellationToken>,
533 result: &mut Vec<Value>,
534 ) -> Result<(), String> {
535 let subform_paths: Vec<String> = self.subforms.keys().cloned().collect();
537
538 for subform_path in subform_paths {
539 let field_key = subform_field_key(&subform_path);
540 let subform_dot_path =
542 path_utils::pointer_to_dot_notation(&subform_path).replace(".properties.", ".");
543 let field_prefix = format!("{}.", field_key);
544 let subform_ptr = normalize_to_json_pointer(&subform_path);
545
546 let item_count =
548 get_value_by_pointer_without_properties(self.eval_data.data(), &subform_ptr)
549 .and_then(|v| v.as_array())
550 .map(|a| a.len())
551 .unwrap_or(0);
552
553 if item_count == 0 {
554 continue;
555 }
556
557 self.eval_cache.prune_subform_caches(item_count);
560
561 let global_sub_re_evaluate = re_evaluate;
565
566 let parent_data_versions_snapshot = self.eval_cache.data_versions.clone();
571 let parent_params_versions_snapshot = self.eval_cache.params_versions.clone();
572
573 for idx in 0..item_count {
574 let prefix_dot = format!("{}.{}.", subform_dot_path, idx);
576 let prefix_bracket = format!("{}[{}].", subform_dot_path, idx);
577 let prefix_field_bracket = format!("{}[{}].", field_key, idx);
578
579 let item_changed_paths: Vec<String> = changed_paths
580 .iter()
581 .filter_map(|p| {
582 if p.starts_with(&prefix_bracket) {
583 Some(p.replacen(&prefix_bracket, &field_prefix, 1))
584 } else if p.starts_with(&prefix_dot) {
585 Some(p.replacen(&prefix_dot, &field_prefix, 1))
586 } else if p.starts_with(&prefix_field_bracket) {
587 Some(p.replacen(&prefix_field_bracket, &field_prefix, 1))
588 } else {
589 None
590 }
591 })
592 .collect();
593
594 let sub_re_evaluate = global_sub_re_evaluate || !item_changed_paths.is_empty();
595
596 if !sub_re_evaluate && item_changed_paths.is_empty() {
598 continue;
599 }
600
601 let item_val =
604 get_value_by_pointer_without_properties(self.eval_data.data(), &subform_ptr)
605 .and_then(|v| v.as_array())
606 .and_then(|a| a.get(idx))
607 .cloned()
608 .unwrap_or(Value::Null);
609
610 let merged_data = {
614 let parent = self.eval_data.data();
615 let mut map = serde_json::Map::new();
616 if let Value::Object(parent_map) = parent {
617 for (k, v) in parent_map {
618 if k == &field_key {
619 continue;
621 }
622 if !v.is_array() {
625 map.insert(k.clone(), v.clone());
626 }
627 }
628 }
629 map.insert(field_key.clone(), item_val.clone());
630 Value::Object(map)
631 };
632
633 let Some(subform) = self.subforms.get_mut(&subform_path) else {
634 continue;
635 };
636
637 self.eval_cache.ensure_active_item_cache(idx);
639 let old_item_val = self
640 .eval_cache
641 .subform_caches
642 .get(&idx)
643 .map(|c| c.item_snapshot.clone())
644 .unwrap_or(Value::Null);
645
646 subform.eval_data.replace_data_and_context(
647 merged_data,
648 self.eval_data
649 .data()
650 .get("$context")
651 .cloned()
652 .unwrap_or(Value::Null),
653 );
654 let new_item_val = subform
655 .eval_data
656 .data()
657 .get(&field_key)
658 .cloned()
659 .unwrap_or(Value::Null);
660
661 let mut parent_cache = std::mem::take(&mut self.eval_cache);
663 parent_cache.ensure_active_item_cache(idx);
664 if let Some(c) = parent_cache.subform_caches.get_mut(&idx) {
665 c.data_versions.merge_from(&parent_data_versions_snapshot);
669 c.data_versions
671 .merge_from_params(&parent_params_versions_snapshot);
672 crate::jsoneval::eval_cache::diff_and_update_versions(
673 &mut c.data_versions,
674 &format!("/{}", field_key),
675 &old_item_val,
676 &new_item_val,
677 );
678 c.item_snapshot = new_item_val;
679 }
680 parent_cache.set_active_item(idx);
681 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
682
683 let subform_result = time_block!(" [subform_pass] rider evaluate_dependents", {
684 subform.evaluate_dependents(
685 &item_changed_paths,
686 None,
687 None,
688 sub_re_evaluate,
689 token,
690 None,
691 false,
692 )
693 });
694
695 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
697 parent_cache.clear_active_item();
698
699 if let Some(parent_item_cache) = self.eval_cache.subform_caches.get(&idx) {
704 let snapshot = parent_item_cache.item_snapshot.clone();
705 subform.eval_cache.ensure_active_item_cache(idx);
706 if let Some(sub_cache) = subform.eval_cache.subform_caches.get_mut(&idx) {
707 sub_cache.item_snapshot = snapshot;
708 }
709 }
710
711 self.eval_cache = parent_cache;
712
713 if let Ok(Value::Array(changes)) = subform_result {
714 let mut had_any_change = false;
715 for change in changes {
716 if let Some(obj) = change.as_object() {
717 if let Some(Value::String(ref_path)) = obj.get("$ref") {
718 let new_ref = if ref_path.starts_with(&field_prefix) {
720 format!(
721 "{}.{}.{}",
722 subform_dot_path,
723 idx,
724 &ref_path[field_prefix.len()..]
725 )
726 } else {
727 format!("{}.{}.{}", subform_dot_path, idx, ref_path)
728 };
729
730 if let Some(val) = obj.get("value") {
736 let data_ptr = format!("/{}", new_ref.replace('.', "/"));
737 self.eval_data.set(&data_ptr, val.clone());
738 had_any_change = true;
739 } else if obj.get("clear").and_then(Value::as_bool) == Some(true) {
740 let data_ptr = format!("/{}", new_ref.replace('.', "/"));
741 self.eval_data.set(&data_ptr, Value::Null);
742 had_any_change = true;
743 }
744
745 let mut new_obj = obj.clone();
746 new_obj.insert("$ref".to_string(), Value::String(new_ref));
747 result.push(Value::Object(new_obj));
748 } else {
749 result.push(change);
751 }
752 }
753 }
754
755 if had_any_change {
763 let item_path = format!("{}/{}", subform_ptr, idx);
764 let updated_item = self
765 .eval_data
766 .get(&item_path)
767 .cloned()
768 .unwrap_or(Value::Null);
769 if let Some(c) = self.eval_cache.subform_caches.get_mut(&idx) {
771 c.item_snapshot = updated_item.clone();
772 }
773 subform.eval_cache.ensure_active_item_cache(idx);
776 if let Some(sub_cache) = subform.eval_cache.subform_caches.get_mut(&idx) {
777 sub_cache.item_snapshot = updated_item;
778 }
779 }
780 }
781 }
782 }
783 Ok(())
784 }
785
786 pub(crate) fn evaluate_dependent_value_static(
788 engine: &RLogic,
789 evaluations: &IndexMap<String, LogicId>,
790 eval_data: &EvalData,
791 value: &Value,
792 changed_field_value: &Value,
793 changed_field_ref_value: &Value,
794 ) -> Result<Value, String> {
795 match value {
796 Value::String(eval_key) => {
798 if let Some(logic_id) = evaluations.get(eval_key) {
799 let mut internal_context = serde_json::Map::new();
802 internal_context.insert("$value".to_string(), changed_field_value.clone());
803 internal_context.insert("$refValue".to_string(), changed_field_ref_value.clone());
804 let context_value = Value::Object(internal_context);
805
806 let result = engine.run_with_context(logic_id, eval_data.data(), &context_value)
807 .map_err(|e| format!("Failed to evaluate dependent logic '{}': {}", eval_key, e))?;
808 Ok(result)
809 } else {
810 Ok(value.clone())
812 }
813 }
814 Value::Object(map) if map.contains_key("$evaluation") => {
817 Err("Dependent evaluation contains unparsed $evaluation - schema was not properly parsed".to_string())
818 }
819 _ => Ok(value.clone()),
821 }
822 }
823
824 pub(crate) fn check_readonly_for_dependents(
826 &self,
827 schema_element: &Value,
828 path: &str,
829 changes: &mut Vec<(String, Value)>,
830 all_values: &mut Vec<(String, Value)>,
831 ) {
832 match schema_element {
833 Value::Object(map) => {
834 let mut is_disabled = false;
836 if let Some(Value::Object(condition)) = map.get("condition") {
837 if let Some(Value::Bool(d)) = condition.get("disabled") {
838 is_disabled = *d;
839 }
840 }
841
842 let mut skip_readonly = false;
844 if let Some(Value::Object(config)) = map.get("config") {
845 if let Some(Value::Object(all)) = config.get("all") {
846 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
847 skip_readonly = *skip;
848 }
849 }
850 }
851
852 if is_disabled && !skip_readonly {
853 if let Some(schema_value) = map.get("value") {
854 let data_path = path_utils::normalize_to_json_pointer(path)
855 .replace("/properties/", "/")
856 .trim_start_matches('#')
857 .to_string();
858
859 let current_data = self
860 .eval_data
861 .data()
862 .pointer(&data_path)
863 .unwrap_or(&Value::Null);
864
865 all_values.push((path.to_string(), schema_value.clone()));
867
868 if current_data != schema_value {
870 changes.push((path.to_string(), schema_value.clone()));
871 }
872 }
873 }
874 }
875 _ => {}
876 }
877 }
878
879 #[allow(dead_code)]
881 pub(crate) fn collect_readonly_fixes(
882 &self,
883 schema_element: &Value,
884 path: &str,
885 changes: &mut Vec<(String, Value)>,
886 ) {
887 match schema_element {
888 Value::Object(map) => {
889 let mut is_disabled = false;
891 if let Some(Value::Object(condition)) = map.get("condition") {
892 if let Some(Value::Bool(d)) = condition.get("disabled") {
893 is_disabled = *d;
894 }
895 }
896
897 let mut skip_readonly = false;
899 if let Some(Value::Object(config)) = map.get("config") {
900 if let Some(Value::Object(all)) = config.get("all") {
901 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
902 skip_readonly = *skip;
903 }
904 }
905 }
906
907 if is_disabled && !skip_readonly {
908 if let Some(schema_value) = map.get("value") {
912 let data_path = path_utils::normalize_to_json_pointer(path)
913 .replace("/properties/", "/")
914 .trim_start_matches('#')
915 .to_string();
916
917 let current_data = self
918 .eval_data
919 .data()
920 .pointer(&data_path)
921 .unwrap_or(&Value::Null);
922
923 if current_data != schema_value {
924 changes.push((path.to_string(), schema_value.clone()));
925 }
926 }
927 }
928
929 if let Some(Value::Object(props)) = map.get("properties") {
931 for (key, val) in props {
932 let next_path = if path == "#" {
933 format!("#/properties/{}", key)
934 } else {
935 format!("{}/properties/{}", path, key)
936 };
937 self.collect_readonly_fixes(val, &next_path, changes);
938 }
939 }
940 }
941 _ => {}
942 }
943 }
944
945 pub(crate) fn check_hidden_field(
947 &self,
948 schema_element: &Value,
949 path: &str,
950 hidden_fields: &mut Vec<String>,
951 ) {
952 match schema_element {
953 Value::Object(map) => {
954 let mut is_hidden = false;
956 if let Some(Value::Object(condition)) = map.get("condition") {
957 if let Some(Value::Bool(h)) = condition.get("hidden") {
958 is_hidden = *h;
959 }
960 }
961
962 let mut keep_hidden = false;
964 if let Some(Value::Object(config)) = map.get("config") {
965 if let Some(Value::Object(all)) = config.get("all") {
966 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
967 keep_hidden = *keep;
968 }
969 }
970 }
971
972 if is_hidden && !keep_hidden {
973 let data_path = path_utils::normalize_to_json_pointer(path)
974 .replace("/properties/", "/")
975 .trim_start_matches('#')
976 .to_string();
977
978 let current_data = self
979 .eval_data
980 .data()
981 .pointer(&data_path)
982 .unwrap_or(&Value::Null);
983
984 if current_data != &Value::Null && current_data != "" {
986 hidden_fields.push(path.to_string());
987 }
988 }
989 }
990 _ => {}
991 }
992 }
993
994 #[allow(dead_code)]
996 pub(crate) fn collect_hidden_fields(
997 &self,
998 schema_element: &Value,
999 path: &str,
1000 hidden_fields: &mut Vec<String>,
1001 ) {
1002 match schema_element {
1003 Value::Object(map) => {
1004 let mut is_hidden = false;
1006 if let Some(Value::Object(condition)) = map.get("condition") {
1007 if let Some(Value::Bool(h)) = condition.get("hidden") {
1008 is_hidden = *h;
1009 }
1010 }
1011
1012 let mut keep_hidden = false;
1014 if let Some(Value::Object(config)) = map.get("config") {
1015 if let Some(Value::Object(all)) = config.get("all") {
1016 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
1017 keep_hidden = *keep;
1018 }
1019 }
1020 }
1021
1022 if is_hidden && !keep_hidden {
1023 let data_path = path_utils::normalize_to_json_pointer(path)
1024 .replace("/properties/", "/")
1025 .trim_start_matches('#')
1026 .to_string();
1027
1028 let current_data = self
1029 .eval_data
1030 .data()
1031 .pointer(&data_path)
1032 .unwrap_or(&Value::Null);
1033
1034 if current_data != &Value::Null && current_data != "" {
1036 hidden_fields.push(path.to_string());
1037 }
1038 }
1039
1040 for (key, val) in map {
1042 if key == "properties" {
1043 if let Value::Object(props) = val {
1044 for (p_key, p_val) in props {
1045 let next_path = if path == "#" {
1046 format!("#/properties/{}", p_key)
1047 } else {
1048 format!("{}/properties/{}", path, p_key)
1049 };
1050 self.collect_hidden_fields(p_val, &next_path, hidden_fields);
1051 }
1052 }
1053 } else if let Value::Object(_) = val {
1054 if key == "condition"
1056 || key == "config"
1057 || key == "rules"
1058 || key == "dependents"
1059 || key == "hideLayout"
1060 || key == "$layout"
1061 || key == "$params"
1062 || key == "definitions"
1063 || key == "$defs"
1064 || key.starts_with('$')
1065 {
1066 continue;
1067 }
1068
1069 let next_path = if path == "#" {
1070 format!("#/{}", key)
1071 } else {
1072 format!("{}/{}", path, key)
1073 };
1074 self.collect_hidden_fields(val, &next_path, hidden_fields);
1075 }
1076 }
1077 }
1078 _ => {}
1079 }
1080 }
1081
1082 pub(crate) fn recursive_hide_effect(
1085 engine: &RLogic,
1086 evaluations: &IndexMap<String, LogicId>,
1087 reffed_by: &IndexMap<String, Vec<String>>,
1088 eval_data: &mut EvalData,
1089 eval_cache: &mut crate::jsoneval::eval_cache::EvalCache,
1090 mut hidden_fields: Vec<String>,
1091 queue: &mut Vec<(String, bool, Option<Vec<usize>>)>,
1092 result: &mut Vec<Value>,
1093 ) {
1094 while let Some(hf) = hidden_fields.pop() {
1095 let data_path = path_utils::normalize_to_json_pointer(&hf)
1096 .replace("/properties/", "/")
1097 .trim_start_matches('#')
1098 .to_string();
1099
1100 eval_data.set(&data_path, Value::Null);
1102 eval_cache.bump_data_version(&data_path);
1103
1104 let mut change_obj = serde_json::Map::new();
1106 change_obj.insert(
1107 "$ref".to_string(),
1108 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
1109 );
1110 change_obj.insert("$hidden".to_string(), Value::Bool(true));
1111 change_obj.insert("clear".to_string(), Value::Bool(true));
1112 result.push(Value::Object(change_obj));
1113
1114 queue.push((hf.clone(), true, None));
1116
1117 if let Some(referencing_fields) = reffed_by.get(&data_path) {
1119 for rb in referencing_fields {
1120 let hidden_eval_key = format!("{}/condition/hidden", rb);
1124
1125 if let Some(logic_id) = evaluations.get(&hidden_eval_key) {
1126 let rb_data_path = path_utils::normalize_to_json_pointer(rb)
1133 .replace("/properties/", "/")
1134 .trim_start_matches('#')
1135 .to_string();
1136 let rb_value = eval_data
1137 .data()
1138 .pointer(&rb_data_path)
1139 .cloned()
1140 .unwrap_or(Value::Null);
1141
1142 if let Ok(Value::Bool(is_hidden)) = engine.run(logic_id, eval_data.data()) {
1144 if is_hidden {
1145 if !hidden_fields.contains(rb) {
1148 let has_value = rb_value != Value::Null && rb_value != "";
1149 if has_value {
1150 hidden_fields.push(rb.clone());
1151 }
1152 }
1153 }
1154 }
1155 }
1156 }
1157 }
1158 }
1159 }
1160
1161 pub(crate) fn process_dependents_queue(
1164 engine: &RLogic,
1165 evaluations: &IndexMap<String, LogicId>,
1166 eval_data: &mut EvalData,
1167 eval_cache: &mut crate::jsoneval::eval_cache::EvalCache,
1168 dependents_evaluations: &IndexMap<String, Vec<DependentItem>>,
1169 dep_formula_triggers: &IndexMap<String, Vec<(String, usize)>>,
1170 evaluated_schema: &Value,
1171 queue: &mut Vec<(String, bool, Option<Vec<usize>>)>,
1172 processed: &mut std::collections::HashMap<String, Option<std::collections::HashSet<usize>>>,
1173 result: &mut Vec<Value>,
1174 token: Option<&CancellationToken>,
1175 canceled_paths: Option<&mut Vec<String>>,
1176 ) -> Result<(), String> {
1177 while let Some((current_path, is_transitive, target_indices)) = queue.pop() {
1178 if let Some(t) = token {
1179 if t.is_cancelled() {
1180 if let Some(cp) = canceled_paths {
1181 cp.push(current_path.clone());
1182 for (path, _, _) in queue.iter() {
1183 cp.push(path.clone());
1184 }
1185 }
1186 return Err("Cancelled".to_string());
1187 }
1188 }
1189
1190 let (should_run, indices_to_run) = match processed.get(¤t_path) {
1191 Some(None) => {
1192 continue;
1194 }
1195 Some(Some(already_processed_indices)) => {
1196 if let Some(targets) = &target_indices {
1197 let new_targets: std::collections::HashSet<usize> = targets
1198 .iter()
1199 .copied()
1200 .filter(|i| !already_processed_indices.contains(i))
1201 .collect();
1202 if new_targets.is_empty() {
1203 continue;
1204 }
1205 (true, Some(new_targets))
1206 } else {
1207 (true, None)
1208 }
1209 }
1210 None => (
1211 true,
1212 target_indices.clone().map(|t| t.into_iter().collect()),
1213 ),
1214 };
1215
1216 if !should_run {
1217 continue;
1218 }
1219
1220 let new_processed_state = if let Some(targets_to_run) = &indices_to_run {
1221 match processed.get(¤t_path) {
1222 Some(Some(existing_targets)) => {
1223 let mut copy = existing_targets.clone();
1224 for t in targets_to_run {
1225 copy.insert(*t);
1226 }
1227 Some(copy)
1228 }
1229 _ => Some(targets_to_run.clone()),
1230 }
1231 } else {
1232 None
1233 };
1234 processed.insert(current_path.clone(), new_processed_state);
1235
1236 let current_data_path = path_utils::normalize_to_json_pointer(¤t_path)
1238 .replace("/properties/", "/")
1239 .trim_start_matches('#')
1240 .to_string();
1241 let mut current_value = eval_data
1242 .data()
1243 .pointer(¤t_data_path)
1244 .cloned()
1245 .unwrap_or(Value::Null);
1246
1247 if let Some(formula_sources) = dep_formula_triggers.get(¤t_data_path) {
1252 let mut targets_by_source: std::collections::HashMap<String, Vec<usize>> =
1253 std::collections::HashMap::new();
1254 for (source_schema_path, dep_idx) in formula_sources {
1255 let source_ptr = path_utils::dot_notation_to_schema_pointer(source_schema_path);
1256 targets_by_source
1257 .entry(source_ptr)
1258 .or_default()
1259 .push(*dep_idx);
1260 }
1261 for (source_ptr, targets) in targets_by_source {
1262 if let Some(None) = processed.get(&source_ptr) {
1264 continue;
1265 }
1266 queue.push((source_ptr, true, Some(targets)));
1267 }
1268 }
1269
1270 if let Some(dependent_items) = dependents_evaluations.get(¤t_path) {
1272 for (dep_idx, dep_item) in dependent_items.iter().enumerate() {
1273 if let Some(targets) = &indices_to_run {
1274 if !targets.contains(&dep_idx) {
1275 continue;
1276 }
1277 }
1278 let ref_path = &dep_item.ref_path;
1279 let pointer_path = path_utils::normalize_to_json_pointer(ref_path);
1280 let data_path = pointer_path.replace("/properties/", "/");
1282
1283 let current_ref_value = eval_data
1284 .data()
1285 .pointer(&data_path)
1286 .cloned()
1287 .unwrap_or(Value::Null);
1288
1289 let field = evaluated_schema.pointer(&pointer_path).cloned();
1291
1292 let parent_path = if let Some(last_slash) = pointer_path.rfind("/properties") {
1294 &pointer_path[..last_slash]
1295 } else {
1296 "/"
1297 };
1298 let mut parent_field = if parent_path.is_empty() || parent_path == "/" {
1299 evaluated_schema.clone()
1300 } else {
1301 evaluated_schema
1302 .pointer(parent_path)
1303 .cloned()
1304 .unwrap_or_else(|| Value::Object(serde_json::Map::new()))
1305 };
1306
1307 if let Value::Object(ref mut map) = parent_field {
1309 map.remove("properties");
1310 map.remove("$layout");
1311 }
1312
1313 let mut change_obj = serde_json::Map::new();
1314 change_obj.insert(
1315 "$ref".to_string(),
1316 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
1317 );
1318 if let Some(f) = field {
1319 change_obj.insert("$field".to_string(), f);
1320 }
1321 change_obj.insert("$parentField".to_string(), parent_field);
1322 change_obj.insert("transitive".to_string(), Value::Bool(is_transitive));
1323
1324 if processed.contains_key(ref_path) {
1329 continue;
1330 }
1331
1332 let mut add_transitive = false;
1333 let mut add_deps = false;
1334 if let Some(clear_val) = &dep_item.clear {
1336 let should_clear = Self::evaluate_dependent_value_static(
1337 engine,
1338 evaluations,
1339 eval_data,
1340 clear_val,
1341 ¤t_value,
1342 ¤t_ref_value,
1343 )?;
1344 let clear_bool = match should_clear {
1345 Value::Bool(b) => b,
1346 _ => false,
1347 };
1348
1349 if clear_bool {
1350 if data_path == current_data_path {
1351 current_value = Value::Null;
1352 }
1353 eval_data.set(&data_path, Value::Null);
1354 eval_cache.bump_data_version(&data_path);
1355 change_obj.insert("clear".to_string(), Value::Bool(true));
1356 add_transitive = true;
1357 add_deps = true;
1358 }
1359 }
1360
1361 if let Some(value_val) = &dep_item.value {
1363 let computed_value = Self::evaluate_dependent_value_static(
1364 engine,
1365 evaluations,
1366 eval_data,
1367 value_val,
1368 ¤t_value,
1369 ¤t_ref_value,
1370 )?;
1371 let cleaned_val = clean_float_noise_scalar(computed_value);
1372
1373 let is_clear =
1374 cleaned_val == Value::Null || cleaned_val.as_str() == Some("");
1375
1376 if cleaned_val != current_ref_value && !is_clear {
1377 if data_path == current_data_path {
1378 current_value = cleaned_val.clone();
1379 }
1380 eval_data.set(&data_path, cleaned_val.clone());
1381 eval_cache.bump_data_version(&data_path);
1382 change_obj.insert("value".to_string(), cleaned_val);
1383 add_transitive = true;
1384 add_deps = true;
1385 }
1386 }
1387
1388 if add_deps {
1390 result.push(Value::Object(change_obj));
1391 }
1392
1393 if add_transitive {
1395 queue.push((ref_path.clone(), true, None));
1396 }
1397 }
1398 }
1399 }
1400 Ok(())
1401 }
1402}
1403
1404fn subform_field_key(subform_path: &str) -> String {
1411 let stripped = subform_path.trim_start_matches('#').trim_start_matches('/');
1413
1414 stripped
1416 .split('/')
1417 .filter(|seg| !seg.is_empty() && *seg != "properties")
1418 .last()
1419 .unwrap_or(stripped)
1420 .to_string()
1421}