1use super::JSONEval;
2use crate::jsoneval::path_utils;
3use crate::jsoneval::types::{LayoutOverlayEntry, ResolvedLayoutResult};
4use crate::time_block;
5
6use indexmap::IndexMap;
7use serde_json::Value;
8
9impl JSONEval {
10 pub fn resolve_layout(&mut self, evaluate: bool) -> Result<ResolvedLayoutResult, String> {
19 if evaluate {
20 let data_str = serde_json::to_string(&self.data)
21 .map_err(|e| format!("Failed to serialize data: {}", e))?;
22 self.evaluate(&data_str, None, None, None)?;
23 }
24
25 Ok(self.resolve_layout_internal())
26 }
27
28 fn resolve_layout_internal(&mut self) -> ResolvedLayoutResult {
29 time_block!(" resolve_layout_internal()", {
30 let layout_paths = self.layout_paths.clone();
31 let mut all_entries = ResolvedLayoutResult::new();
32
33 self.layout_hidden_refs.clear();
36 self.layout_visible_refs.clear();
37 self.layout_condition_hidden_refs.clear();
38 let attached_layout_refs = Self::collect_layout_ref_targets(&self.schema);
42 time_block!(" resolve_layout_elements", {
43 for layout_path in layout_paths.iter().filter(|path| {
44 let owner = Self::layout_owner_pointer(path);
45 owner.is_empty() || !attached_layout_refs.contains(&owner)
46 }) {
47 let resolved_tree = self.resolve_elements_tree(layout_path);
48 let entries = Self::tree_to_overlays(
49 &resolved_tree,
50 layout_path,
51 false,
52 false,
53 false,
54 &mut self.layout_hidden_refs,
55 &mut self.layout_visible_refs,
56 &mut self.layout_condition_hidden_refs,
57 );
58 all_entries.extend(entries);
59 }
60 });
61
62 for visible_ref in &self.layout_visible_refs {
65 self.layout_hidden_refs.shift_remove(visible_ref);
66 self.layout_condition_hidden_refs.shift_remove(visible_ref);
67 }
68
69 all_entries
70 })
71 }
72
73 fn layout_owner_pointer(layout_path: &str) -> String {
77 let owner = layout_path
78 .trim_end_matches("/$layout/elements")
79 .trim_start_matches('#');
80 owner.to_string()
81 }
82
83 fn collect_layout_ref_targets(schema: &Value) -> indexmap::IndexSet<String> {
86 fn collect_elements(elements: &Value, refs: &mut indexmap::IndexSet<String>) {
87 let Some(elements) = elements.as_array() else {
88 return;
89 };
90 for element in elements {
91 let Some(map) = element.as_object() else {
92 continue;
93 };
94 if let Some(reference) = map.get("$ref").and_then(Value::as_str) {
95 let pointer = path_utils::normalize_to_json_pointer(
96 &path_utils::dot_notation_to_schema_pointer(reference),
97 )
98 .trim_start_matches('#')
99 .to_string();
100 refs.insert(pointer);
101 }
102 if let Some(children) = map.get("elements") {
103 collect_elements(children, refs);
104 }
105 }
106 }
107
108 fn walk(value: &Value, refs: &mut indexmap::IndexSet<String>) {
109 let Some(map) = value.as_object() else {
110 return;
111 };
112 if let Some(elements) = map
113 .get("$layout")
114 .and_then(Value::as_object)
115 .and_then(|layout| layout.get("elements"))
116 {
117 collect_elements(elements, refs);
118 }
119 for child in map.values() {
120 walk(child, refs);
121 }
122 }
123
124 let mut refs = indexmap::IndexSet::new();
125 walk(schema, &mut refs);
126 refs
127 }
128
129 fn resolve_elements_tree(&self, layout_elements_path: &str) -> Vec<(Value, String)> {
132 let normalized_path = path_utils::normalize_to_json_pointer(layout_elements_path);
133
134 let elements = if let Some(Value::Array(arr)) = self.schema.pointer(&normalized_path) {
135 arr.clone()
136 } else {
137 return Vec::new();
138 };
139
140 let mut result = Vec::with_capacity(elements.len());
141 for element in elements.into_iter() {
142 let (resolved, schema_ref) = self.resolve_element_ref_recursive(element, "");
143 result.push((resolved, schema_ref));
144 }
145 result
146 }
147
148 fn resolve_element_ref_recursive(
154 &self,
155 element: Value,
156 _path_context: &str,
157 ) -> (Value, String) {
158 let (mut resolved, ref_path) = self.resolve_element_ref(element);
160
161 if let Value::Object(ref mut map) = resolved {
163 if let Some(Value::Array(elements)) = map.get("elements").cloned() {
164 let mut resolved_nested = Vec::with_capacity(elements.len());
165 for nested_element in elements.into_iter() {
166 let (nested_resolved, _) =
167 self.resolve_element_ref_recursive(nested_element, "");
168 resolved_nested.push(nested_resolved);
169 }
170 map.insert("elements".to_string(), Value::Array(resolved_nested));
171 }
172 }
173
174 (resolved, ref_path)
175 }
176
177 fn resolve_element_ref(&self, element: Value) -> (Value, String) {
180 match element {
181 Value::Object(mut map) => {
182 let has_ref = map.get("$ref").is_some();
183 let ref_path = if has_ref {
184 map.get("$ref")
185 .and_then(Value::as_str)
186 .unwrap_or("")
187 .to_string()
188 } else {
189 String::new()
190 };
191
192 if let Some(Value::String(ref_str)) = map.get("$ref").cloned() {
193 let normalized_path = if ref_str.starts_with('#') || ref_str.starts_with('/') {
195 path_utils::normalize_to_json_pointer(&ref_str).into_owned()
196 } else {
197 let schema_pointer = path_utils::dot_notation_to_schema_pointer(&ref_str);
198 let schema_path =
199 path_utils::normalize_to_json_pointer(&schema_pointer).into_owned();
200
201 if self.evaluated_schema.pointer(&schema_path).is_some() {
202 schema_path
203 } else {
204 format!("/properties/{}", ref_str.replace('.', "/properties/"))
205 }
206 };
207
208 let dotted_path = path_utils::pointer_to_dot_notation(&normalized_path);
211 let last_segment = dotted_path.split('.').last().unwrap_or(&dotted_path);
212
213 map.insert("$fullpath".to_string(), Value::String(dotted_path.clone()));
214 map.insert("$path".to_string(), Value::String(last_segment.to_string()));
215 map.insert("$parentHide".to_string(), Value::Bool(false));
216
217 if let Some(referenced_value) = self.evaluated_schema.pointer(&normalized_path)
218 {
219 let resolved = referenced_value.clone();
220
221 if let Value::Object(mut resolved_map) = resolved {
222 map.remove("$ref");
223
224 if let Some(Value::Object(layout_obj)) = resolved_map.remove("$layout")
225 {
226 let mut result = layout_obj.clone();
227 for (key, value) in resolved_map {
228 if key != "type" || !result.contains_key("type") {
229 result.insert(key, value);
230 }
231 }
232 for (key, value) in map {
234 result.insert(key, value);
235 }
236 return (Value::Object(result), dotted_path);
237 } else {
238 for (key, value) in map {
239 resolved_map.insert(key, value);
240 }
241 return (Value::Object(resolved_map), dotted_path);
242 }
243 } else {
244 return (resolved, dotted_path);
245 }
246 }
247 }
248
249 (Value::Object(map), ref_path)
250 }
251 _ => (element, String::new()),
252 }
253 }
254
255 fn tree_to_overlays(
264 tree: &[(Value, String)],
265 layout_path: &str,
266 parent_hidden: bool,
267 parent_condition_hidden: bool,
268 parent_disabled: bool,
269 layout_hidden_refs: &mut indexmap::IndexSet<String>,
270 layout_visible_refs: &mut indexmap::IndexSet<String>,
271 layout_condition_hidden_refs: &mut indexmap::IndexSet<String>,
272 ) -> ResolvedLayoutResult {
273 let mut entries = ResolvedLayoutResult::new();
274
275 for (idx, (element, ref_path)) in tree.iter().enumerate() {
276 let element_idx = idx;
277 let mut overlay = IndexMap::new();
278
279 if let Value::Object(map) = element {
281 const EXCLUDED: &[&str] = &[
286 "$ref",
287 "elements",
288 "properties",
289 "items",
290 "required",
291 "additionalProperties",
292 ];
293 for (key, value) in map {
294 if !EXCLUDED.contains(&key.as_str()) {
295 overlay.insert(key.clone(), value.clone());
296 }
297 }
298
299 if !overlay.contains_key("$fullpath") {
301 if !ref_path.is_empty() {
302 let last_segment = ref_path.split('.').last().unwrap_or(ref_path);
304 overlay.insert("$fullpath".to_string(), Value::String(ref_path.clone()));
305 overlay
306 .insert("$path".to_string(), Value::String(last_segment.to_string()));
307 } else {
308 let base = Self::layout_path_to_field_path(layout_path);
315 let fullpath = if base.is_empty() {
316 format!("{}", element_idx)
317 } else {
318 format!("{}.{}", base, element_idx)
319 };
320 let last_segment =
321 fullpath.split('.').last().unwrap_or(&fullpath).to_string();
322 overlay.insert("$fullpath".to_string(), Value::String(fullpath));
323 overlay.insert("$path".to_string(), Value::String(last_segment));
324 }
325 }
326
327 overlay.insert("$parentHide".to_string(), Value::Bool(parent_hidden));
330
331 let mut element_hidden = parent_hidden;
333 let mut element_condition_hidden = parent_condition_hidden;
334 let mut element_disabled = parent_disabled;
335
336 if let Some(Value::Object(cond)) = overlay.get("condition") {
338 if let Some(Value::Bool(true)) = cond.get("hidden") {
339 element_hidden = true;
340 element_condition_hidden = true;
341 }
342 if let Some(Value::Bool(d)) = cond.get("disabled") {
343 element_disabled = element_disabled || *d;
344 }
345 }
346
347 if let Some(Value::Object(hide)) = overlay.get("hideLayout") {
349 if let Some(Value::Bool(true)) = hide.get("all") {
350 element_hidden = true;
351 }
352 }
353
354 if !ref_path.is_empty() {
355 let pointer = path_utils::normalize_to_json_pointer(
356 &path_utils::dot_notation_to_schema_pointer(ref_path),
357 )
358 .trim_start_matches('#')
359 .to_string();
360 if element_hidden {
361 layout_hidden_refs.insert(pointer.clone());
362 if element_condition_hidden {
363 layout_condition_hidden_refs.insert(pointer);
364 }
365 } else {
366 layout_visible_refs.insert(pointer);
367 }
368 }
369
370 let show_condition_cascade =
372 parent_hidden || parent_disabled || element_hidden || element_disabled;
373
374 if show_condition_cascade {
378 let mut merged_cond = serde_json::Map::new();
379 if let Some(Value::Object(existing)) = overlay.get("condition") {
380 for (k, v) in existing.iter() {
381 merged_cond.insert(k.clone(), v.clone());
382 }
383 }
384 if parent_hidden || element_hidden {
386 merged_cond.insert("hidden".to_string(), Value::Bool(true));
387 }
388 if parent_disabled || element_disabled {
389 merged_cond.insert("disabled".to_string(), Value::Bool(true));
390 }
391 overlay.insert("condition".to_string(), Value::Object(merged_cond));
392
393 if (parent_hidden || element_hidden)
395 && (element.get("hideLayout").is_some() || element.get("type").is_some())
396 {
397 let mut hide_layout =
398 if let Some(Value::Object(h)) = element.get("hideLayout") {
399 h.clone()
400 } else {
401 serde_json::Map::new()
402 };
403 hide_layout.insert("all".to_string(), Value::Bool(true));
404 overlay.insert("hideLayout".to_string(), Value::Object(hide_layout));
405 }
406 }
407
408 if let Some(Value::Array(children)) = element.get("elements") {
410 let child_tree: Vec<(Value, String)> = children
412 .iter()
413 .map(|c| {
414 if let Value::Object(m) = c {
415 (
416 c.clone(),
417 m.get("$fullpath")
418 .and_then(Value::as_str)
419 .unwrap_or("")
420 .to_string(),
421 )
422 } else {
423 (c.clone(), String::new())
424 }
425 })
426 .collect();
427
428 let child_layout_path = format!(
429 "{}/{}/elements",
430 layout_path.trim_end_matches('/'),
431 element_idx
432 );
433
434 let child_overlays = Self::tree_to_overlays(
436 &child_tree,
437 &child_layout_path,
438 element_hidden,
439 element_condition_hidden,
440 element_disabled,
441 layout_hidden_refs,
442 layout_visible_refs,
443 layout_condition_hidden_refs,
444 );
445 entries.extend(child_overlays);
446 }
447 }
448
449 entries.push(LayoutOverlayEntry {
450 layout_path: layout_path.to_string(),
451 element_idx,
452 schema_ref_path: ref_path.clone(),
453 overlay,
454 });
455 }
456
457 entries
458 }
459
460 fn layout_path_to_field_path(layout_path: &str) -> String {
475 let raw = if layout_path.starts_with("#/") {
477 &layout_path[2..]
478 } else if layout_path.starts_with('#') {
479 &layout_path[1..]
480 } else if layout_path.starts_with('/') {
481 &layout_path[1..]
482 } else {
483 layout_path
484 };
485
486 let parts: Vec<&str> = raw.split('/').collect();
488 let mut out: Vec<&str> = Vec::new();
489 let mut i = 0;
490 while i < parts.len() {
491 let seg = parts[i];
492 match seg {
493 "" | "properties" | "$layout" | "elements" | "additionalProperties" => {}
494 _ => out.push(seg),
495 }
496 i += 1;
497 }
498
499 out.join(".")
500 }
501}