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
102 for path in self.conditional_readonly_fields.iter() {
105 let normalized = path_utils::normalize_to_json_pointer(path);
106 if let Some(schema_element) = self.evaluated_schema.pointer(&normalized) {
107 self.check_readonly_fix(schema_element, path, &mut readonly_changes);
108 }
109 }
110
111 for (path, schema_value) in readonly_changes {
113 let data_path = path_utils::normalize_to_json_pointer(&path)
115 .replace("/properties/", "/")
116 .trim_start_matches('#')
117 .to_string();
118
119 self.eval_data.set(&data_path, schema_value.clone());
120
121 let mut change_obj = serde_json::Map::new();
127 change_obj.insert("$ref".to_string(), Value::String(path_utils::pointer_to_dot_notation(&data_path)));
128 change_obj.insert("$readonly".to_string(), Value::Bool(true));
129 change_obj.insert("value".to_string(), schema_value);
130
131 to_process.push((path, true));
133 result.push(Value::Object(change_obj));
135 }
136
137 if !to_process.is_empty() {
139 Self::process_dependents_queue(
140 &self.engine,
141 &self.evaluations,
142 &mut self.eval_data,
143 &self.dependents_evaluations,
144 &self.evaluated_schema,
145 &mut to_process,
146 &mut processed,
147 &mut result
148 )?;
149 }
150
151 let mut hidden_fields = Vec::new();
154 for path in self.conditional_hidden_fields.iter() {
157 let normalized = path_utils::normalize_to_json_pointer(path);
158 if let Some(schema_element) = self.evaluated_schema.pointer(&normalized) {
159 self.check_hidden_field(schema_element, path, &mut hidden_fields);
160 }
161 }
162
163 if !hidden_fields.is_empty() {
165 Self::recursive_hide_effect(
166 &self.engine,
167 &self.evaluations,
168 &self.reffed_by,
169 &mut self.eval_data,
170 hidden_fields,
171 &mut to_process,
172 &mut result
173 );
174 }
175
176 if !to_process.is_empty() {
178 Self::process_dependents_queue(
179 &self.engine,
180 &self.evaluations,
181 &mut self.eval_data,
182 &self.dependents_evaluations,
183 &self.evaluated_schema,
184 &mut to_process,
185 &mut processed,
186 &mut result
187 )?;
188 }
189 }
190
191 Ok(Value::Array(result))
192 }
193
194 pub(crate) fn evaluate_dependent_value_static(
196 engine: &RLogic,
197 evaluations: &IndexMap<String, LogicId>,
198 eval_data: &EvalData,
199 value: &Value,
200 changed_field_value: &Value,
201 changed_field_ref_value: &Value,
202 ) -> Result<Value, String> {
203 match value {
204 Value::String(eval_key) => {
206 if let Some(logic_id) = evaluations.get(eval_key) {
207 let mut internal_context = serde_json::Map::new();
210 internal_context.insert("$value".to_string(), changed_field_value.clone());
211 internal_context.insert("$refValue".to_string(), changed_field_ref_value.clone());
212 let context_value = Value::Object(internal_context);
213
214 let result = engine.run_with_context(logic_id, eval_data.data(), &context_value)
215 .map_err(|e| format!("Failed to evaluate dependent logic '{}': {}", eval_key, e))?;
216 Ok(result)
217 } else {
218 Ok(value.clone())
220 }
221 }
222 Value::Object(map) if map.contains_key("$evaluation") => {
225 Err("Dependent evaluation contains unparsed $evaluation - schema was not properly parsed".to_string())
226 }
227 _ => Ok(value.clone()),
229 }
230 }
231
232 pub(crate) fn check_readonly_fix(
234 &self,
235 schema_element: &Value,
236 path: &str,
237 changes: &mut Vec<(String, Value)>,
238 ) {
239 match schema_element {
240 Value::Object(map) => {
241 let mut is_disabled = false;
243 if let Some(Value::Object(condition)) = map.get("condition") {
244 if let Some(Value::Bool(d)) = condition.get("disabled") {
245 is_disabled = *d;
246 }
247 }
248
249 let mut skip_readonly = false;
251 if let Some(Value::Object(config)) = map.get("config") {
252 if let Some(Value::Object(all)) = config.get("all") {
253 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
254 skip_readonly = *skip;
255 }
256 }
257 }
258
259 if is_disabled && !skip_readonly {
260 if let Some(schema_value) = map.get("value") {
261 let data_path = path_utils::normalize_to_json_pointer(path)
262 .replace("/properties/", "/")
263 .trim_start_matches('#')
264 .to_string();
265
266 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
267
268 if current_data != schema_value {
269 changes.push((path.to_string(), schema_value.clone()));
270 }
271 }
272 }
273 }
274 _ => {}
275 }
276 }
277
278 #[allow(dead_code)]
280 pub(crate) fn collect_readonly_fixes(
281 &self,
282 schema_element: &Value,
283 path: &str,
284 changes: &mut Vec<(String, Value)>,
285 ) {
286 match schema_element {
287 Value::Object(map) => {
288 let mut is_disabled = false;
290 if let Some(Value::Object(condition)) = map.get("condition") {
291 if let Some(Value::Bool(d)) = condition.get("disabled") {
292 is_disabled = *d;
293 }
294 }
295
296 let mut skip_readonly = false;
298 if let Some(Value::Object(config)) = map.get("config") {
299 if let Some(Value::Object(all)) = config.get("all") {
300 if let Some(Value::Bool(skip)) = all.get("skipReadOnlyValue") {
301 skip_readonly = *skip;
302 }
303 }
304 }
305
306 if is_disabled && !skip_readonly {
307 if let Some(schema_value) = map.get("value") {
311 let data_path = path_utils::normalize_to_json_pointer(path)
312 .replace("/properties/", "/")
313 .trim_start_matches('#')
314 .to_string();
315
316 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
317
318 if current_data != schema_value {
319 changes.push((path.to_string(), schema_value.clone()));
320 }
321 }
322 }
323
324 if let Some(Value::Object(props)) = map.get("properties") {
326 for (key, val) in props {
327 let next_path = if path == "#" {
328 format!("#/properties/{}", key)
329 } else {
330 format!("{}/properties/{}", path, key)
331 };
332 self.collect_readonly_fixes(val, &next_path, changes);
333 }
334 }
335 }
336 _ => {}
337 }
338 }
339
340 pub(crate) fn check_hidden_field(
342 &self,
343 schema_element: &Value,
344 path: &str,
345 hidden_fields: &mut Vec<String>,
346 ) {
347 match schema_element {
348 Value::Object(map) => {
349 let mut is_hidden = false;
351 if let Some(Value::Object(condition)) = map.get("condition") {
352 if let Some(Value::Bool(h)) = condition.get("hidden") {
353 is_hidden = *h;
354 }
355 }
356
357 let mut keep_hidden = false;
359 if let Some(Value::Object(config)) = map.get("config") {
360 if let Some(Value::Object(all)) = config.get("all") {
361 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
362 keep_hidden = *keep;
363 }
364 }
365 }
366
367 if is_hidden && !keep_hidden {
368 let data_path = path_utils::normalize_to_json_pointer(path)
369 .replace("/properties/", "/")
370 .trim_start_matches('#')
371 .to_string();
372
373 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
374
375 if current_data != &Value::Null && current_data != "" {
377 hidden_fields.push(path.to_string());
378 }
379 }
380 }
381 _ => {}
382 }
383 }
384
385 #[allow(dead_code)]
387 pub(crate) fn collect_hidden_fields(
388 &self,
389 schema_element: &Value,
390 path: &str,
391 hidden_fields: &mut Vec<String>,
392 ) {
393 match schema_element {
394 Value::Object(map) => {
395 let mut is_hidden = false;
397 if let Some(Value::Object(condition)) = map.get("condition") {
398 if let Some(Value::Bool(h)) = condition.get("hidden") {
399 is_hidden = *h;
400 }
401 }
402
403 let mut keep_hidden = false;
405 if let Some(Value::Object(config)) = map.get("config") {
406 if let Some(Value::Object(all)) = config.get("all") {
407 if let Some(Value::Bool(keep)) = all.get("keepHiddenValue") {
408 keep_hidden = *keep;
409 }
410 }
411 }
412
413 if is_hidden && !keep_hidden {
414 let data_path = path_utils::normalize_to_json_pointer(path)
415 .replace("/properties/", "/")
416 .trim_start_matches('#')
417 .to_string();
418
419 let current_data = self.eval_data.data().pointer(&data_path).unwrap_or(&Value::Null);
420
421 if current_data != &Value::Null && current_data != "" {
423 hidden_fields.push(path.to_string());
424 }
425 }
426
427 for (key, val) in map {
429 if key == "properties" {
430 if let Value::Object(props) = val {
431 for (p_key, p_val) in props {
432 let next_path = if path == "#" {
433 format!("#/properties/{}", p_key)
434 } else {
435 format!("{}/properties/{}", path, p_key)
436 };
437 self.collect_hidden_fields(p_val, &next_path, hidden_fields);
438 }
439 }
440 } else if let Value::Object(_) = val {
441 if key == "condition"
443 || key == "config"
444 || key == "rules"
445 || key == "dependents"
446 || key == "hideLayout"
447 || key == "$layout"
448 || key == "$params"
449 || key == "definitions"
450 || key == "$defs"
451 || key.starts_with('$')
452 {
453 continue;
454 }
455
456 let next_path = if path == "#" {
457 format!("#/{}", key)
458 } else {
459 format!("{}/{}", path, key)
460 };
461 self.collect_hidden_fields(val, &next_path, hidden_fields);
462 }
463 }
464 }
465 _ => {}
466 }
467 }
468
469 pub(crate) fn recursive_hide_effect(
471 engine: &RLogic,
472 evaluations: &IndexMap<String, LogicId>,
473 reffed_by: &IndexMap<String, Vec<String>>,
474 eval_data: &mut EvalData,
475 mut hidden_fields: Vec<String>,
476 queue: &mut Vec<(String, bool)>,
477 result: &mut Vec<Value>
478 ) {
479 while let Some(hf) = hidden_fields.pop() {
480 let data_path = path_utils::normalize_to_json_pointer(&hf)
481 .replace("/properties/", "/")
482 .trim_start_matches('#')
483 .to_string();
484
485 eval_data.set(&data_path, Value::Null);
487
488 let mut change_obj = serde_json::Map::new();
490 change_obj.insert("$ref".to_string(), Value::String(path_utils::pointer_to_dot_notation(&data_path)));
491 change_obj.insert("$hidden".to_string(), Value::Bool(true));
492 change_obj.insert("clear".to_string(), Value::Bool(true));
493 result.push(Value::Object(change_obj));
494
495 queue.push((hf.clone(), true));
497
498 if let Some(referencing_fields) = reffed_by.get(&data_path) {
500 for rb in referencing_fields {
501 let hidden_eval_key = format!("{}/condition/hidden", rb);
505
506 if let Some(logic_id) = evaluations.get(&hidden_eval_key) {
507 let rb_data_path = path_utils::normalize_to_json_pointer(rb)
514 .replace("/properties/", "/")
515 .trim_start_matches('#')
516 .to_string();
517 let rb_value = eval_data.data().pointer(&rb_data_path).cloned().unwrap_or(Value::Null);
518
519 if let Ok(Value::Bool(is_hidden)) = engine.run(
521 logic_id,
522 eval_data.data()
523 ) {
524 if is_hidden {
525 if !hidden_fields.contains(rb) {
528 let has_value = rb_value != Value::Null && rb_value != "";
529 if has_value {
530 hidden_fields.push(rb.clone());
531 }
532 }
533 }
534 }
535 }
536 }
537 }
538 }
539 }
540
541 pub(crate) fn process_dependents_queue(
544 engine: &RLogic,
545 evaluations: &IndexMap<String, LogicId>,
546 eval_data: &mut EvalData,
547 dependents_evaluations: &IndexMap<String, Vec<DependentItem>>,
548 evaluated_schema: &Value,
549 queue: &mut Vec<(String, bool)>,
550 processed: &mut IndexSet<String>,
551 result: &mut Vec<Value>,
552 ) -> Result<(), String> {
553 while let Some((current_path, is_transitive)) = queue.pop() {
554 if processed.contains(¤t_path) {
555 continue;
556 }
557 processed.insert(current_path.clone());
558
559 let current_data_path = path_utils::normalize_to_json_pointer(¤t_path)
561 .replace("/properties/", "/")
562 .trim_start_matches('#')
563 .to_string();
564 let mut current_value = eval_data
565 .data()
566 .pointer(¤t_data_path)
567 .cloned()
568 .unwrap_or(Value::Null);
569
570 if let Some(dependent_items) = dependents_evaluations.get(¤t_path) {
572 for dep_item in dependent_items {
573 let ref_path = &dep_item.ref_path;
574 let pointer_path = path_utils::normalize_to_json_pointer(ref_path);
575 let data_path = pointer_path.replace("/properties/", "/");
577
578 let current_ref_value = eval_data
579 .data()
580 .pointer(&data_path)
581 .cloned()
582 .unwrap_or(Value::Null);
583
584 let field = evaluated_schema.pointer(&pointer_path).cloned();
586
587 let parent_path = if let Some(last_slash) = pointer_path.rfind("/properties") {
589 &pointer_path[..last_slash]
590 } else {
591 "/"
592 };
593 let mut parent_field = if parent_path.is_empty() || parent_path == "/" {
594 evaluated_schema.clone()
595 } else {
596 evaluated_schema
597 .pointer(parent_path)
598 .cloned()
599 .unwrap_or_else(|| Value::Object(serde_json::Map::new()))
600 };
601
602 if let Value::Object(ref mut map) = parent_field {
604 map.remove("properties");
605 map.remove("$layout");
606 }
607
608 let mut change_obj = serde_json::Map::new();
609 change_obj.insert(
610 "$ref".to_string(),
611 Value::String(path_utils::pointer_to_dot_notation(&data_path)),
612 );
613 if let Some(f) = field {
614 change_obj.insert("$field".to_string(), f);
615 }
616 change_obj.insert("$parentField".to_string(), parent_field);
617 change_obj.insert("transitive".to_string(), Value::Bool(is_transitive));
618
619 let mut add_transitive = false;
620 let mut add_deps = false;
621 if let Some(clear_val) = &dep_item.clear {
623 let clear_val_clone = clear_val.clone();
624 let should_clear = Self::evaluate_dependent_value_static(
625 engine,
626 evaluations,
627 eval_data,
628 &clear_val_clone,
629 ¤t_value,
630 ¤t_ref_value,
631 )?;
632 let clear_bool = match should_clear {
633 Value::Bool(b) => b,
634 _ => false,
635 };
636
637 if clear_bool {
638 if data_path == current_data_path {
640 current_value = Value::Null;
641 }
642 eval_data.set(&data_path, Value::Null);
643 change_obj.insert("clear".to_string(), Value::Bool(true));
644 add_transitive = true;
645 add_deps = true;
646 }
647 }
648
649 if let Some(value_val) = &dep_item.value {
651 let value_val_clone = value_val.clone();
652 let computed_value = Self::evaluate_dependent_value_static(
653 engine,
654 evaluations,
655 eval_data,
656 &value_val_clone,
657 ¤t_value,
658 ¤t_ref_value,
659 )?;
660 let cleaned_val = clean_float_noise(computed_value.clone());
661
662 if cleaned_val != current_ref_value && cleaned_val != Value::Null {
663 if data_path == current_data_path {
665 current_value = cleaned_val.clone();
666 }
667 eval_data.set(&data_path, cleaned_val.clone());
668 change_obj.insert("value".to_string(), cleaned_val);
669 add_transitive = true;
670 add_deps = true;
671 }
672 }
673
674 if add_deps {
676 result.push(Value::Object(change_obj));
677 }
678
679 if add_transitive {
681 queue.push((ref_path.clone(), true));
682 }
683 }
684 }
685 }
686 Ok(())
687 }
688}