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_structural_path(layout_path);
313 let fullpath = if base.is_empty() {
314 format!("{}", element_idx)
315 } else {
316 format!("{}.{}", base, element_idx)
317 };
318 let last_segment =
319 fullpath.split('.').last().unwrap_or(&fullpath).to_string();
320 overlay.insert("$fullpath".to_string(), Value::String(fullpath));
321 overlay.insert("$path".to_string(), Value::String(last_segment));
322 }
323 }
324
325 overlay.insert("$parentHide".to_string(), Value::Bool(parent_hidden));
328
329 let mut element_hidden = parent_hidden;
331 let mut element_condition_hidden = parent_condition_hidden;
332 let mut element_disabled = parent_disabled;
333
334 if let Some(Value::Object(cond)) = overlay.get("condition") {
336 if let Some(Value::Bool(true)) = cond.get("hidden") {
337 element_hidden = true;
338 element_condition_hidden = true;
339 }
340 if let Some(Value::Bool(d)) = cond.get("disabled") {
341 element_disabled = element_disabled || *d;
342 }
343 }
344
345 if let Some(Value::Object(hide)) = overlay.get("hideLayout") {
347 if let Some(Value::Bool(true)) = hide.get("all") {
348 element_hidden = true;
349 }
350 }
351
352 if !ref_path.is_empty() {
353 let pointer = path_utils::normalize_to_json_pointer(
354 &path_utils::dot_notation_to_schema_pointer(ref_path),
355 )
356 .trim_start_matches('#')
357 .to_string();
358 if element_hidden {
359 layout_hidden_refs.insert(pointer.clone());
360 if element_condition_hidden {
361 layout_condition_hidden_refs.insert(pointer);
362 }
363 } else {
364 layout_visible_refs.insert(pointer);
365 }
366 }
367
368 let show_condition_cascade =
370 parent_hidden || parent_disabled || element_hidden || element_disabled;
371
372 if show_condition_cascade {
376 let mut merged_cond = serde_json::Map::new();
377 if let Some(Value::Object(existing)) = overlay.get("condition") {
378 for (k, v) in existing.iter() {
379 merged_cond.insert(k.clone(), v.clone());
380 }
381 }
382 if parent_hidden || element_hidden {
384 merged_cond.insert("hidden".to_string(), Value::Bool(true));
385 }
386 if parent_disabled || element_disabled {
387 merged_cond.insert("disabled".to_string(), Value::Bool(true));
388 }
389 overlay.insert("condition".to_string(), Value::Object(merged_cond));
390
391 if (parent_hidden || element_hidden)
393 && (element.get("hideLayout").is_some() || element.get("type").is_some())
394 {
395 let mut hide_layout =
396 if let Some(Value::Object(h)) = element.get("hideLayout") {
397 h.clone()
398 } else {
399 serde_json::Map::new()
400 };
401 hide_layout.insert("all".to_string(), Value::Bool(true));
402 overlay.insert("hideLayout".to_string(), Value::Object(hide_layout));
403 }
404 }
405
406 if let Some(Value::Array(children)) = element.get("elements") {
408 let child_tree: Vec<(Value, String)> = children
410 .iter()
411 .map(|c| {
412 if let Value::Object(m) = c {
413 (
414 c.clone(),
415 m.get("$fullpath")
416 .and_then(Value::as_str)
417 .unwrap_or("")
418 .to_string(),
419 )
420 } else {
421 (c.clone(), String::new())
422 }
423 })
424 .collect();
425
426 let child_layout_path = format!(
427 "{}/{}/elements",
428 layout_path.trim_end_matches('/'),
429 element_idx
430 );
431
432 let child_overlays = Self::tree_to_overlays(
434 &child_tree,
435 &child_layout_path,
436 element_hidden,
437 element_condition_hidden,
438 element_disabled,
439 layout_hidden_refs,
440 layout_visible_refs,
441 layout_condition_hidden_refs,
442 );
443 entries.extend(child_overlays);
444 }
445 }
446
447 entries.push(LayoutOverlayEntry {
448 layout_path: layout_path.to_string(),
449 element_idx,
450 schema_ref_path: ref_path.clone(),
451 overlay,
452 });
453 }
454
455 entries
456 }
457
458 fn layout_path_to_structural_path(layout_path: &str) -> String {
469 layout_path
470 .trim_start_matches('#')
471 .trim_start_matches('/')
472 .split('/')
473 .filter(|segment| !segment.is_empty())
474 .collect::<Vec<_>>()
475 .join(".")
476 }
477}