Skip to main content

json_eval_rs/jsoneval/
layout.rs

1use super::JSONEval;
2use crate::jsoneval::path_utils;
3use crate::time_block;
4
5use serde_json::Value;
6use std::mem;
7
8impl JSONEval {
9    /// Resolve layout references with optional evaluation
10    ///
11    /// # Arguments
12    ///
13    /// * `evaluate` - If true, runs evaluation before resolving layout. If false, only resolves layout.
14    ///
15    /// # Returns
16    ///
17    /// A Result indicating success or an error message.
18    pub fn resolve_layout(&mut self, evaluate: bool) -> Result<(), String> {
19        if evaluate {
20            // Use existing data
21            let data_str = serde_json::to_string(&self.data)
22                .map_err(|e| format!("Failed to serialize data: {}", e))?;
23            self.evaluate(&data_str, None, None, None)?;
24        }
25
26        self.resolve_layout_internal();
27        Ok(())
28    }
29
30    fn resolve_layout_internal(&mut self) {
31        time_block!("  resolve_layout_internal()", {
32            // Use cached layout paths (collected at parse time)
33            let layout_paths = self.layout_paths.clone();
34
35            time_block!("    resolve_layout_elements", {
36                for layout_path in layout_paths.iter() {
37                    self.resolve_layout_elements(layout_path);
38                }
39            });
40
41            // After resolving all references, propagate parent hidden/disabled to children
42            time_block!("    propagate_parent_conditions", {
43                for layout_path in layout_paths.iter() {
44                    self.propagate_parent_conditions(layout_path);
45                }
46            });
47
48            // Sync layout hidden state to schema definitions
49            // This ensures validation (which checks schema) respects layout hiding
50            time_block!("    sync_layout_hidden_to_schema", {
51                 self.sync_layout_hidden_to_schema(&layout_paths);
52            });
53        });
54    }
55
56    /// Resolve $ref references in layout elements (recursively)
57    fn resolve_layout_elements(&mut self, layout_elements_path: &str) {
58        // Normalize path from schema format (#/) to JSON pointer format (/)
59        let normalized_path = path_utils::normalize_to_json_pointer(layout_elements_path);
60
61        // Always read elements from original schema (not evaluated_schema)
62        // This ensures we get fresh $ref entries on re-evaluation
63        let elements = if let Some(Value::Array(arr)) = self.schema.pointer(&normalized_path) {
64            arr.clone()
65        } else {
66            return;
67        };
68
69        // Extract the parent path from normalized_path
70        let parent_path = normalized_path
71            .trim_start_matches('/')
72            .replace("/elements", "")
73            .replace('/', ".");
74
75        // Process elements
76        let mut resolved_elements = Vec::with_capacity(elements.len());
77        for (index, element) in elements.iter().enumerate() {
78            let element_path = if parent_path.is_empty() {
79                format!("elements.{}", index)
80            } else {
81                format!("{}.elements.{}", parent_path, index)
82            };
83            let resolved = self.resolve_element_ref_recursive(element.clone(), &element_path);
84            resolved_elements.push(resolved);
85        }
86
87        // Write back the resolved elements
88        if let Some(target) = self.evaluated_schema.pointer_mut(&normalized_path) {
89            *target = Value::Array(resolved_elements);
90        }
91    }
92
93    /// Recursively resolve $ref in an element and its nested elements
94    /// path_context: The dotted path to the current element (e.g., "form.$layout.elements.0")
95    fn resolve_element_ref_recursive(&self, element: Value, path_context: &str) -> Value {
96        // First resolve the current element's $ref
97        let resolved = self.resolve_element_ref(element);
98
99        // Then recursively resolve any nested elements arrays
100        if let Value::Object(mut map) = resolved {
101            // Ensure all layout elements have metadata fields
102            if !map.contains_key("$parentHide") {
103                map.insert("$parentHide".to_string(), Value::Bool(false));
104            }
105
106            // Set path metadata for direct layout elements (without $ref)
107            if !map.contains_key("$fullpath") {
108                map.insert("$fullpath".to_string(), Value::String(path_context.to_string()));
109            }
110
111            if !map.contains_key("$path") {
112                let last_segment = path_context.split('.').last().unwrap_or(path_context);
113                map.insert("$path".to_string(), Value::String(last_segment.to_string()));
114            }
115
116            // Check if this object has an "elements" array
117            if let Some(Value::Array(elements)) = map.get("elements") {
118                let mut resolved_nested = Vec::with_capacity(elements.len());
119                for (index, nested_element) in elements.iter().enumerate() {
120                    let nested_path = format!("{}.elements.{}", path_context, index);
121                    resolved_nested.push(self.resolve_element_ref_recursive(nested_element.clone(), &nested_path));
122                }
123                map.insert("elements".to_string(), Value::Array(resolved_nested));
124            }
125
126            return Value::Object(map);
127        }
128
129        resolved
130    }
131
132    /// Resolve $ref in a single element
133    fn resolve_element_ref(&self, element: Value) -> Value {
134        match element {
135            Value::Object(mut map) => {
136                // Check if element has $ref
137                if let Some(Value::String(ref_path)) = map.get("$ref").cloned() {
138                    // Convert ref_path to dotted notation for metadata storage
139                    let dotted_path = path_utils::pointer_to_dot_notation(&ref_path);
140
141                    // Extract last segment for $path
142                    let last_segment = dotted_path.split('.').last().unwrap_or(&dotted_path);
143
144                    // Inject metadata fields with dotted notation
145                    map.insert("$fullpath".to_string(), Value::String(dotted_path.clone()));
146                    map.insert("$path".to_string(), Value::String(last_segment.to_string()));
147                    map.insert("$parentHide".to_string(), Value::Bool(false));
148
149                    // Normalize to JSON pointer for actual lookup
150                    let normalized_path = if ref_path.starts_with('#') || ref_path.starts_with('/') {
151                        path_utils::normalize_to_json_pointer(&ref_path)
152                    } else {
153                        // Try as schema path first
154                        let schema_pointer = path_utils::dot_notation_to_schema_pointer(&ref_path);
155                        let schema_path = path_utils::normalize_to_json_pointer(&schema_pointer);
156
157                        // Check if it exists
158                        if self.evaluated_schema.pointer(&schema_path).is_some() {
159                            schema_path
160                        } else {
161                            // Try with /properties/ prefix
162                            format!("/properties/{}", ref_path.replace('.', "/properties/"))
163                        }
164                    };
165
166                    // Get the referenced value
167                    if let Some(referenced_value) = self.evaluated_schema.pointer(&normalized_path) {
168                        let resolved = referenced_value.clone();
169
170                        if let Value::Object(mut resolved_map) = resolved {
171                            map.remove("$ref");
172
173                            // Special case: if resolved has $layout, flatten it
174                            if let Some(Value::Object(layout_obj)) = resolved_map.remove("$layout") {
175                                let mut result = layout_obj.clone();
176
177                                // properties are now preserved and will be merged below
178
179                                // Merge remaining resolved_map properties
180                                for (key, value) in resolved_map {
181                                    if key != "type" || !result.contains_key("type") {
182                                        result.insert(key, value);
183                                    }
184                                }
185
186                                // Finally, merge element override properties
187                                for (key, value) in map {
188                                    result.insert(key, value);
189                                }
190
191                                return Value::Object(result);
192                            } else {
193                                // Normal merge: element properties override referenced properties
194                                for (key, value) in map {
195                                    resolved_map.insert(key, value);
196                                }
197
198                                return Value::Object(resolved_map);
199                            }
200                        } else {
201                            return resolved;
202                        }
203                    }
204                }
205
206                Value::Object(map)
207            }
208            _ => element,
209        }
210    }
211
212    /// Propagate parent hidden/disabled conditions to children recursively
213    fn propagate_parent_conditions(&mut self, layout_elements_path: &str) {
214        let normalized_path = path_utils::normalize_to_json_pointer(layout_elements_path);
215
216        // Extract elements array to avoid borrow checker issues
217        let elements = if let Some(Value::Array(arr)) = self.evaluated_schema.pointer_mut(&normalized_path) {
218            mem::take(arr)
219        } else {
220            return;
221        };
222
223        // Process elements
224        let mut updated_elements = Vec::with_capacity(elements.len());
225        for element in elements {
226            updated_elements.push(self.apply_parent_conditions(element, false, false));
227        }
228
229        // Write back  the updated elements
230        if let Some(target) = self.evaluated_schema.pointer_mut(&normalized_path) {
231            *target = Value::Array(updated_elements);
232        }
233    }
234
235    /// Recursively apply parent hidden/disabled conditions to an element and its children
236    fn apply_parent_conditions(&self, element: Value, parent_hidden: bool, parent_disabled: bool) -> Value {
237        if let Value::Object(mut map) = element {
238            // Get current element's condition
239            let mut element_hidden = parent_hidden;
240            let mut element_disabled = parent_disabled;
241
242            // Check condition field (from $ref elements)
243            if let Some(Value::Object(condition)) = map.get("condition") {
244                if let Some(Value::Bool(hidden)) = condition.get("hidden") {
245                    element_hidden = element_hidden || *hidden;
246                }
247                if let Some(Value::Bool(disabled)) = condition.get("disabled") {
248                    element_disabled = element_disabled || *disabled;
249                }
250            }
251
252            // Check hideLayout field (from direct layout elements)
253            if let Some(Value::Object(hide_layout)) = map.get("hideLayout") {
254                if let Some(Value::Bool(all)) = hide_layout.get("all") {
255                    if *all {
256                        element_hidden = true;
257                    }
258                }
259            }
260
261            // Update condition to include parent state (for field elements)
262            if parent_hidden || parent_disabled {
263                // Update condition field if it exists or if this is a field element
264                if map.contains_key("condition")
265                    || map.contains_key("$ref")
266                    || map.contains_key("$fullpath")
267                {
268                    let mut condition = if let Some(Value::Object(c)) = map.get("condition") {
269                        c.clone()
270                    } else {
271                        serde_json::Map::new()
272                    };
273
274                    if parent_hidden {
275                        condition.insert("hidden".to_string(), Value::Bool(true));
276                        element_hidden = true;
277                    }
278                    if parent_disabled {
279                        condition.insert("disabled".to_string(), Value::Bool(true));
280                        element_disabled = true;
281                    }
282
283                    map.insert("condition".to_string(), Value::Object(condition));
284                }
285
286                // Update hideLayout for direct layout elements
287                if parent_hidden && (map.contains_key("hideLayout") || map.contains_key("type")) {
288                    let mut hide_layout = if let Some(Value::Object(h)) = map.get("hideLayout") {
289                        h.clone()
290                    } else {
291                        serde_json::Map::new()
292                    };
293
294                    // Set hideLayout.all to true when parent is hidden
295                    hide_layout.insert("all".to_string(), Value::Bool(true));
296                    map.insert("hideLayout".to_string(), Value::Object(hide_layout));
297                }
298            }
299
300            // Update $parentHide flag if element has it
301            if map.contains_key("$parentHide") {
302                map.insert("$parentHide".to_string(), Value::Bool(parent_hidden));
303            }
304
305            // Recursively process children if elements array exists
306            if let Some(Value::Array(elements)) = map.get("elements") {
307                let mut updated_children = Vec::with_capacity(elements.len());
308                for child in elements {
309                    updated_children.push(self.apply_parent_conditions(child.clone(), element_hidden, element_disabled));
310                }
311                map.insert("elements".to_string(), Value::Array(updated_children));
312            }
313
314            return Value::Object(map);
315        }
316
317        element
318    }
319
320    /// Sync hidden state from layout elements to their schema definitions
321    fn sync_layout_hidden_to_schema(&mut self, layout_paths: &[String]) {
322        let mut hidden_paths = Vec::new();
323
324        // Collect all schema paths that are effectively hidden in the layout
325        for layout_path in layout_paths {
326             let normalized_path = path_utils::normalize_to_json_pointer(layout_path);
327             if let Some(Value::Array(elements)) = self.evaluated_schema.pointer(&normalized_path) {
328                 for element in elements {
329                     self.collect_hidden_paths_recursive(element, &mut hidden_paths);
330                 }
331             }
332        }
333
334        // Apply hidden state to schema definitions
335        for schema_path_dot in hidden_paths {
336            let schema_pointer = path_utils::dot_notation_to_schema_pointer(&schema_path_dot);
337            let normalized_pointer = path_utils::normalize_to_json_pointer(&schema_pointer);
338            
339            if let Some(schema_node) = self.evaluated_schema.pointer_mut(&normalized_pointer) {
340                if let Value::Object(map) = schema_node {
341                    // Update condition.hidden = true
342                     let mut condition = if let Some(Value::Object(c)) = map.get("condition") {
343                        c.clone()
344                    } else {
345                        serde_json::Map::new()
346                    };
347                    
348                    condition.insert("hidden".to_string(), Value::Bool(true));
349                    map.insert("condition".to_string(), Value::Object(condition));
350                }
351            } else {
352                // Try with /properties prefix if direct lookup fails
353                let alt_pointer = format!("/properties{}", normalized_pointer);
354                if let Some(schema_node) = self.evaluated_schema.pointer_mut(&alt_pointer) {
355                    if let Value::Object(map) = schema_node {
356                        let mut condition = if let Some(Value::Object(c)) = map.get("condition") {
357                            c.clone()
358                        } else {
359                            serde_json::Map::new()
360                        };
361                        
362                        condition.insert("hidden".to_string(), Value::Bool(true));
363                        map.insert("condition".to_string(), Value::Object(condition));
364                    }
365                }
366            }
367        }
368    }
369
370    fn collect_hidden_paths_recursive(&self, element: &Value, hidden_paths: &mut Vec<String>) {
371        if let Value::Object(map) = element {
372            // Check if this element is effectively hidden
373            let is_hidden = if let Some(Value::Object(condition)) = map.get("condition") {
374                condition.get("hidden").and_then(|v| v.as_bool()).unwrap_or(false)
375            } else {
376                false
377            };
378
379            if is_hidden {
380                // If hidden and has $fullpath, verify it points to a schema definition
381                if let Some(Value::String(fullpath)) = map.get("fullpath") {
382                     hidden_paths.push(fullpath.clone());
383                } else if let Some(Value::String(fullpath)) = map.get("$fullpath") {
384                     hidden_paths.push(fullpath.clone());
385                }
386            }
387
388            // Recurse children
389            if let Some(Value::Array(elements)) = map.get("elements") {
390                for child in elements {
391                    self.collect_hidden_paths_recursive(child, hidden_paths);
392                }
393            }
394        }
395    }
396}