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::utils::clean_float_noise;
7use crate::EvalData;
8
9use indexmap::{IndexMap, IndexSet};
10use serde_json::Value;
11
12
13impl JSONEval {
14 pub fn evaluate_dependents(
17 &mut self,
18 changed_paths: &[String],
19 data: Option<&str>,
20 context: Option<&str>,
21 re_evaluate: bool,
22 ) -> Result<Value, String> {
23 let _lock = self.eval_lock.lock().unwrap();
25
26 if let Some(data_str) = data {
28 let old_data = self.eval_data.clone_data_without(&["$params"]);
30
31 let data_value = json_parser::parse_json_str(data_str)?;
32 let context_value = if let Some(ctx) = context {
33 json_parser::parse_json_str(ctx)?
34 } else {
35 Value::Object(serde_json::Map::new())
36 };
37 self.eval_data
38 .replace_data_and_context(data_value.clone(), context_value);
39
40 let data_paths: Vec<String> = changed_paths
44 .iter()
45 .map(|path| {
46 let schema_ptr = path_utils::dot_notation_to_schema_pointer(path);
49
50 let normalized = schema_ptr
52 .trim_start_matches('#')
53 .replace("/properties/", "/");
54
55 if normalized.starts_with('/') {
57 normalized
58 } else {
59 format!("/{}", normalized)
60 }
61 })
62 .collect();
63 self.purge_cache_for_changed_data_with_comparison(&data_paths, &old_data, &data_value);
64 }
65
66 let mut result = Vec::new();
67 let mut processed = IndexSet::new();
68
69 let mut to_process: Vec<(String, bool)> = changed_paths
72 .iter()
73 .map(|path| (path_utils::dot_notation_to_schema_pointer(path), false))
74 .collect(); Self::process_dependents_queue(
78 &self.engine,
79 &self.evaluations,
80 &mut self.eval_data,
81 &self.dependents_evaluations,
82 &self.evaluated_schema,
83 &mut to_process,
84 &mut processed,
85 &mut result
86 )?;
87
88 if re_evaluate {
91 drop(_lock);
93 self.evaluate_internal(None)?;
94
95 let _lock = self.eval_lock.lock().unwrap();
97
98 let mut readonly_changes = Vec::new();
101 let mut readonly_values = Vec::new(); for path in self.conditional_readonly_fields.iter() {
106 let normalized = path_utils::normalize_to_json_pointer(path);
107 if let Some(schema_element) = self.evaluated_schema.pointer(&normalized) {
108 self.check_readonly_for_dependents(schema_element, path, &mut readonly_changes, &mut readonly_values);
109 }
110 }
111
112 for (path, schema_value) in readonly_changes {
114 let data_path = path_utils::normalize_to_json_pointer(&path)
116 .replace("/properties/", "/")
117 .trim_start_matches('#')
118 .to_string();
119
120 self.eval_data.set(&data_path, schema_value.clone());
121
122 to_process.push((path, true));
124 }
125
126 for (path, schema_value) in readonly_values {
128 let data_path = path_utils::normalize_to_json_pointer(&path)
129 .replace("/properties/", "/")
130 .trim_start_matches('#')
131 .to_string();
132
133 let mut change_obj = serde_json::Map::new();
134 change_obj.insert("$ref".to_string(), Value::String(path_utils::pointer_to_dot_notation(&data_path)));
135 change_obj.insert("$readonly".to_string(), Value::Bool(true));
136 change_obj.insert("value".to_string(), schema_value);
137
138 result.push(Value::Object(change_obj));
139 }
140
141 if !to_process.is_empty() {
143 Self::process_dependents_queue(
144 &self.engine,
145 &self.evaluations,
146 &mut self.eval_data,
147 &self.dependents_evaluations,
148 &self.evaluated_schema,
149 &mut to_process,
150 &mut processed,
151 &mut result
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 )?;
192 }
193 }
194
195 Ok(Value::Array(result))
196 }
197
198 pub(crate) fn evaluate_dependent_value_static(
200 engine: &RLogic,
201 evaluations: &IndexMap<String, LogicId>,
202 eval_data: &EvalData,
203 value: &Value,
204 changed_field_value: &Value,
205 changed_field_ref_value: &Value,
206 ) -> Result<Value, String> {
207 match value {
208 Value::String(eval_key) => {
210 if let Some(logic_id) = evaluations.get(eval_key) {
211 let mut internal_context = serde_json::Map::new();
214 internal_context.insert("$value".to_string(), changed_field_value.clone());
215 internal_context.insert("$refValue".to_string(), changed_field_ref_value.clone());
216 let context_value = Value::Object(internal_context);
217
218 let result = engine.run_with_context(logic_id, eval_data.data(), &context_value)
219 .map_err(|e| format!("Failed to evaluate dependent logic '{}': {}", eval_key, e))?;
220 Ok(result)
221 } else {
222 Ok(value.clone())
224 }
225 }
226 Value::Object(map) if map.contains_key("$evaluation") => {
229 Err("Dependent evaluation contains unparsed $evaluation - schema was not properly parsed".to_string())
230 }
231 _ => Ok(value.clone()),
233 }
234 }
235
236 pub(crate) fn check_readonly_for_dependents(
238 &self,
239 schema_element: &Value,
240 path: &str,
241 changes: &mut Vec<(String, Value)>,
242 all_values: &mut Vec<(String, Value)>,
243 ) {
244 match schema_element {
245 Value::Object(map) => {
246 let mut is_disabled = false;
248 if let Some(Value::Object(condition)) = map.get("condition") {
249 if let Some(Value::Bool(d)) = condition.get("disabled") {
250 is_disabled = *d;
251 }
252 }
253
254 let mut skip_readonly = false;
256 if let Some(Value::Object(config)) = map.get("config") {
257 if let Some(Value::Object(all)) = config.get("all") {
258 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
259 skip_readonly = *skip;
260 }
261 }
262 }
263
264 if is_disabled && !skip_readonly {
265 if let Some(schema_value) = map.get("value") {
266 let data_path = path_utils::normalize_to_json_pointer(path)
267 .replace("/properties/", "/")
268 .trim_start_matches('#')
269 .to_string();
270
271 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
272
273 all_values.push((path.to_string(), schema_value.clone()));
275
276 if current_data != schema_value {
278 changes.push((path.to_string(), schema_value.clone()));
279 }
280 }
281 }
282 }
283 _ => {}
284 }
285 }
286
287 #[allow(dead_code)]
289 pub(crate) fn collect_readonly_fixes(
290 &self,
291 schema_element: &Value,
292 path: &str,
293 changes: &mut Vec<(String, Value)>,
294 ) {
295 match schema_element {
296 Value::Object(map) => {
297 let mut is_disabled = false;
299 if let Some(Value::Object(condition)) = map.get("condition") {
300 if let Some(Value::Bool(d)) = condition.get("disabled") {
301 is_disabled = *d;
302 }
303 }
304
305 let mut skip_readonly = false;
307 if let Some(Value::Object(config)) = map.get("config") {
308 if let Some(Value::Object(all)) = config.get("all") {
309 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
310 skip_readonly = *skip;
311 }
312 }
313 }
314
315 if is_disabled && !skip_readonly {
316 if let Some(schema_value) = map.get("value") {
320 let data_path = path_utils::normalize_to_json_pointer(path)
321 .replace("/properties/", "/")
322 .trim_start_matches('#')
323 .to_string();
324
325 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
326
327 if current_data != schema_value {
328 changes.push((path.to_string(), schema_value.clone()));
329 }
330 }
331 }
332
333 if let Some(Value::Object(props)) = map.get("properties") {
335 for (key, val) in props {
336 let next_path = if path == "#" {
337 format!("#/properties/{}", key)
338 } else {
339 format!("{}/properties/{}", path, key)
340 };
341 self.collect_readonly_fixes(val, &next_path, changes);
342 }
343 }
344 }
345 _ => {}
346 }
347 }
348
349 pub(crate) fn check_hidden_field(
351 &self,
352 schema_element: &Value,
353 path: &str,
354 hidden_fields: &mut Vec<String>,
355 ) {
356 match schema_element {
357 Value::Object(map) => {
358 let mut is_hidden = false;
360 if let Some(Value::Object(condition)) = map.get("condition") {
361 if let Some(Value::Bool(h)) = condition.get("hidden") {
362 is_hidden = *h;
363 }
364 }
365
366 let mut keep_hidden = false;
368 if let Some(Value::Object(config)) = map.get("config") {
369 if let Some(Value::Object(all)) = config.get("all") {
370 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
371 keep_hidden = *keep;
372 }
373 }
374 }
375
376 if is_hidden && !keep_hidden {
377 let data_path = path_utils::normalize_to_json_pointer(path)
378 .replace("/properties/", "/")
379 .trim_start_matches('#')
380 .to_string();
381
382 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
383
384 if current_data != &Value::Null && current_data != "" {
386 hidden_fields.push(path.to_string());
387 }
388 }
389 }
390 _ => {}
391 }
392 }
393
394 #[allow(dead_code)]
396 pub(crate) fn collect_hidden_fields(
397 &self,
398 schema_element: &Value,
399 path: &str,
400 hidden_fields: &mut Vec<String>,
401 ) {
402 match schema_element {
403 Value::Object(map) => {
404 let mut is_hidden = false;
406 if let Some(Value::Object(condition)) = map.get("condition") {
407 if let Some(Value::Bool(h)) = condition.get("hidden") {
408 is_hidden = *h;
409 }
410 }
411
412 let mut keep_hidden = false;
414 if let Some(Value::Object(config)) = map.get("config") {
415 if let Some(Value::Object(all)) = config.get("all") {
416 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
417 keep_hidden = *keep;
418 }
419 }
420 }
421
422 if is_hidden && !keep_hidden {
423 let data_path = path_utils::normalize_to_json_pointer(path)
424 .replace("/properties/", "/")
425 .trim_start_matches('#')
426 .to_string();
427
428 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
429
430 if current_data != &Value::Null && current_data != "" {
432 hidden_fields.push(path.to_string());
433 }
434 }
435
436 for (key, val) in map {
438 if key == "properties" {
439 if let Value::Object(props) = val {
440 for (p_key, p_val) in props {
441 let next_path = if path == "#" {
442 format!("#/properties/{}", p_key)
443 } else {
444 format!("{}/properties/{}", path, p_key)
445 };
446 self.collect_hidden_fields(p_val, &next_path, hidden_fields);
447 }
448 }
449 } else if let Value::Object(_) = val {
450 if key == "condition"
452 || key == "config"
453 || key == "rules"
454 || key == "dependents"
455 || key == "hideLayout"
456 || key == "$layout"
457 || key == "$params"
458 || key == "definitions"
459 || key == "$defs"
460 || key.starts_with('$')
461 {
462 continue;
463 }
464
465 let next_path = if path == "#" {
466 format!("#/{}", key)
467 } else {
468 format!("{}/{}", path, key)
469 };
470 self.collect_hidden_fields(val, &next_path, hidden_fields);
471 }
472 }
473 }
474 _ => {}
475 }
476 }
477
478 pub(crate) fn recursive_hide_effect(
480 engine: &RLogic,
481 evaluations: &IndexMap<String, LogicId>,
482 reffed_by: &IndexMap<String, Vec<String>>,
483 eval_data: &mut EvalData,
484 mut hidden_fields: Vec<String>,
485 queue: &mut Vec<(String, bool)>,
486 result: &mut Vec<Value>
487 ) {
488 while let Some(hf) = hidden_fields.pop() {
489 let data_path = path_utils::normalize_to_json_pointer(&hf)
490 .replace("/properties/", "/")
491 .trim_start_matches('#')
492 .to_string();
493
494 eval_data.set(&data_path, Value::Null);
496
497 let mut change_obj = serde_json::Map::new();
499 change_obj.insert("$ref".to_string(), Value::String(path_utils::pointer_to_dot_notation(&data_path)));
500 change_obj.insert("$hidden".to_string(), Value::Bool(true));
501 change_obj.insert("clear".to_string(), Value::Bool(true));
502 result.push(Value::Object(change_obj));
503
504 queue.push((hf.clone(), true));
506
507 if let Some(referencing_fields) = reffed_by.get(&data_path) {
509 for rb in referencing_fields {
510 let hidden_eval_key = format!("{}/condition/hidden", rb);
514
515 if let Some(logic_id) = evaluations.get(&hidden_eval_key) {
516 let rb_data_path = path_utils::normalize_to_json_pointer(rb)
523 .replace("/properties/", "/")
524 .trim_start_matches('#')
525 .to_string();
526 let rb_value = eval_data.data().pointer(&rb_data_path).cloned().unwrap_or(Value::Null);
527
528 if let Ok(Value::Bool(is_hidden)) = engine.run(
530 logic_id,
531 eval_data.data()
532 ) {
533 if is_hidden {
534 if !hidden_fields.contains(rb) {
537 let has_value = rb_value != Value::Null && rb_value != "";
538 if has_value {
539 hidden_fields.push(rb.clone());
540 }
541 }
542 }
543 }
544 }
545 }
546 }
547 }
548 }
549
550 pub(crate) fn process_dependents_queue(
553 engine: &RLogic,
554 evaluations: &IndexMap<String, LogicId>,
555 eval_data: &mut EvalData,
556 dependents_evaluations: &IndexMap<String, Vec<DependentItem>>,
557 evaluated_schema: &Value,
558 queue: &mut Vec<(String, bool)>,
559 processed: &mut IndexSet<String>,
560 result: &mut Vec<Value>,
561 ) -> Result<(), String> {
562 while let Some((current_path, is_transitive)) = queue.pop() {
563 if processed.contains(¤t_path) {
564 continue;
565 }
566 processed.insert(current_path.clone());
567
568 let current_data_path = path_utils::normalize_to_json_pointer(¤t_path)
570 .replace("/properties/", "/")
571 .trim_start_matches('#')
572 .to_string();
573 let mut current_value = eval_data
574 .data()
575 .pointer(¤t_data_path)
576 .cloned()
577 .unwrap_or(Value::Null);
578
579 if let Some(dependent_items) = dependents_evaluations.get(¤t_path) {
581 for dep_item in dependent_items {
582 let ref_path = &dep_item.ref_path;
583 let pointer_path = path_utils::normalize_to_json_pointer(ref_path);
584 let data_path = pointer_path.replace("/properties/", "/");
586
587 let current_ref_value = eval_data
588 .data()
589 .pointer(&data_path)
590 .cloned()
591 .unwrap_or(Value::Null);
592
593 let field = evaluated_schema.pointer(&pointer_path).cloned();
595
596 let parent_path = if let Some(last_slash) = pointer_path.rfind("/properties") {
598 &pointer_path[..last_slash]
599 } else {
600 "/"
601 };
602 let mut parent_field = if parent_path.is_empty() || parent_path == "/" {
603 evaluated_schema.clone()
604 } else {
605 evaluated_schema
606 .pointer(parent_path)
607 .cloned()
608 .unwrap_or_else(|| Value::Object(serde_json::Map::new()))
609 };
610
611 if let Value::Object(ref mut map) = parent_field {
613 map.remove("properties");
614 map.remove("$layout");
615 }
616
617 let mut change_obj = serde_json::Map::new();
618 change_obj.insert(
619 "$ref".to_string(),
620 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
621 );
622 if let Some(f) = field {
623 change_obj.insert("$field".to_string(), f);
624 }
625 change_obj.insert("$parentField".to_string(), parent_field);
626 change_obj.insert("transitive".to_string(), Value::Bool(is_transitive));
627
628 let mut add_transitive = false;
629 let mut add_deps = false;
630 if let Some(clear_val) = &dep_item.clear {
632 let clear_val_clone = clear_val.clone();
633 let should_clear = Self::evaluate_dependent_value_static(
634 engine,
635 evaluations,
636 eval_data,
637 &clear_val_clone,
638 ¤t_value,
639 ¤t_ref_value,
640 )?;
641 let clear_bool = match should_clear {
642 Value::Bool(b) => b,
643 _ => false,
644 };
645
646 if clear_bool {
647 if data_path == current_data_path {
649 current_value = Value::Null;
650 }
651 eval_data.set(&data_path, Value::Null);
652 change_obj.insert("clear".to_string(), Value::Bool(true));
653 add_transitive = true;
654 add_deps = true;
655 }
656 }
657
658 if let Some(value_val) = &dep_item.value {
660 let value_val_clone = value_val.clone();
661 let computed_value = Self::evaluate_dependent_value_static(
662 engine,
663 evaluations,
664 eval_data,
665 &value_val_clone,
666 ¤t_value,
667 ¤t_ref_value,
668 )?;
669 let cleaned_val = clean_float_noise(computed_value.clone());
670
671 if cleaned_val != current_ref_value && cleaned_val != Value::Null {
672 if data_path == current_data_path {
674 current_value = cleaned_val.clone();
675 }
676 eval_data.set(&data_path, cleaned_val.clone());
677 change_obj.insert("value".to_string(), cleaned_val);
678 add_transitive = true;
679 add_deps = true;
680 }
681 }
682
683 if add_deps {
685 result.push(Value::Object(change_obj));
686 }
687
688 if add_transitive {
690 queue.push((ref_path.clone(), true));
691 }
692 }
693 }
694 }
695 Ok(())
696 }
697}