Skip to main content

rh_foundation/snapshot/
merger.rs

1use crate::snapshot::error::{SnapshotError, SnapshotResult};
2use crate::snapshot::path::ElementPath;
3use crate::snapshot::types::ElementDefinition;
4use std::collections::HashMap;
5use tracing::{debug, trace};
6
7pub struct ElementMerger;
8
9impl ElementMerger {
10    pub fn merge_elements(
11        base: &[ElementDefinition],
12        differential: &[ElementDefinition],
13    ) -> SnapshotResult<Vec<ElementDefinition>> {
14        debug!(
15            "Merging {} base elements with {} differential elements",
16            base.len(),
17            differential.len()
18        );
19
20        let mut element_map = Self::create_element_map(base);
21        Self::apply_differential(&mut element_map, differential)?;
22        Self::expand_slice_children(&mut element_map);
23
24        let mut result: Vec<ElementDefinition> = element_map.into_values().collect();
25        Self::sort_elements_by_path(&mut result);
26
27        debug!("Merge complete: {} elements in result", result.len());
28        Ok(result)
29    }
30
31    fn create_element_map(
32        base: &[ElementDefinition],
33    ) -> HashMap<(String, Option<String>), ElementDefinition> {
34        base.iter()
35            .map(|e| ((e.path.clone(), e.slice_name.clone()), e.clone()))
36            .collect()
37    }
38
39    fn apply_differential(
40        element_map: &mut HashMap<(String, Option<String>), ElementDefinition>,
41        differential: &[ElementDefinition],
42    ) -> SnapshotResult<()> {
43        for diff_element in differential {
44            let key = (diff_element.path.clone(), diff_element.slice_name.clone());
45
46            if let Some(existing) = element_map.get(&key) {
47                let merged = Self::merge_element(existing, diff_element)?;
48                element_map.insert(key, merged);
49            } else if diff_element.slice_name.is_some() {
50                let base_key = (diff_element.path.clone(), None);
51                if let Some(base_element) = element_map.get(&base_key) {
52                    let merged = Self::merge_element(base_element, diff_element)?;
53                    element_map.insert(key, merged);
54                } else {
55                    element_map.insert(key, diff_element.clone());
56                }
57            } else {
58                element_map.insert(key, diff_element.clone());
59            }
60        }
61        Ok(())
62    }
63
64    fn sort_elements_by_path(elements: &mut [ElementDefinition]) {
65        elements.sort_by(|a, b| match a.path.cmp(&b.path) {
66            std::cmp::Ordering::Equal => match (&a.slice_name, &b.slice_name) {
67                (None, None) => std::cmp::Ordering::Equal,
68                (None, Some(_)) => std::cmp::Ordering::Less,
69                (Some(_), None) => std::cmp::Ordering::Greater,
70                (Some(a_name), Some(b_name)) => a_name.cmp(b_name),
71            },
72            other => other,
73        });
74    }
75
76    fn expand_slice_children(
77        element_map: &mut HashMap<(String, Option<String>), ElementDefinition>,
78    ) {
79        let mut new_elements = Vec::new();
80
81        // Cache ElementPaths for base elements to avoid repeated parsing
82        let base_elements: Vec<(&String, ElementPath, &ElementDefinition)> = element_map
83            .iter()
84            .filter(|((_, sn), _)| sn.is_none())
85            .map(|((path, _), elem)| (path, ElementPath::new(path), elem))
86            .collect();
87
88        // Collect slice roots
89        let slice_roots: Vec<(String, String)> = element_map
90            .keys()
91            .filter_map(|(path, slice_name)| {
92                slice_name.as_ref().map(|name| (path.clone(), name.clone()))
93            })
94            .collect();
95
96        for (slice_path_str, slice_name) in slice_roots {
97            let slice_base_path = ElementPath::new(&slice_path_str);
98
99            for (path, elem_path, elem) in &base_elements {
100                if elem_path.is_child_of(&slice_base_path) {
101                    let slice_child_key = ((*path).clone(), Some(slice_name.clone()));
102
103                    if !element_map.contains_key(&slice_child_key) {
104                        let mut new_elem = (*elem).clone();
105                        new_elem.slice_name = Some(slice_name.clone());
106                        new_elements.push((slice_child_key, new_elem));
107                    }
108                }
109            }
110        }
111
112        for (key, elem) in new_elements {
113            element_map.insert(key, elem);
114        }
115    }
116
117    fn merge_element(
118        base: &ElementDefinition,
119        diff: &ElementDefinition,
120    ) -> Result<ElementDefinition, SnapshotError> {
121        let (merged_min, merged_max) = Self::merge_cardinality(
122            base.min,
123            base.max.as_deref(),
124            diff.min,
125            diff.max.as_deref(),
126            &diff.path,
127        )?;
128
129        let merged_type = Self::merge_types(base.type_.as_ref(), diff.type_.as_ref(), &diff.path)?;
130
131        let merged_binding =
132            Self::merge_binding(base.binding.as_ref(), diff.binding.as_ref(), &diff.path)?;
133
134        let merged_constraints =
135            Self::merge_constraints(&base.constraint, &diff.constraint, &diff.path)?;
136
137        Ok(ElementDefinition {
138            path: diff.path.clone(),
139            id: diff.id.clone().or_else(|| base.id.clone()),
140            min: Some(merged_min),
141            max: Some(merged_max.to_string()),
142            type_: merged_type,
143            binding: merged_binding,
144            constraint: merged_constraints,
145            definition: diff.definition.clone().or_else(|| base.definition.clone()),
146            short: diff.short.clone().or_else(|| base.short.clone()),
147            comment: diff.comment.clone().or_else(|| base.comment.clone()),
148            requirements: diff
149                .requirements
150                .clone()
151                .or_else(|| base.requirements.clone()),
152            must_support: diff.must_support.or(base.must_support),
153            is_summary: diff.is_summary.or(base.is_summary),
154            is_modifier: diff.is_modifier.or(base.is_modifier),
155            is_modifier_reason: diff
156                .is_modifier_reason
157                .clone()
158                .or_else(|| base.is_modifier_reason.clone()),
159            slicing: diff.slicing.clone().or_else(|| base.slicing.clone()),
160            slice_name: diff.slice_name.clone().or_else(|| base.slice_name.clone()),
161        })
162    }
163
164    fn merge_cardinality(
165        base_min: Option<u32>,
166        base_max: Option<&str>,
167        diff_min: Option<u32>,
168        diff_max: Option<&str>,
169        path: &str,
170    ) -> Result<(u32, String), SnapshotError> {
171        let base_min = base_min.unwrap_or(0);
172        let base_max = base_max.unwrap_or("*");
173        let diff_min = diff_min.unwrap_or(base_min);
174        let diff_max = diff_max.unwrap_or(base_max);
175
176        if diff_min < base_min {
177            return Err(SnapshotError::MergeError(format!(
178                "Invalid cardinality for {path}: differential min ({diff_min}) is less than base min ({base_min})"
179            )));
180        }
181
182        let base_max_numeric = Self::parse_max_cardinality(base_max);
183        let diff_max_numeric = Self::parse_max_cardinality(diff_max);
184
185        match (base_max_numeric, diff_max_numeric) {
186            (Some(base_max_val), Some(diff_max_val)) => {
187                if diff_max_val > base_max_val {
188                    return Err(SnapshotError::MergeError(format!(
189                        "Invalid cardinality for {path}: differential max ({diff_max}) is greater than base max ({base_max})"
190                    )));
191                }
192            }
193            (Some(_), None) => {
194                return Err(SnapshotError::MergeError(format!(
195                    "Invalid cardinality for {path}: differential max ({diff_max}) is greater than base max ({base_max})"
196                )));
197            }
198            (None, _) => {}
199        }
200
201        if let Some(diff_max_val) = diff_max_numeric {
202            if diff_min > diff_max_val {
203                return Err(SnapshotError::MergeError(format!(
204                    "Invalid cardinality for {path}: min ({diff_min}) is greater than max ({diff_max})"
205                )));
206            }
207        }
208
209        trace!(
210            "Merged cardinality for {}: {}..{} (from base {}..{}, diff {}..{})",
211            path,
212            diff_min,
213            diff_max,
214            base_min,
215            base_max,
216            diff_min,
217            diff_max
218        );
219
220        Ok((diff_min, diff_max.to_string()))
221    }
222
223    fn parse_max_cardinality(max: &str) -> Option<u32> {
224        if max == "*" {
225            None
226        } else {
227            max.parse::<u32>().ok()
228        }
229    }
230
231    fn merge_types(
232        base_types: Option<&Vec<crate::snapshot::types::ElementType>>,
233        diff_types: Option<&Vec<crate::snapshot::types::ElementType>>,
234        path: &str,
235    ) -> Result<Option<Vec<crate::snapshot::types::ElementType>>, SnapshotError> {
236        match (base_types, diff_types) {
237            (Some(base), Some(diff)) => {
238                for diff_type in diff {
239                    let is_valid = base.iter().any(|base_type| {
240                        if base_type.code != diff_type.code {
241                            return false;
242                        }
243                        true
244                    });
245
246                    if !is_valid {
247                        return Err(SnapshotError::MergeError(format!(
248                            "Invalid type restriction for {path}: differential type '{}' is not in base types",
249                            diff_type.code
250                        )));
251                    }
252                }
253
254                trace!("Merged types for {path}: {} differential types (restricted from {} base types)", diff.len(), base.len());
255                Ok(Some(diff.clone()))
256            }
257            (Some(base), None) => Ok(Some(base.clone())),
258            (None, Some(diff)) => Ok(Some(diff.clone())),
259            (None, None) => Ok(None),
260        }
261    }
262
263    fn merge_binding(
264        base_binding: Option<&crate::snapshot::types::ElementBinding>,
265        diff_binding: Option<&crate::snapshot::types::ElementBinding>,
266        path: &str,
267    ) -> Result<Option<crate::snapshot::types::ElementBinding>, SnapshotError> {
268        match (base_binding, diff_binding) {
269            (Some(base), Some(diff)) => {
270                let base_strength = Self::parse_binding_strength(&base.strength);
271                let diff_strength = Self::parse_binding_strength(&diff.strength);
272
273                if diff_strength < base_strength {
274                    return Err(SnapshotError::MergeError(format!(
275                        "Invalid binding for {path}: differential strength '{}' is weaker than base strength '{}'",
276                        diff.strength, base.strength
277                    )));
278                }
279
280                trace!(
281                    "Merged binding for {path}: {} (from base {})",
282                    diff.strength,
283                    base.strength
284                );
285                Ok(Some(diff.clone()))
286            }
287            (Some(base), None) => Ok(Some(base.clone())),
288            (None, Some(diff)) => Ok(Some(diff.clone())),
289            (None, None) => Ok(None),
290        }
291    }
292
293    fn parse_binding_strength(strength: &str) -> u8 {
294        match strength.to_lowercase().as_str() {
295            "example" => 0,
296            "preferred" => 1,
297            "extensible" => 2,
298            "required" => 3,
299            _ => 0,
300        }
301    }
302
303    fn merge_constraints(
304        base: &Option<Vec<crate::snapshot::types::ElementConstraint>>,
305        diff: &Option<Vec<crate::snapshot::types::ElementConstraint>>,
306        path: &str,
307    ) -> Result<Option<Vec<crate::snapshot::types::ElementConstraint>>, SnapshotError> {
308        match (base, diff) {
309            (Some(base_constraints), Some(diff_constraints)) => {
310                let mut merged = base_constraints.clone();
311                let mut seen_keys: HashMap<String, String> = base_constraints
312                    .iter()
313                    .map(|c| (c.key.clone(), c.expression.clone().unwrap_or_default()))
314                    .collect();
315
316                for diff_constraint in diff_constraints {
317                    if let Some(existing_expr) = seen_keys.get(&diff_constraint.key) {
318                        let diff_expr = diff_constraint.expression.as_deref().unwrap_or("");
319                        if existing_expr != diff_expr {
320                            return Err(SnapshotError::MergeError(format!(
321                                "Duplicate constraint key '{}' for {path} with different expressions",
322                                diff_constraint.key
323                            )));
324                        }
325                        continue;
326                    }
327                    seen_keys.insert(
328                        diff_constraint.key.clone(),
329                        diff_constraint.expression.clone().unwrap_or_default(),
330                    );
331                    merged.push(diff_constraint.clone());
332                }
333
334                trace!("Merged constraints for {path}: {} total constraints (from {} base + {} differential)", 
335                    merged.len(), base_constraints.len(), diff_constraints.len());
336                Ok(Some(merged))
337            }
338            (Some(base_constraints), None) => Ok(Some(base_constraints.clone())),
339            (None, Some(diff_constraints)) => Ok(Some(diff_constraints.clone())),
340            (None, None) => Ok(None),
341        }
342    }
343}
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348    use crate::snapshot::types::ElementDefinition;
349
350    fn create_element(path: &str, slice_name: Option<&str>) -> ElementDefinition {
351        ElementDefinition {
352            path: path.to_string(),
353            id: None,
354            min: None,
355            max: None,
356            type_: None,
357            binding: None,
358            constraint: None,
359            definition: None,
360            short: None,
361            comment: None,
362            requirements: None,
363            must_support: None,
364            is_summary: None,
365            is_modifier: None,
366            is_modifier_reason: None,
367            slicing: None,
368            slice_name: slice_name.map(|s| s.to_string()),
369        }
370    }
371
372    #[test]
373    fn test_merge_elements_basic() {
374        let base_elem = create_element("Patient", None);
375        let diff_elem = create_element("Patient", None);
376
377        let base = vec![base_elem];
378        let diff = vec![diff_elem];
379
380        let result = ElementMerger::merge_elements(&base, &diff).unwrap();
381        assert_eq!(result.len(), 1);
382        assert_eq!(result[0].path, "Patient");
383    }
384
385    #[test]
386    fn test_expand_slice_children() {
387        let mut map = HashMap::new();
388
389        // Base elements
390        let base_root = create_element("Patient.identifier", None);
391        let base_child = create_element("Patient.identifier.system", None);
392
393        map.insert(("Patient.identifier".to_string(), None), base_root);
394        map.insert(("Patient.identifier.system".to_string(), None), base_child);
395
396        // Slice root
397        let slice_root = create_element("Patient.identifier", Some("MRN"));
398        map.insert(
399            ("Patient.identifier".to_string(), Some("MRN".to_string())),
400            slice_root,
401        );
402
403        ElementMerger::expand_slice_children(&mut map);
404
405        // Check if child was copied to slice
406        assert!(map.contains_key(&(
407            "Patient.identifier.system".to_string(),
408            Some("MRN".to_string())
409        )));
410
411        let slice_child = map
412            .get(&(
413                "Patient.identifier.system".to_string(),
414                Some("MRN".to_string()),
415            ))
416            .unwrap();
417        assert_eq!(slice_child.slice_name.as_deref(), Some("MRN"));
418    }
419}