1use super::JSONEval;
2use crate::jsoneval::json_parser;
3use crate::jsoneval::path_utils;
4use crate::jsoneval::path_utils::get_value_by_pointer_without_properties;
5use crate::jsoneval::path_utils::normalize_to_json_pointer;
6use crate::rlogic::{LogicId, RLogic};
7use crate::jsoneval::types::DependentItem;
8use crate::jsoneval::cancellation::CancellationToken;
9use crate::utils::clean_float_noise_scalar;
10use crate::EvalData;
11
12use indexmap::{IndexMap, IndexSet};
13use serde_json::Value;
14
15
16impl JSONEval {
17 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 {
31 if t.is_cancelled() {
32 return Err("Cancelled".to_string());
33 }
34 }
35 let _lock = self.eval_lock.lock().unwrap();
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 self.eval_data
47 .replace_data_and_context(data_value.clone(), context_value);
48 }
49
50 let mut result = Vec::new();
51 let mut processed = IndexSet::new();
52
53 let mut to_process: Vec<(String, bool)> = changed_paths
54 .iter()
55 .map(|path| (path_utils::dot_notation_to_schema_pointer(path), false))
56 .collect();
57
58 Self::process_dependents_queue(
59 &self.engine,
60 &self.evaluations,
61 &mut self.eval_data,
62 &self.dependents_evaluations,
63 &self.evaluated_schema,
64 &mut to_process,
65 &mut processed,
66 &mut result,
67 token,
68 canceled_paths.as_mut().map(|v| &mut **v),
69 )?;
70
71
72 if re_evaluate {
75 drop(_lock);
77
78 self.evaluate_internal(None, token)?;
79
80
81 let _lock = self.eval_lock.lock().unwrap();
83
84 let mut readonly_changes = Vec::new();
87 let mut readonly_values = Vec::new(); for path in self.conditional_readonly_fields.iter() {
92 let normalized = path_utils::normalize_to_json_pointer(path);
93 if let Some(schema_element) = self.evaluated_schema.pointer(&normalized) {
94 self.check_readonly_for_dependents(schema_element, path, &mut readonly_changes, &mut readonly_values);
95 }
96 }
97
98 for (path, schema_value) in readonly_changes {
100 let data_path = path_utils::normalize_to_json_pointer(&path)
102 .replace("/properties/", "/")
103 .trim_start_matches('#')
104 .to_string();
105
106 self.eval_data.set(&data_path, schema_value.clone());
107
108 to_process.push((path, true));
110 }
111
112 for (path, schema_value) in readonly_values {
114 let data_path = path_utils::normalize_to_json_pointer(&path)
115 .replace("/properties/", "/")
116 .trim_start_matches('#')
117 .to_string();
118
119 let mut change_obj = serde_json::Map::new();
120 change_obj.insert("$ref".to_string(), Value::String(path_utils::pointer_to_dot_notation(&data_path)));
121 change_obj.insert("$readonly".to_string(), Value::Bool(true));
122 change_obj.insert("value".to_string(), schema_value);
123
124 result.push(Value::Object(change_obj));
125 }
126
127 if !to_process.is_empty() {
128 Self::process_dependents_queue(
129 &self.engine,
130 &self.evaluations,
131 &mut self.eval_data,
132 &self.dependents_evaluations,
133 &self.evaluated_schema,
134 &mut to_process,
135 &mut processed,
136 &mut result,
137 token,
138 canceled_paths.as_mut().map(|v| &mut **v),
139 )?;
140 }
141
142
143 let mut hidden_fields = Vec::new();
146 for path in self.conditional_hidden_fields.iter() {
149 let normalized = path_utils::normalize_to_json_pointer(path);
150 if let Some(schema_element) = self.evaluated_schema.pointer(&normalized) {
151 self.check_hidden_field(schema_element, path, &mut hidden_fields);
152 }
153 }
154
155 if !hidden_fields.is_empty() {
156 Self::recursive_hide_effect(
157 &self.engine,
158 &self.evaluations,
159 &self.reffed_by,
160 &mut self.eval_data,
161 hidden_fields,
162 &mut to_process,
163 &mut result,
164 );
165 }
166
167
168 if !to_process.is_empty() {
169 Self::process_dependents_queue(
170 &self.engine,
171 &self.evaluations,
172 &mut self.eval_data,
173 &self.dependents_evaluations,
174 &self.evaluated_schema,
175 &mut to_process,
176 &mut processed,
177 &mut result,
178 token,
179 canceled_paths.as_mut().map(|v| &mut **v),
180 )?;
181 }
182
183 }
184
185 if include_subforms {
186 let subform_paths: Vec<String> = self.subforms.keys().cloned().collect();
187 for subform_path in subform_paths {
188 let field_key = subform_field_key(&subform_path);
189 let subform_dot_path = crate::jsoneval::path_utils::pointer_to_dot_notation(&subform_path).replace(".properties.", ".");
190 let field_prefix = format!("{}.", field_key);
191
192 let items: Vec<Value> = get_value_by_pointer_without_properties(self.eval_data.data(), &normalize_to_json_pointer(&subform_path))
194 .and_then(|v| v.as_array())
195 .cloned() .unwrap_or_default();
197
198 if items.is_empty() {
199 continue;
200 }
201
202 for (idx, item) in items.iter().enumerate() {
203 let mut merged_data = self.eval_data.data().clone();
206 if let Value::Object(ref mut map) = merged_data {
207 map.insert(field_key.clone(), item.clone());
208 } else {
209 let mut map = serde_json::Map::new();
210 map.insert(field_key.clone(), item.clone());
211 merged_data = Value::Object(map);
212 }
213
214 let mut item_changed_paths = Vec::new();
216 let prefix_with_bracket = format!("{}[{}].", subform_dot_path, idx);
217 let prefix_with_dot = format!("{}.{}.", subform_dot_path, idx);
218
219 for path in changed_paths {
220 if path.starts_with(&prefix_with_bracket) {
221 item_changed_paths.push(path.replacen(&prefix_with_bracket, &field_prefix, 1));
222 } else if path.starts_with(&prefix_with_dot) {
223 item_changed_paths.push(path.replacen(&prefix_with_dot, &field_prefix, 1));
224 }
225 else if path.starts_with(&format!("{}[{}].", field_key, idx)) {
227 item_changed_paths.push(path.replacen(&format!("{}[{}].", field_key, idx), &field_prefix, 1));
228 }
229 }
230
231 if item_changed_paths.is_empty() && !re_evaluate {
233 continue;
234 }
235
236 if let Some(subform) = self.subforms.get_mut(&subform_path) {
237 subform.eval_data.replace_data_and_context(
241 merged_data,
242 self.eval_data.data().get("$context").cloned().unwrap_or(serde_json::Value::Null)
243 );
244
245 let subform_result_or_err = subform.evaluate_dependents(
247 &item_changed_paths,
248 None, None,
250 re_evaluate,
251 token,
252 None,
253 false,
254 );
255
256 if let Ok(Value::Array(changes)) = subform_result_or_err {
257 for change in changes {
258 if let Some(obj) = change.as_object() {
259 let mut new_obj = obj.clone();
260 if let Some(Value::String(ref_path)) = obj.get("$ref") {
261 let new_ref = if ref_path.starts_with(&field_prefix) {
264 let trailing_path = &ref_path[field_prefix.len()..];
265 format!("{}.{}.{}", subform_dot_path, idx, trailing_path)
266 } else {
267 format!("{}.{}.{}", subform_dot_path, idx, ref_path)
269 };
270 new_obj.insert("$ref".to_string(), Value::String(new_ref));
271 }
272 result.push(Value::Object(new_obj));
273 }
274 }
275 }
276 }
277 }
278 }
279 }
280
281 Ok(Value::Array(result))
282 }
283
284 pub(crate) fn evaluate_dependent_value_static(
286 engine: &RLogic,
287 evaluations: &IndexMap<String, LogicId>,
288 eval_data: &EvalData,
289 value: &Value,
290 changed_field_value: &Value,
291 changed_field_ref_value: &Value,
292 ) -> Result<Value, String> {
293 match value {
294 Value::String(eval_key) => {
296 if let Some(logic_id) = evaluations.get(eval_key) {
297 let mut internal_context = serde_json::Map::new();
300 internal_context.insert("$value".to_string(), changed_field_value.clone());
301 internal_context.insert("$refValue".to_string(), changed_field_ref_value.clone());
302 let context_value = Value::Object(internal_context);
303
304 let result = engine.run_with_context(logic_id, eval_data.data(), &context_value)
305 .map_err(|e| format!("Failed to evaluate dependent logic '{}': {}", eval_key, e))?;
306 Ok(result)
307 } else {
308 Ok(value.clone())
310 }
311 }
312 Value::Object(map) if map.contains_key("$evaluation") => {
315 Err("Dependent evaluation contains unparsed $evaluation - schema was not properly parsed".to_string())
316 }
317 _ => Ok(value.clone()),
319 }
320 }
321
322 pub(crate) fn check_readonly_for_dependents(
324 &self,
325 schema_element: &Value,
326 path: &str,
327 changes: &mut Vec<(String, Value)>,
328 all_values: &mut Vec<(String, Value)>,
329 ) {
330 match schema_element {
331 Value::Object(map) => {
332 let mut is_disabled = false;
334 if let Some(Value::Object(condition)) = map.get("condition") {
335 if let Some(Value::Bool(d)) = condition.get("disabled") {
336 is_disabled = *d;
337 }
338 }
339
340 let mut skip_readonly = false;
342 if let Some(Value::Object(config)) = map.get("config") {
343 if let Some(Value::Object(all)) = config.get("all") {
344 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
345 skip_readonly = *skip;
346 }
347 }
348 }
349
350 if is_disabled && !skip_readonly {
351 if let Some(schema_value) = map.get("value") {
352 let data_path = path_utils::normalize_to_json_pointer(path)
353 .replace("/properties/", "/")
354 .trim_start_matches('#')
355 .to_string();
356
357 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
358
359 all_values.push((path.to_string(), schema_value.clone()));
361
362 if current_data != schema_value {
364 changes.push((path.to_string(), schema_value.clone()));
365 }
366 }
367 }
368 }
369 _ => {}
370 }
371 }
372
373 #[allow(dead_code)]
375 pub(crate) fn collect_readonly_fixes(
376 &self,
377 schema_element: &Value,
378 path: &str,
379 changes: &mut Vec<(String, Value)>,
380 ) {
381 match schema_element {
382 Value::Object(map) => {
383 let mut is_disabled = false;
385 if let Some(Value::Object(condition)) = map.get("condition") {
386 if let Some(Value::Bool(d)) = condition.get("disabled") {
387 is_disabled = *d;
388 }
389 }
390
391 let mut skip_readonly = false;
393 if let Some(Value::Object(config)) = map.get("config") {
394 if let Some(Value::Object(all)) = config.get("all") {
395 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
396 skip_readonly = *skip;
397 }
398 }
399 }
400
401 if is_disabled && !skip_readonly {
402 if let Some(schema_value) = map.get("value") {
406 let data_path = path_utils::normalize_to_json_pointer(path)
407 .replace("/properties/", "/")
408 .trim_start_matches('#')
409 .to_string();
410
411 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
412
413 if current_data != schema_value {
414 changes.push((path.to_string(), schema_value.clone()));
415 }
416 }
417 }
418
419 if let Some(Value::Object(props)) = map.get("properties") {
421 for (key, val) in props {
422 let next_path = if path == "#" {
423 format!("#/properties/{}", key)
424 } else {
425 format!("{}/properties/{}", path, key)
426 };
427 self.collect_readonly_fixes(val, &next_path, changes);
428 }
429 }
430 }
431 _ => {}
432 }
433 }
434
435 pub(crate) fn check_hidden_field(
437 &self,
438 schema_element: &Value,
439 path: &str,
440 hidden_fields: &mut Vec<String>,
441 ) {
442 match schema_element {
443 Value::Object(map) => {
444 let mut is_hidden = false;
446 if let Some(Value::Object(condition)) = map.get("condition") {
447 if let Some(Value::Bool(h)) = condition.get("hidden") {
448 is_hidden = *h;
449 }
450 }
451
452 let mut keep_hidden = false;
454 if let Some(Value::Object(config)) = map.get("config") {
455 if let Some(Value::Object(all)) = config.get("all") {
456 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
457 keep_hidden = *keep;
458 }
459 }
460 }
461
462 if is_hidden && !keep_hidden {
463 let data_path = path_utils::normalize_to_json_pointer(path)
464 .replace("/properties/", "/")
465 .trim_start_matches('#')
466 .to_string();
467
468 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
469
470 if current_data != &Value::Null && current_data != "" {
472 hidden_fields.push(path.to_string());
473 }
474 }
475 }
476 _ => {}
477 }
478 }
479
480 #[allow(dead_code)]
482 pub(crate) fn collect_hidden_fields(
483 &self,
484 schema_element: &Value,
485 path: &str,
486 hidden_fields: &mut Vec<String>,
487 ) {
488 match schema_element {
489 Value::Object(map) => {
490 let mut is_hidden = false;
492 if let Some(Value::Object(condition)) = map.get("condition") {
493 if let Some(Value::Bool(h)) = condition.get("hidden") {
494 is_hidden = *h;
495 }
496 }
497
498 let mut keep_hidden = false;
500 if let Some(Value::Object(config)) = map.get("config") {
501 if let Some(Value::Object(all)) = config.get("all") {
502 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
503 keep_hidden = *keep;
504 }
505 }
506 }
507
508 if is_hidden && !keep_hidden {
509 let data_path = path_utils::normalize_to_json_pointer(path)
510 .replace("/properties/", "/")
511 .trim_start_matches('#')
512 .to_string();
513
514 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
515
516 if current_data != &Value::Null && current_data != "" {
518 hidden_fields.push(path.to_string());
519 }
520 }
521
522 for (key, val) in map {
524 if key == "properties" {
525 if let Value::Object(props) = val {
526 for (p_key, p_val) in props {
527 let next_path = if path == "#" {
528 format!("#/properties/{}", p_key)
529 } else {
530 format!("{}/properties/{}", path, p_key)
531 };
532 self.collect_hidden_fields(p_val, &next_path, hidden_fields);
533 }
534 }
535 } else if let Value::Object(_) = val {
536 if key == "condition"
538 || key == "config"
539 || key == "rules"
540 || key == "dependents"
541 || key == "hideLayout"
542 || key == "$layout"
543 || key == "$params"
544 || key == "definitions"
545 || key == "$defs"
546 || key.starts_with('$')
547 {
548 continue;
549 }
550
551 let next_path = if path == "#" {
552 format!("#/{}", key)
553 } else {
554 format!("{}/{}", path, key)
555 };
556 self.collect_hidden_fields(val, &next_path, hidden_fields);
557 }
558 }
559 }
560 _ => {}
561 }
562 }
563
564 pub(crate) fn recursive_hide_effect(
567 engine: &RLogic,
568 evaluations: &IndexMap<String, LogicId>,
569 reffed_by: &IndexMap<String, Vec<String>>,
570 eval_data: &mut EvalData,
571 mut hidden_fields: Vec<String>,
572 queue: &mut Vec<(String, bool)>,
573 result: &mut Vec<Value>,
574 ) {
575 while let Some(hf) = hidden_fields.pop() {
576 let data_path = path_utils::normalize_to_json_pointer(&hf)
577 .replace("/properties/", "/")
578 .trim_start_matches('#')
579 .to_string();
580
581 eval_data.set(&data_path, Value::Null);
583
584 let mut change_obj = serde_json::Map::new();
586 change_obj.insert("$ref".to_string(), Value::String(path_utils::pointer_to_dot_notation(&data_path)));
587 change_obj.insert("$hidden".to_string(), Value::Bool(true));
588 change_obj.insert("clear".to_string(), Value::Bool(true));
589 result.push(Value::Object(change_obj));
590
591 queue.push((hf.clone(), true));
593
594 if let Some(referencing_fields) = reffed_by.get(&data_path) {
596 for rb in referencing_fields {
597 let hidden_eval_key = format!("{}/condition/hidden", rb);
601
602 if let Some(logic_id) = evaluations.get(&hidden_eval_key) {
603 let rb_data_path = path_utils::normalize_to_json_pointer(rb)
610 .replace("/properties/", "/")
611 .trim_start_matches('#')
612 .to_string();
613 let rb_value = eval_data.data().pointer(&rb_data_path).cloned().unwrap_or(Value::Null);
614
615 if let Ok(Value::Bool(is_hidden)) = engine.run(
617 logic_id,
618 eval_data.data()
619 ) {
620 if is_hidden {
621 if !hidden_fields.contains(rb) {
624 let has_value = rb_value != Value::Null && rb_value != "";
625 if has_value {
626 hidden_fields.push(rb.clone());
627 }
628 }
629 }
630 }
631 }
632 }
633 }
634 }
635 }
636
637 pub(crate) fn process_dependents_queue(
640 engine: &RLogic,
641 evaluations: &IndexMap<String, LogicId>,
642 eval_data: &mut EvalData,
643 dependents_evaluations: &IndexMap<String, Vec<DependentItem>>,
644 evaluated_schema: &Value,
645 queue: &mut Vec<(String, bool)>,
646 processed: &mut IndexSet<String>,
647 result: &mut Vec<Value>,
648 token: Option<&CancellationToken>,
649 canceled_paths: Option<&mut Vec<String>>,
650 ) -> Result<(), String> {
651 while let Some((current_path, is_transitive)) = queue.pop() {
652 if let Some(t) = token {
653 if t.is_cancelled() {
654 if let Some(cp) = canceled_paths {
656 cp.push(current_path.clone());
657 for (path, _) in queue.iter() {
663 cp.push(path.clone());
664 }
665 }
666 return Err("Cancelled".to_string());
667 }
668 }
669 if processed.contains(¤t_path) {
670 continue;
671 }
672 processed.insert(current_path.clone());
673
674 let current_data_path = path_utils::normalize_to_json_pointer(¤t_path)
676 .replace("/properties/", "/")
677 .trim_start_matches('#')
678 .to_string();
679 let mut current_value = eval_data
680 .data()
681 .pointer(¤t_data_path)
682 .cloned()
683 .unwrap_or(Value::Null);
684
685 if let Some(dependent_items) = dependents_evaluations.get(¤t_path) {
687 for dep_item in dependent_items {
688 let ref_path = &dep_item.ref_path;
689 let pointer_path = path_utils::normalize_to_json_pointer(ref_path);
690 let data_path = pointer_path.replace("/properties/", "/");
692
693 let current_ref_value = eval_data
694 .data()
695 .pointer(&data_path)
696 .cloned()
697 .unwrap_or(Value::Null);
698
699 let field = evaluated_schema.pointer(&pointer_path).cloned();
701
702 let parent_path = if let Some(last_slash) = pointer_path.rfind("/properties") {
704 &pointer_path[..last_slash]
705 } else {
706 "/"
707 };
708 let mut parent_field = if parent_path.is_empty() || parent_path == "/" {
709 evaluated_schema.clone()
710 } else {
711 evaluated_schema
712 .pointer(parent_path)
713 .cloned()
714 .unwrap_or_else(|| Value::Object(serde_json::Map::new()))
715 };
716
717 if let Value::Object(ref mut map) = parent_field {
719 map.remove("properties");
720 map.remove("$layout");
721 }
722
723 let mut change_obj = serde_json::Map::new();
724 change_obj.insert(
725 "$ref".to_string(),
726 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
727 );
728 if let Some(f) = field {
729 change_obj.insert("$field".to_string(), f);
730 }
731 change_obj.insert("$parentField".to_string(), parent_field);
732 change_obj.insert("transitive".to_string(), Value::Bool(is_transitive));
733
734 let mut add_transitive = false;
735 let mut add_deps = false;
736 if let Some(clear_val) = &dep_item.clear {
738 let should_clear = Self::evaluate_dependent_value_static(
739 engine,
740 evaluations,
741 eval_data,
742 clear_val,
743 ¤t_value,
744 ¤t_ref_value,
745 )?;
746 let clear_bool = match should_clear {
747 Value::Bool(b) => b,
748 _ => false,
749 };
750
751 if clear_bool {
752 if data_path == current_data_path {
753 current_value = Value::Null;
754 }
755 eval_data.set(&data_path, Value::Null);
756 change_obj.insert("clear".to_string(), Value::Bool(true));
757 add_transitive = true;
758 add_deps = true;
759 }
760 }
761
762 if let Some(value_val) = &dep_item.value {
764 let computed_value = Self::evaluate_dependent_value_static(
765 engine,
766 evaluations,
767 eval_data,
768 value_val,
769 ¤t_value,
770 ¤t_ref_value,
771 )?;
772 let cleaned_val = clean_float_noise_scalar(computed_value);
773
774 if cleaned_val != current_ref_value && cleaned_val != Value::Null {
775 if data_path == current_data_path {
776 current_value = cleaned_val.clone();
777 }
778 eval_data.set(&data_path, cleaned_val.clone());
779 change_obj.insert("value".to_string(), cleaned_val);
780 add_transitive = true;
781 add_deps = true;
782 }
783 }
784
785 if add_deps {
787 result.push(Value::Object(change_obj));
788 }
789
790 if add_transitive {
792 queue.push((ref_path.clone(), true));
793 }
794 }
795 }
796 }
797 Ok(())
798 }
799}
800
801fn subform_field_key(subform_path: &str) -> String {
808 let stripped = subform_path.trim_start_matches('#').trim_start_matches('/');
810
811 stripped
813 .split('/')
814 .filter(|seg| !seg.is_empty() && *seg != "properties")
815 .last()
816 .unwrap_or(stripped)
817 .to_string()
818}