use crate::fhir_types::{ElementDefinition, StructureDefinition};
use crate::metadata::{
FhirFieldType, FhirPrimitiveType, FieldInfo, MetadataRegistry, TypeMetadata,
};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MetadataCategory {
Resources,
Datatypes,
Primitives,
Profiles,
Other,
}
impl MetadataCategory {
fn module_name(self) -> &'static str {
match self {
Self::Resources => "resources",
Self::Datatypes => "datatypes",
Self::Primitives => "primitives",
Self::Profiles => "profiles",
Self::Other => "other",
}
}
fn doc_label(self) -> &'static str {
match self {
Self::Resources => "FHIR resources",
Self::Datatypes => "FHIR datatypes",
Self::Primitives => "FHIR primitive types",
Self::Profiles => "FHIR profiles",
Self::Other => "other FHIR types",
}
}
}
fn classify_metadata(sd: &StructureDefinition) -> MetadataCategory {
match sd.kind.as_str() {
"resource" => MetadataCategory::Resources,
"complex-type" => MetadataCategory::Datatypes,
"primitive-type" => MetadataCategory::Primitives,
_ => MetadataCategory::Other,
}
}
fn metadata_category_priority(category: MetadataCategory) -> u8 {
match category {
MetadataCategory::Primitives => 0,
MetadataCategory::Datatypes => 1,
MetadataCategory::Resources => 2,
MetadataCategory::Profiles => 3,
MetadataCategory::Other => 4,
}
}
fn sorted_structure_definitions(
structure_defs: &[StructureDefinition],
) -> Vec<&StructureDefinition> {
let mut sorted_defs: Vec<_> = structure_defs.iter().collect();
sorted_defs.sort_by(|left, right| {
left.name
.cmp(&right.name)
.then_with(|| {
metadata_category_priority(classify_metadata(left))
.cmp(&metadata_category_priority(classify_metadata(right)))
})
.then_with(|| left.url.cmp(&right.url))
.then_with(|| left.id.cmp(&right.id))
});
sorted_defs
}
pub fn build_metadata_registry(structure_defs: &[StructureDefinition]) -> MetadataRegistry {
let mut registry = MetadataRegistry::new();
for structure_def in sorted_structure_definitions(structure_defs) {
if let Some(type_metadata) = extract_type_metadata(structure_def) {
if !registry.types.contains_key(&type_metadata.name) {
registry.add_type(type_metadata);
}
}
}
registry
}
fn extract_type_metadata(structure_def: &StructureDefinition) -> Option<TypeMetadata> {
let type_name = structure_def.name.as_str();
let mut fields = HashMap::new();
let snapshot = structure_def.snapshot.as_ref()?;
let elements = &snapshot.element;
for element in elements.iter().skip(1) {
if let Some(field_info) = extract_field_info(element, type_name) {
if let Some(field_name) = extract_field_name(&element.path, type_name) {
fields.insert(field_name, field_info);
}
}
}
Some(TypeMetadata {
name: type_name.to_string(),
fields,
})
}
fn extract_field_name(path: &str, type_name: &str) -> Option<String> {
let prefix = format!("{type_name}.");
if !path.starts_with(&prefix) {
return None;
}
let field_path = &path[prefix.len()..];
let field_name = field_path.split('.').next()?;
Some(field_name.to_string())
}
fn extract_field_info(element: &ElementDefinition, _type_name: &str) -> Option<FieldInfo> {
let element_types = element.element_type.as_ref()?;
if element_types.is_empty() {
return None;
}
let min = element.min.unwrap_or(0);
let max = element.max.as_ref().and_then(|m| {
if m == "*" {
None
} else {
m.parse::<u32>().ok()
}
});
let is_choice_type = element.path.contains("[x]");
let choice_types: Vec<String> = element_types
.iter()
.filter_map(|et| et.code.clone())
.collect();
let primary_type_code = element_types[0].code.as_ref()?;
let field_type = determine_field_type(primary_type_code);
Some(FieldInfo {
field_type,
min,
max,
is_choice_type,
choice_types,
})
}
fn determine_field_type(type_code: &str) -> FhirFieldType {
if let Some(primitive) = FhirPrimitiveType::from_fhir_type(type_code) {
return FhirFieldType::Primitive(primitive);
}
if type_code == "Reference" {
return FhirFieldType::Reference;
}
if type_code == "BackboneElement" {
return FhirFieldType::BackboneElement(type_code.to_string());
}
FhirFieldType::Complex(type_code.to_string())
}
pub fn generate_metadata_code(registry: &MetadataRegistry) -> String {
let mut code = String::new();
code.push_str(
r#"//! FHIR type metadata
//!
//! This module provides compile-time metadata about FHIR types, enabling
//! path resolution like "Patient.name.given" -> FhirPrimitiveType::String.
//!
//! Generated automatically - do not edit manually.
use phf::{phf_map, Map};
"#,
);
code.push_str(
r#"/// FHIR primitive types
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FhirPrimitiveType {
Boolean,
Integer,
String,
Date,
DateTime,
Instant,
Time,
Decimal,
Uri,
Url,
Canonical,
Code,
Oid,
Id,
Markdown,
Base64Binary,
UnsignedInt,
PositiveInt,
}
"#,
);
code.push_str(
r#"/// FHIR field type (primitive, complex, reference, or backbone element)
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FhirFieldType {
Primitive(FhirPrimitiveType),
Complex(&'static str),
Reference,
BackboneElement(&'static str),
}
"#,
);
code.push_str(
r#"/// Information about a field in a FHIR resource or datatype
#[derive(Debug, Clone)]
pub struct FieldInfo {
pub field_type: FhirFieldType,
pub min: u32,
pub max: Option<u32>,
pub is_choice_type: bool,
}
"#,
);
let mut generated_consts = std::collections::HashSet::new();
let mut sorted_types: Vec<_> = registry.types.iter().collect();
sorted_types.sort_by_key(|(left_name, _)| *left_name);
for (type_name, type_metadata) in sorted_types {
let sanitized_name: String = type_name
.chars()
.map(|c| if c.is_alphanumeric() { c } else { '_' })
.collect();
let const_name = format!("{}_FIELDS", sanitized_name.to_uppercase());
if generated_consts.contains(&const_name) {
continue;
}
generate_type_map(&mut code, type_name, type_metadata);
generated_consts.insert(const_name);
}
generate_registry_map(&mut code, ®istry.types, &generated_consts);
code.push_str(
r#"
/// Get field information for a specific field in a type
pub fn get_field_info(type_name: &str, field_name: &str) -> Option<&'static FieldInfo> {
FHIR_TYPE_REGISTRY
.get(type_name)
.and_then(|fields| fields.get(field_name))
}
/// Resolve a nested path like "Patient.name.given" to its field type
pub fn resolve_path(path: &str) -> Option<&'static FhirFieldType> {
let parts: Vec<&str> = path.split('.').collect();
if parts.is_empty() {
return None;
}
let mut current_type_name = parts[0];
for (idx, &field_name) in parts[1..].iter().enumerate() {
let field_info = get_field_info(current_type_name, field_name)?;
// If this is the last field, return its type
if idx == parts.len() - 2 {
return Some(&field_info.field_type);
}
// Otherwise, navigate to the next type
match &field_info.field_type {
FhirFieldType::Complex(type_name) | FhirFieldType::BackboneElement(type_name) => {
current_type_name = type_name;
}
_ => return None, // Can't navigate further
}
}
None
}
"#,
);
code
}
fn generate_type_map(code: &mut String, type_name: &str, type_metadata: &TypeMetadata) {
let sanitized_name: String = type_name
.chars()
.map(|c| if c.is_alphanumeric() { c } else { '_' })
.collect();
let const_name = format!("{}_FIELDS", sanitized_name.to_uppercase());
code.push_str(&format!("/// Field metadata for {type_name}\n"));
code.push_str("#[rustfmt::skip]\n");
code.push_str(&format!(
"pub static {const_name}: Map<&'static str, FieldInfo> = phf_map! {{\n"
));
let mut sorted_fields: Vec<_> = type_metadata.fields.iter().collect();
sorted_fields.sort_by_key(|(left_name, _)| *left_name);
for (field_name, field_info) in sorted_fields {
code.push_str(&format!(" \"{field_name}\" => FieldInfo {{\n"));
code.push_str(" field_type: ");
match &field_info.field_type {
FhirFieldType::Primitive(prim) => {
code.push_str(&format!(
"FhirFieldType::Primitive(FhirPrimitiveType::{})",
prim.variant_name()
));
}
FhirFieldType::Complex(name) => {
code.push_str(&format!("FhirFieldType::Complex(\"{name}\")"));
}
FhirFieldType::Reference => {
code.push_str("FhirFieldType::Reference");
}
FhirFieldType::BackboneElement(name) => {
code.push_str(&format!("FhirFieldType::BackboneElement(\"{name}\")"));
}
}
code.push_str(",\n");
code.push_str(&format!(" min: {},\n", field_info.min));
code.push_str(" max: ");
if let Some(max) = field_info.max {
code.push_str(&format!("Some({max})"));
} else {
code.push_str("None");
}
code.push_str(",\n");
code.push_str(&format!(
" is_choice_type: {},\n",
field_info.is_choice_type
));
code.push_str(" },\n");
}
code.push_str("};\n\n");
}
fn generate_registry_map(
code: &mut String,
types: &HashMap<String, TypeMetadata>,
generated_consts: &std::collections::HashSet<String>,
) {
code.push_str("/// Main FHIR type registry mapping type names to their field metadata\n");
code.push_str(
"pub static FHIR_TYPE_REGISTRY: Map<&'static str, &'static Map<&'static str, FieldInfo>> = phf_map! {\n",
);
let mut sorted_type_names: Vec<_> = types.keys().collect();
sorted_type_names.sort();
for type_name in sorted_type_names {
let sanitized_name: String = type_name
.chars()
.map(|c| if c.is_alphanumeric() { c } else { '_' })
.collect();
let const_name = format!("{}_FIELDS", sanitized_name.to_uppercase());
if generated_consts.contains(&const_name) {
code.push_str(&format!(" \"{type_name}\" => &{const_name},\n"));
}
}
code.push_str("};\n");
}
pub fn generate_metadata_code_split(
registry: &MetadataRegistry,
structure_defs: &[StructureDefinition],
) -> HashMap<String, String> {
let mut files = HashMap::new();
let mut name_to_category: HashMap<&str, MetadataCategory> = HashMap::new();
for sd in sorted_structure_definitions(structure_defs) {
name_to_category
.entry(&sd.name)
.or_insert_with(|| classify_metadata(sd));
}
let mut categories: HashMap<MetadataCategory, Vec<(&String, &TypeMetadata)>> = HashMap::new();
let mut sorted_registry_types: Vec<_> = registry.types.iter().collect();
sorted_registry_types.sort_by_key(|(left_name, _)| *left_name);
for (type_name, type_metadata) in sorted_registry_types {
let category = name_to_category
.get(type_name.as_str())
.copied()
.unwrap_or(MetadataCategory::Other);
categories
.entry(category)
.or_default()
.push((type_name, type_metadata));
}
let mut all_generated_consts = std::collections::HashSet::new();
let category_order = [
MetadataCategory::Resources,
MetadataCategory::Datatypes,
MetadataCategory::Primitives,
MetadataCategory::Profiles,
MetadataCategory::Other,
];
for &category in &category_order {
let mut types = categories.get(&category).cloned().unwrap_or_default();
types.sort_by_key(|(left_name, _)| *left_name);
if types.is_empty() {
continue;
}
let mut code = String::new();
code.push_str(&format!(
"//! Field metadata for {}\n\nuse phf::{{phf_map, Map}};\nuse super::*;\n\n",
category.doc_label()
));
let mut category_consts = std::collections::HashSet::new();
for (type_name, type_metadata) in &types {
let sanitized_name: String = type_name
.chars()
.map(|c| if c.is_alphanumeric() { c } else { '_' })
.collect();
let const_name = format!("{}_FIELDS", sanitized_name.to_uppercase());
if all_generated_consts.contains(&const_name) {
continue;
}
if category_consts.contains(&const_name) {
continue;
}
generate_type_map(&mut code, type_name, type_metadata);
category_consts.insert(const_name.clone());
all_generated_consts.insert(const_name);
}
let filename = format!("{}.rs", category.module_name());
files.insert(filename, code);
}
let mut mod_code = String::new();
mod_code.push_str(
r#"//! FHIR type metadata
//!
//! This module provides compile-time metadata about FHIR types, enabling
//! path resolution like "Patient.name.given" -> FhirPrimitiveType::String.
//!
//! Generated automatically - do not edit manually.
pub use phf;
use phf::{phf_map, Map};
"#,
);
for &category in &category_order {
let filename = format!("{}.rs", category.module_name());
if files.contains_key(&filename) {
mod_code.push_str(&format!("mod {};\n", category.module_name()));
mod_code.push_str(&format!("pub use {}::*;\n", category.module_name()));
}
}
mod_code.push('\n');
mod_code.push_str(
r#"/// FHIR primitive types
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FhirPrimitiveType {
Boolean,
Integer,
String,
Date,
DateTime,
Instant,
Time,
Decimal,
Uri,
Url,
Canonical,
Code,
Oid,
Id,
Markdown,
Base64Binary,
UnsignedInt,
PositiveInt,
}
"#,
);
mod_code.push_str(
r#"/// FHIR field type (primitive, complex, reference, or backbone element)
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FhirFieldType {
Primitive(FhirPrimitiveType),
Complex(&'static str),
Reference,
BackboneElement(&'static str),
}
"#,
);
mod_code.push_str(
r#"/// Information about a field in a FHIR resource or datatype
#[derive(Debug, Clone)]
pub struct FieldInfo {
pub field_type: FhirFieldType,
pub min: u32,
pub max: Option<u32>,
pub is_choice_type: bool,
}
"#,
);
generate_registry_map(&mut mod_code, ®istry.types, &all_generated_consts);
mod_code.push_str(
r#"
/// Get field information for a specific field in a type
pub fn get_field_info(type_name: &str, field_name: &str) -> Option<&'static FieldInfo> {
FHIR_TYPE_REGISTRY
.get(type_name)
.and_then(|fields| fields.get(field_name))
}
/// Resolve a nested path like "Patient.name.given" to its field type
pub fn resolve_path(path: &str) -> Option<&'static FhirFieldType> {
let parts: Vec<&str> = path.split('.').collect();
if parts.is_empty() {
return None;
}
let mut current_type_name = parts[0];
for (idx, &field_name) in parts[1..].iter().enumerate() {
let field_info = get_field_info(current_type_name, field_name)?;
// If this is the last field, return its type
if idx == parts.len() - 2 {
return Some(&field_info.field_type);
}
// Otherwise, navigate to the next type
match &field_info.field_type {
FhirFieldType::Complex(type_name) | FhirFieldType::BackboneElement(type_name) => {
current_type_name = type_name;
}
_ => return None, // Can't navigate further
}
}
None
}
"#,
);
files.insert("mod.rs".to_string(), mod_code);
files
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_field_name() {
assert_eq!(
extract_field_name("Patient.birthDate", "Patient"),
Some("birthDate".to_string())
);
assert_eq!(
extract_field_name("Patient.name.given", "Patient"),
Some("name".to_string())
);
assert_eq!(extract_field_name("Patient", "Patient"), None);
}
#[test]
fn test_determine_field_type() {
assert_eq!(
determine_field_type("date"),
FhirFieldType::Primitive(FhirPrimitiveType::Date)
);
assert_eq!(
determine_field_type("string"),
FhirFieldType::Primitive(FhirPrimitiveType::String)
);
assert_eq!(determine_field_type("Reference"), FhirFieldType::Reference);
assert!(matches!(
determine_field_type("HumanName"),
FhirFieldType::Complex(_)
));
}
}