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    pub fn resolve_layout(&mut self, evaluate: bool) {
11        if evaluate {
12            // Use existing data
13            if let Ok(data_str) = serde_json::to_string(&self.data) {
14                let _ = self.evaluate(&data_str, None, None);
15            }
16        }
17
18        self.resolve_layout_internal();
19    }
20
21    fn resolve_layout_internal(&mut self) {
22        time_block!("  resolve_layout_internal()", {
23            // Use cached layout paths (collected at parse time)
24            let layout_paths = self.layout_paths.clone();
25
26            time_block!("    resolve_layout_elements", {
27                for layout_path in layout_paths.iter() {
28                    self.resolve_layout_elements(layout_path);
29                }
30            });
31
32            // After resolving all references, propagate parent hidden/disabled to children
33            time_block!("    propagate_parent_conditions", {
34                for layout_path in layout_paths.iter() {
35                    self.propagate_parent_conditions(layout_path);
36                }
37            });
38        });
39    }
40
41    /// Resolve $ref references in layout elements (recursively)
42    fn resolve_layout_elements(&mut self, layout_elements_path: &str) {
43        // Normalize path from schema format (#/) to JSON pointer format (/)
44        let normalized_path = path_utils::normalize_to_json_pointer(layout_elements_path);
45
46        // Always read elements from original schema (not evaluated_schema)
47        // This ensures we get fresh $ref entries on re-evaluation
48        let elements = if let Some(Value::Array(arr)) = self.schema.pointer(&normalized_path) {
49            arr.clone()
50        } else {
51            return;
52        };
53
54        // Extract the parent path from normalized_path
55        let parent_path = normalized_path
56            .trim_start_matches('/')
57            .replace("/elements", "")
58            .replace('/', ".");
59
60        // Process elements
61        let mut resolved_elements = Vec::with_capacity(elements.len());
62        for (index, element) in elements.iter().enumerate() {
63            let element_path = if parent_path.is_empty() {
64                format!("elements.{}", index)
65            } else {
66                format!("{}.elements.{}", parent_path, index)
67            };
68            let resolved = self.resolve_element_ref_recursive(element.clone(), &element_path);
69            resolved_elements.push(resolved);
70        }
71
72        // Write back the resolved elements
73        if let Some(target) = self.evaluated_schema.pointer_mut(&normalized_path) {
74            *target = Value::Array(resolved_elements);
75        }
76    }
77
78    /// Recursively resolve $ref in an element and its nested elements
79    /// path_context: The dotted path to the current element (e.g., "form.$layout.elements.0")
80    fn resolve_element_ref_recursive(&self, element: Value, path_context: &str) -> Value {
81        // First resolve the current element's $ref
82        let resolved = self.resolve_element_ref(element);
83
84        // Then recursively resolve any nested elements arrays
85        if let Value::Object(mut map) = resolved {
86            // Ensure all layout elements have metadata fields
87            if !map.contains_key("$parentHide") {
88                map.insert("$parentHide".to_string(), Value::Bool(false));
89            }
90
91            // Set path metadata for direct layout elements (without $ref)
92            if !map.contains_key("$fullpath") {
93                map.insert("$fullpath".to_string(), Value::String(path_context.to_string()));
94            }
95
96            if !map.contains_key("$path") {
97                let last_segment = path_context.split('.').last().unwrap_or(path_context);
98                map.insert("$path".to_string(), Value::String(last_segment.to_string()));
99            }
100
101            // Check if this object has an "elements" array
102            if let Some(Value::Array(elements)) = map.get("elements") {
103                let mut resolved_nested = Vec::with_capacity(elements.len());
104                for (index, nested_element) in elements.iter().enumerate() {
105                    let nested_path = format!("{}.elements.{}", path_context, index);
106                    resolved_nested.push(self.resolve_element_ref_recursive(nested_element.clone(), &nested_path));
107                }
108                map.insert("elements".to_string(), Value::Array(resolved_nested));
109            }
110
111            return Value::Object(map);
112        }
113
114        resolved
115    }
116
117    /// Resolve $ref in a single element
118    fn resolve_element_ref(&self, element: Value) -> Value {
119        match element {
120            Value::Object(mut map) => {
121                // Check if element has $ref
122                if let Some(Value::String(ref_path)) = map.get("$ref").cloned() {
123                    // Convert ref_path to dotted notation for metadata storage
124                    let dotted_path = path_utils::pointer_to_dot_notation(&ref_path);
125
126                    // Extract last segment for $path
127                    let last_segment = dotted_path.split('.').last().unwrap_or(&dotted_path);
128
129                    // Inject metadata fields with dotted notation
130                    map.insert("$fullpath".to_string(), Value::String(dotted_path.clone()));
131                    map.insert("$path".to_string(), Value::String(last_segment.to_string()));
132                    map.insert("$parentHide".to_string(), Value::Bool(false));
133
134                    // Normalize to JSON pointer for actual lookup
135                    let normalized_path = if ref_path.starts_with('#') || ref_path.starts_with('/') {
136                        path_utils::normalize_to_json_pointer(&ref_path)
137                    } else {
138                        // Try as schema path first
139                        let schema_pointer = path_utils::dot_notation_to_schema_pointer(&ref_path);
140                        let schema_path = path_utils::normalize_to_json_pointer(&schema_pointer);
141
142                        // Check if it exists
143                        if self.evaluated_schema.pointer(&schema_path).is_some() {
144                            schema_path
145                        } else {
146                            // Try with /properties/ prefix
147                            format!("/properties/{}", ref_path.replace('.', "/properties/"))
148                        }
149                    };
150
151                    // Get the referenced value
152                    if let Some(referenced_value) = self.evaluated_schema.pointer(&normalized_path) {
153                        let resolved = referenced_value.clone();
154
155                        if let Value::Object(mut resolved_map) = resolved {
156                            map.remove("$ref");
157
158                            // Special case: if resolved has $layout, flatten it
159                            if let Some(Value::Object(layout_obj)) = resolved_map.remove("$layout") {
160                                let mut result = layout_obj.clone();
161
162                                // Remove properties from resolved
163                                resolved_map.remove("properties");
164
165                                // Merge remaining resolved_map properties
166                                for (key, value) in resolved_map {
167                                    if key != "type" || !result.contains_key("type") {
168                                        result.insert(key, value);
169                                    }
170                                }
171
172                                // Finally, merge element override properties
173                                for (key, value) in map {
174                                    result.insert(key, value);
175                                }
176
177                                return Value::Object(result);
178                            } else {
179                                // Normal merge: element properties override referenced properties
180                                for (key, value) in map {
181                                    resolved_map.insert(key, value);
182                                }
183
184                                return Value::Object(resolved_map);
185                            }
186                        } else {
187                            return resolved;
188                        }
189                    }
190                }
191
192                Value::Object(map)
193            }
194            _ => element,
195        }
196    }
197
198    /// Propagate parent hidden/disabled conditions to children recursively
199    fn propagate_parent_conditions(&mut self, layout_elements_path: &str) {
200        let normalized_path = path_utils::normalize_to_json_pointer(layout_elements_path);
201
202        // Extract elements array to avoid borrow checker issues
203        let elements = if let Some(Value::Array(arr)) = self.evaluated_schema.pointer_mut(&normalized_path) {
204            mem::take(arr)
205        } else {
206            return;
207        };
208
209        // Process elements
210        let mut updated_elements = Vec::with_capacity(elements.len());
211        for element in elements {
212            updated_elements.push(self.apply_parent_conditions(element, false, false));
213        }
214
215        // Write back  the updated elements
216        if let Some(target) = self.evaluated_schema.pointer_mut(&normalized_path) {
217            *target = Value::Array(updated_elements);
218        }
219    }
220
221    /// Recursively apply parent hidden/disabled conditions to an element and its children
222    fn apply_parent_conditions(&self, element: Value, parent_hidden: bool, parent_disabled: bool) -> Value {
223        if let Value::Object(mut map) = element {
224            // Get current element's condition
225            let mut element_hidden = parent_hidden;
226            let mut element_disabled = parent_disabled;
227
228            // Check condition field (from $ref elements)
229            if let Some(Value::Object(condition)) = map.get("condition") {
230                if let Some(Value::Bool(hidden)) = condition.get("hidden") {
231                    element_hidden = element_hidden || *hidden;
232                }
233                if let Some(Value::Bool(disabled)) = condition.get("disabled") {
234                    element_disabled = element_disabled || *disabled;
235                }
236            }
237
238            // Check hideLayout field (from direct layout elements)
239            if let Some(Value::Object(hide_layout)) = map.get("hideLayout") {
240                if let Some(Value::Bool(all)) = hide_layout.get("all") {
241                    if *all {
242                        element_hidden = true;
243                    }
244                }
245            }
246
247            // Update condition to include parent state (for field elements)
248            if parent_hidden || parent_disabled {
249                // Update condition field if it exists or if this is a field element
250                if map.contains_key("condition")
251                    || map.contains_key("$ref")
252                    || map.contains_key("$fullpath")
253                {
254                    let mut condition = if let Some(Value::Object(c)) = map.get("condition") {
255                        c.clone()
256                    } else {
257                        serde_json::Map::new()
258                    };
259
260                    if parent_hidden {
261                        condition.insert("hidden".to_string(), Value::Bool(true));
262                        element_hidden = true;
263                    }
264                    if parent_disabled {
265                        condition.insert("disabled".to_string(), Value::Bool(true));
266                        element_disabled = true;
267                    }
268
269                    map.insert("condition".to_string(), Value::Object(condition));
270                }
271
272                // Update hideLayout for direct layout elements
273                if parent_hidden && (map.contains_key("hideLayout") || map.contains_key("type")) {
274                    let mut hide_layout = if let Some(Value::Object(h)) = map.get("hideLayout") {
275                        h.clone()
276                    } else {
277                        serde_json::Map::new()
278                    };
279
280                    // Set hideLayout.all to true when parent is hidden
281                    hide_layout.insert("all".to_string(), Value::Bool(true));
282                    map.insert("hideLayout".to_string(), Value::Object(hide_layout));
283                }
284            }
285
286            // Update $parentHide flag if element has it
287            if map.contains_key("$parentHide") {
288                map.insert("$parentHide".to_string(), Value::Bool(parent_hidden));
289            }
290
291            // Recursively process children if elements array exists
292            if let Some(Value::Array(elements)) = map.get("elements") {
293                let mut updated_children = Vec::with_capacity(elements.len());
294                for child in elements {
295                    updated_children.push(self.apply_parent_conditions(child.clone(), element_hidden, element_disabled));
296                }
297                map.insert("elements".to_string(), Value::Array(updated_children));
298            }
299
300            return Value::Object(map);
301        }
302
303        element
304    }
305}