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