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;
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 self.evaluate_internal(None, token)?;
105
106 let _lock = self.eval_lock.lock().unwrap();
108
109 let mut readonly_changes = Vec::new();
112 let mut readonly_values = Vec::new(); for path in self.conditional_readonly_fields.iter() {
117 let normalized = path_utils::normalize_to_json_pointer(path);
118 if let Some(schema_element) = self.evaluated_schema.pointer(&normalized) {
119 self.check_readonly_for_dependents(schema_element, path, &mut readonly_changes, &mut readonly_values);
120 }
121 }
122
123 for (path, schema_value) in readonly_changes {
125 let data_path = path_utils::normalize_to_json_pointer(&path)
127 .replace("/properties/", "/")
128 .trim_start_matches('#')
129 .to_string();
130
131 self.eval_data.set(&data_path, schema_value.clone());
132
133 to_process.push((path, true));
135 }
136
137 for (path, schema_value) in readonly_values {
139 let data_path = path_utils::normalize_to_json_pointer(&path)
140 .replace("/properties/", "/")
141 .trim_start_matches('#')
142 .to_string();
143
144 let mut change_obj = serde_json::Map::new();
145 change_obj.insert("$ref".to_string(), Value::String(path_utils::pointer_to_dot_notation(&data_path)));
146 change_obj.insert("$readonly".to_string(), Value::Bool(true));
147 change_obj.insert("value".to_string(), schema_value);
148
149 result.push(Value::Object(change_obj));
150 }
151
152 if !to_process.is_empty() {
154 Self::process_dependents_queue(
155 &self.engine,
156 &self.evaluations,
157 &mut self.eval_data,
158 &self.dependents_evaluations,
159 &self.evaluated_schema,
160 &mut to_process,
161 &mut processed,
162 &mut result,
163 token,
164 canceled_paths.as_mut().map(|v| &mut **v)
165 )?;
166 }
167
168 let mut hidden_fields = Vec::new();
171 for path in self.conditional_hidden_fields.iter() {
174 let normalized = path_utils::normalize_to_json_pointer(path);
175 if let Some(schema_element) = self.evaluated_schema.pointer(&normalized) {
176 self.check_hidden_field(schema_element, path, &mut hidden_fields);
177 }
178 }
179
180 if !hidden_fields.is_empty() {
182 Self::recursive_hide_effect(
183 &self.engine,
184 &self.evaluations,
185 &self.reffed_by,
186 &mut self.eval_data,
187 hidden_fields,
188 &mut to_process,
189 &mut result
190 );
191 }
192
193 if !to_process.is_empty() {
195 Self::process_dependents_queue(
196 &self.engine,
197 &self.evaluations,
198 &mut self.eval_data,
199 &self.dependents_evaluations,
200 &self.evaluated_schema,
201 &mut to_process,
202 &mut processed,
203 &mut result,
204 token,
205 canceled_paths.as_mut().map(|v| &mut **v)
206 )?;
207 }
208 }
209
210 Ok(Value::Array(result))
211 }
212
213 pub(crate) fn evaluate_dependent_value_static(
215 engine: &RLogic,
216 evaluations: &IndexMap<String, LogicId>,
217 eval_data: &EvalData,
218 value: &Value,
219 changed_field_value: &Value,
220 changed_field_ref_value: &Value,
221 ) -> Result<Value, String> {
222 match value {
223 Value::String(eval_key) => {
225 if let Some(logic_id) = evaluations.get(eval_key) {
226 let mut internal_context = serde_json::Map::new();
229 internal_context.insert("$value".to_string(), changed_field_value.clone());
230 internal_context.insert("$refValue".to_string(), changed_field_ref_value.clone());
231 let context_value = Value::Object(internal_context);
232
233 let result = engine.run_with_context(logic_id, eval_data.data(), &context_value)
234 .map_err(|e| format!("Failed to evaluate dependent logic '{}': {}", eval_key, e))?;
235 Ok(result)
236 } else {
237 Ok(value.clone())
239 }
240 }
241 Value::Object(map) if map.contains_key("$evaluation") => {
244 Err("Dependent evaluation contains unparsed $evaluation - schema was not properly parsed".to_string())
245 }
246 _ => Ok(value.clone()),
248 }
249 }
250
251 pub(crate) fn check_readonly_for_dependents(
253 &self,
254 schema_element: &Value,
255 path: &str,
256 changes: &mut Vec<(String, Value)>,
257 all_values: &mut Vec<(String, Value)>,
258 ) {
259 match schema_element {
260 Value::Object(map) => {
261 let mut is_disabled = false;
263 if let Some(Value::Object(condition)) = map.get("condition") {
264 if let Some(Value::Bool(d)) = condition.get("disabled") {
265 is_disabled = *d;
266 }
267 }
268
269 let mut skip_readonly = false;
271 if let Some(Value::Object(config)) = map.get("config") {
272 if let Some(Value::Object(all)) = config.get("all") {
273 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
274 skip_readonly = *skip;
275 }
276 }
277 }
278
279 if is_disabled && !skip_readonly {
280 if let Some(schema_value) = map.get("value") {
281 let data_path = path_utils::normalize_to_json_pointer(path)
282 .replace("/properties/", "/")
283 .trim_start_matches('#')
284 .to_string();
285
286 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
287
288 all_values.push((path.to_string(), schema_value.clone()));
290
291 if current_data != schema_value {
293 changes.push((path.to_string(), schema_value.clone()));
294 }
295 }
296 }
297 }
298 _ => {}
299 }
300 }
301
302 #[allow(dead_code)]
304 pub(crate) fn collect_readonly_fixes(
305 &self,
306 schema_element: &Value,
307 path: &str,
308 changes: &mut Vec<(String, Value)>,
309 ) {
310 match schema_element {
311 Value::Object(map) => {
312 let mut is_disabled = false;
314 if let Some(Value::Object(condition)) = map.get("condition") {
315 if let Some(Value::Bool(d)) = condition.get("disabled") {
316 is_disabled = *d;
317 }
318 }
319
320 let mut skip_readonly = false;
322 if let Some(Value::Object(config)) = map.get("config") {
323 if let Some(Value::Object(all)) = config.get("all") {
324 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
325 skip_readonly = *skip;
326 }
327 }
328 }
329
330 if is_disabled && !skip_readonly {
331 if let Some(schema_value) = map.get("value") {
335 let data_path = path_utils::normalize_to_json_pointer(path)
336 .replace("/properties/", "/")
337 .trim_start_matches('#')
338 .to_string();
339
340 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
341
342 if current_data != schema_value {
343 changes.push((path.to_string(), schema_value.clone()));
344 }
345 }
346 }
347
348 if let Some(Value::Object(props)) = map.get("properties") {
350 for (key, val) in props {
351 let next_path = if path == "#" {
352 format!("#/properties/{}", key)
353 } else {
354 format!("{}/properties/{}", path, key)
355 };
356 self.collect_readonly_fixes(val, &next_path, changes);
357 }
358 }
359 }
360 _ => {}
361 }
362 }
363
364 pub(crate) fn check_hidden_field(
366 &self,
367 schema_element: &Value,
368 path: &str,
369 hidden_fields: &mut Vec<String>,
370 ) {
371 match schema_element {
372 Value::Object(map) => {
373 let mut is_hidden = false;
375 if let Some(Value::Object(condition)) = map.get("condition") {
376 if let Some(Value::Bool(h)) = condition.get("hidden") {
377 is_hidden = *h;
378 }
379 }
380
381 let mut keep_hidden = false;
383 if let Some(Value::Object(config)) = map.get("config") {
384 if let Some(Value::Object(all)) = config.get("all") {
385 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
386 keep_hidden = *keep;
387 }
388 }
389 }
390
391 if is_hidden && !keep_hidden {
392 let data_path = path_utils::normalize_to_json_pointer(path)
393 .replace("/properties/", "/")
394 .trim_start_matches('#')
395 .to_string();
396
397 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
398
399 if current_data != &Value::Null && current_data != "" {
401 hidden_fields.push(path.to_string());
402 }
403 }
404 }
405 _ => {}
406 }
407 }
408
409 #[allow(dead_code)]
411 pub(crate) fn collect_hidden_fields(
412 &self,
413 schema_element: &Value,
414 path: &str,
415 hidden_fields: &mut Vec<String>,
416 ) {
417 match schema_element {
418 Value::Object(map) => {
419 let mut is_hidden = false;
421 if let Some(Value::Object(condition)) = map.get("condition") {
422 if let Some(Value::Bool(h)) = condition.get("hidden") {
423 is_hidden = *h;
424 }
425 }
426
427 let mut keep_hidden = false;
429 if let Some(Value::Object(config)) = map.get("config") {
430 if let Some(Value::Object(all)) = config.get("all") {
431 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
432 keep_hidden = *keep;
433 }
434 }
435 }
436
437 if is_hidden && !keep_hidden {
438 let data_path = path_utils::normalize_to_json_pointer(path)
439 .replace("/properties/", "/")
440 .trim_start_matches('#')
441 .to_string();
442
443 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
444
445 if current_data != &Value::Null && current_data != "" {
447 hidden_fields.push(path.to_string());
448 }
449 }
450
451 for (key, val) in map {
453 if key == "properties" {
454 if let Value::Object(props) = val {
455 for (p_key, p_val) in props {
456 let next_path = if path == "#" {
457 format!("#/properties/{}", p_key)
458 } else {
459 format!("{}/properties/{}", path, p_key)
460 };
461 self.collect_hidden_fields(p_val, &next_path, hidden_fields);
462 }
463 }
464 } else if let Value::Object(_) = val {
465 if key == "condition"
467 || key == "config"
468 || key == "rules"
469 || key == "dependents"
470 || key == "hideLayout"
471 || key == "$layout"
472 || key == "$params"
473 || key == "definitions"
474 || key == "$defs"
475 || key.starts_with('$')
476 {
477 continue;
478 }
479
480 let next_path = if path == "#" {
481 format!("#/{}", key)
482 } else {
483 format!("{}/{}", path, key)
484 };
485 self.collect_hidden_fields(val, &next_path, hidden_fields);
486 }
487 }
488 }
489 _ => {}
490 }
491 }
492
493 pub(crate) fn recursive_hide_effect(
495 engine: &RLogic,
496 evaluations: &IndexMap<String, LogicId>,
497 reffed_by: &IndexMap<String, Vec<String>>,
498 eval_data: &mut EvalData,
499 mut hidden_fields: Vec<String>,
500 queue: &mut Vec<(String, bool)>,
501 result: &mut Vec<Value>
502 ) {
503 while let Some(hf) = hidden_fields.pop() {
504 let data_path = path_utils::normalize_to_json_pointer(&hf)
505 .replace("/properties/", "/")
506 .trim_start_matches('#')
507 .to_string();
508
509 eval_data.set(&data_path, Value::Null);
511
512 let mut change_obj = serde_json::Map::new();
514 change_obj.insert("$ref".to_string(), Value::String(path_utils::pointer_to_dot_notation(&data_path)));
515 change_obj.insert("$hidden".to_string(), Value::Bool(true));
516 change_obj.insert("clear".to_string(), Value::Bool(true));
517 result.push(Value::Object(change_obj));
518
519 queue.push((hf.clone(), true));
521
522 if let Some(referencing_fields) = reffed_by.get(&data_path) {
524 for rb in referencing_fields {
525 let hidden_eval_key = format!("{}/condition/hidden", rb);
529
530 if let Some(logic_id) = evaluations.get(&hidden_eval_key) {
531 let rb_data_path = path_utils::normalize_to_json_pointer(rb)
538 .replace("/properties/", "/")
539 .trim_start_matches('#')
540 .to_string();
541 let rb_value = eval_data.data().pointer(&rb_data_path).cloned().unwrap_or(Value::Null);
542
543 if let Ok(Value::Bool(is_hidden)) = engine.run(
545 logic_id,
546 eval_data.data()
547 ) {
548 if is_hidden {
549 if !hidden_fields.contains(rb) {
552 let has_value = rb_value != Value::Null && rb_value != "";
553 if has_value {
554 hidden_fields.push(rb.clone());
555 }
556 }
557 }
558 }
559 }
560 }
561 }
562 }
563 }
564
565 pub(crate) fn process_dependents_queue(
568 engine: &RLogic,
569 evaluations: &IndexMap<String, LogicId>,
570 eval_data: &mut EvalData,
571 dependents_evaluations: &IndexMap<String, Vec<DependentItem>>,
572 evaluated_schema: &Value,
573 queue: &mut Vec<(String, bool)>,
574 processed: &mut IndexSet<String>,
575 result: &mut Vec<Value>,
576 token: Option<&CancellationToken>,
577 canceled_paths: Option<&mut Vec<String>>,
578 ) -> Result<(), String> {
579 while let Some((current_path, is_transitive)) = queue.pop() {
580 if let Some(t) = token {
581 if t.is_cancelled() {
582 if let Some(cp) = canceled_paths {
584 cp.push(current_path.clone());
585 for (path, _) in queue.iter() {
591 cp.push(path.clone());
592 }
593 }
594 return Err("Cancelled".to_string());
595 }
596 }
597 if processed.contains(¤t_path) {
598 continue;
599 }
600 processed.insert(current_path.clone());
601
602 let current_data_path = path_utils::normalize_to_json_pointer(¤t_path)
604 .replace("/properties/", "/")
605 .trim_start_matches('#')
606 .to_string();
607 let mut current_value = eval_data
608 .data()
609 .pointer(¤t_data_path)
610 .cloned()
611 .unwrap_or(Value::Null);
612
613 if let Some(dependent_items) = dependents_evaluations.get(¤t_path) {
615 for dep_item in dependent_items {
616 let ref_path = &dep_item.ref_path;
617 let pointer_path = path_utils::normalize_to_json_pointer(ref_path);
618 let data_path = pointer_path.replace("/properties/", "/");
620
621 let current_ref_value = eval_data
622 .data()
623 .pointer(&data_path)
624 .cloned()
625 .unwrap_or(Value::Null);
626
627 let field = evaluated_schema.pointer(&pointer_path).cloned();
629
630 let parent_path = if let Some(last_slash) = pointer_path.rfind("/properties") {
632 &pointer_path[..last_slash]
633 } else {
634 "/"
635 };
636 let mut parent_field = if parent_path.is_empty() || parent_path == "/" {
637 evaluated_schema.clone()
638 } else {
639 evaluated_schema
640 .pointer(parent_path)
641 .cloned()
642 .unwrap_or_else(|| Value::Object(serde_json::Map::new()))
643 };
644
645 if let Value::Object(ref mut map) = parent_field {
647 map.remove("properties");
648 map.remove("$layout");
649 }
650
651 let mut change_obj = serde_json::Map::new();
652 change_obj.insert(
653 "$ref".to_string(),
654 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
655 );
656 if let Some(f) = field {
657 change_obj.insert("$field".to_string(), f);
658 }
659 change_obj.insert("$parentField".to_string(), parent_field);
660 change_obj.insert("transitive".to_string(), Value::Bool(is_transitive));
661
662 let mut add_transitive = false;
663 let mut add_deps = false;
664 if let Some(clear_val) = &dep_item.clear {
666 let clear_val_clone = clear_val.clone();
667 let should_clear = Self::evaluate_dependent_value_static(
668 engine,
669 evaluations,
670 eval_data,
671 &clear_val_clone,
672 ¤t_value,
673 ¤t_ref_value,
674 )?;
675 let clear_bool = match should_clear {
676 Value::Bool(b) => b,
677 _ => false,
678 };
679
680 if clear_bool {
681 if data_path == current_data_path {
683 current_value = Value::Null;
684 }
685 eval_data.set(&data_path, Value::Null);
686 change_obj.insert("clear".to_string(), Value::Bool(true));
687 add_transitive = true;
688 add_deps = true;
689 }
690 }
691
692 if let Some(value_val) = &dep_item.value {
694 let value_val_clone = value_val.clone();
695 let computed_value = Self::evaluate_dependent_value_static(
696 engine,
697 evaluations,
698 eval_data,
699 &value_val_clone,
700 ¤t_value,
701 ¤t_ref_value,
702 )?;
703 let cleaned_val = clean_float_noise(computed_value.clone());
704
705 if cleaned_val != current_ref_value && cleaned_val != Value::Null {
706 if data_path == current_data_path {
708 current_value = cleaned_val.clone();
709 }
710 eval_data.set(&data_path, cleaned_val.clone());
711 change_obj.insert("value".to_string(), cleaned_val);
712 add_transitive = true;
713 add_deps = true;
714 }
715 }
716
717 if add_deps {
719 result.push(Value::Object(change_obj));
720 }
721
722 if add_transitive {
724 queue.push((ref_path.clone(), true));
725 }
726 }
727 }
728 }
729 Ok(())
730 }
731}