Skip to main content

haste_codegen/
traversal.rs

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