Skip to main content

selfcheck/
selfcheck.rs

1//! Compare each profile's effective tree with its published snapshot (min, max, mustSupport
2//! of every element the tree has): `cargo run --example selfcheck -- <package>`.
3use fhir_diff::{Node, Pool, load};
4
5fn find<'a>(node: &'a Node, parts: &[&str]) -> Option<&'a Node> {
6    let Some((head, rest)) = parts.split_first() else {
7        return Some(node);
8    };
9    let (name, slice) = head
10        .split_once(':')
11        .map_or((*head, None), |(n, s)| (n, Some(s)));
12    let name = name.trim_end_matches("[x]");
13    let child = node.children.get(name)?;
14    let child = match slice {
15        Some(s) => child.slices.get(s)?,
16        None => child,
17    };
18    find(child, rest)
19}
20
21fn main() {
22    let spec = std::env::args().nth(1).expect("package");
23    let loaded = load(&spec).expect("load");
24    let pool = Pool::new(&loaded);
25    let (mut checked, mut differ) = (0, 0);
26    for profile in pool.profiles() {
27        let Some(definition) = loaded
28            .package
29            .definitions
30            .iter()
31            .find(|d| d.file == profile.file)
32        else {
33            continue;
34        };
35        for element in definition.json["snapshot"]["element"]
36            .as_array()
37            .into_iter()
38            .flatten()
39        {
40            let id = element["id"].as_str().unwrap_or_default();
41            let parts: Vec<&str> = id.split('.').skip(1).collect();
42            if parts.is_empty() || parts.iter().any(|p| p.contains("[x]:")) {
43                continue;
44            }
45            let Some(node) = find(&profile.tree, &parts) else {
46                continue;
47            };
48            checked += 1;
49            let min = element["min"].as_u64().unwrap_or(0) as u32;
50            let max = match element["max"].as_str() {
51                Some("*") => None,
52                Some(m) => m.parse().ok(),
53                None => None,
54            };
55            let ms = element["mustSupport"].as_bool().unwrap_or(false);
56            if node.min != min || node.max != max || node.must_support != ms {
57                differ += 1;
58                if differ <= 25 {
59                    println!(
60                        "{} {id}: tree {}..{:?} ms={} / snapshot {min}..{max:?} ms={ms}",
61                        profile.name, node.min, node.max, node.must_support
62                    );
63                }
64            }
65        }
66    }
67    println!("checked {checked} elements, {differ} differ");
68}