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.snapshot_data();
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 self.purge_cache_for_changed_data_with_comparison(&old_data, &data_value);
52 }
53
54 let mut result = Vec::new();
55 let mut processed = IndexSet::new();
56
57 let mut to_process: Vec<(String, bool)> = changed_paths
60 .iter()
61 .map(|path| (path_utils::dot_notation_to_schema_pointer(path), false))
62 .collect(); Self::process_dependents_queue(
66 &self.engine,
67 &self.evaluations,
68 &mut self.eval_data,
69 &self.dependents_evaluations,
70 &self.evaluated_schema,
71 &mut to_process,
72 &mut processed,
73 &mut result,
74 token,
75 canceled_paths.as_mut().map(|v| &mut **v)
76 )?;
77
78 if re_evaluate {
81 drop(_lock);
83
84 self.eval_cache.clear();
90
91 self.evaluate_internal(None, token)?;
92
93 let _lock = self.eval_lock.lock().unwrap();
95
96 let mut readonly_changes = Vec::new();
99 let mut readonly_values = Vec::new(); for path in self.conditional_readonly_fields.iter() {
104 let normalized = path_utils::normalize_to_json_pointer(path);
105 if let Some(schema_element) = self.evaluated_schema.pointer(&normalized) {
106 self.check_readonly_for_dependents(schema_element, path, &mut readonly_changes, &mut readonly_values);
107 }
108 }
109
110 for (path, schema_value) in readonly_changes {
112 let data_path = path_utils::normalize_to_json_pointer(&path)
114 .replace("/properties/", "/")
115 .trim_start_matches('#')
116 .to_string();
117
118 self.eval_data.set(&data_path, schema_value.clone());
119
120 to_process.push((path, true));
122 }
123
124 for (path, schema_value) in readonly_values {
126 let data_path = path_utils::normalize_to_json_pointer(&path)
127 .replace("/properties/", "/")
128 .trim_start_matches('#')
129 .to_string();
130
131 let mut change_obj = serde_json::Map::new();
132 change_obj.insert("$ref".to_string(), Value::String(path_utils::pointer_to_dot_notation(&data_path)));
133 change_obj.insert("$readonly".to_string(), Value::Bool(true));
134 change_obj.insert("value".to_string(), schema_value);
135
136 result.push(Value::Object(change_obj));
137 }
138
139 if !to_process.is_empty() {
141 Self::process_dependents_queue(
142 &self.engine,
143 &self.evaluations,
144 &mut self.eval_data,
145 &self.dependents_evaluations,
146 &self.evaluated_schema,
147 &mut to_process,
148 &mut processed,
149 &mut result,
150 token,
151 canceled_paths.as_mut().map(|v| &mut **v)
152 )?;
153 }
154
155 let mut hidden_fields = Vec::new();
158 for path in self.conditional_hidden_fields.iter() {
161 let normalized = path_utils::normalize_to_json_pointer(path);
162 if let Some(schema_element) = self.evaluated_schema.pointer(&normalized) {
163 self.check_hidden_field(schema_element, path, &mut hidden_fields);
164 }
165 }
166
167 if !hidden_fields.is_empty() {
169 Self::recursive_hide_effect(
170 &self.engine,
171 &self.evaluations,
172 &self.reffed_by,
173 &mut self.eval_data,
174 hidden_fields,
175 &mut to_process,
176 &mut result
177 );
178 }
179
180 if !to_process.is_empty() {
182 Self::process_dependents_queue(
183 &self.engine,
184 &self.evaluations,
185 &mut self.eval_data,
186 &self.dependents_evaluations,
187 &self.evaluated_schema,
188 &mut to_process,
189 &mut processed,
190 &mut result,
191 token,
192 canceled_paths.as_mut().map(|v| &mut **v)
193 )?;
194 }
195 }
196
197 Ok(Value::Array(result))
198 }
199
200 pub(crate) fn evaluate_dependent_value_static(
202 engine: &RLogic,
203 evaluations: &IndexMap<String, LogicId>,
204 eval_data: &EvalData,
205 value: &Value,
206 changed_field_value: &Value,
207 changed_field_ref_value: &Value,
208 ) -> Result<Value, String> {
209 match value {
210 Value::String(eval_key) => {
212 if let Some(logic_id) = evaluations.get(eval_key) {
213 let mut internal_context = serde_json::Map::new();
216 internal_context.insert("$value".to_string(), changed_field_value.clone());
217 internal_context.insert("$refValue".to_string(), changed_field_ref_value.clone());
218 let context_value = Value::Object(internal_context);
219
220 let result = engine.run_with_context(logic_id, eval_data.data(), &context_value)
221 .map_err(|e| format!("Failed to evaluate dependent logic '{}': {}", eval_key, e))?;
222 Ok(result)
223 } else {
224 Ok(value.clone())
226 }
227 }
228 Value::Object(map) if map.contains_key("$evaluation") => {
231 Err("Dependent evaluation contains unparsed $evaluation - schema was not properly parsed".to_string())
232 }
233 _ => Ok(value.clone()),
235 }
236 }
237
238 pub(crate) fn check_readonly_for_dependents(
240 &self,
241 schema_element: &Value,
242 path: &str,
243 changes: &mut Vec<(String, Value)>,
244 all_values: &mut Vec<(String, Value)>,
245 ) {
246 match schema_element {
247 Value::Object(map) => {
248 let mut is_disabled = false;
250 if let Some(Value::Object(condition)) = map.get("condition") {
251 if let Some(Value::Bool(d)) = condition.get("disabled") {
252 is_disabled = *d;
253 }
254 }
255
256 let mut skip_readonly = false;
258 if let Some(Value::Object(config)) = map.get("config") {
259 if let Some(Value::Object(all)) = config.get("all") {
260 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
261 skip_readonly = *skip;
262 }
263 }
264 }
265
266 if is_disabled && !skip_readonly {
267 if let Some(schema_value) = map.get("value") {
268 let data_path = path_utils::normalize_to_json_pointer(path)
269 .replace("/properties/", "/")
270 .trim_start_matches('#')
271 .to_string();
272
273 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
274
275 all_values.push((path.to_string(), schema_value.clone()));
277
278 if current_data != schema_value {
280 changes.push((path.to_string(), schema_value.clone()));
281 }
282 }
283 }
284 }
285 _ => {}
286 }
287 }
288
289 #[allow(dead_code)]
291 pub(crate) fn collect_readonly_fixes(
292 &self,
293 schema_element: &Value,
294 path: &str,
295 changes: &mut Vec<(String, Value)>,
296 ) {
297 match schema_element {
298 Value::Object(map) => {
299 let mut is_disabled = false;
301 if let Some(Value::Object(condition)) = map.get("condition") {
302 if let Some(Value::Bool(d)) = condition.get("disabled") {
303 is_disabled = *d;
304 }
305 }
306
307 let mut skip_readonly = false;
309 if let Some(Value::Object(config)) = map.get("config") {
310 if let Some(Value::Object(all)) = config.get("all") {
311 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
312 skip_readonly = *skip;
313 }
314 }
315 }
316
317 if is_disabled && !skip_readonly {
318 if let Some(schema_value) = map.get("value") {
322 let data_path = path_utils::normalize_to_json_pointer(path)
323 .replace("/properties/", "/")
324 .trim_start_matches('#')
325 .to_string();
326
327 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
328
329 if current_data != schema_value {
330 changes.push((path.to_string(), schema_value.clone()));
331 }
332 }
333 }
334
335 if let Some(Value::Object(props)) = map.get("properties") {
337 for (key, val) in props {
338 let next_path = if path == "#" {
339 format!("#/properties/{}", key)
340 } else {
341 format!("{}/properties/{}", path, key)
342 };
343 self.collect_readonly_fixes(val, &next_path, changes);
344 }
345 }
346 }
347 _ => {}
348 }
349 }
350
351 pub(crate) fn check_hidden_field(
353 &self,
354 schema_element: &Value,
355 path: &str,
356 hidden_fields: &mut Vec<String>,
357 ) {
358 match schema_element {
359 Value::Object(map) => {
360 let mut is_hidden = false;
362 if let Some(Value::Object(condition)) = map.get("condition") {
363 if let Some(Value::Bool(h)) = condition.get("hidden") {
364 is_hidden = *h;
365 }
366 }
367
368 let mut keep_hidden = false;
370 if let Some(Value::Object(config)) = map.get("config") {
371 if let Some(Value::Object(all)) = config.get("all") {
372 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
373 keep_hidden = *keep;
374 }
375 }
376 }
377
378 if is_hidden && !keep_hidden {
379 let data_path = path_utils::normalize_to_json_pointer(path)
380 .replace("/properties/", "/")
381 .trim_start_matches('#')
382 .to_string();
383
384 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
385
386 if current_data != &Value::Null && current_data != "" {
388 hidden_fields.push(path.to_string());
389 }
390 }
391 }
392 _ => {}
393 }
394 }
395
396 #[allow(dead_code)]
398 pub(crate) fn collect_hidden_fields(
399 &self,
400 schema_element: &Value,
401 path: &str,
402 hidden_fields: &mut Vec<String>,
403 ) {
404 match schema_element {
405 Value::Object(map) => {
406 let mut is_hidden = false;
408 if let Some(Value::Object(condition)) = map.get("condition") {
409 if let Some(Value::Bool(h)) = condition.get("hidden") {
410 is_hidden = *h;
411 }
412 }
413
414 let mut keep_hidden = false;
416 if let Some(Value::Object(config)) = map.get("config") {
417 if let Some(Value::Object(all)) = config.get("all") {
418 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
419 keep_hidden = *keep;
420 }
421 }
422 }
423
424 if is_hidden && !keep_hidden {
425 let data_path = path_utils::normalize_to_json_pointer(path)
426 .replace("/properties/", "/")
427 .trim_start_matches('#')
428 .to_string();
429
430 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
431
432 if current_data != &Value::Null && current_data != "" {
434 hidden_fields.push(path.to_string());
435 }
436 }
437
438 for (key, val) in map {
440 if key == "properties" {
441 if let Value::Object(props) = val {
442 for (p_key, p_val) in props {
443 let next_path = if path == "#" {
444 format!("#/properties/{}", p_key)
445 } else {
446 format!("{}/properties/{}", path, p_key)
447 };
448 self.collect_hidden_fields(p_val, &next_path, hidden_fields);
449 }
450 }
451 } else if let Value::Object(_) = val {
452 if key == "condition"
454 || key == "config"
455 || key == "rules"
456 || key == "dependents"
457 || key == "hideLayout"
458 || key == "$layout"
459 || key == "$params"
460 || key == "definitions"
461 || key == "$defs"
462 || key.starts_with('$')
463 {
464 continue;
465 }
466
467 let next_path = if path == "#" {
468 format!("#/{}", key)
469 } else {
470 format!("{}/{}", path, key)
471 };
472 self.collect_hidden_fields(val, &next_path, hidden_fields);
473 }
474 }
475 }
476 _ => {}
477 }
478 }
479
480 pub(crate) fn recursive_hide_effect(
482 engine: &RLogic,
483 evaluations: &IndexMap<String, LogicId>,
484 reffed_by: &IndexMap<String, Vec<String>>,
485 eval_data: &mut EvalData,
486 mut hidden_fields: Vec<String>,
487 queue: &mut Vec<(String, bool)>,
488 result: &mut Vec<Value>
489 ) {
490 while let Some(hf) = hidden_fields.pop() {
491 let data_path = path_utils::normalize_to_json_pointer(&hf)
492 .replace("/properties/", "/")
493 .trim_start_matches('#')
494 .to_string();
495
496 eval_data.set(&data_path, Value::Null);
498
499 let mut change_obj = serde_json::Map::new();
501 change_obj.insert("$ref".to_string(), Value::String(path_utils::pointer_to_dot_notation(&data_path)));
502 change_obj.insert("$hidden".to_string(), Value::Bool(true));
503 change_obj.insert("clear".to_string(), Value::Bool(true));
504 result.push(Value::Object(change_obj));
505
506 queue.push((hf.clone(), true));
508
509 if let Some(referencing_fields) = reffed_by.get(&data_path) {
511 for rb in referencing_fields {
512 let hidden_eval_key = format!("{}/condition/hidden", rb);
516
517 if let Some(logic_id) = evaluations.get(&hidden_eval_key) {
518 let rb_data_path = path_utils::normalize_to_json_pointer(rb)
525 .replace("/properties/", "/")
526 .trim_start_matches('#')
527 .to_string();
528 let rb_value = eval_data.data().pointer(&rb_data_path).cloned().unwrap_or(Value::Null);
529
530 if let Ok(Value::Bool(is_hidden)) = engine.run(
532 logic_id,
533 eval_data.data()
534 ) {
535 if is_hidden {
536 if !hidden_fields.contains(rb) {
539 let has_value = rb_value != Value::Null && rb_value != "";
540 if has_value {
541 hidden_fields.push(rb.clone());
542 }
543 }
544 }
545 }
546 }
547 }
548 }
549 }
550 }
551
552 pub(crate) fn process_dependents_queue(
555 engine: &RLogic,
556 evaluations: &IndexMap<String, LogicId>,
557 eval_data: &mut EvalData,
558 dependents_evaluations: &IndexMap<String, Vec<DependentItem>>,
559 evaluated_schema: &Value,
560 queue: &mut Vec<(String, bool)>,
561 processed: &mut IndexSet<String>,
562 result: &mut Vec<Value>,
563 token: Option<&CancellationToken>,
564 canceled_paths: Option<&mut Vec<String>>,
565 ) -> Result<(), String> {
566 while let Some((current_path, is_transitive)) = queue.pop() {
567 if let Some(t) = token {
568 if t.is_cancelled() {
569 if let Some(cp) = canceled_paths {
571 cp.push(current_path.clone());
572 for (path, _) in queue.iter() {
578 cp.push(path.clone());
579 }
580 }
581 return Err("Cancelled".to_string());
582 }
583 }
584 if processed.contains(¤t_path) {
585 continue;
586 }
587 processed.insert(current_path.clone());
588
589 let current_data_path = path_utils::normalize_to_json_pointer(¤t_path)
591 .replace("/properties/", "/")
592 .trim_start_matches('#')
593 .to_string();
594 let mut current_value = eval_data
595 .data()
596 .pointer(¤t_data_path)
597 .cloned()
598 .unwrap_or(Value::Null);
599
600 if let Some(dependent_items) = dependents_evaluations.get(¤t_path) {
602 for dep_item in dependent_items {
603 let ref_path = &dep_item.ref_path;
604 let pointer_path = path_utils::normalize_to_json_pointer(ref_path);
605 let data_path = pointer_path.replace("/properties/", "/");
607
608 let current_ref_value = eval_data
609 .data()
610 .pointer(&data_path)
611 .cloned()
612 .unwrap_or(Value::Null);
613
614 let field = evaluated_schema.pointer(&pointer_path).cloned();
616
617 let parent_path = if let Some(last_slash) = pointer_path.rfind("/properties") {
619 &pointer_path[..last_slash]
620 } else {
621 "/"
622 };
623 let mut parent_field = if parent_path.is_empty() || parent_path == "/" {
624 evaluated_schema.clone()
625 } else {
626 evaluated_schema
627 .pointer(parent_path)
628 .cloned()
629 .unwrap_or_else(|| Value::Object(serde_json::Map::new()))
630 };
631
632 if let Value::Object(ref mut map) = parent_field {
634 map.remove("properties");
635 map.remove("$layout");
636 }
637
638 let mut change_obj = serde_json::Map::new();
639 change_obj.insert(
640 "$ref".to_string(),
641 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
642 );
643 if let Some(f) = field {
644 change_obj.insert("$field".to_string(), f);
645 }
646 change_obj.insert("$parentField".to_string(), parent_field);
647 change_obj.insert("transitive".to_string(), Value::Bool(is_transitive));
648
649 let mut add_transitive = false;
650 let mut add_deps = false;
651 if let Some(clear_val) = &dep_item.clear {
653 let should_clear = Self::evaluate_dependent_value_static(
654 engine,
655 evaluations,
656 eval_data,
657 clear_val,
658 ¤t_value,
659 ¤t_ref_value,
660 )?;
661 let clear_bool = match should_clear {
662 Value::Bool(b) => b,
663 _ => false,
664 };
665
666 if clear_bool {
667 if data_path == current_data_path {
669 current_value = Value::Null;
670 }
671 eval_data.set(&data_path, Value::Null);
672 change_obj.insert("clear".to_string(), Value::Bool(true));
673 add_transitive = true;
674 add_deps = true;
675 }
676 }
677
678 if let Some(value_val) = &dep_item.value {
680 let computed_value = Self::evaluate_dependent_value_static(
681 engine,
682 evaluations,
683 eval_data,
684 value_val,
685 ¤t_value,
686 ¤t_ref_value,
687 )?;
688 let cleaned_val = clean_float_noise_scalar(computed_value);
689
690 if cleaned_val != current_ref_value && cleaned_val != Value::Null {
691 if data_path == current_data_path {
693 current_value = cleaned_val.clone();
694 }
695 eval_data.set(&data_path, cleaned_val.clone());
696 change_obj.insert("value".to_string(), cleaned_val);
697 add_transitive = true;
698 add_deps = true;
699 }
700 }
701
702 if add_deps {
704 result.push(Value::Object(change_obj));
705 }
706
707 if add_transitive {
709 queue.push((ref_path.clone(), true));
710 }
711 }
712 }
713 }
714 Ok(())
715 }
716}