1use super::JSONEval;
2use crate::jsoneval::path_utils;
3use crate::jsoneval::types::{ResolvedLayoutResult, ReturnFormat};
4use crate::time_block;
5use crate::utils::clean_float_noise_scalar;
6use serde_json::Value;
7
8impl JSONEval {
9 pub(crate) fn is_effective_hidden(&self, schema_pointer: &str) -> bool {
14 self.ensure_layout_resolved();
15 let schema_pointer = schema_pointer.trim_start_matches('#');
16 if let Ok(state) = self.layout_state.read() {
17 if state.layout_hidden_refs.iter().any(|hidden_ref| {
18 schema_pointer == hidden_ref
19 || schema_pointer
20 .strip_prefix(hidden_ref)
21 .is_some_and(|suffix| {
22 suffix.starts_with("/properties/") || suffix.starts_with("/items/")
23 })
24 }) {
25 return true;
26 }
27 }
28
29 self.is_schema_effective_hidden(schema_pointer)
30 }
31
32 pub(crate) fn is_effective_hidden_with_cache(
34 &self,
35 schema_pointer: &str,
36 layout_hidden_refs: &indexmap::IndexSet<String>,
37 cache: &mut std::collections::HashMap<String, bool>,
38 ) -> bool {
39 let schema_pointer = schema_pointer.trim_start_matches('#');
40 if let Some(&is_hidden) = cache.get(schema_pointer) {
41 return is_hidden;
42 }
43
44 if layout_hidden_refs.iter().any(|hidden_ref| {
45 schema_pointer == hidden_ref
46 || schema_pointer
47 .strip_prefix(hidden_ref)
48 .is_some_and(|suffix| {
49 suffix.starts_with("/properties/") || suffix.starts_with("/items/")
50 })
51 }) {
52 cache.insert(schema_pointer.to_string(), true);
53 return true;
54 }
55
56 let is_hidden = self.is_schema_effective_hidden_cached(schema_pointer, cache);
57 cache.insert(schema_pointer.to_string(), is_hidden);
58 is_hidden
59 }
60
61 pub(crate) fn is_schema_effective_hidden_cached(
63 &self,
64 schema_pointer: &str,
65 cache: &mut std::collections::HashMap<String, bool>,
66 ) -> bool {
67 let schema_pointer = schema_pointer.trim_start_matches('#');
68 let mut end = schema_pointer.len();
69
70 loop {
71 let current_path = &schema_pointer[..end];
72
73 if let Some(&ancestor_hidden) = cache.get(current_path) {
74 if ancestor_hidden {
75 return true;
76 }
77 break;
78 }
79
80 if let Some(schema_node) = self.evaluated_schema.pointer(current_path) {
81 if let Value::Object(map) = schema_node {
82 if let Some(Value::Object(condition)) = map.get("condition") {
83 if let Some(Value::Bool(true)) = condition.get("hidden") {
84 cache.insert(current_path.to_string(), true);
85 return true;
86 }
87 }
88
89 if let Some(Value::Object(layout)) = map.get("$layout") {
90 if let Some(Value::Object(hide_layout)) = layout.get("hideLayout") {
91 if let Some(Value::Bool(true)) = hide_layout.get("all") {
92 cache.insert(current_path.to_string(), true);
93 return true;
94 }
95 }
96 }
97 }
98 }
99
100 if end == 0 {
101 break;
102 }
103
104 match schema_pointer[..end].rfind('/') {
105 Some(0) | None => {
106 end = 0;
107 }
108 Some(last_slash) => {
109 end = last_slash;
110 let parent = &schema_pointer[..end];
111 if parent.ends_with("/properties") {
112 end -= "/properties".len();
113 } else if parent.ends_with("/items") {
114 end -= "/items".len();
115 }
116 }
117 }
118 }
119
120 false
121 }
122
123 pub(crate) fn is_schema_effective_hidden(&self, schema_pointer: &str) -> bool {
126 let schema_pointer = schema_pointer.trim_start_matches('#');
127 let mut end = schema_pointer.len();
128
129 loop {
130 let current_path = &schema_pointer[..end];
131
132 if let Some(schema_node) = self.evaluated_schema.pointer(current_path) {
133 if let Value::Object(map) = schema_node {
134 if let Some(Value::Object(condition)) = map.get("condition") {
135 if let Some(Value::Bool(true)) = condition.get("hidden") {
136 return true;
137 }
138 }
139
140 if let Some(Value::Object(layout)) = map.get("$layout") {
141 if let Some(Value::Object(hide_layout)) = layout.get("hideLayout") {
142 if let Some(Value::Bool(true)) = hide_layout.get("all") {
143 return true;
144 }
145 }
146 }
147 }
148 }
149
150 if end == 0 {
151 break;
152 }
153
154 match schema_pointer[..end].rfind('/') {
156 Some(0) | None => {
157 end = 0;
158 }
159 Some(last_slash) => {
160 end = last_slash;
161 let parent = &schema_pointer[..end];
162 if parent.ends_with("/properties") {
163 end -= "/properties".len();
164 } else if parent.ends_with("/items") {
165 end -= "/items".len();
166 }
167 }
168 }
169 }
170
171 false
172 }
173
174 pub(crate) fn is_effective_readonly_with_cache(
176 &self,
177 schema_pointer: &str,
178 layout_disabled_refs: &indexmap::IndexSet<String>,
179 cache: &mut std::collections::HashMap<String, bool>,
180 ) -> bool {
181 let schema_pointer = schema_pointer.trim_start_matches('#');
182 if let Some(&is_readonly) = cache.get(schema_pointer) {
183 return is_readonly;
184 }
185
186 if layout_disabled_refs.iter().any(|disabled_ref| {
187 schema_pointer == disabled_ref
188 || schema_pointer
189 .strip_prefix(disabled_ref)
190 .is_some_and(|suffix| {
191 suffix.starts_with("/properties/") || suffix.starts_with("/items/")
192 })
193 }) {
194 cache.insert(schema_pointer.to_string(), true);
195 return true;
196 }
197
198 let is_readonly = self.is_schema_effective_readonly_cached(schema_pointer, cache);
199 cache.insert(schema_pointer.to_string(), is_readonly);
200 is_readonly
201 }
202
203 pub(crate) fn is_schema_effective_readonly_cached(
205 &self,
206 schema_pointer: &str,
207 cache: &mut std::collections::HashMap<String, bool>,
208 ) -> bool {
209 let schema_pointer = schema_pointer.trim_start_matches('#');
210 let mut end = schema_pointer.len();
211
212 loop {
213 let current_path = &schema_pointer[..end];
214
215 if let Some(&ancestor_readonly) = cache.get(current_path) {
216 if ancestor_readonly {
217 return true;
218 }
219 break;
220 }
221
222 if let Some(schema_node) = self.evaluated_schema.pointer(current_path) {
223 if let Value::Object(map) = schema_node {
224 if map.get("disabled").and_then(Value::as_bool) == Some(true)
225 || map.get("readonly").and_then(Value::as_bool) == Some(true)
226 || map.get("readOnly").and_then(Value::as_bool) == Some(true)
227 || map.get("$readonly").and_then(Value::as_bool) == Some(true)
228 {
229 cache.insert(current_path.to_string(), true);
230 return true;
231 }
232
233 if let Some(Value::Object(condition)) = map.get("condition") {
234 if condition.get("disabled").and_then(Value::as_bool) == Some(true)
235 || condition.get("readonly").and_then(Value::as_bool) == Some(true)
236 || condition.get("readOnly").and_then(Value::as_bool) == Some(true)
237 {
238 cache.insert(current_path.to_string(), true);
239 return true;
240 }
241 }
242 }
243 }
244
245 if end == 0 {
246 break;
247 }
248
249 match schema_pointer[..end].rfind('/') {
250 Some(0) | None => {
251 end = 0;
252 }
253 Some(last_slash) => {
254 end = last_slash;
255 let parent = &schema_pointer[..end];
256 if parent.ends_with("/properties") {
257 end -= "/properties".len();
258 } else if parent.ends_with("/items") {
259 end -= "/items".len();
260 }
261 }
262 }
263 }
264
265 false
266 }
267
268 fn is_mapped_in_any_layout(&self, schema_path: &str) -> bool {
271 self.layout_field_refs
272 .contains(schema_path.trim_start_matches('#'))
273 }
274
275 fn prune_hidden_values(&self, data: &mut Value, current_path: &str) {
277 if let Value::Object(map) = data {
278 let mut keys_to_remove = Vec::new();
280
281 for (key, value) in map.iter_mut() {
282 if key == "$params" || key == "$context" {
284 continue;
285 }
286
287 let schema_path = if current_path.is_empty() {
291 format!("/properties/{}", key)
292 } else {
293 format!("{}/properties/{}", current_path, key)
294 };
295
296 if self.is_effective_hidden(&schema_path) {
298 keys_to_remove.push(key.clone());
299 } else {
300 if value.is_object() {
302 self.prune_hidden_values(value, &schema_path);
303 }
304 }
305 }
306
307 for key in keys_to_remove {
309 map.remove(&key);
310 }
311 }
312 }
313
314 fn resolve_static_markers_in_value(&self, schema_output: &mut Value) {
320 for (static_key, array_arc) in self.static_arrays.iter() {
321 let schema_path = if static_key.starts_with("/$table") {
323 &static_key["/$table".len()..] } else {
325 static_key.as_str() };
327
328 if let Some(target_val) = schema_output.pointer_mut(schema_path) {
330 *target_val = (**array_arc).clone();
332 }
333 }
334 }
335
336 pub fn get_evaluated_schema(&mut self) -> Value {
343 time_block!("get_evaluated_schema()", {
344 let mut schema = self.evaluated_schema.clone();
345 self.resolve_static_markers_in_value(&mut schema);
346 schema
347 })
348 }
349
350 pub fn get_resolved_layout(&mut self) -> ResolvedLayoutResult {
353 time_block!("get_resolved_layout()", {
354 self.ensure_layout_resolved();
355 self.layout_state
356 .read()
357 .unwrap()
358 .cache
359 .as_ref()
360 .map(|c| (**c).clone())
361 .unwrap_or_default()
362 })
363 }
364
365 pub fn get_evaluated_schema_resolved(&mut self) -> Value {
374 time_block!("get_evaluated_schema_resolved()", {
375 let mut schema = self.get_evaluated_schema_without_params();
376 let overlays = self.get_resolved_layout();
377
378 struct ResolveEntry {
379 layout_path: String,
380 element_idx: usize,
381 overlay: indexmap::IndexMap<String, Value>,
382 }
383
384 let mut entries: Vec<ResolveEntry> = overlays
385 .iter()
386 .map(|entry| {
387 let layout_path =
388 path_utils::normalize_to_json_pointer(&entry.layout_path).into_owned();
389 ResolveEntry {
390 layout_path,
391 element_idx: entry.element_idx,
392 overlay: entry.overlay.clone(),
393 }
394 })
395 .collect();
396 drop(overlays);
397
398 entries.sort_by(|a, b| {
402 let depth_a = a.layout_path.matches('/').count();
403 let depth_b = b.layout_path.matches('/').count();
404 depth_a
405 .cmp(&depth_b)
406 .then_with(|| a.element_idx.cmp(&b.element_idx))
407 });
408
409 for entry in entries {
413 let resolved_value: Option<Value> = (|| -> Option<Value> {
416 let arr = schema.pointer(&entry.layout_path)?.as_array()?;
417 let element = arr.get(entry.element_idx)?;
418 let ref_str = element.get("$ref")?.as_str()?;
419
420 let ref_pointer = if ref_str.starts_with('#') || ref_str.starts_with('/') {
421 path_utils::normalize_to_json_pointer(ref_str).into_owned()
422 } else {
423 let schema_pointer = path_utils::dot_notation_to_schema_pointer(ref_str);
424 let normalized =
425 path_utils::normalize_to_json_pointer(&schema_pointer).into_owned();
426 if schema.pointer(&normalized).is_some() {
427 normalized
428 } else {
429 format!("/properties/{}", ref_str.replace('.', "/properties/"))
430 }
431 };
432
433 let mut resolved = schema.pointer(&ref_pointer)?.clone();
434
435 if let Value::Object(ref mut resolved_map) = resolved {
437 if let Some(Value::Object(layout_obj)) = resolved_map.remove("$layout") {
438 let mut result = layout_obj;
439 for (key, value) in resolved_map.clone().into_iter() {
440 if key != "type" || !result.contains_key("type") {
441 result.insert(key, value);
442 }
443 }
444 resolved = Value::Object(result);
445 }
446 }
447
448 Some(resolved)
449 })();
450
451 if let Some(Value::Array(arr)) = schema.pointer_mut(&entry.layout_path) {
452 if entry.element_idx < arr.len() {
453 let element = &mut arr[entry.element_idx];
454
455 if let Some(resolved) = resolved_value {
457 if let Value::Object(mut resolved_map) = resolved {
458 if let Value::Object(mut map) = element.take() {
459 map.remove("$ref");
460 for (key, value) in map {
461 resolved_map.insert(key, value);
462 }
463 }
464 *element = Value::Object(resolved_map);
465 } else {
466 *element = resolved;
467 }
468 }
469
470 if let Value::Object(ref mut map) = element {
472 for (k, v) in &entry.overlay {
473 map.insert(k.clone(), v.clone());
474 }
475 }
476 }
477 }
478 }
479
480 Self::stamp_property_metadata(&mut schema);
481 schema
482 })
483 }
484
485 fn stamp_property_metadata(schema: &mut Value) {
487 fn walk(value: &mut Value, path: &str, parent_hidden: bool) {
488 let Some(map) = value.as_object_mut() else {
489 return;
490 };
491
492 let hidden = parent_hidden
493 || map
494 .get("condition")
495 .and_then(Value::as_object)
496 .and_then(|condition| condition.get("hidden"))
497 .is_some_and(|hidden| hidden == &Value::Bool(true));
498
499 if let Some(Value::Object(properties)) = map.get_mut("properties") {
500 for (name, property) in properties {
501 let property_path = if path.is_empty() {
502 format!("properties.{}", name)
503 } else {
504 format!("{}.properties.{}", path, name)
505 };
506 if let Value::Object(property_map) = property {
507 property_map.insert(
508 "$fullpath".to_string(),
509 Value::String(property_path.clone()),
510 );
511 property_map.insert("$path".to_string(), Value::String(name.clone()));
512 property_map.insert("$parentHide".to_string(), Value::Bool(hidden));
513 }
514 walk(property, &property_path, hidden);
515 }
516 }
517
518 for (name, child) in map {
519 if name != "properties" && !name.starts_with('$') && child.is_object() {
520 let child_path = if path.is_empty() {
521 name.clone()
522 } else {
523 format!("{}.{}", path, name)
524 };
525 walk(child, &child_path, hidden);
526 }
527 }
528 }
529
530 walk(schema, "", false);
531 }
532
533 pub(crate) fn resolve_static_markers_at_path(&self, schema_prefix: &str) -> Option<Value> {
544 for (static_key, array_arc) in self.static_arrays.iter() {
546 let schema_path: &str = if static_key.starts_with("/$table") {
547 &static_key["/$table".len()..]
548 } else {
549 static_key.as_str()
550 };
551
552 if let Some(relative) = schema_prefix
553 .strip_prefix(schema_path)
554 .and_then(|relative| relative.strip_prefix('/'))
555 {
556 return array_arc.pointer(&format!("/{}", relative)).cloned();
557 }
558 }
559
560 let mut subtree = self.evaluated_schema.pointer(schema_prefix)?.clone();
561
562 let prefix_slash = format!("{}/", schema_prefix);
564
565 for (static_key, array_arc) in self.static_arrays.iter() {
566 let schema_path: &str = if static_key.starts_with("/$table") {
568 &static_key["/$table".len()..]
569 } else {
570 static_key.as_str()
571 };
572
573 let relative: &str = if schema_path == schema_prefix {
575 ""
577 } else if schema_path.starts_with(&prefix_slash) {
578 &schema_path[schema_prefix.len()..]
580 } else {
581 continue; };
583
584 if relative.is_empty() {
585 subtree = (**array_arc).clone();
586 } else if let Some(target) = subtree.pointer_mut(relative) {
587 *target = (**array_arc).clone();
588 }
589 }
590
591 Some(subtree)
592 }
593
594 pub fn get_schema_value_by_path(&self, path: &str) -> Option<Value> {
597 let pointer_path = path_utils::dot_notation_to_schema_pointer(path);
598 self.resolve_static_markers_at_path(pointer_path.trim_start_matches('#'))
599 }
600
601 pub fn get_schema_value(&mut self) -> Value {
607 self.ensure_layout_resolved();
608 let mut current_data = self.eval_data.data().clone();
610
611 if !current_data.is_object() {
613 current_data = Value::Object(serde_json::Map::new());
614 }
615
616 if let Some(obj) = current_data.as_object_mut() {
618 obj.remove("$params");
619 obj.remove("$context");
620 }
621
622 self.prune_hidden_values(&mut current_data, "");
624
625 for eval_key in self.value_evaluations.iter() {
628 let clean_key = eval_key.strip_prefix('#').unwrap_or(eval_key);
629
630 if clean_key.starts_with("/$params")
632 || (clean_key.ends_with("/value")
633 && (clean_key.contains("/rules/") || clean_key.contains("/options/")))
634 {
635 continue;
636 }
637
638 let path = clean_key.replace("/properties", "").replace("/value", "");
639
640 let schema_path = clean_key.strip_suffix("/value").unwrap_or(&clean_key);
643 if self.is_effective_hidden(schema_path) {
644 continue;
645 }
646
647 let value = match self.resolve_static_markers_at_path(clean_key) {
649 Some(v) => v,
650 None => continue,
651 };
652
653 let path_parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
655
656 if path_parts.is_empty() {
657 continue;
658 }
659
660 let mut current = &mut current_data;
662 for (i, part) in path_parts.iter().enumerate() {
663 let is_last = i == path_parts.len() - 1;
664
665 if is_last {
666 let schema_value = self.schema.pointer(clean_key);
669 let computed_value = schema_value
670 .and_then(Value::as_object)
671 .is_some_and(|value| value.contains_key("$evaluation"));
672 let disabled = self
673 .evaluated_schema
674 .pointer(schema_path)
675 .and_then(Value::as_object)
676 .and_then(|field| field.get("condition"))
677 .and_then(Value::as_object)
678 .and_then(|condition| condition.get("disabled"))
679 .is_some_and(|disabled| disabled == &Value::Bool(true));
680 let computed_disabled =
681 computed_value && (disabled || !self.is_mapped_in_any_layout(schema_path));
682 if let Some(obj) = current.as_object_mut() {
683 let should_update = computed_disabled
684 || match obj.get(*part) {
685 Some(v) => v.is_null(),
686 None => true,
687 };
688 if should_update {
689 obj.insert(
690 (*part).to_string(),
691 crate::utils::clean_float_noise(value.clone()),
692 );
693 }
694 }
695 } else {
696 if let Some(obj) = current.as_object_mut() {
698 if !obj.contains_key(*part) {
699 obj.insert((*part).to_string(), Value::Object(serde_json::Map::new()));
700 }
701
702 current = obj.get_mut(*part).unwrap();
703 } else {
704 break;
706 }
707 }
708 }
709 }
710
711 crate::utils::clean_float_noise(current_data)
712 }
713
714 pub fn get_schema_value_array(&self) -> Value {
721 self.ensure_layout_resolved();
722 let mut result = Vec::new();
723
724 for eval_key in self.value_evaluations.iter() {
725 let clean_key = eval_key.strip_prefix('#').unwrap_or(eval_key);
726
727 if clean_key.starts_with("/$params")
729 || (clean_key.ends_with("/value")
730 && (clean_key.contains("/rules/") || clean_key.contains("/options/")))
731 {
732 continue;
733 }
734
735 let schema_path = clean_key.strip_suffix("/value").unwrap_or(&clean_key);
737 if self.is_effective_hidden(schema_path) {
738 continue;
739 }
740
741 let dotted_path = clean_key
743 .replace("/properties", "")
744 .replace("/value", "")
745 .trim_start_matches('/')
746 .replace('/', ".");
747
748 if dotted_path.is_empty() {
749 continue;
750 }
751
752 let value = match self.resolve_static_markers_at_path(clean_key) {
754 Some(v) => crate::utils::clean_float_noise(v),
755 None => continue,
756 };
757
758 let mut item = serde_json::Map::new();
760 item.insert("path".to_string(), Value::String(dotted_path));
761 item.insert("value".to_string(), value);
762 result.push(Value::Object(item));
763 }
764
765 Value::Array(result)
766 }
767
768 pub fn get_schema_value_object(&self) -> Value {
775 self.ensure_layout_resolved();
776 let mut result = serde_json::Map::new();
777
778 for eval_key in self.value_evaluations.iter() {
779 let clean_key = eval_key.strip_prefix('#').unwrap_or(eval_key);
780
781 if clean_key.starts_with("/$params")
783 || (clean_key.ends_with("/value")
784 && (clean_key.contains("/rules/") || clean_key.contains("/options/")))
785 {
786 continue;
787 }
788
789 let schema_path = clean_key.strip_suffix("/value").unwrap_or(&clean_key);
791 if self.is_effective_hidden(schema_path) {
792 continue;
793 }
794
795 let dotted_path = clean_key
797 .replace("/properties", "")
798 .replace("/value", "")
799 .trim_start_matches('/')
800 .replace('/', ".");
801
802 if dotted_path.is_empty() {
803 continue;
804 }
805
806 let value = match self.resolve_static_markers_at_path(clean_key) {
808 Some(v) => crate::utils::clean_float_noise(v),
809 None => continue,
810 };
811
812 result.insert(dotted_path, value);
813 }
814
815 Value::Object(result)
816 }
817
818 pub fn get_evaluated_schema_without_params(&mut self) -> Value {
820 time_block!("get_evaluated_schema_without_params()", {
821 let mut schema = if let Value::Object(map) = &self.evaluated_schema {
822 let mut filtered = serde_json::Map::with_capacity(map.len().saturating_sub(1));
823 for (k, v) in map {
824 if k != "$params" {
825 filtered.insert(k.clone(), v.clone());
826 }
827 }
828 Value::Object(filtered)
829 } else {
830 self.evaluated_schema.clone()
831 };
832 self.resolve_static_markers_in_value(&mut schema);
833 schema
834 })
835 }
836
837 fn process_params_static_arrays(&self, params_val: &Value, with_static_array: bool) -> Value {
838 let mut params = params_val.clone();
839 if with_static_array {
840 for (static_key, array_arc) in self.static_arrays.iter() {
841 let rel_path = if let Some(path) = static_key.strip_prefix("/$params") {
842 path
843 } else if let Some(path) = static_key.strip_prefix("/$table/$params") {
844 path
845 } else {
846 continue;
847 };
848
849 if let Some(target) = params.pointer_mut(rel_path) {
850 *target = (**array_arc).clone();
851 }
852 }
853 } else {
854 Self::strip_static_array_markers(&mut params);
855 }
856 params
857 }
858
859 fn strip_static_array_markers(val: &mut Value) {
860 match val {
861 Value::Object(map) => {
862 map.retain(|_, v| {
863 if let Value::Object(child_map) = v {
864 !child_map.contains_key("$static_array")
865 } else {
866 true
867 }
868 });
869 for v in map.values_mut() {
870 Self::strip_static_array_markers(v);
871 }
872 }
873 Value::Array(arr) => {
874 arr.retain(|v| {
875 if let Value::Object(child_map) = v {
876 !child_map.contains_key("$static_array")
877 } else {
878 true
879 }
880 });
881 for v in arr.iter_mut() {
882 Self::strip_static_array_markers(v);
883 }
884 }
885 _ => {}
886 }
887 }
888
889 pub fn get_plain_params(&self) -> Option<Value> {
891 let raw_params = self.schema.get("$params")?;
892 Some(self.process_params_static_arrays(raw_params, false))
893 }
894
895 pub fn get_evaluated_params(&mut self, with_static_array: bool) -> Option<Value> {
901 let raw_params = self.evaluated_schema.get("$params")?;
902 Some(self.process_params_static_arrays(raw_params, with_static_array))
903 }
904
905 pub fn get_evaluated_schema_msgpack(&mut self) -> Result<Vec<u8>, String> {
907 let schema = self.get_evaluated_schema();
908 rmp_serde::to_vec(&schema).map_err(|e| format!("MessagePack serialization failed: {}", e))
909 }
910
911 pub fn get_evaluated_schema_resolved_msgpack(&mut self) -> Result<Vec<u8>, String> {
916 let schema = self.get_evaluated_schema_resolved();
917 rmp_serde::to_vec(&schema).map_err(|e| format!("MessagePack serialization failed: {}", e))
918 }
919
920 pub fn get_evaluated_schema_by_path(&mut self, path: &str) -> Option<Value> {
922 self.get_schema_value_by_path(path)
923 }
924
925 pub fn get_evaluated_schema_by_paths(
927 &mut self,
928 paths: &[String],
929 format: Option<ReturnFormat>,
930 ) -> Value {
931 match format.unwrap_or(ReturnFormat::Nested) {
932 ReturnFormat::Nested => {
933 let mut result = Value::Object(serde_json::Map::new());
934 for path in paths {
935 if let Some(val) = self.get_schema_value_by_path(path) {
936 Self::insert_at_path(&mut result, path, val);
938 }
939 }
940 result
941 }
942 ReturnFormat::Flat => {
943 let mut result = serde_json::Map::new();
944 for path in paths {
945 if let Some(val) = self.get_schema_value_by_path(path) {
946 result.insert(path.clone(), val);
947 }
948 }
949 Value::Object(result)
950 }
951 ReturnFormat::Array => {
952 let mut result = Vec::new();
953 for path in paths {
954 if let Some(val) = self.get_schema_value_by_path(path) {
955 result.push(val);
956 } else {
957 result.push(Value::Null);
958 }
959 }
960 Value::Array(result)
961 }
962 }
963 }
964
965 pub fn get_schema_by_path(&self, path: &str) -> Option<Value> {
967 let pointer_path = path_utils::dot_notation_to_schema_pointer(path);
968 self.schema
969 .pointer(&pointer_path.trim_start_matches('#'))
970 .cloned()
971 }
972
973 pub fn get_schema_by_paths(&self, paths: &[String], format: Option<ReturnFormat>) -> Value {
975 match format.unwrap_or(ReturnFormat::Nested) {
976 ReturnFormat::Nested => {
977 let mut result = Value::Object(serde_json::Map::new());
978 for path in paths {
979 if let Some(val) = self.get_schema_by_path(path) {
980 Self::insert_at_path(&mut result, path, val);
981 }
982 }
983 result
984 }
985 ReturnFormat::Flat => {
986 let mut result = serde_json::Map::new();
987 for path in paths {
988 if let Some(val) = self.get_schema_by_path(path) {
989 result.insert(path.clone(), val);
990 }
991 }
992 Value::Object(result)
993 }
994 ReturnFormat::Array => {
995 let mut result = Vec::new();
996 for path in paths {
997 if let Some(val) = self.get_schema_by_path(path) {
998 result.push(val);
999 } else {
1000 result.push(Value::Null);
1001 }
1002 }
1003 Value::Array(result)
1004 }
1005 }
1006 }
1007
1008 pub(crate) fn insert_at_path(root: &mut Value, path: &str, value: Value) {
1010 let parts: Vec<&str> = path.split('.').collect();
1011 let mut current = root;
1012
1013 for (i, part) in parts.iter().enumerate() {
1014 if i == parts.len() - 1 {
1015 if let Value::Object(map) = current {
1017 map.insert(part.to_string(), value);
1018 return; }
1020 } else {
1021 if !current.is_object() {
1026 *current = Value::Object(serde_json::Map::new());
1027 }
1028
1029 if let Value::Object(map) = current {
1030 if !map.contains_key(*part) {
1031 map.insert(part.to_string(), Value::Object(serde_json::Map::new()));
1032 }
1033 current = map.get_mut(*part).unwrap();
1034 }
1035 }
1036 }
1037 }
1038
1039 pub fn flatten_object(
1041 prefix: &str,
1042 value: &Value,
1043 result: &mut serde_json::Map<String, Value>,
1044 ) {
1045 match value {
1046 Value::Object(map) => {
1047 for (k, v) in map {
1048 let new_key = if prefix.is_empty() {
1049 k.clone()
1050 } else {
1051 format!("{}.{}", prefix, k)
1052 };
1053 Self::flatten_object(&new_key, v, result);
1054 }
1055 }
1056 _ => {
1057 result.insert(prefix.to_string(), value.clone());
1058 }
1059 }
1060 }
1061
1062 pub fn convert_to_format(value: Value, format: ReturnFormat) -> Value {
1063 match format {
1064 ReturnFormat::Nested => value,
1065 ReturnFormat::Flat => {
1066 let mut result = serde_json::Map::new();
1067 Self::flatten_object("", &value, &mut result);
1068 Value::Object(result)
1069 }
1070 ReturnFormat::Array => {
1071 if let Value::Object(map) = value {
1072 Value::Array(map.values().cloned().collect())
1073 } else if let Value::Array(arr) = value {
1074 Value::Array(arr)
1075 } else {
1076 Value::Array(vec![value])
1077 }
1078 }
1079 }
1080 }
1081
1082 pub fn get_field_options(&mut self, field_path: &str) -> Option<Value> {
1091 let schema_ptr = if field_path.starts_with('#') || field_path.starts_with('/') {
1093 path_utils::normalize_to_json_pointer(field_path).into_owned()
1094 } else {
1095 path_utils::dot_notation_to_schema_pointer(field_path)
1096 };
1097
1098 let options_schema_key = format!("{}/options", schema_ptr);
1100 let options_pointer =
1101 path_utils::normalize_to_json_pointer(&options_schema_key).into_owned();
1102
1103 let options_node = self.evaluated_schema.pointer(&options_pointer)?.clone();
1105
1106 if let Value::Object(ref map) = options_node {
1108 if map.contains_key("$evaluation") {
1109 let eval_key = options_schema_key.clone();
1110
1111 if let Some(logic_id) = self.evaluations.get(&eval_key).copied() {
1112 let snap = self.eval_data.snapshot_data();
1113 if let Ok(result) = self.engine.run(&logic_id, &*snap) {
1114 let cleaned = clean_float_noise_scalar(result);
1115 if let Some(node) = self.evaluated_schema.pointer_mut(&options_pointer) {
1116 *node = cleaned.clone();
1117 }
1118 return Some(cleaned);
1119 }
1120 }
1121 return None;
1123 }
1124 }
1125
1126 let url_pointer =
1128 path_utils::normalize_to_json_pointer(&format!("{}/options/url", schema_ptr))
1129 .into_owned();
1130
1131 let templates = self.options_templates.clone();
1132 for (tmpl_url_path, tmpl_str, tmpl_params_path) in templates.iter() {
1133 if *tmpl_url_path == url_pointer {
1134 if let Some(params) = self.evaluated_schema.pointer(tmpl_params_path) {
1135 let params = params.clone();
1136 if let Ok(resolved_url) = self.evaluate_template(tmpl_str, ¶ms) {
1137 if let Some(target) = self.evaluated_schema.pointer_mut(&url_pointer) {
1138 *target = Value::String(resolved_url);
1139 }
1140 return self.evaluated_schema.pointer(&options_pointer).cloned();
1141 }
1142 }
1143 break;
1144 }
1145 }
1146
1147 Some(options_node)
1149 }
1150}