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 let mut schema = self.get_evaluated_schema();
821 if let Value::Object(ref mut map) = schema {
822 map.remove("$params");
823 }
824 schema
825 }
826
827 pub fn get_evaluated_schema_msgpack(&mut self) -> Result<Vec<u8>, String> {
829 let schema = self.get_evaluated_schema();
830 rmp_serde::to_vec(&schema).map_err(|e| format!("MessagePack serialization failed: {}", e))
831 }
832
833 pub fn get_evaluated_schema_resolved_msgpack(&mut self) -> Result<Vec<u8>, String> {
838 let schema = self.get_evaluated_schema_resolved();
839 rmp_serde::to_vec(&schema).map_err(|e| format!("MessagePack serialization failed: {}", e))
840 }
841
842 pub fn get_evaluated_schema_by_path(&mut self, path: &str) -> Option<Value> {
844 self.get_schema_value_by_path(path)
845 }
846
847 pub fn get_evaluated_schema_by_paths(
849 &mut self,
850 paths: &[String],
851 format: Option<ReturnFormat>,
852 ) -> Value {
853 match format.unwrap_or(ReturnFormat::Nested) {
854 ReturnFormat::Nested => {
855 let mut result = Value::Object(serde_json::Map::new());
856 for path in paths {
857 if let Some(val) = self.get_schema_value_by_path(path) {
858 Self::insert_at_path(&mut result, path, val);
860 }
861 }
862 result
863 }
864 ReturnFormat::Flat => {
865 let mut result = serde_json::Map::new();
866 for path in paths {
867 if let Some(val) = self.get_schema_value_by_path(path) {
868 result.insert(path.clone(), val);
869 }
870 }
871 Value::Object(result)
872 }
873 ReturnFormat::Array => {
874 let mut result = Vec::new();
875 for path in paths {
876 if let Some(val) = self.get_schema_value_by_path(path) {
877 result.push(val);
878 } else {
879 result.push(Value::Null);
880 }
881 }
882 Value::Array(result)
883 }
884 }
885 }
886
887 pub fn get_schema_by_path(&self, path: &str) -> Option<Value> {
889 let pointer_path = path_utils::dot_notation_to_schema_pointer(path);
890 self.schema
891 .pointer(&pointer_path.trim_start_matches('#'))
892 .cloned()
893 }
894
895 pub fn get_schema_by_paths(&self, paths: &[String], format: Option<ReturnFormat>) -> Value {
897 match format.unwrap_or(ReturnFormat::Nested) {
898 ReturnFormat::Nested => {
899 let mut result = Value::Object(serde_json::Map::new());
900 for path in paths {
901 if let Some(val) = self.get_schema_by_path(path) {
902 Self::insert_at_path(&mut result, path, val);
903 }
904 }
905 result
906 }
907 ReturnFormat::Flat => {
908 let mut result = serde_json::Map::new();
909 for path in paths {
910 if let Some(val) = self.get_schema_by_path(path) {
911 result.insert(path.clone(), val);
912 }
913 }
914 Value::Object(result)
915 }
916 ReturnFormat::Array => {
917 let mut result = Vec::new();
918 for path in paths {
919 if let Some(val) = self.get_schema_by_path(path) {
920 result.push(val);
921 } else {
922 result.push(Value::Null);
923 }
924 }
925 Value::Array(result)
926 }
927 }
928 }
929
930 pub(crate) fn insert_at_path(root: &mut Value, path: &str, value: Value) {
932 let parts: Vec<&str> = path.split('.').collect();
933 let mut current = root;
934
935 for (i, part) in parts.iter().enumerate() {
936 if i == parts.len() - 1 {
937 if let Value::Object(map) = current {
939 map.insert(part.to_string(), value);
940 return; }
942 } else {
943 if !current.is_object() {
948 *current = Value::Object(serde_json::Map::new());
949 }
950
951 if let Value::Object(map) = current {
952 if !map.contains_key(*part) {
953 map.insert(part.to_string(), Value::Object(serde_json::Map::new()));
954 }
955 current = map.get_mut(*part).unwrap();
956 }
957 }
958 }
959 }
960
961 pub fn flatten_object(
963 prefix: &str,
964 value: &Value,
965 result: &mut serde_json::Map<String, Value>,
966 ) {
967 match value {
968 Value::Object(map) => {
969 for (k, v) in map {
970 let new_key = if prefix.is_empty() {
971 k.clone()
972 } else {
973 format!("{}.{}", prefix, k)
974 };
975 Self::flatten_object(&new_key, v, result);
976 }
977 }
978 _ => {
979 result.insert(prefix.to_string(), value.clone());
980 }
981 }
982 }
983
984 pub fn convert_to_format(value: Value, format: ReturnFormat) -> Value {
985 match format {
986 ReturnFormat::Nested => value,
987 ReturnFormat::Flat => {
988 let mut result = serde_json::Map::new();
989 Self::flatten_object("", &value, &mut result);
990 Value::Object(result)
991 }
992 ReturnFormat::Array => {
993 if let Value::Object(map) = value {
994 Value::Array(map.values().cloned().collect())
995 } else if let Value::Array(arr) = value {
996 Value::Array(arr)
997 } else {
998 Value::Array(vec![value])
999 }
1000 }
1001 }
1002 }
1003
1004 pub fn get_field_options(&mut self, field_path: &str) -> Option<Value> {
1013 let schema_ptr = if field_path.starts_with('#') || field_path.starts_with('/') {
1015 path_utils::normalize_to_json_pointer(field_path).into_owned()
1016 } else {
1017 path_utils::dot_notation_to_schema_pointer(field_path)
1018 };
1019
1020 let options_schema_key = format!("{}/options", schema_ptr);
1022 let options_pointer =
1023 path_utils::normalize_to_json_pointer(&options_schema_key).into_owned();
1024
1025 let options_node = self.evaluated_schema.pointer(&options_pointer)?.clone();
1027
1028 if let Value::Object(ref map) = options_node {
1030 if map.contains_key("$evaluation") {
1031 let eval_key = options_schema_key.clone();
1032
1033 if let Some(logic_id) = self.evaluations.get(&eval_key).copied() {
1034 let snap = self.eval_data.snapshot_data();
1035 if let Ok(result) = self.engine.run(&logic_id, &*snap) {
1036 let cleaned = clean_float_noise_scalar(result);
1037 if let Some(node) = self.evaluated_schema.pointer_mut(&options_pointer) {
1038 *node = cleaned.clone();
1039 }
1040 return Some(cleaned);
1041 }
1042 }
1043 return None;
1045 }
1046 }
1047
1048 let url_pointer =
1050 path_utils::normalize_to_json_pointer(&format!("{}/options/url", schema_ptr))
1051 .into_owned();
1052
1053 let templates = self.options_templates.clone();
1054 for (tmpl_url_path, tmpl_str, tmpl_params_path) in templates.iter() {
1055 if *tmpl_url_path == url_pointer {
1056 if let Some(params) = self.evaluated_schema.pointer(tmpl_params_path) {
1057 let params = params.clone();
1058 if let Ok(resolved_url) = self.evaluate_template(tmpl_str, ¶ms) {
1059 if let Some(target) = self.evaluated_schema.pointer_mut(&url_pointer) {
1060 *target = Value::String(resolved_url);
1061 }
1062 return self.evaluated_schema.pointer(&options_pointer).cloned();
1063 }
1064 }
1065 break;
1066 }
1067 }
1068
1069 Some(options_node)
1071 }
1072}