use crate::fhir_types::StructureDefinition;
use crate::invariants;
pub struct ValidationTraitGenerator;
impl ValidationTraitGenerator {
pub fn generate_trait_definition() -> String {
let mut code = String::new();
code.push_str("/// Trait for FHIR types that can be validated using invariants\n");
code.push_str("///\n");
code.push_str("/// This trait provides access to the invariants (constraints) defined\n");
code.push_str("/// in the FHIR specification for this resource or datatype.\n");
code.push_str("pub trait ValidatableResource {\n");
code.push_str(" /// Returns the FHIR resource type name\n");
code.push_str(" fn resource_type(&self) -> &'static str;\n");
code.push('\n');
code.push_str(" /// Returns the invariants (constraints) for this resource/datatype\n");
code.push_str(" ///\n");
code.push_str(" /// These are the FHIRPath expressions that must evaluate to true\n");
code.push_str(" /// for the resource to be considered valid.\n");
code.push_str(" fn invariants() -> &'static [rh_foundation::Invariant];\n");
code.push('\n');
code.push_str(" /// Returns the required bindings for this resource/datatype\n");
code.push_str(" ///\n");
code.push_str(" /// These are the ValueSet bindings with \"required\" strength that\n");
code.push_str(" /// must be validated at runtime.\n");
code.push_str(" fn bindings() -> &'static [rh_foundation::ElementBinding] {\n");
code.push_str(" &[]\n");
code.push_str(" }\n");
code.push('\n');
code.push_str(" /// Returns the cardinality constraints for this resource/datatype\n");
code.push_str(" ///\n");
code.push_str(
" /// These define the minimum and maximum occurrences allowed for each element.\n",
);
code.push_str(" fn cardinalities() -> &'static [rh_foundation::ElementCardinality] {\n");
code.push_str(" &[]\n");
code.push_str(" }\n");
code.push('\n');
code.push_str(" /// Returns the profile URL if this is a profile, None otherwise\n");
code.push_str(" fn profile_url() -> Option<&'static str> {\n");
code.push_str(" None\n");
code.push_str(" }\n");
code.push_str("}\n");
code
}
pub fn generate_trait_impl(structure_def: &StructureDefinition) -> String {
let invariants = invariants::extract_invariants(structure_def);
let bindings = crate::bindings::extract_required_bindings(structure_def);
if invariants.is_empty() && bindings.is_empty() {
return String::new();
}
let struct_name = crate::naming::Naming::struct_name(structure_def);
let resource_type = &structure_def.base_type;
let mut code = String::new();
code.push_str(&format!(
"impl crate::validation::ValidatableResource for {struct_name} {{\n"
));
code.push_str(" fn resource_type(&self) -> &'static str {\n");
code.push_str(&format!(" \"{resource_type}\"\n"));
code.push_str(" }\n");
code.push('\n');
code.push_str(" fn invariants() -> &'static [rh_foundation::Invariant] {\n");
if invariants.is_empty() {
code.push_str(" &[]\n");
} else {
code.push_str(" &INVARIANTS\n");
}
code.push_str(" }\n");
if !bindings.is_empty() {
code.push('\n');
code.push_str(" fn bindings() -> &'static [rh_foundation::ElementBinding] {\n");
code.push_str(" &BINDINGS\n");
code.push_str(" }\n");
}
if structure_def.kind == "resource" || structure_def.kind == "complex-type" {
code.push('\n');
code.push_str(
" fn cardinalities() -> &'static [rh_foundation::ElementCardinality] {\n",
);
code.push_str(" &CARDINALITIES\n");
code.push_str(" }\n");
}
if structure_def.base_definition.is_some() && !structure_def.is_abstract {
let url = &structure_def.url;
code.push('\n');
code.push_str(" fn profile_url() -> Option<&'static str> {\n");
code.push_str(&format!(" Some(\"{url}\")\n"));
code.push_str(" }\n");
}
code.push_str("}\n");
code
}
pub fn generate_validation_module() -> String {
let mut code = String::new();
code.push_str("//! Validation support for FHIR resources\n");
code.push_str("//!\n");
code.push_str(
"//! This module provides traits for validating FHIR resources using invariants.\n",
);
code.push('\n');
code.push_str(&Self::generate_trait_definition());
code
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fhir_types::{ElementConstraint, ElementDefinition, StructureDefinitionSnapshot};
fn create_test_structure_def(
name: &str,
base_type: &str,
constraints: Vec<ElementConstraint>,
url: String,
is_profile: bool,
) -> StructureDefinition {
let element = ElementDefinition {
id: Some(base_type.to_string()),
path: base_type.to_string(),
element_type: None,
min: Some(0),
max: Some("*".to_string()),
short: None,
definition: None,
fixed: None,
pattern: None,
binding: None,
constraint: Some(constraints),
};
StructureDefinition {
resource_type: "StructureDefinition".to_string(),
id: name.to_string(),
url,
version: None,
name: name.to_string(),
title: None,
status: "active".to_string(),
description: None,
purpose: None,
kind: "resource".to_string(),
is_abstract: false,
base_type: base_type.to_string(),
base_definition: if is_profile {
Some(format!(
"http://hl7.org/fhir/StructureDefinition/{base_type}"
))
} else {
None
},
snapshot: Some(StructureDefinitionSnapshot {
element: vec![element],
}),
differential: None,
}
}
#[test]
fn test_generate_trait_definition() {
let trait_def = ValidationTraitGenerator::generate_trait_definition();
assert!(trait_def.contains("pub trait ValidatableResource"));
assert!(trait_def.contains("fn resource_type(&self) -> &'static str"));
assert!(trait_def.contains("fn invariants() -> &'static [rh_foundation::Invariant]"));
assert!(trait_def.contains("fn profile_url() -> Option<&'static str>"));
assert!(trait_def.contains("None"));
}
#[test]
fn test_generate_trait_impl_with_invariants() {
let constraints = vec![ElementConstraint {
key: "pat-1".to_string(),
severity: "error".to_string(),
human: "Test constraint".to_string(),
expression: Some("name.exists()".to_string()),
xpath: None,
source: None,
}];
let structure_def = create_test_structure_def(
"Patient",
"Patient",
constraints,
"http://hl7.org/fhir/StructureDefinition/Patient".to_string(),
false,
);
let impl_code = ValidationTraitGenerator::generate_trait_impl(&structure_def);
assert!(impl_code.contains("impl crate::validation::ValidatableResource for Patient"));
assert!(impl_code.contains("fn resource_type(&self) -> &'static str"));
assert!(impl_code.contains("\"Patient\""));
assert!(impl_code.contains("fn invariants() -> &'static [rh_foundation::Invariant]"));
assert!(impl_code.contains("&INVARIANTS"));
}
#[test]
fn test_generate_trait_impl_with_profile() {
let constraints = vec![ElementConstraint {
key: "prof-1".to_string(),
severity: "warning".to_string(),
human: "Profile constraint".to_string(),
expression: Some("identifier.exists()".to_string()),
xpath: None,
source: None,
}];
let structure_def = create_test_structure_def(
"USCorePatient",
"Patient",
constraints,
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient".to_string(),
true,
);
let impl_code = ValidationTraitGenerator::generate_trait_impl(&structure_def);
assert!(impl_code.contains("fn profile_url() -> Option<&'static str>"));
assert!(impl_code
.contains("Some(\"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient\")"));
}
#[test]
fn test_generate_trait_impl_no_invariants() {
let structure_def = create_test_structure_def(
"Simple",
"Simple",
vec![],
"http://hl7.org/fhir/StructureDefinition/Simple".to_string(),
false,
);
let impl_code = ValidationTraitGenerator::generate_trait_impl(&structure_def);
assert_eq!(impl_code, "");
}
#[test]
fn test_generate_validation_module() {
let module_code = ValidationTraitGenerator::generate_validation_module();
assert!(module_code.contains("//! Validation support for FHIR resources"));
assert!(module_code.contains("pub trait ValidatableResource"));
assert!(module_code.contains("rh_foundation::Invariant")); assert!(!module_code.contains("use rh_foundation::Invariant")); }
#[test]
fn test_trait_impl_base_resource() {
let constraints = vec![ElementConstraint {
key: "res-1".to_string(),
severity: "error".to_string(),
human: "Resource constraint".to_string(),
expression: Some("id.exists()".to_string()),
xpath: None,
source: None,
}];
let structure_def = create_test_structure_def(
"Observation",
"Observation",
constraints,
"http://hl7.org/fhir/StructureDefinition/Observation".to_string(),
false,
);
let impl_code = ValidationTraitGenerator::generate_trait_impl(&structure_def);
assert!(!impl_code.contains("fn profile_url()"));
assert!(impl_code.contains("impl crate::validation::ValidatableResource for Observation"));
}
}