rh_codegen/generators/
validation_trait_generator.rs1use crate::fhir_types::StructureDefinition;
7use crate::invariants;
8
9pub struct ValidationTraitGenerator;
11
12impl ValidationTraitGenerator {
13 pub fn generate_trait_definition() -> String {
31 let mut code = String::new();
32
33 code.push_str("/// Trait for FHIR types that can be validated using invariants\n");
34 code.push_str("///\n");
35 code.push_str("/// This trait provides access to the invariants (constraints) defined\n");
36 code.push_str("/// in the FHIR specification for this resource or datatype.\n");
37 code.push_str("pub trait ValidatableResource {\n");
38 code.push_str(" /// Returns the FHIR resource type name\n");
39 code.push_str(" fn resource_type(&self) -> &'static str;\n");
40 code.push('\n');
41 code.push_str(" /// Returns the invariants (constraints) for this resource/datatype\n");
42 code.push_str(" ///\n");
43 code.push_str(" /// These are the FHIRPath expressions that must evaluate to true\n");
44 code.push_str(" /// for the resource to be considered valid.\n");
45 code.push_str(" fn invariants() -> &'static [rh_foundation::Invariant];\n");
46 code.push('\n');
47 code.push_str(" /// Returns the required bindings for this resource/datatype\n");
48 code.push_str(" ///\n");
49 code.push_str(" /// These are the ValueSet bindings with \"required\" strength that\n");
50 code.push_str(" /// must be validated at runtime.\n");
51 code.push_str(" fn bindings() -> &'static [rh_foundation::ElementBinding] {\n");
52 code.push_str(" &[]\n");
53 code.push_str(" }\n");
54 code.push('\n');
55 code.push_str(" /// Returns the cardinality constraints for this resource/datatype\n");
56 code.push_str(" ///\n");
57 code.push_str(
58 " /// These define the minimum and maximum occurrences allowed for each element.\n",
59 );
60 code.push_str(" fn cardinalities() -> &'static [rh_foundation::ElementCardinality] {\n");
61 code.push_str(" &[]\n");
62 code.push_str(" }\n");
63 code.push('\n');
64 code.push_str(" /// Returns the profile URL if this is a profile, None otherwise\n");
65 code.push_str(" fn profile_url() -> Option<&'static str> {\n");
66 code.push_str(" None\n");
67 code.push_str(" }\n");
68 code.push_str("}\n");
69
70 code
71 }
72
73 pub fn generate_trait_impl(structure_def: &StructureDefinition) -> String {
98 let invariants = invariants::extract_invariants(structure_def);
99 let bindings = crate::bindings::extract_required_bindings(structure_def);
100
101 if invariants.is_empty() && bindings.is_empty() {
103 return String::new();
104 }
105
106 let struct_name = crate::naming::Naming::struct_name(structure_def);
107 let resource_type = &structure_def.base_type;
108
109 let mut code = String::new();
110
111 code.push_str(&format!(
112 "impl crate::validation::ValidatableResource for {struct_name} {{\n"
113 ));
114 code.push_str(" fn resource_type(&self) -> &'static str {\n");
115 code.push_str(&format!(" \"{resource_type}\"\n"));
116 code.push_str(" }\n");
117 code.push('\n');
118 code.push_str(" fn invariants() -> &'static [rh_foundation::Invariant] {\n");
119 if invariants.is_empty() {
120 code.push_str(" &[]\n");
121 } else {
122 code.push_str(" &INVARIANTS\n");
123 }
124 code.push_str(" }\n");
125
126 if !bindings.is_empty() {
128 code.push('\n');
129 code.push_str(" fn bindings() -> &'static [rh_foundation::ElementBinding] {\n");
130 code.push_str(" &BINDINGS\n");
131 code.push_str(" }\n");
132 }
133
134 if structure_def.kind == "resource" || structure_def.kind == "complex-type" {
137 code.push('\n');
138 code.push_str(
139 " fn cardinalities() -> &'static [rh_foundation::ElementCardinality] {\n",
140 );
141 code.push_str(" &CARDINALITIES\n");
142 code.push_str(" }\n");
143 }
144
145 if structure_def.base_definition.is_some() && !structure_def.is_abstract {
149 let url = &structure_def.url;
150 code.push('\n');
151 code.push_str(" fn profile_url() -> Option<&'static str> {\n");
152 code.push_str(&format!(" Some(\"{url}\")\n"));
153 code.push_str(" }\n");
154 }
155
156 code.push_str("}\n");
157
158 code
159 }
160
161 pub fn generate_validation_module() -> String {
165 let mut code = String::new();
166
167 code.push_str("//! Validation support for FHIR resources\n");
168 code.push_str("//!\n");
169 code.push_str(
170 "//! This module provides traits for validating FHIR resources using invariants.\n",
171 );
172 code.push('\n');
173 code.push_str(&Self::generate_trait_definition());
176
177 code
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use crate::fhir_types::{ElementConstraint, ElementDefinition, StructureDefinitionSnapshot};
185
186 fn create_test_structure_def(
187 name: &str,
188 base_type: &str,
189 constraints: Vec<ElementConstraint>,
190 url: String,
191 is_profile: bool,
192 ) -> StructureDefinition {
193 let element = ElementDefinition {
194 id: Some(base_type.to_string()),
195 path: base_type.to_string(),
196 element_type: None,
197 min: Some(0),
198 max: Some("*".to_string()),
199 short: None,
200 definition: None,
201 fixed: None,
202 pattern: None,
203 binding: None,
204 constraint: Some(constraints),
205 };
206
207 StructureDefinition {
208 resource_type: "StructureDefinition".to_string(),
209 id: name.to_string(),
210 url,
211 version: None,
212 name: name.to_string(),
213 title: None,
214 status: "active".to_string(),
215 description: None,
216 purpose: None,
217 kind: "resource".to_string(),
218 is_abstract: false,
219 base_type: base_type.to_string(),
220 base_definition: if is_profile {
221 Some(format!(
222 "http://hl7.org/fhir/StructureDefinition/{base_type}"
223 ))
224 } else {
225 None
226 },
227 snapshot: Some(StructureDefinitionSnapshot {
228 element: vec![element],
229 }),
230 differential: None,
231 }
232 }
233
234 #[test]
235 fn test_generate_trait_definition() {
236 let trait_def = ValidationTraitGenerator::generate_trait_definition();
237
238 assert!(trait_def.contains("pub trait ValidatableResource"));
239 assert!(trait_def.contains("fn resource_type(&self) -> &'static str"));
240 assert!(trait_def.contains("fn invariants() -> &'static [rh_foundation::Invariant]"));
241 assert!(trait_def.contains("fn profile_url() -> Option<&'static str>"));
242 assert!(trait_def.contains("None"));
243 }
244
245 #[test]
246 fn test_generate_trait_impl_with_invariants() {
247 let constraints = vec![ElementConstraint {
248 key: "pat-1".to_string(),
249 severity: "error".to_string(),
250 human: "Test constraint".to_string(),
251 expression: Some("name.exists()".to_string()),
252 xpath: None,
253 source: None,
254 }];
255
256 let structure_def = create_test_structure_def(
257 "Patient",
258 "Patient",
259 constraints,
260 "http://hl7.org/fhir/StructureDefinition/Patient".to_string(),
261 false,
262 );
263
264 let impl_code = ValidationTraitGenerator::generate_trait_impl(&structure_def);
265
266 assert!(impl_code.contains("impl crate::validation::ValidatableResource for Patient"));
267 assert!(impl_code.contains("fn resource_type(&self) -> &'static str"));
268 assert!(impl_code.contains("\"Patient\""));
269 assert!(impl_code.contains("fn invariants() -> &'static [rh_foundation::Invariant]"));
270 assert!(impl_code.contains("&INVARIANTS"));
271 }
272
273 #[test]
274 fn test_generate_trait_impl_with_profile() {
275 let constraints = vec![ElementConstraint {
276 key: "prof-1".to_string(),
277 severity: "warning".to_string(),
278 human: "Profile constraint".to_string(),
279 expression: Some("identifier.exists()".to_string()),
280 xpath: None,
281 source: None,
282 }];
283
284 let structure_def = create_test_structure_def(
285 "USCorePatient",
286 "Patient",
287 constraints,
288 "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient".to_string(),
289 true,
290 );
291
292 let impl_code = ValidationTraitGenerator::generate_trait_impl(&structure_def);
293
294 assert!(impl_code.contains("fn profile_url() -> Option<&'static str>"));
295 assert!(impl_code
296 .contains("Some(\"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient\")"));
297 }
298
299 #[test]
300 fn test_generate_trait_impl_no_invariants() {
301 let structure_def = create_test_structure_def(
302 "Simple",
303 "Simple",
304 vec![],
305 "http://hl7.org/fhir/StructureDefinition/Simple".to_string(),
306 false,
307 );
308
309 let impl_code = ValidationTraitGenerator::generate_trait_impl(&structure_def);
310
311 assert_eq!(impl_code, "");
312 }
313
314 #[test]
315 fn test_generate_validation_module() {
316 let module_code = ValidationTraitGenerator::generate_validation_module();
317
318 assert!(module_code.contains("//! Validation support for FHIR resources"));
319 assert!(module_code.contains("pub trait ValidatableResource"));
320 assert!(module_code.contains("rh_foundation::Invariant")); assert!(!module_code.contains("use rh_foundation::Invariant")); }
323
324 #[test]
325 fn test_trait_impl_base_resource() {
326 let constraints = vec![ElementConstraint {
327 key: "res-1".to_string(),
328 severity: "error".to_string(),
329 human: "Resource constraint".to_string(),
330 expression: Some("id.exists()".to_string()),
331 xpath: None,
332 source: None,
333 }];
334
335 let structure_def = create_test_structure_def(
336 "Observation",
337 "Observation",
338 constraints,
339 "http://hl7.org/fhir/StructureDefinition/Observation".to_string(),
340 false,
341 );
342
343 let impl_code = ValidationTraitGenerator::generate_trait_impl(&structure_def);
344
345 assert!(!impl_code.contains("fn profile_url()"));
347 assert!(impl_code.contains("impl crate::validation::ValidatableResource for Observation"));
348 }
349}