use std::collections::BTreeMap;
use std::fmt::Write as _;
use super::spec::{self, ElementDefinition};
use super::version::Version;
pub fn collect(version: Version) -> std::io::Result<BTreeMap<String, ElementDefinition>> {
let mut table: BTreeMap<String, ElementDefinition> = BTreeMap::new();
for bundle in [version.resources_bundle(), version.types_bundle()] {
for sd in spec::read_structure_definitions(&bundle)? {
let Some(snapshot) = sd.snapshot else { continue };
for element in snapshot.element {
if !element.path.contains('.') {
continue;
}
table.entry(element.path.clone()).or_insert(element);
}
}
}
Ok(table)
}
#[must_use]
pub fn render(table: &BTreeMap<String, ElementDefinition>, version: Version) -> String {
let label = version.label();
let module = version.module();
let mut out = format!(
"//! Generated element-metadata table — DO NOT EDIT.\n\
//!\n\
//! Produced from the FHIR {label} specification JSON by `crate::codegen::meta_gen`.\n\
//! Regenerate with `cargo run -- {module}`.\n\
\n\
use crate::meta::{{BindingMeta, BindingStrength, ElementMeta, TypeRef}};\n\
\n\
pub(super) static ELEMENTS: &[ElementMeta] = &[\n"
);
for (path, element) in table {
let _ = writeln!(
out,
" ElementMeta {{ path: {path:?}, min: {}, max: {:?}, is_summary: {}, binding: {}, types: {} }},",
element.min,
element.max.as_deref().unwrap_or("1"),
element.is_summary.unwrap_or(false),
render_binding(element),
render_types(element),
);
}
out.push_str("];\n");
out
}
fn render_binding(element: &ElementDefinition) -> String {
let Some(binding) = &element.binding else { return "None".to_string() };
let strength = match binding.strength.as_str() {
"required" => "Required",
"extensible" => "Extensible",
"preferred" => "Preferred",
_ => "Example",
};
let value_set = binding
.value_set()
.map_or_else(|| "None".to_string(), |vs| format!("Some({vs:?})"));
format!("Some(BindingMeta {{ strength: BindingStrength::{strength}, value_set: {value_set} }})")
}
fn render_types(element: &ElementDefinition) -> String {
let entries: Vec<String> = element
.types
.iter()
.map(|t| {
let profiles: Vec<String> =
t.target_profile.iter().map(|p| format!("{p:?}")).collect();
format!(
"TypeRef {{ code: {:?}, target_profiles: &[{}] }}",
t.code,
profiles.join(", ")
)
})
.collect();
format!("&[{}]", entries.join(", "))
}
#[cfg(test)]
mod tests {
use super::*;
fn element(json: ::serde_json::Value) -> ElementDefinition {
::serde_json::from_value(json).unwrap()
}
fn table(elements: Vec<ElementDefinition>) -> BTreeMap<String, ElementDefinition> {
elements.into_iter().map(|e| (e.path.clone(), e)).collect()
}
#[test]
fn renders_a_sorted_static_table() {
let out = render(
&table(vec![
element(::serde_json::json!({ "path": "Patient.active", "min": 0, "max": "1",
"isSummary": true, "type": [{ "code": "boolean" }] })),
element(::serde_json::json!({ "path": "Patient.name", "min": 0, "max": "*",
"type": [{ "code": "HumanName" }] })),
]),
Version::R4,
);
assert!(out.contains("pub(super) static ELEMENTS: &[ElementMeta] = &["));
let active = out.find("Patient.active").unwrap();
let name = out.find("Patient.name").unwrap();
assert!(active < name, "entries must be sorted for binary search");
assert!(out.contains(
r#"ElementMeta { path: "Patient.active", min: 0, max: "1", is_summary: true, binding: None, types: &[TypeRef { code: "boolean", target_profiles: &[] }] }"#
));
}
#[test]
fn bindings_and_targets_survive() {
let out = render(
&table(vec![element(::serde_json::json!({
"path": "Observation.status", "min": 1, "max": "1",
"type": [{ "code": "code" }],
"binding": { "strength": "required",
"valueSet": "http://hl7.org/fhir/ValueSet/observation-status|4.0.1" }
}))]),
Version::R4,
);
assert!(out.contains("BindingStrength::Required"));
assert!(out.contains("http://hl7.org/fhir/ValueSet/observation-status|4.0.1"));
let out = render(
&table(vec![element(::serde_json::json!({
"path": "Observation.subject", "min": 0, "max": "1",
"type": [{ "code": "Reference",
"targetProfile": ["http://hl7.org/fhir/StructureDefinition/Patient"] }]
}))]),
Version::R4,
);
assert!(out.contains(
r#"target_profiles: &["http://hl7.org/fhir/StructureDefinition/Patient"]"#
));
}
#[test]
fn an_unknown_strength_degrades_to_example() {
let out = render(
&table(vec![element(::serde_json::json!({
"path": "X.y", "min": 0, "max": "1", "type": [{ "code": "code" }],
"binding": { "strength": "whatever" }
}))]),
Version::R4,
);
assert!(out.contains("BindingStrength::Example"));
assert!(out.contains("value_set: None"));
}
}