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::utils::clean_float_noise_scalar;
10use crate::EvalData;
11
12use indexmap::{IndexMap, IndexSet};
13use serde_json::Value;
14
15impl JSONEval {
16 pub fn evaluate_dependents(
20 &mut self,
21 changed_paths: &[String],
22 data: Option<&str>,
23 context: Option<&str>,
24 re_evaluate: bool,
25 token: Option<&CancellationToken>,
26 mut canceled_paths: Option<&mut Vec<String>>,
27 include_subforms: bool,
28 ) -> Result<Value, String> {
29 if let Some(t) = token {
30 if t.is_cancelled() {
31 return Err("Cancelled".to_string());
32 }
33 }
34 let _lock = self.eval_lock.lock().unwrap();
35
36 if let Some(data_str) = data {
38 let data_value = json_parser::parse_json_str(data_str)?;
39 let context_value = if let Some(ctx) = context {
40 json_parser::parse_json_str(ctx)?
41 } else {
42 Value::Object(serde_json::Map::new())
43 };
44 let old_data = self.eval_data.snapshot_data_clone();
45 self.eval_data
46 .replace_data_and_context(data_value, context_value);
47 let new_data = self.eval_data.snapshot_data_clone();
48 self.eval_cache
49 .store_snapshot_and_diff_versions(&old_data, &new_data);
50 }
51
52 let mut result = Vec::new();
53 let mut processed = IndexSet::new();
54 let mut to_process: Vec<(String, bool)> = changed_paths
55 .iter()
56 .map(|path| (path_utils::dot_notation_to_schema_pointer(path), false))
57 .collect();
58
59 Self::process_dependents_queue(
60 &self.engine,
61 &self.evaluations,
62 &mut self.eval_data,
63 &mut self.eval_cache,
64 &self.dependents_evaluations,
65 &self.dep_formula_triggers,
66 &self.evaluated_schema,
67 &mut to_process,
68 &mut processed,
69 &mut result,
70 token,
71 canceled_paths.as_mut().map(|v| &mut **v),
72 )?;
73
74 drop(_lock);
76
77 if re_evaluate {
78 self.run_re_evaluate_pass(
79 token,
80 &mut to_process,
81 &mut processed,
82 &mut result,
83 canceled_paths.as_mut().map(|v| &mut **v),
84 )?;
85 }
86
87 if include_subforms {
88 let extended_paths: Vec<String> = {
94 let mut paths = changed_paths.to_vec();
95 for item in &result {
96 if let Some(ref_val) = item.get("$ref").and_then(|v| v.as_str()) {
97 let s = ref_val.to_string();
98 if !paths.contains(&s) {
99 paths.push(s);
100 }
101 }
102 }
103 paths
104 };
105 self.run_subform_pass(&extended_paths, re_evaluate, token, &mut result)?;
106 }
107
108 let deduped = {
113 let mut seen: IndexMap<String, usize> = IndexMap::new();
114 for (i, item) in result.iter().enumerate() {
115 if let Some(r) = item.get("$ref").and_then(|v| v.as_str()) {
116 seen.insert(r.to_string(), i);
117 }
118 }
119 let last_indices: IndexSet<usize> = seen.values().copied().collect();
120 let out: Vec<Value> = result
121 .into_iter()
122 .enumerate()
123 .filter(|(i, _)| last_indices.contains(i))
124 .map(|(_, item)| item)
125 .collect();
126 out
127 };
128
129 if self.eval_cache.active_item_index.is_none() {
137 let current_snapshot = self.eval_data.snapshot_data_clone();
138 self.eval_cache.main_form_snapshot = Some(current_snapshot);
139 }
140
141 Ok(Value::Array(deduped))
142 }
143
144 fn run_re_evaluate_pass(
147 &mut self,
148 token: Option<&CancellationToken>,
149 to_process: &mut Vec<(String, bool)>,
150 processed: &mut IndexSet<String>,
151 result: &mut Vec<Value>,
152 mut canceled_paths: Option<&mut Vec<String>>,
153 ) -> Result<(), String> {
154 self.run_schema_default_value_pass(
156 token,
157 to_process,
158 processed,
159 result,
160 canceled_paths.as_mut().map(|v| &mut **v),
161 )?;
162
163 let pre_eval_versions = if let Some(idx) = self.eval_cache.active_item_index {
169 self.eval_cache
170 .subform_caches
171 .get(&idx)
172 .map(|c| c.data_versions.clone())
173 .unwrap_or_else(|| self.eval_cache.data_versions.clone())
174 } else {
175 self.eval_cache.data_versions.clone()
176 };
177
178 self.evaluate_internal(None, token)?;
179
180 self.run_schema_default_value_pass(
182 token,
183 to_process,
184 processed,
185 result,
186 canceled_paths.as_mut().map(|v| &mut **v),
187 )?;
188
189 let active_idx = self.eval_cache.active_item_index;
193 for eval_key in self.sorted_evaluations.iter().flatten() {
194 if eval_key.contains("/$params/") || eval_key.contains("/$") {
195 continue;
196 }
197
198 let schema_ptr = path_utils::normalize_to_json_pointer(eval_key);
199 let data_path = schema_ptr
200 .replace("/properties/", "/")
201 .trim_start_matches('#')
202 .trim_start_matches('/')
203 .to_string();
204
205 let version_path = format!("/{}", data_path);
206 let old_ver = pre_eval_versions.get(&version_path);
207 let new_ver = if let Some(idx) = active_idx {
208 self.eval_cache
209 .subform_caches
210 .get(&idx)
211 .map(|c| c.data_versions.get(&version_path))
212 .unwrap_or_else(|| self.eval_cache.data_versions.get(&version_path))
213 } else {
214 self.eval_cache.data_versions.get(&version_path)
215 };
216
217 if new_ver > old_ver {
218 if let Some(new_val) = self.evaluated_schema.pointer(&schema_ptr) {
219 let dot_path = data_path.trim_end_matches("/value").replace('/', ".");
220 let mut obj = serde_json::Map::new();
221 obj.insert("$ref".to_string(), Value::String(dot_path));
222 let is_clear = new_val == &Value::Null || new_val.as_str() == Some("");
223 if is_clear {
224 obj.insert("clear".to_string(), Value::Bool(true));
225 } else {
226 obj.insert("value".to_string(), new_val.clone());
227 }
228 result.push(Value::Object(obj));
229 }
230 }
231 }
232
233 let _lock = self.eval_lock.lock().unwrap();
235
236 let mut readonly_changes = Vec::new();
238 let mut readonly_values = Vec::new();
239 for path in self.conditional_readonly_fields.iter() {
240 let normalized = path_utils::normalize_to_json_pointer(path);
241 if let Some(schema_el) = self.evaluated_schema.pointer(&normalized) {
242 self.check_readonly_for_dependents(
243 schema_el,
244 path,
245 &mut readonly_changes,
246 &mut readonly_values,
247 );
248 }
249 }
250 for (path, schema_value) in readonly_changes {
251 let data_path = path_utils::normalize_to_json_pointer(&path)
252 .replace("/properties/", "/")
253 .trim_start_matches('#')
254 .to_string();
255 self.eval_data.set(&data_path, schema_value.clone());
256 self.eval_cache.bump_data_version(&data_path);
257 to_process.push((path, true));
258 }
259 for (path, schema_value) in readonly_values {
260 let data_path = path_utils::normalize_to_json_pointer(&path)
261 .replace("/properties/", "/")
262 .trim_start_matches('#')
263 .to_string();
264 let mut obj = serde_json::Map::new();
265 obj.insert(
266 "$ref".to_string(),
267 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
268 );
269 obj.insert("$readonly".to_string(), Value::Bool(true));
270 let is_clear = schema_value == Value::Null || schema_value.as_str() == Some("");
271 if is_clear {
272 obj.insert("clear".to_string(), Value::Bool(true));
273 } else {
274 obj.insert("value".to_string(), schema_value);
275 }
276 result.push(Value::Object(obj));
277 }
278
279 let had_readonly_changes = !to_process.is_empty();
286 if had_readonly_changes {
287 if let Some(active_idx) = self.eval_cache.active_item_index {
288 let params_table_keys: Vec<String> = self
289 .table_metadata
290 .keys()
291 .filter(|k| k.starts_with("#/$params"))
292 .cloned()
293 .collect();
294 if !params_table_keys.is_empty() {
295 self.eval_cache
296 .invalidate_params_tables_for_item(active_idx, ¶ms_table_keys);
297 }
298 drop(_lock);
299 self.evaluate_internal(None, token)?;
300 }
304 }
305
306 if !to_process.is_empty() {
307 Self::process_dependents_queue(
308 &self.engine,
309 &self.evaluations,
310 &mut self.eval_data,
311 &mut self.eval_cache,
312 &self.dependents_evaluations,
313 &self.dep_formula_triggers,
314 &self.evaluated_schema,
315 to_process,
316 processed,
317 result,
318 token,
319 canceled_paths.as_mut().map(|v| &mut **v),
320 )?;
321 }
322
323 let mut hidden_fields = Vec::new();
325 for path in self.conditional_hidden_fields.iter() {
326 let normalized = path_utils::normalize_to_json_pointer(path);
327 if let Some(schema_el) = self.evaluated_schema.pointer(&normalized) {
328 self.check_hidden_field(schema_el, path, &mut hidden_fields);
329 }
330 }
331 if !hidden_fields.is_empty() {
332 Self::recursive_hide_effect(
333 &self.engine,
334 &self.evaluations,
335 &self.reffed_by,
336 &mut self.eval_data,
337 &mut self.eval_cache,
338 hidden_fields,
339 to_process,
340 result,
341 );
342 }
343 if !to_process.is_empty() {
344 Self::process_dependents_queue(
345 &self.engine,
346 &self.evaluations,
347 &mut self.eval_data,
348 &mut self.eval_cache,
349 &self.dependents_evaluations,
350 &self.dep_formula_triggers,
351 &self.evaluated_schema,
352 to_process,
353 processed,
354 result,
355 token,
356 canceled_paths.as_mut().map(|v| &mut **v),
357 )?;
358 }
359
360 Ok(())
361 }
362
363 fn run_schema_default_value_pass(
366 &mut self,
367 token: Option<&CancellationToken>,
368 to_process: &mut Vec<(String, bool)>,
369 processed: &mut IndexSet<String>,
370 result: &mut Vec<Value>,
371 mut canceled_paths: Option<&mut Vec<String>>,
372 ) -> Result<(), String> {
373 let mut default_value_changes = Vec::new();
374 let schema_values = self.get_schema_value_array();
375
376 if let Value::Array(values) = schema_values {
377 for item in values {
378 if let Value::Object(map) = item {
379 if let (Some(Value::String(dot_path)), Some(schema_val)) =
380 (map.get("path"), map.get("value"))
381 {
382 let schema_ptr = path_utils::dot_notation_to_schema_pointer(dot_path);
383 if let Some(Value::Object(schema_node)) = self
384 .evaluated_schema
385 .pointer(schema_ptr.trim_start_matches('#'))
386 {
387 if let Some(Value::Object(condition)) = schema_node.get("condition") {
388 if let Some(hidden_val) = condition.get("hidden") {
389 if !hidden_val.is_boolean()
391 || hidden_val.as_bool() == Some(true)
392 {
393 continue;
394 }
395 }
396 }
397 }
398
399 let data_path = dot_path.replace('.', "/");
400 let current_data = self
401 .eval_data
402 .data()
403 .pointer(&format!("/{}", data_path))
404 .unwrap_or(&Value::Null);
405
406 let is_empty = match current_data {
407 Value::Null => true,
408 Value::String(s) if s.is_empty() => true,
409 _ => false,
410 };
411
412 let is_schema_val_empty = match schema_val {
413 Value::Null => true,
414 Value::String(s) if s.is_empty() => true,
415 Value::Object(map) if map.contains_key("$evaluation") => true,
416 _ => false,
417 };
418
419 if is_empty && !is_schema_val_empty && current_data != schema_val {
420 default_value_changes.push((
421 data_path,
422 schema_val.clone(),
423 dot_path.clone(),
424 ));
425 }
426 }
427 }
428 }
429 }
430
431 let mut has_changes = false;
432 for (data_path, schema_val, dot_path) in default_value_changes {
433 self.eval_data
434 .set(&format!("/{}", data_path), schema_val.clone());
435 self.eval_cache
436 .bump_data_version(&format!("/{}", data_path));
437
438 let mut change_obj = serde_json::Map::new();
439 change_obj.insert("$ref".to_string(), Value::String(dot_path));
440 let is_clear = schema_val == Value::Null || schema_val.as_str() == Some("");
441 if is_clear {
442 change_obj.insert("clear".to_string(), Value::Bool(true));
443 } else {
444 change_obj.insert("value".to_string(), schema_val);
445 }
446 result.push(Value::Object(change_obj));
447
448 let schema_ptr = format!("#/{}", data_path.replace('/', "/properties/"));
449 to_process.push((schema_ptr, true));
450 has_changes = true;
451 }
452
453 if has_changes {
454 Self::process_dependents_queue(
455 &self.engine,
456 &self.evaluations,
457 &mut self.eval_data,
458 &mut self.eval_cache,
459 &self.dependents_evaluations,
460 &self.dep_formula_triggers,
461 &self.evaluated_schema,
462 to_process,
463 processed,
464 result,
465 token,
466 canceled_paths.as_mut().map(|v| &mut **v),
467 )?;
468 }
469
470 Ok(())
471 }
472
473 fn run_subform_pass(
483 &mut self,
484 changed_paths: &[String],
485 re_evaluate: bool,
486 token: Option<&CancellationToken>,
487 result: &mut Vec<Value>,
488 ) -> Result<(), String> {
489 let subform_paths: Vec<String> = self.subforms.keys().cloned().collect();
491
492 for subform_path in subform_paths {
493 let field_key = subform_field_key(&subform_path);
494 let subform_dot_path =
496 path_utils::pointer_to_dot_notation(&subform_path).replace(".properties.", ".");
497 let field_prefix = format!("{}.", field_key);
498 let subform_ptr = normalize_to_json_pointer(&subform_path);
499
500 let item_count =
502 get_value_by_pointer_without_properties(self.eval_data.data(), &subform_ptr)
503 .and_then(|v| v.as_array())
504 .map(|a| a.len())
505 .unwrap_or(0);
506
507 if item_count == 0 {
508 continue;
509 }
510
511 self.eval_cache.prune_subform_caches(item_count);
514
515 let global_sub_re_evaluate = re_evaluate;
519
520 let parent_data_versions_snapshot = self.eval_cache.data_versions.clone();
525 let parent_params_versions_snapshot = self.eval_cache.params_versions.clone();
526
527 for idx in 0..item_count {
528 let prefix_dot = format!("{}.{}.", subform_dot_path, idx);
530 let prefix_bracket = format!("{}[{}].", subform_dot_path, idx);
531 let prefix_field_bracket = format!("{}[{}].", field_key, idx);
532
533 let item_changed_paths: Vec<String> = changed_paths
534 .iter()
535 .filter_map(|p| {
536 if p.starts_with(&prefix_bracket) {
537 Some(p.replacen(&prefix_bracket, &field_prefix, 1))
538 } else if p.starts_with(&prefix_dot) {
539 Some(p.replacen(&prefix_dot, &field_prefix, 1))
540 } else if p.starts_with(&prefix_field_bracket) {
541 Some(p.replacen(&prefix_field_bracket, &field_prefix, 1))
542 } else {
543 None
544 }
545 })
546 .collect();
547
548 let sub_re_evaluate = global_sub_re_evaluate || !item_changed_paths.is_empty();
549
550 if !sub_re_evaluate && item_changed_paths.is_empty() {
552 continue;
553 }
554
555 let item_val =
558 get_value_by_pointer_without_properties(self.eval_data.data(), &subform_ptr)
559 .and_then(|v| v.as_array())
560 .and_then(|a| a.get(idx))
561 .cloned()
562 .unwrap_or(Value::Null);
563
564 let merged_data = {
568 let parent = self.eval_data.data();
569 let mut map = serde_json::Map::new();
570 if let Value::Object(parent_map) = parent {
571 for (k, v) in parent_map {
572 if k == &field_key {
573 continue;
575 }
576 if !v.is_array() {
579 map.insert(k.clone(), v.clone());
580 }
581 }
582 }
583 map.insert(field_key.clone(), item_val.clone());
584 Value::Object(map)
585 };
586
587 let Some(subform) = self.subforms.get_mut(&subform_path) else {
588 continue;
589 };
590
591 self.eval_cache.ensure_active_item_cache(idx);
593 let old_item_val = self
594 .eval_cache
595 .subform_caches
596 .get(&idx)
597 .map(|c| c.item_snapshot.clone())
598 .unwrap_or(Value::Null);
599
600 subform.eval_data.replace_data_and_context(
601 merged_data,
602 self.eval_data
603 .data()
604 .get("$context")
605 .cloned()
606 .unwrap_or(Value::Null),
607 );
608 let new_item_val = subform
609 .eval_data
610 .data()
611 .get(&field_key)
612 .cloned()
613 .unwrap_or(Value::Null);
614
615 let mut parent_cache = std::mem::take(&mut self.eval_cache);
617 parent_cache.ensure_active_item_cache(idx);
618 if let Some(c) = parent_cache.subform_caches.get_mut(&idx) {
619 c.data_versions.merge_from(&parent_data_versions_snapshot);
623 c.data_versions
625 .merge_from_params(&parent_params_versions_snapshot);
626 crate::jsoneval::eval_cache::diff_and_update_versions(
627 &mut c.data_versions,
628 &format!("/{}", field_key),
629 &old_item_val,
630 &new_item_val,
631 );
632 c.item_snapshot = new_item_val;
633 }
634 parent_cache.set_active_item(idx);
635 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
636
637 let subform_result = subform.evaluate_dependents(
638 &item_changed_paths,
639 None,
640 None,
641 sub_re_evaluate,
642 token,
643 None,
644 false,
645 );
646
647 std::mem::swap(&mut subform.eval_cache, &mut parent_cache);
649 parent_cache.clear_active_item();
650
651 if let Some(parent_item_cache) = self.eval_cache.subform_caches.get(&idx) {
656 let snapshot = parent_item_cache.item_snapshot.clone();
657 subform.eval_cache.ensure_active_item_cache(idx);
658 if let Some(sub_cache) = subform.eval_cache.subform_caches.get_mut(&idx) {
659 sub_cache.item_snapshot = snapshot;
660 }
661 }
662
663 self.eval_cache = parent_cache;
664
665 if let Ok(Value::Array(changes)) = subform_result {
666 for change in changes {
667 if let Some(obj) = change.as_object() {
668 if let Some(Value::String(ref_path)) = obj.get("$ref") {
669 let new_ref = if ref_path.starts_with(&field_prefix) {
671 format!(
672 "{}.{}.{}",
673 subform_dot_path,
674 idx,
675 &ref_path[field_prefix.len()..]
676 )
677 } else {
678 format!("{}.{}.{}", subform_dot_path, idx, ref_path)
679 };
680
681 if let Some(val) = obj.get("value") {
687 let data_ptr = format!("/{}", new_ref.replace('.', "/"));
688 self.eval_data.set(&data_ptr, val.clone());
689 } else if obj.get("clear").and_then(Value::as_bool) == Some(true) {
690 let data_ptr = format!("/{}", new_ref.replace('.', "/"));
691 self.eval_data.set(&data_ptr, Value::Null);
692 }
693
694 let mut new_obj = obj.clone();
695 new_obj.insert("$ref".to_string(), Value::String(new_ref));
696 result.push(Value::Object(new_obj));
697 } else {
698 result.push(change);
700 }
701 }
702 }
703 }
704 }
705 }
706 Ok(())
707 }
708
709 pub(crate) fn evaluate_dependent_value_static(
711 engine: &RLogic,
712 evaluations: &IndexMap<String, LogicId>,
713 eval_data: &EvalData,
714 value: &Value,
715 changed_field_value: &Value,
716 changed_field_ref_value: &Value,
717 ) -> Result<Value, String> {
718 match value {
719 Value::String(eval_key) => {
721 if let Some(logic_id) = evaluations.get(eval_key) {
722 let mut internal_context = serde_json::Map::new();
725 internal_context.insert("$value".to_string(), changed_field_value.clone());
726 internal_context.insert("$refValue".to_string(), changed_field_ref_value.clone());
727 let context_value = Value::Object(internal_context);
728
729 let result = engine.run_with_context(logic_id, eval_data.data(), &context_value)
730 .map_err(|e| format!("Failed to evaluate dependent logic '{}': {}", eval_key, e))?;
731 Ok(result)
732 } else {
733 Ok(value.clone())
735 }
736 }
737 Value::Object(map) if map.contains_key("$evaluation") => {
740 Err("Dependent evaluation contains unparsed $evaluation - schema was not properly parsed".to_string())
741 }
742 _ => Ok(value.clone()),
744 }
745 }
746
747 pub(crate) fn check_readonly_for_dependents(
749 &self,
750 schema_element: &Value,
751 path: &str,
752 changes: &mut Vec<(String, Value)>,
753 all_values: &mut Vec<(String, Value)>,
754 ) {
755 match schema_element {
756 Value::Object(map) => {
757 let mut is_disabled = false;
759 if let Some(Value::Object(condition)) = map.get("condition") {
760 if let Some(Value::Bool(d)) = condition.get("disabled") {
761 is_disabled = *d;
762 }
763 }
764
765 let mut skip_readonly = false;
767 if let Some(Value::Object(config)) = map.get("config") {
768 if let Some(Value::Object(all)) = config.get("all") {
769 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
770 skip_readonly = *skip;
771 }
772 }
773 }
774
775 if is_disabled && !skip_readonly {
776 if let Some(schema_value) = map.get("value") {
777 let data_path = path_utils::normalize_to_json_pointer(path)
778 .replace("/properties/", "/")
779 .trim_start_matches('#')
780 .to_string();
781
782 let current_data = self
783 .eval_data
784 .data()
785 .pointer(&data_path)
786 .unwrap_or(&Value::Null);
787
788 all_values.push((path.to_string(), schema_value.clone()));
790
791 if current_data != schema_value {
793 changes.push((path.to_string(), schema_value.clone()));
794 }
795 }
796 }
797 }
798 _ => {}
799 }
800 }
801
802 #[allow(dead_code)]
804 pub(crate) fn collect_readonly_fixes(
805 &self,
806 schema_element: &Value,
807 path: &str,
808 changes: &mut Vec<(String, Value)>,
809 ) {
810 match schema_element {
811 Value::Object(map) => {
812 let mut is_disabled = false;
814 if let Some(Value::Object(condition)) = map.get("condition") {
815 if let Some(Value::Bool(d)) = condition.get("disabled") {
816 is_disabled = *d;
817 }
818 }
819
820 let mut skip_readonly = false;
822 if let Some(Value::Object(config)) = map.get("config") {
823 if let Some(Value::Object(all)) = config.get("all") {
824 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
825 skip_readonly = *skip;
826 }
827 }
828 }
829
830 if is_disabled && !skip_readonly {
831 if let Some(schema_value) = map.get("value") {
835 let data_path = path_utils::normalize_to_json_pointer(path)
836 .replace("/properties/", "/")
837 .trim_start_matches('#')
838 .to_string();
839
840 let current_data = self
841 .eval_data
842 .data()
843 .pointer(&data_path)
844 .unwrap_or(&Value::Null);
845
846 if current_data != schema_value {
847 changes.push((path.to_string(), schema_value.clone()));
848 }
849 }
850 }
851
852 if let Some(Value::Object(props)) = map.get("properties") {
854 for (key, val) in props {
855 let next_path = if path == "#" {
856 format!("#/properties/{}", key)
857 } else {
858 format!("{}/properties/{}", path, key)
859 };
860 self.collect_readonly_fixes(val, &next_path, changes);
861 }
862 }
863 }
864 _ => {}
865 }
866 }
867
868 pub(crate) fn check_hidden_field(
870 &self,
871 schema_element: &Value,
872 path: &str,
873 hidden_fields: &mut Vec<String>,
874 ) {
875 match schema_element {
876 Value::Object(map) => {
877 let mut is_hidden = false;
879 if let Some(Value::Object(condition)) = map.get("condition") {
880 if let Some(Value::Bool(h)) = condition.get("hidden") {
881 is_hidden = *h;
882 }
883 }
884
885 let mut keep_hidden = false;
887 if let Some(Value::Object(config)) = map.get("config") {
888 if let Some(Value::Object(all)) = config.get("all") {
889 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
890 keep_hidden = *keep;
891 }
892 }
893 }
894
895 if is_hidden && !keep_hidden {
896 let data_path = path_utils::normalize_to_json_pointer(path)
897 .replace("/properties/", "/")
898 .trim_start_matches('#')
899 .to_string();
900
901 let current_data = self
902 .eval_data
903 .data()
904 .pointer(&data_path)
905 .unwrap_or(&Value::Null);
906
907 if current_data != &Value::Null && current_data != "" {
909 hidden_fields.push(path.to_string());
910 }
911 }
912 }
913 _ => {}
914 }
915 }
916
917 #[allow(dead_code)]
919 pub(crate) fn collect_hidden_fields(
920 &self,
921 schema_element: &Value,
922 path: &str,
923 hidden_fields: &mut Vec<String>,
924 ) {
925 match schema_element {
926 Value::Object(map) => {
927 let mut is_hidden = false;
929 if let Some(Value::Object(condition)) = map.get("condition") {
930 if let Some(Value::Bool(h)) = condition.get("hidden") {
931 is_hidden = *h;
932 }
933 }
934
935 let mut keep_hidden = false;
937 if let Some(Value::Object(config)) = map.get("config") {
938 if let Some(Value::Object(all)) = config.get("all") {
939 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
940 keep_hidden = *keep;
941 }
942 }
943 }
944
945 if is_hidden && !keep_hidden {
946 let data_path = path_utils::normalize_to_json_pointer(path)
947 .replace("/properties/", "/")
948 .trim_start_matches('#')
949 .to_string();
950
951 let current_data = self
952 .eval_data
953 .data()
954 .pointer(&data_path)
955 .unwrap_or(&Value::Null);
956
957 if current_data != &Value::Null && current_data != "" {
959 hidden_fields.push(path.to_string());
960 }
961 }
962
963 for (key, val) in map {
965 if key == "properties" {
966 if let Value::Object(props) = val {
967 for (p_key, p_val) in props {
968 let next_path = if path == "#" {
969 format!("#/properties/{}", p_key)
970 } else {
971 format!("{}/properties/{}", path, p_key)
972 };
973 self.collect_hidden_fields(p_val, &next_path, hidden_fields);
974 }
975 }
976 } else if let Value::Object(_) = val {
977 if key == "condition"
979 || key == "config"
980 || key == "rules"
981 || key == "dependents"
982 || key == "hideLayout"
983 || key == "$layout"
984 || key == "$params"
985 || key == "definitions"
986 || key == "$defs"
987 || key.starts_with('$')
988 {
989 continue;
990 }
991
992 let next_path = if path == "#" {
993 format!("#/{}", key)
994 } else {
995 format!("{}/{}", path, key)
996 };
997 self.collect_hidden_fields(val, &next_path, hidden_fields);
998 }
999 }
1000 }
1001 _ => {}
1002 }
1003 }
1004
1005 pub(crate) fn recursive_hide_effect(
1008 engine: &RLogic,
1009 evaluations: &IndexMap<String, LogicId>,
1010 reffed_by: &IndexMap<String, Vec<String>>,
1011 eval_data: &mut EvalData,
1012 eval_cache: &mut crate::jsoneval::eval_cache::EvalCache,
1013 mut hidden_fields: Vec<String>,
1014 queue: &mut Vec<(String, bool)>,
1015 result: &mut Vec<Value>,
1016 ) {
1017 while let Some(hf) = hidden_fields.pop() {
1018 let data_path = path_utils::normalize_to_json_pointer(&hf)
1019 .replace("/properties/", "/")
1020 .trim_start_matches('#')
1021 .to_string();
1022
1023 eval_data.set(&data_path, Value::Null);
1025 eval_cache.bump_data_version(&data_path);
1026
1027 let mut change_obj = serde_json::Map::new();
1029 change_obj.insert(
1030 "$ref".to_string(),
1031 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
1032 );
1033 change_obj.insert("$hidden".to_string(), Value::Bool(true));
1034 change_obj.insert("clear".to_string(), Value::Bool(true));
1035 result.push(Value::Object(change_obj));
1036
1037 queue.push((hf.clone(), true));
1039
1040 if let Some(referencing_fields) = reffed_by.get(&data_path) {
1042 for rb in referencing_fields {
1043 let hidden_eval_key = format!("{}/condition/hidden", rb);
1047
1048 if let Some(logic_id) = evaluations.get(&hidden_eval_key) {
1049 let rb_data_path = path_utils::normalize_to_json_pointer(rb)
1056 .replace("/properties/", "/")
1057 .trim_start_matches('#')
1058 .to_string();
1059 let rb_value = eval_data
1060 .data()
1061 .pointer(&rb_data_path)
1062 .cloned()
1063 .unwrap_or(Value::Null);
1064
1065 if let Ok(Value::Bool(is_hidden)) = engine.run(logic_id, eval_data.data()) {
1067 if is_hidden {
1068 if !hidden_fields.contains(rb) {
1071 let has_value = rb_value != Value::Null && rb_value != "";
1072 if has_value {
1073 hidden_fields.push(rb.clone());
1074 }
1075 }
1076 }
1077 }
1078 }
1079 }
1080 }
1081 }
1082 }
1083
1084 pub(crate) fn process_dependents_queue(
1087 engine: &RLogic,
1088 evaluations: &IndexMap<String, LogicId>,
1089 eval_data: &mut EvalData,
1090 eval_cache: &mut crate::jsoneval::eval_cache::EvalCache,
1091 dependents_evaluations: &IndexMap<String, Vec<DependentItem>>,
1092 dep_formula_triggers: &IndexMap<String, Vec<String>>,
1093 evaluated_schema: &Value,
1094 queue: &mut Vec<(String, bool)>,
1095 processed: &mut IndexSet<String>,
1096 result: &mut Vec<Value>,
1097 token: Option<&CancellationToken>,
1098 canceled_paths: Option<&mut Vec<String>>,
1099 ) -> Result<(), String> {
1100 while let Some((current_path, is_transitive)) = queue.pop() {
1101 if let Some(t) = token {
1102 if t.is_cancelled() {
1103 if let Some(cp) = canceled_paths {
1105 cp.push(current_path.clone());
1106 for (path, _) in queue.iter() {
1112 cp.push(path.clone());
1113 }
1114 }
1115 return Err("Cancelled".to_string());
1116 }
1117 }
1118 if processed.contains(¤t_path) {
1119 continue;
1120 }
1121 processed.insert(current_path.clone());
1122
1123 let current_data_path = path_utils::normalize_to_json_pointer(¤t_path)
1125 .replace("/properties/", "/")
1126 .trim_start_matches('#')
1127 .to_string();
1128 let mut current_value = eval_data
1129 .data()
1130 .pointer(¤t_data_path)
1131 .cloned()
1132 .unwrap_or(Value::Null);
1133
1134 if let Some(formula_sources) = dep_formula_triggers.get(¤t_data_path) {
1139 for source_schema_path in formula_sources {
1140 if !processed.contains(source_schema_path) {
1141 queue.push((source_schema_path.clone(), true));
1142 }
1143 }
1144 }
1145
1146 if let Some(dependent_items) = dependents_evaluations.get(¤t_path) {
1148 for dep_item in dependent_items {
1149 let ref_path = &dep_item.ref_path;
1150 let pointer_path = path_utils::normalize_to_json_pointer(ref_path);
1151 let data_path = pointer_path.replace("/properties/", "/");
1153
1154 let current_ref_value = eval_data
1155 .data()
1156 .pointer(&data_path)
1157 .cloned()
1158 .unwrap_or(Value::Null);
1159
1160 let field = evaluated_schema.pointer(&pointer_path).cloned();
1162
1163 let parent_path = if let Some(last_slash) = pointer_path.rfind("/properties") {
1165 &pointer_path[..last_slash]
1166 } else {
1167 "/"
1168 };
1169 let mut parent_field = if parent_path.is_empty() || parent_path == "/" {
1170 evaluated_schema.clone()
1171 } else {
1172 evaluated_schema
1173 .pointer(parent_path)
1174 .cloned()
1175 .unwrap_or_else(|| Value::Object(serde_json::Map::new()))
1176 };
1177
1178 if let Value::Object(ref mut map) = parent_field {
1180 map.remove("properties");
1181 map.remove("$layout");
1182 }
1183
1184 let mut change_obj = serde_json::Map::new();
1185 change_obj.insert(
1186 "$ref".to_string(),
1187 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
1188 );
1189 if let Some(f) = field {
1190 change_obj.insert("$field".to_string(), f);
1191 }
1192 change_obj.insert("$parentField".to_string(), parent_field);
1193 change_obj.insert("transitive".to_string(), Value::Bool(is_transitive));
1194
1195 if processed.contains(ref_path) {
1200 continue;
1201 }
1202
1203 let mut add_transitive = false;
1204 let mut add_deps = false;
1205 if let Some(clear_val) = &dep_item.clear {
1207 let should_clear = Self::evaluate_dependent_value_static(
1208 engine,
1209 evaluations,
1210 eval_data,
1211 clear_val,
1212 ¤t_value,
1213 ¤t_ref_value,
1214 )?;
1215 let clear_bool = match should_clear {
1216 Value::Bool(b) => b,
1217 _ => false,
1218 };
1219
1220 if clear_bool {
1221 if data_path == current_data_path {
1222 current_value = Value::Null;
1223 }
1224 eval_data.set(&data_path, Value::Null);
1225 eval_cache.bump_data_version(&data_path);
1226 change_obj.insert("clear".to_string(), Value::Bool(true));
1227 add_transitive = true;
1228 add_deps = true;
1229 }
1230 }
1231
1232 if let Some(value_val) = &dep_item.value {
1234 let computed_value = Self::evaluate_dependent_value_static(
1235 engine,
1236 evaluations,
1237 eval_data,
1238 value_val,
1239 ¤t_value,
1240 ¤t_ref_value,
1241 )?;
1242 let cleaned_val = clean_float_noise_scalar(computed_value);
1243
1244 let is_clear =
1245 cleaned_val == Value::Null || cleaned_val.as_str() == Some("");
1246 let ref_is_clear = current_ref_value == Value::Null
1247 || current_ref_value.as_str() == Some("");
1248
1249 if is_clear && !ref_is_clear {
1250 if data_path == current_data_path {
1253 current_value = Value::Null;
1254 }
1255 eval_data.set(&data_path, Value::Null);
1256 eval_cache.bump_data_version(&data_path);
1257 change_obj.insert("clear".to_string(), Value::Bool(true));
1258 add_transitive = true;
1259 add_deps = true;
1260 } else if cleaned_val != current_ref_value && !is_clear {
1261 if data_path == current_data_path {
1262 current_value = cleaned_val.clone();
1263 }
1264 eval_data.set(&data_path, cleaned_val.clone());
1265 eval_cache.bump_data_version(&data_path);
1266 change_obj.insert("value".to_string(), cleaned_val);
1267 add_transitive = true;
1268 add_deps = true;
1269 }
1270 }
1271
1272 if add_deps {
1274 result.push(Value::Object(change_obj));
1275 }
1276
1277 if add_transitive {
1279 queue.push((ref_path.clone(), true));
1280 }
1281 }
1282 }
1283 }
1284 Ok(())
1285 }
1286}
1287
1288fn subform_field_key(subform_path: &str) -> String {
1295 let stripped = subform_path.trim_start_matches('#').trim_start_matches('/');
1297
1298 stripped
1300 .split('/')
1301 .filter(|seg| !seg.is_empty() && *seg != "properties")
1302 .last()
1303 .unwrap_or(stripped)
1304 .to_string()
1305}