1use super::JSONEval;
2use crate::jsoneval::json_parser;
3use crate::jsoneval::path_utils;
4use crate::rlogic::{LogicId, RLogic};
5use crate::jsoneval::types::DependentItem;
6use crate::jsoneval::cancellation::CancellationToken;
7use crate::utils::clean_float_noise_scalar;
8use crate::EvalData;
9
10use indexmap::{IndexMap, IndexSet};
11use serde_json::Value;
12
13
14impl JSONEval {
15 pub fn evaluate_dependents(
18 &mut self,
19 changed_paths: &[String],
20 data: Option<&str>,
21 context: Option<&str>,
22 re_evaluate: bool,
23 token: Option<&CancellationToken>,
24 mut canceled_paths: Option<&mut Vec<String>>,
25 ) -> Result<Value, String> {
26 if let Some(t) = token {
28 if t.is_cancelled() {
29 return Err("Cancelled".to_string());
30 }
31 }
32 let _lock = self.eval_lock.lock().unwrap();
34
35 if let Some(data_str) = data {
37 let old_data = self.eval_data.clone_data_without(&["$params"]);
39
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 let data_paths: Vec<String> = changed_paths
53 .iter()
54 .map(|path| {
55 let schema_ptr = path_utils::dot_notation_to_schema_pointer(path);
58
59 let normalized = schema_ptr
61 .trim_start_matches('#')
62 .replace("/properties/", "/");
63
64 if normalized.starts_with('/') {
66 normalized
67 } else {
68 format!("/{}", normalized)
69 }
70 })
71 .collect();
72 self.purge_cache_for_changed_data_with_comparison(&data_paths, &old_data, &data_value);
73 }
74
75 let mut result = Vec::new();
76 let mut processed = IndexSet::new();
77
78 let mut to_process: Vec<(String, bool)> = changed_paths
81 .iter()
82 .map(|path| (path_utils::dot_notation_to_schema_pointer(path), false))
83 .collect(); Self::process_dependents_queue(
87 &self.engine,
88 &self.evaluations,
89 &mut self.eval_data,
90 &self.dependents_evaluations,
91 &self.evaluated_schema,
92 &mut to_process,
93 &mut processed,
94 &mut result,
95 token,
96 canceled_paths.as_mut().map(|v| &mut **v)
97 )?;
98
99 if re_evaluate {
102 drop(_lock);
104
105 self.eval_cache.clear();
111
112 self.evaluate_internal(None, token)?;
113
114 let _lock = self.eval_lock.lock().unwrap();
116
117 let mut readonly_changes = Vec::new();
120 let mut readonly_values = Vec::new(); for path in self.conditional_readonly_fields.iter() {
125 let normalized = path_utils::normalize_to_json_pointer(path);
126 if let Some(schema_element) = self.evaluated_schema.pointer(&normalized) {
127 self.check_readonly_for_dependents(schema_element, path, &mut readonly_changes, &mut readonly_values);
128 }
129 }
130
131 for (path, schema_value) in readonly_changes {
133 let data_path = path_utils::normalize_to_json_pointer(&path)
135 .replace("/properties/", "/")
136 .trim_start_matches('#')
137 .to_string();
138
139 self.eval_data.set(&data_path, schema_value.clone());
140
141 to_process.push((path, true));
143 }
144
145 for (path, schema_value) in readonly_values {
147 let data_path = path_utils::normalize_to_json_pointer(&path)
148 .replace("/properties/", "/")
149 .trim_start_matches('#')
150 .to_string();
151
152 let mut change_obj = serde_json::Map::new();
153 change_obj.insert("$ref".to_string(), Value::String(path_utils::pointer_to_dot_notation(&data_path)));
154 change_obj.insert("$readonly".to_string(), Value::Bool(true));
155 change_obj.insert("value".to_string(), schema_value);
156
157 result.push(Value::Object(change_obj));
158 }
159
160 if !to_process.is_empty() {
162 Self::process_dependents_queue(
163 &self.engine,
164 &self.evaluations,
165 &mut self.eval_data,
166 &self.dependents_evaluations,
167 &self.evaluated_schema,
168 &mut to_process,
169 &mut processed,
170 &mut result,
171 token,
172 canceled_paths.as_mut().map(|v| &mut **v)
173 )?;
174 }
175
176 let mut hidden_fields = Vec::new();
179 for path in self.conditional_hidden_fields.iter() {
182 let normalized = path_utils::normalize_to_json_pointer(path);
183 if let Some(schema_element) = self.evaluated_schema.pointer(&normalized) {
184 self.check_hidden_field(schema_element, path, &mut hidden_fields);
185 }
186 }
187
188 if !hidden_fields.is_empty() {
190 Self::recursive_hide_effect(
191 &self.engine,
192 &self.evaluations,
193 &self.reffed_by,
194 &mut self.eval_data,
195 hidden_fields,
196 &mut to_process,
197 &mut result
198 );
199 }
200
201 if !to_process.is_empty() {
203 Self::process_dependents_queue(
204 &self.engine,
205 &self.evaluations,
206 &mut self.eval_data,
207 &self.dependents_evaluations,
208 &self.evaluated_schema,
209 &mut to_process,
210 &mut processed,
211 &mut result,
212 token,
213 canceled_paths.as_mut().map(|v| &mut **v)
214 )?;
215 }
216 }
217
218 Ok(Value::Array(result))
219 }
220
221 pub(crate) fn evaluate_dependent_value_static(
223 engine: &RLogic,
224 evaluations: &IndexMap<String, LogicId>,
225 eval_data: &EvalData,
226 value: &Value,
227 changed_field_value: &Value,
228 changed_field_ref_value: &Value,
229 ) -> Result<Value, String> {
230 match value {
231 Value::String(eval_key) => {
233 if let Some(logic_id) = evaluations.get(eval_key) {
234 let mut internal_context = serde_json::Map::new();
237 internal_context.insert("$value".to_string(), changed_field_value.clone());
238 internal_context.insert("$refValue".to_string(), changed_field_ref_value.clone());
239 let context_value = Value::Object(internal_context);
240
241 let result = engine.run_with_context(logic_id, eval_data.data(), &context_value)
242 .map_err(|e| format!("Failed to evaluate dependent logic '{}': {}", eval_key, e))?;
243 Ok(result)
244 } else {
245 Ok(value.clone())
247 }
248 }
249 Value::Object(map) if map.contains_key("$evaluation") => {
252 Err("Dependent evaluation contains unparsed $evaluation - schema was not properly parsed".to_string())
253 }
254 _ => Ok(value.clone()),
256 }
257 }
258
259 pub(crate) fn check_readonly_for_dependents(
261 &self,
262 schema_element: &Value,
263 path: &str,
264 changes: &mut Vec<(String, Value)>,
265 all_values: &mut Vec<(String, Value)>,
266 ) {
267 match schema_element {
268 Value::Object(map) => {
269 let mut is_disabled = false;
271 if let Some(Value::Object(condition)) = map.get("condition") {
272 if let Some(Value::Bool(d)) = condition.get("disabled") {
273 is_disabled = *d;
274 }
275 }
276
277 let mut skip_readonly = false;
279 if let Some(Value::Object(config)) = map.get("config") {
280 if let Some(Value::Object(all)) = config.get("all") {
281 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
282 skip_readonly = *skip;
283 }
284 }
285 }
286
287 if is_disabled && !skip_readonly {
288 if let Some(schema_value) = map.get("value") {
289 let data_path = path_utils::normalize_to_json_pointer(path)
290 .replace("/properties/", "/")
291 .trim_start_matches('#')
292 .to_string();
293
294 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
295
296 all_values.push((path.to_string(), schema_value.clone()));
298
299 if current_data != schema_value {
301 changes.push((path.to_string(), schema_value.clone()));
302 }
303 }
304 }
305 }
306 _ => {}
307 }
308 }
309
310 #[allow(dead_code)]
312 pub(crate) fn collect_readonly_fixes(
313 &self,
314 schema_element: &Value,
315 path: &str,
316 changes: &mut Vec<(String, Value)>,
317 ) {
318 match schema_element {
319 Value::Object(map) => {
320 let mut is_disabled = false;
322 if let Some(Value::Object(condition)) = map.get("condition") {
323 if let Some(Value::Bool(d)) = condition.get("disabled") {
324 is_disabled = *d;
325 }
326 }
327
328 let mut skip_readonly = false;
330 if let Some(Value::Object(config)) = map.get("config") {
331 if let Some(Value::Object(all)) = config.get("all") {
332 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
333 skip_readonly = *skip;
334 }
335 }
336 }
337
338 if is_disabled && !skip_readonly {
339 if let Some(schema_value) = map.get("value") {
343 let data_path = path_utils::normalize_to_json_pointer(path)
344 .replace("/properties/", "/")
345 .trim_start_matches('#')
346 .to_string();
347
348 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
349
350 if current_data != schema_value {
351 changes.push((path.to_string(), schema_value.clone()));
352 }
353 }
354 }
355
356 if let Some(Value::Object(props)) = map.get("properties") {
358 for (key, val) in props {
359 let next_path = if path == "#" {
360 format!("#/properties/{}", key)
361 } else {
362 format!("{}/properties/{}", path, key)
363 };
364 self.collect_readonly_fixes(val, &next_path, changes);
365 }
366 }
367 }
368 _ => {}
369 }
370 }
371
372 pub(crate) fn check_hidden_field(
374 &self,
375 schema_element: &Value,
376 path: &str,
377 hidden_fields: &mut Vec<String>,
378 ) {
379 match schema_element {
380 Value::Object(map) => {
381 let mut is_hidden = false;
383 if let Some(Value::Object(condition)) = map.get("condition") {
384 if let Some(Value::Bool(h)) = condition.get("hidden") {
385 is_hidden = *h;
386 }
387 }
388
389 let mut keep_hidden = false;
391 if let Some(Value::Object(config)) = map.get("config") {
392 if let Some(Value::Object(all)) = config.get("all") {
393 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
394 keep_hidden = *keep;
395 }
396 }
397 }
398
399 if is_hidden && !keep_hidden {
400 let data_path = path_utils::normalize_to_json_pointer(path)
401 .replace("/properties/", "/")
402 .trim_start_matches('#')
403 .to_string();
404
405 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
406
407 if current_data != &Value::Null && current_data != "" {
409 hidden_fields.push(path.to_string());
410 }
411 }
412 }
413 _ => {}
414 }
415 }
416
417 #[allow(dead_code)]
419 pub(crate) fn collect_hidden_fields(
420 &self,
421 schema_element: &Value,
422 path: &str,
423 hidden_fields: &mut Vec<String>,
424 ) {
425 match schema_element {
426 Value::Object(map) => {
427 let mut is_hidden = false;
429 if let Some(Value::Object(condition)) = map.get("condition") {
430 if let Some(Value::Bool(h)) = condition.get("hidden") {
431 is_hidden = *h;
432 }
433 }
434
435 let mut keep_hidden = false;
437 if let Some(Value::Object(config)) = map.get("config") {
438 if let Some(Value::Object(all)) = config.get("all") {
439 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
440 keep_hidden = *keep;
441 }
442 }
443 }
444
445 if is_hidden && !keep_hidden {
446 let data_path = path_utils::normalize_to_json_pointer(path)
447 .replace("/properties/", "/")
448 .trim_start_matches('#')
449 .to_string();
450
451 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
452
453 if current_data != &Value::Null && current_data != "" {
455 hidden_fields.push(path.to_string());
456 }
457 }
458
459 for (key, val) in map {
461 if key == "properties" {
462 if let Value::Object(props) = val {
463 for (p_key, p_val) in props {
464 let next_path = if path == "#" {
465 format!("#/properties/{}", p_key)
466 } else {
467 format!("{}/properties/{}", path, p_key)
468 };
469 self.collect_hidden_fields(p_val, &next_path, hidden_fields);
470 }
471 }
472 } else if let Value::Object(_) = val {
473 if key == "condition"
475 || key == "config"
476 || key == "rules"
477 || key == "dependents"
478 || key == "hideLayout"
479 || key == "$layout"
480 || key == "$params"
481 || key == "definitions"
482 || key == "$defs"
483 || key.starts_with('$')
484 {
485 continue;
486 }
487
488 let next_path = if path == "#" {
489 format!("#/{}", key)
490 } else {
491 format!("{}/{}", path, key)
492 };
493 self.collect_hidden_fields(val, &next_path, hidden_fields);
494 }
495 }
496 }
497 _ => {}
498 }
499 }
500
501 pub(crate) fn recursive_hide_effect(
503 engine: &RLogic,
504 evaluations: &IndexMap<String, LogicId>,
505 reffed_by: &IndexMap<String, Vec<String>>,
506 eval_data: &mut EvalData,
507 mut hidden_fields: Vec<String>,
508 queue: &mut Vec<(String, bool)>,
509 result: &mut Vec<Value>
510 ) {
511 while let Some(hf) = hidden_fields.pop() {
512 let data_path = path_utils::normalize_to_json_pointer(&hf)
513 .replace("/properties/", "/")
514 .trim_start_matches('#')
515 .to_string();
516
517 eval_data.set(&data_path, Value::Null);
519
520 let mut change_obj = serde_json::Map::new();
522 change_obj.insert("$ref".to_string(), Value::String(path_utils::pointer_to_dot_notation(&data_path)));
523 change_obj.insert("$hidden".to_string(), Value::Bool(true));
524 change_obj.insert("clear".to_string(), Value::Bool(true));
525 result.push(Value::Object(change_obj));
526
527 queue.push((hf.clone(), true));
529
530 if let Some(referencing_fields) = reffed_by.get(&data_path) {
532 for rb in referencing_fields {
533 let hidden_eval_key = format!("{}/condition/hidden", rb);
537
538 if let Some(logic_id) = evaluations.get(&hidden_eval_key) {
539 let rb_data_path = path_utils::normalize_to_json_pointer(rb)
546 .replace("/properties/", "/")
547 .trim_start_matches('#')
548 .to_string();
549 let rb_value = eval_data.data().pointer(&rb_data_path).cloned().unwrap_or(Value::Null);
550
551 if let Ok(Value::Bool(is_hidden)) = engine.run(
553 logic_id,
554 eval_data.data()
555 ) {
556 if is_hidden {
557 if !hidden_fields.contains(rb) {
560 let has_value = rb_value != Value::Null && rb_value != "";
561 if has_value {
562 hidden_fields.push(rb.clone());
563 }
564 }
565 }
566 }
567 }
568 }
569 }
570 }
571 }
572
573 pub(crate) fn process_dependents_queue(
576 engine: &RLogic,
577 evaluations: &IndexMap<String, LogicId>,
578 eval_data: &mut EvalData,
579 dependents_evaluations: &IndexMap<String, Vec<DependentItem>>,
580 evaluated_schema: &Value,
581 queue: &mut Vec<(String, bool)>,
582 processed: &mut IndexSet<String>,
583 result: &mut Vec<Value>,
584 token: Option<&CancellationToken>,
585 canceled_paths: Option<&mut Vec<String>>,
586 ) -> Result<(), String> {
587 while let Some((current_path, is_transitive)) = queue.pop() {
588 if let Some(t) = token {
589 if t.is_cancelled() {
590 if let Some(cp) = canceled_paths {
592 cp.push(current_path.clone());
593 for (path, _) in queue.iter() {
599 cp.push(path.clone());
600 }
601 }
602 return Err("Cancelled".to_string());
603 }
604 }
605 if processed.contains(¤t_path) {
606 continue;
607 }
608 processed.insert(current_path.clone());
609
610 let current_data_path = path_utils::normalize_to_json_pointer(¤t_path)
612 .replace("/properties/", "/")
613 .trim_start_matches('#')
614 .to_string();
615 let mut current_value = eval_data
616 .data()
617 .pointer(¤t_data_path)
618 .cloned()
619 .unwrap_or(Value::Null);
620
621 if let Some(dependent_items) = dependents_evaluations.get(¤t_path) {
623 for dep_item in dependent_items {
624 let ref_path = &dep_item.ref_path;
625 let pointer_path = path_utils::normalize_to_json_pointer(ref_path);
626 let data_path = pointer_path.replace("/properties/", "/");
628
629 let current_ref_value = eval_data
630 .data()
631 .pointer(&data_path)
632 .cloned()
633 .unwrap_or(Value::Null);
634
635 let field = evaluated_schema.pointer(&pointer_path).cloned();
637
638 let parent_path = if let Some(last_slash) = pointer_path.rfind("/properties") {
640 &pointer_path[..last_slash]
641 } else {
642 "/"
643 };
644 let mut parent_field = if parent_path.is_empty() || parent_path == "/" {
645 evaluated_schema.clone()
646 } else {
647 evaluated_schema
648 .pointer(parent_path)
649 .cloned()
650 .unwrap_or_else(|| Value::Object(serde_json::Map::new()))
651 };
652
653 if let Value::Object(ref mut map) = parent_field {
655 map.remove("properties");
656 map.remove("$layout");
657 }
658
659 let mut change_obj = serde_json::Map::new();
660 change_obj.insert(
661 "$ref".to_string(),
662 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
663 );
664 if let Some(f) = field {
665 change_obj.insert("$field".to_string(), f);
666 }
667 change_obj.insert("$parentField".to_string(), parent_field);
668 change_obj.insert("transitive".to_string(), Value::Bool(is_transitive));
669
670 let mut add_transitive = false;
671 let mut add_deps = false;
672 if let Some(clear_val) = &dep_item.clear {
674 let clear_val_clone = clear_val.clone();
675 let should_clear = Self::evaluate_dependent_value_static(
676 engine,
677 evaluations,
678 eval_data,
679 &clear_val_clone,
680 ¤t_value,
681 ¤t_ref_value,
682 )?;
683 let clear_bool = match should_clear {
684 Value::Bool(b) => b,
685 _ => false,
686 };
687
688 if clear_bool {
689 if data_path == current_data_path {
691 current_value = Value::Null;
692 }
693 eval_data.set(&data_path, Value::Null);
694 change_obj.insert("clear".to_string(), Value::Bool(true));
695 add_transitive = true;
696 add_deps = true;
697 }
698 }
699
700 if let Some(value_val) = &dep_item.value {
702 let value_val_clone = value_val.clone();
703 let computed_value = Self::evaluate_dependent_value_static(
704 engine,
705 evaluations,
706 eval_data,
707 &value_val_clone,
708 ¤t_value,
709 ¤t_ref_value,
710 )?;
711 let cleaned_val = clean_float_noise_scalar(computed_value.clone());
712
713 if cleaned_val != current_ref_value && cleaned_val != Value::Null {
714 if data_path == current_data_path {
716 current_value = cleaned_val.clone();
717 }
718 eval_data.set(&data_path, cleaned_val.clone());
719 change_obj.insert("value".to_string(), cleaned_val);
720 add_transitive = true;
721 add_deps = true;
722 }
723 }
724
725 if add_deps {
727 result.push(Value::Object(change_obj));
728 }
729
730 if add_transitive {
732 queue.push((ref_path.clone(), true));
733 }
734 }
735 }
736 }
737 Ok(())
738 }
739}