Skip to main content

haste_codegen/
traversal.rs

1use haste_fhir_model::r4::generated::{resources::StructureDefinition, types::ElementDefinition};
2
3/// Returns the indices of the direct child elements of the element at `index`.
4///
5/// # Errors
6///
7/// Returns an error if:
8/// - `index` is out of bounds.
9/// - An element does not contain a path.
10pub fn ele_index_to_child_indices(
11    elements: &[ElementDefinition],
12    index: usize,
13) -> Result<Vec<usize>, String> {
14    let parent = elements
15        .get(index)
16        .ok_or_else(|| format!("Index {index} out of bounds"))?;
17
18    let parent_path: &str = parent.path.value.as_deref().ok_or("Element has no path")?;
19
20    let depth = parent_path.matches('.').count();
21
22    let mut children_indices = Vec::new();
23
24    for (cur_index, element) in elements.iter().enumerate().skip(index + 1) {
25        let path = element.path.value.as_deref().ok_or("Not Found")?;
26        let path_depth = path.matches('.').count();
27
28        if path_depth <= depth {
29            break;
30        }
31
32        // A direct child has exactly one more path segment than its parent, with the
33        // parent's path as a strict prefix followed by a `.` separator - equivalent to
34        // the previous `^{parent}\.[^.]+$` regex without paying for regex compilation
35        // on every call.
36        if path_depth == depth + 1
37            && path.len() > parent_path.len()
38            && path.starts_with(parent_path)
39            && path.as_bytes()[parent_path.len()] == b'.'
40        {
41            children_indices.push(cur_index);
42        }
43    }
44
45    Ok(children_indices)
46}
47
48fn traversal_bottom_up_sd_elements<'a, F, V>(
49    elements: &'a Vec<ElementDefinition>,
50    index: usize,
51    visitor_function: &mut F,
52) -> Result<V, String>
53where
54    F: FnMut(&'a ElementDefinition, Vec<V>, usize) -> V,
55{
56    let child_indices = ele_index_to_child_indices(elements.as_slice(), index)?;
57
58    let child_traversal_values: Vec<V> = child_indices
59        .iter()
60        .map(|&child_index| {
61            traversal_bottom_up_sd_elements(elements, child_index, visitor_function)
62        })
63        .collect::<Result<Vec<V>, String>>()?;
64
65    Ok(visitor_function(
66        &elements[index],
67        child_traversal_values,
68        index,
69    ))
70}
71
72/// Traverses the elements of a [`StructureDefinition`] in bottom-up order.
73///
74/// The visitor function is called after all child elements have been traversed.
75///
76/// # Errors
77///
78/// Returns an error if the [`StructureDefinition`] does not contain a snapshot.
79pub fn traversal<'a, F, V>(sd: &'a StructureDefinition, visitor: &mut F) -> Result<V, String>
80where
81    F: FnMut(&'a ElementDefinition, Vec<V>, usize) -> V,
82{
83    let elements = &sd
84        .snapshot
85        .as_ref()
86        .ok_or("StructureDefinition has no snapshot")?
87        .element;
88
89    traversal_bottom_up_sd_elements(elements, 0, visitor)
90}
91
92#[cfg(test)]
93mod tests {
94
95    use haste_fhir_model::r4::generated::resources::{Bundle, Resource};
96
97    use super::*;
98
99    #[test]
100    fn test_traversal() {
101        let bundle = serde_json::from_str::<Bundle>(
102            &std::fs::read_to_string(
103                "../../../artifacts/r4/hl7-core/definitions/hl7/profiles-resources.min.json",
104            )
105            .unwrap(),
106        )
107        .unwrap();
108
109        let sds: Vec<&StructureDefinition> = bundle
110            .entry
111            .as_ref()
112            .unwrap()
113            .iter()
114            .filter_map(
115                |e| match e.resource.as_ref().map(std::convert::AsRef::as_ref) {
116                    Some(Resource::StructureDefinition(sd)) => Some(sd),
117                    _ => None,
118                },
119            )
120            .collect();
121
122        let mut visitor =
123            |element: &ElementDefinition, children: Vec<String>, _index: usize| -> String {
124                let path: String = element.path.value.as_ref().unwrap().clone();
125                children.join("\n") + "\n" + &path
126            };
127
128        println!("StructureDefinitions: {}", sds.len());
129
130        for sd in sds {
131            let result = traversal(sd, &mut visitor);
132
133            println!("Result: {result:?}");
134        }
135    }
136}