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 if self
292 .evaluated_schema
293 .pointer(&format!("/properties/{}", key))
294 .is_some()
295 {
296 format!("/properties/{}", key)
297 } else if self
298 .evaluated_schema
299 .pointer(&format!("/{}", key))
300 .is_some()
301 {
302 format!("/{}", key)
303 } else {
304 format!("/properties/{}", key)
305 }
306 } else {
307 format!("{}/properties/{}", current_path, key)
308 };
309
310 if self.is_effective_hidden(&schema_path) {
311 keys_to_remove.push(key.clone());
312 } else {
313 if value.is_object() {
315 self.prune_hidden_values(value, &schema_path);
316 }
317 }
318 }
319
320 for key in keys_to_remove {
322 map.remove(&key);
323 }
324 }
325 }
326
327 fn resolve_static_markers_in_value(&self, schema_output: &mut Value) {
333 for (static_key, array_arc) in self.static_arrays.iter() {
334 let schema_path = if static_key.starts_with("/$table") {
336 &static_key["/$table".len()..] } else {
338 static_key.as_str() };
340
341 if let Some(target_val) = schema_output.pointer_mut(schema_path) {
343 *target_val = (**array_arc).clone();
345 }
346 }
347 }
348
349 pub fn get_evaluated_schema(&mut self) -> Value {
356 time_block!("get_evaluated_schema()", {
357 let mut schema = self.evaluated_schema.clone();
358 self.resolve_static_markers_in_value(&mut schema);
359 schema
360 })
361 }
362
363 pub fn get_resolved_layout(&mut self) -> ResolvedLayoutResult {
366 time_block!("get_resolved_layout()", {
367 self.ensure_layout_resolved();
368 self.layout_state
369 .read()
370 .unwrap()
371 .cache
372 .as_ref()
373 .map(|c| (**c).clone())
374 .unwrap_or_default()
375 })
376 }
377
378 pub fn get_evaluated_schema_resolved(&mut self) -> Value {
387 time_block!("get_evaluated_schema_resolved()", {
388 let mut schema = self.get_evaluated_schema_without_params();
389 let overlays = self.get_resolved_layout();
390
391 struct ResolveEntry {
392 layout_path: String,
393 element_idx: usize,
394 overlay: indexmap::IndexMap<String, Value>,
395 }
396
397 let mut entries: Vec<ResolveEntry> = overlays
398 .iter()
399 .map(|entry| {
400 let layout_path =
401 path_utils::normalize_to_json_pointer(&entry.layout_path).into_owned();
402 ResolveEntry {
403 layout_path,
404 element_idx: entry.element_idx,
405 overlay: entry.overlay.clone(),
406 }
407 })
408 .collect();
409 drop(overlays);
410
411 entries.sort_by(|a, b| {
415 let depth_a = a.layout_path.matches('/').count();
416 let depth_b = b.layout_path.matches('/').count();
417 depth_a
418 .cmp(&depth_b)
419 .then_with(|| a.element_idx.cmp(&b.element_idx))
420 });
421
422 for entry in entries {
426 let resolved_value: Option<Value> = (|| -> Option<Value> {
429 let arr = schema.pointer(&entry.layout_path)?.as_array()?;
430 let element = arr.get(entry.element_idx)?;
431 let ref_str = element.get("$ref")?.as_str()?;
432
433 let ref_pointer = if ref_str.starts_with('#') || ref_str.starts_with('/') {
434 path_utils::normalize_to_json_pointer(ref_str).into_owned()
435 } else {
436 let schema_pointer = path_utils::dot_notation_to_schema_pointer(ref_str);
437 let normalized =
438 path_utils::normalize_to_json_pointer(&schema_pointer).into_owned();
439 if schema.pointer(&normalized).is_some() {
440 normalized
441 } else {
442 format!("/properties/{}", ref_str.replace('.', "/properties/"))
443 }
444 };
445
446 let mut resolved = schema.pointer(&ref_pointer)?.clone();
447
448 if let Value::Object(ref mut resolved_map) = resolved {
450 if let Some(Value::Object(layout_obj)) = resolved_map.remove("$layout") {
451 let mut result = layout_obj;
452 for (key, value) in resolved_map.clone().into_iter() {
453 if key != "type" || !result.contains_key("type") {
454 result.insert(key, value);
455 }
456 }
457 resolved = Value::Object(result);
458 }
459 }
460
461 Some(resolved)
462 })();
463
464 if let Some(Value::Array(arr)) = schema.pointer_mut(&entry.layout_path) {
465 if entry.element_idx < arr.len() {
466 let element = &mut arr[entry.element_idx];
467
468 if let Some(resolved) = resolved_value {
470 if let Value::Object(mut resolved_map) = resolved {
471 if let Value::Object(mut map) = element.take() {
472 map.remove("$ref");
473 for (key, value) in map {
474 resolved_map.insert(key, value);
475 }
476 }
477 *element = Value::Object(resolved_map);
478 } else {
479 *element = resolved;
480 }
481 }
482
483 if let Value::Object(ref mut map) = element {
485 for (k, v) in &entry.overlay {
486 map.insert(k.clone(), v.clone());
487 }
488 }
489 }
490 }
491 }
492
493 Self::stamp_property_metadata(&mut schema);
494 schema
495 })
496 }
497
498 fn stamp_property_metadata(schema: &mut Value) {
500 fn walk(value: &mut Value, path: &str, parent_hidden: bool) {
501 let Some(map) = value.as_object_mut() else {
502 return;
503 };
504
505 let hidden = parent_hidden
506 || map
507 .get("condition")
508 .and_then(Value::as_object)
509 .and_then(|condition| condition.get("hidden"))
510 .is_some_and(|hidden| hidden == &Value::Bool(true));
511
512 if let Some(Value::Object(properties)) = map.get_mut("properties") {
513 for (name, property) in properties {
514 let property_path = if path.is_empty() {
515 format!("properties.{}", name)
516 } else {
517 format!("{}.properties.{}", path, name)
518 };
519 if let Value::Object(property_map) = property {
520 property_map.insert(
521 "$fullpath".to_string(),
522 Value::String(property_path.clone()),
523 );
524 property_map.insert("$path".to_string(), Value::String(name.clone()));
525 property_map.insert("$parentHide".to_string(), Value::Bool(hidden));
526 }
527 walk(property, &property_path, hidden);
528 }
529 }
530
531 for (name, child) in map {
532 if name != "properties" && !name.starts_with('$') && child.is_object() {
533 let child_path = if path.is_empty() {
534 name.clone()
535 } else {
536 format!("{}.{}", path, name)
537 };
538 walk(child, &child_path, hidden);
539 }
540 }
541 }
542
543 walk(schema, "", false);
544 }
545
546 pub(crate) fn resolve_static_markers_at_path(&self, schema_prefix: &str) -> Option<Value> {
557 for (static_key, array_arc) in self.static_arrays.iter() {
559 let schema_path: &str = if static_key.starts_with("/$table") {
560 &static_key["/$table".len()..]
561 } else {
562 static_key.as_str()
563 };
564
565 if let Some(relative) = schema_prefix
566 .strip_prefix(schema_path)
567 .and_then(|relative| relative.strip_prefix('/'))
568 {
569 return array_arc.pointer(&format!("/{}", relative)).cloned();
570 }
571 }
572
573 let mut subtree = self.evaluated_schema.pointer(schema_prefix)?.clone();
574
575 let prefix_slash = format!("{}/", schema_prefix);
577
578 for (static_key, array_arc) in self.static_arrays.iter() {
579 let schema_path: &str = if static_key.starts_with("/$table") {
581 &static_key["/$table".len()..]
582 } else {
583 static_key.as_str()
584 };
585
586 let relative: &str = if schema_path == schema_prefix {
588 ""
590 } else if schema_path.starts_with(&prefix_slash) {
591 &schema_path[schema_prefix.len()..]
593 } else {
594 continue; };
596
597 if relative.is_empty() {
598 subtree = (**array_arc).clone();
599 } else if let Some(target) = subtree.pointer_mut(relative) {
600 *target = (**array_arc).clone();
601 }
602 }
603
604 Some(subtree)
605 }
606
607 pub fn get_schema_value_by_path(&self, path: &str) -> Option<Value> {
610 let pointer_path = path_utils::dot_notation_to_schema_pointer(path);
611 self.resolve_static_markers_at_path(pointer_path.trim_start_matches('#'))
612 }
613
614 pub fn get_schema_value(&mut self, include_subforms: Option<bool>) -> Value {
620 self.ensure_layout_resolved();
621 let mut current_data = self.eval_data.data().clone();
623
624 if !current_data.is_object() {
626 current_data = Value::Object(serde_json::Map::new());
627 }
628
629 if let Some(obj) = current_data.as_object_mut() {
631 obj.remove("$params");
632 obj.remove("$context");
633 }
634
635 self.prune_hidden_values(&mut current_data, "");
637
638 for eval_key in self.value_evaluations.iter() {
641 let clean_key = eval_key.strip_prefix('#').unwrap_or(eval_key);
642
643 if clean_key.starts_with("/$params")
645 || (clean_key.ends_with("/value")
646 && (clean_key.contains("/rules/") || clean_key.contains("/options/")))
647 {
648 continue;
649 }
650
651 let path = clean_key.replace("/properties", "").replace("/value", "");
652
653 let schema_path = clean_key.strip_suffix("/value").unwrap_or(&clean_key);
656 if self.is_effective_hidden(schema_path) {
657 continue;
658 }
659
660 let value = match self.resolve_static_markers_at_path(clean_key) {
662 Some(v) => v,
663 None => continue,
664 };
665
666 let path_parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
668
669 if path_parts.is_empty() {
670 continue;
671 }
672
673 let mut current = &mut current_data;
675 for (i, part) in path_parts.iter().enumerate() {
676 let is_last = i == path_parts.len() - 1;
677
678 if is_last {
679 let schema_value = self.schema.pointer(clean_key);
682 let computed_value = schema_value
683 .and_then(Value::as_object)
684 .is_some_and(|value| value.contains_key("$evaluation"));
685 let disabled = self
686 .evaluated_schema
687 .pointer(schema_path)
688 .and_then(Value::as_object)
689 .and_then(|field| field.get("condition"))
690 .and_then(Value::as_object)
691 .and_then(|condition| condition.get("disabled"))
692 .is_some_and(|disabled| disabled == &Value::Bool(true));
693 let computed_disabled =
694 computed_value && (disabled || !self.is_mapped_in_any_layout(schema_path));
695 if let Some(obj) = current.as_object_mut() {
696 let should_update = computed_disabled
697 || match obj.get(*part) {
698 Some(v) => v.is_null(),
699 None => true,
700 };
701 if should_update {
702 obj.insert(
703 (*part).to_string(),
704 crate::utils::clean_float_noise(value.clone()),
705 );
706 }
707 }
708 } else {
709 if let Some(obj) = current.as_object_mut() {
711 if !obj.contains_key(*part) {
712 obj.insert((*part).to_string(), Value::Object(serde_json::Map::new()));
713 }
714
715 current = obj.get_mut(*part).unwrap();
716 } else {
717 break;
719 }
720 }
721 }
722 }
723
724 if include_subforms.unwrap_or(false) {
725 let subform_keys: Vec<String> = self.subforms.keys().cloned().collect();
726
727 for subform_path in &subform_keys {
729 if let Some(subform) = self.subforms.get_mut(subform_path) {
730 if let Some(params) = self.evaluated_schema.pointer("/$params") {
731 if let Some(sub_params) = subform.evaluated_schema.pointer_mut("/$params") {
732 *sub_params = params.clone();
733 }
734 }
735 subform.static_arrays = std::sync::Arc::clone(&self.static_arrays);
736 subform
737 .engine
738 .set_static_arrays(std::sync::Arc::clone(&subform.static_arrays));
739 }
740 }
741
742 for subform_path in subform_keys {
743 let data_ptr = path_utils::schema_path_to_data_pointer(&subform_path);
744
745 let schema_pointer = if subform_path.starts_with("#/") {
746 &subform_path[1..]
747 } else if subform_path.starts_with('#') {
748 &subform_path[1..]
749 } else {
750 &subform_path
751 };
752
753 let original_field_key = subform_path
754 .split('/')
755 .filter(|seg| !seg.is_empty() && *seg != "properties")
756 .last()
757 .unwrap_or(&subform_path)
758 .to_string();
759
760 let root_key = path_utils::get_value_by_pointer(&self.schema, schema_pointer)
761 .and_then(|node| node.get("itemsRootKey"))
762 .and_then(|v| v.as_str())
763 .unwrap_or(&original_field_key)
764 .to_string();
765
766 let item_count = current_data
767 .pointer(&data_ptr)
768 .and_then(Value::as_array)
769 .map(|a| a.len())
770 .unwrap_or(0);
771
772 if item_count > 0 {
773 let full_data = self.eval_data.snapshot_data_clone();
774 let context_value = self
775 .eval_data
776 .data()
777 .get("$context")
778 .cloned()
779 .unwrap_or_else(|| Value::Object(serde_json::Map::new()));
780
781 let existing_items = current_data
782 .pointer(&data_ptr)
783 .and_then(Value::as_array)
784 .cloned()
785 .unwrap_or_default();
786
787 let mut new_items = Vec::with_capacity(item_count);
788
789 for (idx, raw_item) in existing_items.into_iter().enumerate() {
790 let evaluated_item_res = self.with_item_cache_swap(
791 &subform_path,
792 idx,
793 full_data.clone(),
794 context_value.clone(),
795 false,
796 |sf| {
797 sf.evaluate_internal_pre_diffed(None, None)?;
798 if sf.apply_visible_static_defaults_with_dependents(None)? {
799 sf.evaluate_internal_pre_diffed(None, None)?;
800 }
801 Ok(sf.get_schema_value(Some(true)))
802 },
803 );
804
805 match evaluated_item_res {
806 Ok(mut val) => {
807 let item_val = val
808 .as_object_mut()
809 .and_then(|obj| obj.remove(&root_key))
810 .unwrap_or(raw_item);
811 new_items.push(item_val);
812 }
813 Err(_) => {
814 new_items.push(raw_item);
815 }
816 }
817 }
818
819 if let Some(target) = current_data.pointer_mut(&data_ptr) {
820 *target = Value::Array(new_items);
821 }
822 }
823 }
824 }
825
826 crate::utils::clean_float_noise(current_data)
827 }
828
829 pub fn get_schema_value_array(&self) -> Value {
836 self.ensure_layout_resolved();
837 let mut result = Vec::new();
838
839 for eval_key in self.value_evaluations.iter() {
840 let clean_key = eval_key.strip_prefix('#').unwrap_or(eval_key);
841
842 if clean_key.starts_with("/$params")
844 || (clean_key.ends_with("/value")
845 && (clean_key.contains("/rules/") || clean_key.contains("/options/")))
846 {
847 continue;
848 }
849
850 let schema_path = clean_key.strip_suffix("/value").unwrap_or(&clean_key);
852 if self.is_effective_hidden(schema_path) {
853 continue;
854 }
855
856 let dotted_path = clean_key
858 .replace("/properties", "")
859 .replace("/value", "")
860 .trim_start_matches('/')
861 .replace('/', ".");
862
863 if dotted_path.is_empty() {
864 continue;
865 }
866
867 let value = match self.resolve_static_markers_at_path(clean_key) {
869 Some(v) => crate::utils::clean_float_noise(v),
870 None => continue,
871 };
872
873 let mut item = serde_json::Map::new();
875 item.insert("path".to_string(), Value::String(dotted_path));
876 item.insert("value".to_string(), value);
877 result.push(Value::Object(item));
878 }
879
880 Value::Array(result)
881 }
882
883 pub fn get_schema_value_object(&self) -> Value {
890 self.ensure_layout_resolved();
891 let mut result = serde_json::Map::new();
892
893 for eval_key in self.value_evaluations.iter() {
894 let clean_key = eval_key.strip_prefix('#').unwrap_or(eval_key);
895
896 if clean_key.starts_with("/$params")
898 || (clean_key.ends_with("/value")
899 && (clean_key.contains("/rules/") || clean_key.contains("/options/")))
900 {
901 continue;
902 }
903
904 let schema_path = clean_key.strip_suffix("/value").unwrap_or(&clean_key);
906 if self.is_effective_hidden(schema_path) {
907 continue;
908 }
909
910 let dotted_path = clean_key
912 .replace("/properties", "")
913 .replace("/value", "")
914 .trim_start_matches('/')
915 .replace('/', ".");
916
917 if dotted_path.is_empty() {
918 continue;
919 }
920
921 let value = match self.resolve_static_markers_at_path(clean_key) {
923 Some(v) => crate::utils::clean_float_noise(v),
924 None => continue,
925 };
926
927 result.insert(dotted_path, value);
928 }
929
930 Value::Object(result)
931 }
932
933 pub fn get_evaluated_schema_without_params(&mut self) -> Value {
935 time_block!("get_evaluated_schema_without_params()", {
936 let mut schema = if let Value::Object(map) = &self.evaluated_schema {
937 let mut filtered = serde_json::Map::with_capacity(map.len().saturating_sub(1));
938 for (k, v) in map {
939 if k != "$params" {
940 filtered.insert(k.clone(), v.clone());
941 }
942 }
943 Value::Object(filtered)
944 } else {
945 self.evaluated_schema.clone()
946 };
947 self.resolve_static_markers_in_value(&mut schema);
948 schema
949 })
950 }
951
952 fn process_params_static_arrays(&self, params_val: &Value, with_static_array: bool) -> Value {
953 let mut params = params_val.clone();
954 if with_static_array {
955 for (static_key, array_arc) in self.static_arrays.iter() {
956 let rel_path = if let Some(path) = static_key.strip_prefix("/$params") {
957 path
958 } else if let Some(path) = static_key.strip_prefix("/$table/$params") {
959 path
960 } else {
961 continue;
962 };
963
964 if let Some(target) = params.pointer_mut(rel_path) {
965 *target = (**array_arc).clone();
966 }
967 }
968 } else {
969 Self::strip_static_array_markers(&mut params);
970 }
971 params
972 }
973
974 fn strip_static_array_markers(val: &mut Value) {
975 match val {
976 Value::Object(map) => {
977 map.retain(|_, v| {
978 if let Value::Object(child_map) = v {
979 !child_map.contains_key("$static_array")
980 } else {
981 true
982 }
983 });
984 for v in map.values_mut() {
985 Self::strip_static_array_markers(v);
986 }
987 }
988 Value::Array(arr) => {
989 arr.retain(|v| {
990 if let Value::Object(child_map) = v {
991 !child_map.contains_key("$static_array")
992 } else {
993 true
994 }
995 });
996 for v in arr.iter_mut() {
997 Self::strip_static_array_markers(v);
998 }
999 }
1000 _ => {}
1001 }
1002 }
1003
1004 pub fn get_plain_params(&self) -> Option<Value> {
1006 let raw_params = self.schema.get("$params")?;
1007 Some(self.process_params_static_arrays(raw_params, false))
1008 }
1009
1010 pub fn get_evaluated_params(&mut self, with_static_array: bool) -> Option<Value> {
1016 let raw_params = self.evaluated_schema.get("$params")?;
1017 Some(self.process_params_static_arrays(raw_params, with_static_array))
1018 }
1019
1020 pub fn get_evaluated_schema_msgpack(&mut self) -> Result<Vec<u8>, String> {
1022 let schema = self.get_evaluated_schema();
1023 rmp_serde::to_vec(&schema).map_err(|e| format!("MessagePack serialization failed: {}", e))
1024 }
1025
1026 pub fn get_evaluated_schema_resolved_msgpack(&mut self) -> Result<Vec<u8>, String> {
1031 let schema = self.get_evaluated_schema_resolved();
1032 rmp_serde::to_vec(&schema).map_err(|e| format!("MessagePack serialization failed: {}", e))
1033 }
1034
1035 pub fn get_evaluated_schema_by_path(&mut self, path: &str) -> Option<Value> {
1037 self.get_schema_value_by_path(path)
1038 }
1039
1040 pub fn get_evaluated_schema_by_paths(
1042 &mut self,
1043 paths: &[String],
1044 format: Option<ReturnFormat>,
1045 ) -> Value {
1046 match format.unwrap_or(ReturnFormat::Nested) {
1047 ReturnFormat::Nested => {
1048 let mut result = Value::Object(serde_json::Map::new());
1049 for path in paths {
1050 if let Some(val) = self.get_schema_value_by_path(path) {
1051 Self::insert_at_path(&mut result, path, val);
1053 }
1054 }
1055 result
1056 }
1057 ReturnFormat::Flat => {
1058 let mut result = serde_json::Map::new();
1059 for path in paths {
1060 if let Some(val) = self.get_schema_value_by_path(path) {
1061 result.insert(path.clone(), val);
1062 }
1063 }
1064 Value::Object(result)
1065 }
1066 ReturnFormat::Array => {
1067 let mut result = Vec::new();
1068 for path in paths {
1069 if let Some(val) = self.get_schema_value_by_path(path) {
1070 result.push(val);
1071 } else {
1072 result.push(Value::Null);
1073 }
1074 }
1075 Value::Array(result)
1076 }
1077 }
1078 }
1079
1080 pub fn get_schema_by_path(&self, path: &str) -> Option<Value> {
1082 let pointer_path = path_utils::dot_notation_to_schema_pointer(path);
1083 self.schema
1084 .pointer(&pointer_path.trim_start_matches('#'))
1085 .cloned()
1086 }
1087
1088 pub fn get_schema_by_paths(&self, paths: &[String], format: Option<ReturnFormat>) -> Value {
1090 match format.unwrap_or(ReturnFormat::Nested) {
1091 ReturnFormat::Nested => {
1092 let mut result = Value::Object(serde_json::Map::new());
1093 for path in paths {
1094 if let Some(val) = self.get_schema_by_path(path) {
1095 Self::insert_at_path(&mut result, path, val);
1096 }
1097 }
1098 result
1099 }
1100 ReturnFormat::Flat => {
1101 let mut result = serde_json::Map::new();
1102 for path in paths {
1103 if let Some(val) = self.get_schema_by_path(path) {
1104 result.insert(path.clone(), val);
1105 }
1106 }
1107 Value::Object(result)
1108 }
1109 ReturnFormat::Array => {
1110 let mut result = Vec::new();
1111 for path in paths {
1112 if let Some(val) = self.get_schema_by_path(path) {
1113 result.push(val);
1114 } else {
1115 result.push(Value::Null);
1116 }
1117 }
1118 Value::Array(result)
1119 }
1120 }
1121 }
1122
1123 pub(crate) fn insert_at_path(root: &mut Value, path: &str, value: Value) {
1125 let parts: Vec<&str> = path.split('.').collect();
1126 let mut current = root;
1127
1128 for (i, part) in parts.iter().enumerate() {
1129 if i == parts.len() - 1 {
1130 if let Value::Object(map) = current {
1132 map.insert(part.to_string(), value);
1133 return; }
1135 } else {
1136 if !current.is_object() {
1141 *current = Value::Object(serde_json::Map::new());
1142 }
1143
1144 if let Value::Object(map) = current {
1145 if !map.contains_key(*part) {
1146 map.insert(part.to_string(), Value::Object(serde_json::Map::new()));
1147 }
1148 current = map.get_mut(*part).unwrap();
1149 }
1150 }
1151 }
1152 }
1153
1154 pub fn flatten_object(
1156 prefix: &str,
1157 value: &Value,
1158 result: &mut serde_json::Map<String, Value>,
1159 ) {
1160 match value {
1161 Value::Object(map) => {
1162 for (k, v) in map {
1163 let new_key = if prefix.is_empty() {
1164 k.clone()
1165 } else {
1166 format!("{}.{}", prefix, k)
1167 };
1168 Self::flatten_object(&new_key, v, result);
1169 }
1170 }
1171 _ => {
1172 result.insert(prefix.to_string(), value.clone());
1173 }
1174 }
1175 }
1176
1177 pub fn convert_to_format(value: Value, format: ReturnFormat) -> Value {
1178 match format {
1179 ReturnFormat::Nested => value,
1180 ReturnFormat::Flat => {
1181 let mut result = serde_json::Map::new();
1182 Self::flatten_object("", &value, &mut result);
1183 Value::Object(result)
1184 }
1185 ReturnFormat::Array => {
1186 if let Value::Object(map) = value {
1187 Value::Array(map.values().cloned().collect())
1188 } else if let Value::Array(arr) = value {
1189 Value::Array(arr)
1190 } else {
1191 Value::Array(vec![value])
1192 }
1193 }
1194 }
1195 }
1196
1197 pub fn get_field_options(&mut self, field_path: &str) -> Option<Value> {
1206 let schema_ptr = if field_path.starts_with('#') || field_path.starts_with('/') {
1208 path_utils::normalize_to_json_pointer(field_path).into_owned()
1209 } else {
1210 path_utils::dot_notation_to_schema_pointer(field_path)
1211 };
1212
1213 let options_schema_key = format!("{}/options", schema_ptr);
1215 let options_pointer =
1216 path_utils::normalize_to_json_pointer(&options_schema_key).into_owned();
1217
1218 let options_node = self.evaluated_schema.pointer(&options_pointer)?.clone();
1220
1221 if let Value::Object(ref map) = options_node {
1223 if map.contains_key("$evaluation") {
1224 let eval_key = options_schema_key.clone();
1225
1226 if let Some(logic_id) = self.evaluations.get(&eval_key).copied() {
1227 let snap = self.eval_data.snapshot_data();
1228 if let Ok(result) = self.engine.run(&logic_id, &*snap) {
1229 let cleaned = clean_float_noise_scalar(result);
1230 if let Some(node) = self.evaluated_schema.pointer_mut(&options_pointer) {
1231 *node = cleaned.clone();
1232 }
1233 return Some(cleaned);
1234 }
1235 }
1236 return None;
1238 }
1239 }
1240
1241 let url_pointer =
1243 path_utils::normalize_to_json_pointer(&format!("{}/options/url", schema_ptr))
1244 .into_owned();
1245
1246 let templates = self.options_templates.clone();
1247 for (tmpl_url_path, tmpl_str, tmpl_params_path) in templates.iter() {
1248 if *tmpl_url_path == url_pointer {
1249 if let Some(params) = self.evaluated_schema.pointer(tmpl_params_path) {
1250 let params = params.clone();
1251 if let Ok(resolved_url) = self.evaluate_template(tmpl_str, ¶ms) {
1252 if let Some(target) = self.evaluated_schema.pointer_mut(&url_pointer) {
1253 *target = Value::String(resolved_url);
1254 }
1255 return self.evaluated_schema.pointer(&options_pointer).cloned();
1256 }
1257 }
1258 break;
1259 }
1260 }
1261
1262 Some(options_node)
1264 }
1265}