1#![allow(dead_code)] use std::collections::HashMap;
9use std::path::Path;
10use std::sync::{Arc, Mutex};
11
12use crate::config::CodegenConfig;
13use crate::fhir_types::StructureDefinition;
14use crate::generators::token_generator::TokenGenerator;
15#[cfg(test)]
16use crate::generators::ImportManager;
17use crate::generators::{
18 EnumGenerator, FieldGenerator, FileGenerator, FileIoManager, NestedStructGenerator,
19 PrimitiveGenerator, StructGenerator, TraitGenerator, TypeRegistry, TypeUtilities,
20};
21use crate::rust_types::{RustEnum, RustStruct, RustTrait};
22use crate::value_sets::ValueSetManager;
23use crate::CodegenResult;
24
25pub use crate::generators::file_generator::FhirTypeCategory;
27
28pub type TypeCacheKey = String;
30
31pub struct CodeGenerator {
36 config: CodegenConfig,
37 type_cache: Arc<Mutex<HashMap<TypeCacheKey, RustStruct>>>,
39 enum_cache: Arc<Mutex<HashMap<String, RustEnum>>>,
41 value_set_manager: Arc<Mutex<ValueSetManager>>,
43 token_generator: TokenGenerator,
45}
46
47impl CodeGenerator {
48 pub fn new(config: CodegenConfig) -> Self {
50 let value_set_manager = ValueSetManager::new();
51 let token_generator = TokenGenerator::new();
52
53 Self {
54 config,
55 type_cache: Arc::new(Mutex::new(HashMap::new())),
56 enum_cache: Arc::new(Mutex::new(HashMap::new())),
57 value_set_manager: Arc::new(Mutex::new(value_set_manager)),
58 token_generator,
59 }
60 }
61
62 pub fn new_with_value_set_directory<P: AsRef<Path>>(
64 config: CodegenConfig,
65 value_set_dir: P,
66 ) -> Self {
67 let value_set_manager = ValueSetManager::new_with_directory(value_set_dir);
68 let token_generator = TokenGenerator::new();
69
70 Self {
71 config,
72 type_cache: Arc::new(Mutex::new(HashMap::new())),
73 enum_cache: Arc::new(Mutex::new(HashMap::new())),
74 value_set_manager: Arc::new(Mutex::new(value_set_manager)),
75 token_generator,
76 }
77 }
78
79 pub fn load_structure_definition<P: AsRef<Path>>(
81 &self,
82 path: P,
83 ) -> CodegenResult<StructureDefinition> {
84 FileIoManager::load_structure_definition(path)
85 }
86
87 pub fn generate_struct(
89 &mut self,
90 structure_def: &StructureDefinition,
91 ) -> CodegenResult<RustStruct> {
92 TypeRegistry::register_from_structure_definition(structure_def);
94
95 let mut type_cache = self
96 .type_cache
97 .lock()
98 .expect("codegen bug: type_cache lock poisoned");
99 let mut value_set_manager = self
100 .value_set_manager
101 .lock()
102 .expect("codegen bug: value_set_manager lock poisoned");
103 let mut struct_generator =
104 StructGenerator::new(&self.config, &mut type_cache, &mut value_set_manager);
105 let rust_struct = struct_generator.generate_struct(structure_def)?;
106
107 Ok(rust_struct)
108 }
109
110 pub fn generate_trait(
112 &mut self,
113 structure_def: &StructureDefinition,
114 ) -> CodegenResult<Vec<RustTrait>> {
115 if TypeUtilities::is_example_structure_definition(structure_def) {
116 return Err(crate::CodegenError::Generation {
117 message: format!(
118 "Skipping example StructureDefinition '{}'",
119 structure_def.name
120 ),
121 });
122 }
123
124 let crate_lib_name = self
125 .config
126 .crate_name
127 .as_deref()
128 .map(|n| n.replace('-', "_"))
129 .unwrap_or_else(|| "hl7_fhir_r4_core".to_string());
130 let mut trait_generator = TraitGenerator::new_with_crate_name(crate_lib_name);
131 let mut traits = Vec::new();
132 let categories = ["Accessors", "Mutators", "Existence"];
133
134 for category in &categories {
135 let rust_trait = trait_generator.generate_trait(structure_def, category)?;
136 traits.push(rust_trait);
137 }
138
139 Ok(traits)
140 }
141
142 fn generate_primitive_type_struct(
144 &mut self,
145 structure_def: &StructureDefinition,
146 rust_struct: RustStruct,
147 ) -> CodegenResult<RustStruct> {
148 let mut type_cache = self
149 .type_cache
150 .lock()
151 .expect("codegen bug: type_cache lock poisoned");
152 let mut primitive_generator = PrimitiveGenerator::new(&self.config, &mut type_cache);
153 primitive_generator.generate_primitive_type_struct(structure_def, rust_struct)
154 }
155
156 fn generate_primitive_type_alias(
158 &self,
159 structure_def: &StructureDefinition,
160 ) -> CodegenResult<crate::rust_types::RustTypeAlias> {
161 let mut temp_cache = HashMap::new();
162 let primitive_generator = PrimitiveGenerator::new(&self.config, &mut temp_cache);
163 primitive_generator.generate_primitive_type_alias(structure_def)
164 }
165
166 fn generate_primitive_element_struct(
168 &mut self,
169 structure_def: &StructureDefinition,
170 ) -> CodegenResult<RustStruct> {
171 let mut type_cache = self
172 .type_cache
173 .lock()
174 .expect("codegen bug: type_cache lock poisoned");
175 let mut primitive_generator = PrimitiveGenerator::new(&self.config, &mut type_cache);
176 primitive_generator.generate_primitive_element_struct(structure_def)
177 }
178
179 fn generate_nested_struct(
181 &mut self,
182 parent_struct_name: &str,
183 nested_field_name: &str,
184 nested_elements: &[crate::fhir_types::ElementDefinition],
185 parent_structure_def: &StructureDefinition,
186 ) -> CodegenResult<Option<crate::rust_types::RustStruct>> {
187 let mut type_cache = self
188 .type_cache
189 .lock()
190 .expect("codegen bug: type_cache lock poisoned");
191 let mut value_set_manager = self
192 .value_set_manager
193 .lock()
194 .expect("codegen bug: value_set_manager lock poisoned");
195 let mut nested_struct_generator =
196 NestedStructGenerator::new(&self.config, &mut type_cache, &mut value_set_manager);
197 nested_struct_generator.generate_nested_struct(
198 parent_struct_name,
199 nested_field_name,
200 nested_elements,
201 parent_structure_def,
202 )
203 }
204
205 fn create_field_from_element(
207 &mut self,
208 element: &crate::fhir_types::ElementDefinition,
209 ) -> CodegenResult<Option<crate::rust_types::RustField>> {
210 let type_cache = self
211 .type_cache
212 .lock()
213 .expect("codegen bug: type_cache lock poisoned");
214 let mut value_set_manager = self
215 .value_set_manager
216 .lock()
217 .expect("codegen bug: value_set_manager lock poisoned");
218 let mut field_generator =
219 FieldGenerator::new(&self.config, &type_cache, &mut value_set_manager);
220 field_generator.create_field_from_element(element)
221 }
222
223 fn to_rust_field_name(&self, name: &str) -> String {
225 crate::naming::Naming::field_name(name)
226 }
227
228 pub fn generate_to_organized_directories<P: AsRef<Path>>(
230 &mut self,
231 structure_def: &StructureDefinition,
232 base_output_dir: P,
233 ) -> CodegenResult<()> {
234 let rust_struct = self.generate_struct(structure_def)?;
235 let nested_structs = {
236 let type_cache = self
237 .type_cache
238 .lock()
239 .expect("codegen bug: type_cache lock poisoned");
240 FileIoManager::collect_nested_structs(&rust_struct.name, &type_cache)
241 };
242
243 let file_io_manager = FileIoManager::new(&self.config, &self.token_generator);
244 file_io_manager.generate_to_organized_directories(
245 structure_def,
246 base_output_dir,
247 &rust_struct,
248 &nested_structs,
249 )
250 }
251
252 pub fn generate_trait_to_organized_directory<P: AsRef<Path>>(
254 &mut self,
255 structure_def: &StructureDefinition,
256 base_output_dir: P,
257 ) -> CodegenResult<()> {
258 let rust_traits = self.generate_trait(structure_def)?;
259
260 let file_io_manager = FileIoManager::new(&self.config, &self.token_generator);
261 file_io_manager.generate_traits_to_organized_directory(
262 structure_def,
263 base_output_dir.as_ref(),
264 &rust_traits,
265 )
266 }
267
268 pub fn classify_fhir_structure_def(
270 &self,
271 structure_def: &StructureDefinition,
272 ) -> FhirTypeCategory {
273 let file_generator = FileGenerator::new(&self.config, &self.token_generator);
274 file_generator.classify_fhir_structure_def(structure_def)
275 }
276
277 fn is_fhir_datatype(&self, name: &str) -> bool {
279 TypeUtilities::is_fhir_datatype(name)
280 }
281
282 pub fn generate_to_file<P: AsRef<Path>>(
284 &mut self,
285 structure_def: &StructureDefinition,
286 output_path: P,
287 ) -> CodegenResult<()> {
288 if structure_def.kind == "primitive-type" {
289 let empty_struct = RustStruct::new("".to_string());
291 let nested_structs = vec![];
292 let file_io_manager = FileIoManager::new(&self.config, &self.token_generator);
293 file_io_manager.generate_to_file(
294 structure_def,
295 output_path,
296 &empty_struct,
297 &nested_structs,
298 )
299 } else {
300 let rust_struct = self.generate_struct(structure_def)?;
302 let nested_structs = {
303 let type_cache = self
304 .type_cache
305 .lock()
306 .expect("codegen bug: type_cache lock poisoned");
307 FileIoManager::collect_nested_structs(&rust_struct.name, &type_cache)
308 };
309 let file_io_manager = FileIoManager::new(&self.config, &self.token_generator);
310
311 file_io_manager.generate_to_file(
312 structure_def,
313 output_path,
314 &rust_struct,
315 &nested_structs,
316 )
317 }
318 }
319
320 pub fn generate_trait_to_file<P: AsRef<Path>>(
322 &mut self,
323 structure_def: &StructureDefinition,
324 output_path: P,
325 ) -> CodegenResult<()> {
326 let rust_traits = self.generate_trait(structure_def)?;
328
329 let file_io_manager = FileIoManager::new(&self.config, &self.token_generator);
331
332 file_io_manager.generate_traits_to_file(
334 structure_def,
335 output_path.as_ref(),
336 &rust_traits,
337 )?;
338
339 Ok(())
340 }
341
342 pub fn pre_register_value_set_enums<P: AsRef<Path>>(
345 &mut self,
346 package_dir: P,
347 ) -> CodegenResult<()> {
348 let package_path = package_dir.as_ref();
349
350 if !package_path.exists() {
352 return Ok(()); }
354
355 let entries = match std::fs::read_dir(package_path) {
356 Ok(entries) => entries,
357 Err(_) => return Ok(()), };
359
360 for entry in entries {
361 let entry = match entry {
362 Ok(entry) => entry,
363 Err(_) => continue, };
365
366 let path = entry.path();
367 if !path.is_file() || path.extension().is_none_or(|ext| ext != "json") {
368 continue;
369 }
370
371 let content = match std::fs::read_to_string(&path) {
373 Ok(content) => content,
374 Err(_) => continue, };
376
377 let json_value: serde_json::Value = match serde_json::from_str(&content) {
378 Ok(value) => value,
379 Err(_) => continue, };
381
382 if json_value.get("resourceType").and_then(|v| v.as_str()) != Some("ValueSet") {
384 continue;
385 }
386
387 if let Some(url) = json_value.get("url").and_then(|v| v.as_str()) {
389 let enum_name = {
391 let value_set_manager = self
392 .value_set_manager
393 .lock()
394 .expect("codegen bug: value_set_manager lock poisoned");
395 value_set_manager.generate_enum_name(url)
396 };
397
398 crate::generators::type_registry::TypeRegistry::register_type_classification_only(
400 &enum_name,
401 crate::generators::type_registry::TypeClassification::ValueSetEnum,
402 );
403 }
404 }
405
406 Ok(())
407 }
408
409 pub fn generate_enum_files<P: AsRef<Path>>(&mut self, enums_dir: P) -> CodegenResult<()> {
411 let mut value_set_manager = self
412 .value_set_manager
413 .lock()
414 .expect("codegen bug: value_set_manager lock poisoned");
415 let mut enum_cache = self
416 .enum_cache
417 .lock()
418 .expect("codegen bug: enum_cache lock poisoned");
419 let enum_generator = EnumGenerator::new(&mut value_set_manager, &mut enum_cache);
420 let token_generator = TokenGenerator::new();
421 let file_generator = FileGenerator::new(&self.config, &token_generator);
422
423 file_generator.generate_enum_files(enums_dir, &enum_generator)
424 }
425
426 pub fn generate_enums_mod_file<P: AsRef<Path>>(&mut self, enums_dir: P) -> CodegenResult<()> {
428 let mut value_set_manager = self
429 .value_set_manager
430 .lock()
431 .expect("codegen bug: value_set_manager lock poisoned");
432 let mut enum_cache = self
433 .enum_cache
434 .lock()
435 .expect("codegen bug: enum_cache lock poisoned");
436 let enum_generator = EnumGenerator::new(&mut value_set_manager, &mut enum_cache);
437 let file_generator = FileGenerator::new(&self.config, &self.token_generator);
438 file_generator.generate_enums_mod_file(enums_dir, &enum_generator)
439 }
440
441 pub fn generate_enum_for_value_set(
443 &mut self,
444 value_set_url: &str,
445 ) -> CodegenResult<Option<RustEnum>> {
446 let mut value_set_manager = self
447 .value_set_manager
448 .lock()
449 .expect("codegen bug: value_set_manager lock poisoned");
450 let mut enum_cache = self
451 .enum_cache
452 .lock()
453 .expect("codegen bug: enum_cache lock poisoned");
454 let mut enum_generator = EnumGenerator::new(&mut value_set_manager, &mut enum_cache);
455 let result = enum_generator.generate_enum_for_value_set(value_set_url)?;
456
457 Ok(result)
458 }
459
460 pub fn has_cached_enums(&self) -> bool {
462 let value_set_manager = self
463 .value_set_manager
464 .lock()
465 .expect("codegen bug: value_set_manager lock poisoned");
466 TypeUtilities::has_cached_enums(&value_set_manager)
467 }
468
469 pub fn to_filename(&self, structure_def: &StructureDefinition) -> String {
471 crate::naming::Naming::filename(structure_def)
472 }
473
474 pub fn pre_generate_base_definitions(&mut self, structure_defs: &[StructureDefinition]) {
481 let base_names = ["Element", "BackboneElement", "DomainResource", "Resource"];
482
483 let base_defs: Vec<_> = structure_defs
484 .iter()
485 .filter(|sd| base_names.contains(&sd.name.as_str()))
486 .collect();
487
488 for base_def in base_defs {
489 let name = base_def.name.clone();
490 match self.generate_struct(base_def) {
491 Ok(_) => tracing::debug!("Pre-generated base definition: {name}"),
492 Err(e) => tracing::warn!("Failed to pre-generate base definition {name}: {e}"),
493 }
494 }
495 }
496
497 pub fn generate_trait_file_from_trait<P: AsRef<Path>>(
499 &self,
500 rust_trait: &RustTrait,
501 output_path: P,
502 ) -> CodegenResult<()> {
503 let file_generator = FileGenerator::new(&self.config, &self.token_generator);
505 file_generator.generate_trait_file_from_trait(rust_trait, output_path)
506 }
507
508 pub fn type_cache_snapshot(&self) -> HashMap<TypeCacheKey, RustStruct> {
510 let cache = self
511 .type_cache
512 .lock()
513 .expect("codegen bug: type_cache lock poisoned");
514 cache.clone()
515 }
516
517 pub fn generate_structs_parallel(
531 &self,
532 structure_defs: &[StructureDefinition],
533 ) -> Vec<(String, CodegenResult<RustStruct>)> {
534 use rayon::prelude::*;
535
536 let config = self.config.clone();
537 let type_cache = Arc::clone(&self.type_cache);
538 let value_set_manager = Arc::clone(&self.value_set_manager);
539
540 structure_defs
541 .par_iter()
542 .map(|structure_def| {
543 let name = structure_def.name.clone();
544
545 if structure_def.kind == "logical" {
547 return (
548 name,
549 Err(crate::CodegenError::Generation {
550 message: format!("Skipping LogicalModel '{}'", structure_def.name),
551 }),
552 );
553 }
554 if structure_def.status == "retired" {
555 return (
556 name,
557 Err(crate::CodegenError::Generation {
558 message: format!("Skipping retired '{}'", structure_def.name),
559 }),
560 );
561 }
562
563 let mut type_cache_guard = type_cache
564 .lock()
565 .expect("codegen bug: type_cache lock poisoned");
566 let mut value_set_manager_guard = value_set_manager
567 .lock()
568 .expect("codegen bug: value_set_manager lock poisoned");
569
570 let mut struct_generator = StructGenerator::new(
571 &config,
572 &mut type_cache_guard,
573 &mut value_set_manager_guard,
574 );
575
576 let result = struct_generator.generate_struct(structure_def);
577 (name, result)
578 })
579 .collect()
580 }
581
582 pub fn write_all_generated_files<P: AsRef<Path>>(
588 &self,
589 structure_defs: &[StructureDefinition],
590 base_output_dir: P,
591 ) -> Vec<CodegenResult<()>> {
592 let base_dir = base_output_dir.as_ref();
593 let file_io_manager = FileIoManager::new(&self.config, &self.token_generator);
594 let type_cache = self
595 .type_cache
596 .lock()
597 .expect("codegen bug: type_cache lock poisoned");
598
599 let mut results = Vec::with_capacity(structure_defs.len());
600
601 for structure_def in structure_defs {
602 if structure_def.kind == "logical"
603 || structure_def.status == "retired"
604 || TypeUtilities::is_example_structure_definition(structure_def)
605 {
606 continue;
607 }
608
609 let struct_name = crate::naming::Naming::struct_name(structure_def);
610
611 if let Some(rust_struct) = type_cache.get(&struct_name) {
612 let nested_structs =
613 FileIoManager::collect_nested_structs(&struct_name, &type_cache);
614
615 let result = file_io_manager.generate_to_organized_directories(
616 structure_def,
617 base_dir,
618 rust_struct,
619 &nested_structs,
620 );
621 results.push(result);
622 }
623 }
624
625 results
626 }
627
628 pub fn write_all_trait_files<P: AsRef<Path>>(
632 &self,
633 structure_defs: &[StructureDefinition],
634 base_output_dir: P,
635 ) -> Vec<CodegenResult<()>> {
636 let base_dir = base_output_dir.as_ref();
637 let file_io_manager = FileIoManager::new(&self.config, &self.token_generator);
638 let mut results = Vec::with_capacity(structure_defs.len());
639
640 for structure_def in structure_defs {
641 if structure_def.kind == "logical"
642 || structure_def.status == "retired"
643 || TypeUtilities::is_example_structure_definition(structure_def)
644 {
645 continue;
646 }
647
648 let category = file_io_manager.classify_fhir_structure_def(structure_def);
649 if category != FhirTypeCategory::Resource && category != FhirTypeCategory::Profile {
650 continue;
651 }
652
653 let crate_lib_name = self
654 .config
655 .crate_name
656 .as_deref()
657 .map(|n| n.replace('-', "_"))
658 .unwrap_or_else(|| "hl7_fhir_r4_core".to_string());
659 let mut trait_generator = TraitGenerator::new_with_crate_name(crate_lib_name);
660
661 let categories = ["Accessors", "Mutators", "Existence"];
662 let mut traits = Vec::new();
663 for category in &categories {
664 match trait_generator.generate_trait(structure_def, category) {
665 Ok(t) => traits.push(t),
666 Err(e) => {
667 results.push(Err(e));
668 continue;
669 }
670 }
671 }
672
673 let result = file_io_manager.generate_traits_to_organized_directory(
674 structure_def,
675 base_dir,
676 &traits,
677 );
678 results.push(result);
679 }
680
681 results
682 }
683}
684
685#[cfg(test)]
686mod tests {
687 use super::*;
688
689 #[test]
690 fn test_to_valid_rust_identifier_conversion() {
691 assert_eq!(
693 crate::naming::Naming::to_rust_identifier("StructureDefinition"),
694 "StructureDefinition"
695 );
696 assert_eq!(
697 crate::naming::Naming::to_rust_identifier("Patient"),
698 "Patient"
699 );
700 assert_eq!(
701 crate::naming::Naming::to_rust_identifier("Observation"),
702 "Observation"
703 );
704 assert_eq!(
705 crate::naming::Naming::to_rust_identifier("CodeSystem"),
706 "CodeSystem"
707 );
708
709 assert_eq!(
711 crate::naming::Naming::to_rust_identifier("patient"),
712 "patient"
713 );
714
715 assert_eq!(
717 crate::naming::Naming::to_rust_identifier("Relative Date Criteria"),
718 "RelativeDateCriteria"
719 );
720 assert_eq!(
721 crate::naming::Naming::to_rust_identifier("Care Plan"),
722 "CarePlan"
723 );
724
725 assert_eq!(
727 crate::naming::Naming::to_rust_identifier("patient-name"),
728 "PatientName"
729 );
730 assert_eq!(
731 crate::naming::Naming::to_rust_identifier("patient_name"),
732 "patient_name"
733 );
734
735 assert_eq!(
737 crate::naming::Naming::to_rust_identifier("some-complex_name with.spaces"),
738 "SomeComplexNameWithSpaces"
739 );
740
741 assert_eq!(crate::naming::Naming::to_rust_identifier(""), "_");
743 assert_eq!(crate::naming::Naming::to_rust_identifier(" "), "_");
744 assert_eq!(crate::naming::Naming::to_rust_identifier("a"), "a");
745 }
746
747 #[test]
748 fn test_logical_model_skipping() {
749 use crate::fhir_types::StructureDefinition;
750
751 let config = CodegenConfig::default();
752 let mut generator = CodeGenerator::new(config);
753
754 let logical_model = StructureDefinition {
756 resource_type: "StructureDefinition".to_string(),
757 id: "test-logical-model".to_string(),
758 url: "http://example.org/fhir/StructureDefinition/test-logical-model".to_string(),
759 name: "test-logical-model".to_string(),
760 title: Some("Test Logical Model".to_string()),
761 status: "active".to_string(),
762 kind: "logical".to_string(),
763 is_abstract: false,
764 description: Some("A test logical model".to_string()),
765 purpose: None,
766 base_type: "Base".to_string(),
767 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Base".to_string()),
768 version: None,
769 differential: None,
770 snapshot: None,
771 };
772
773 let result = generator.generate_struct(&logical_model);
775 assert!(result.is_err());
776
777 if let Err(crate::CodegenError::Generation { message }) = result {
778 assert!(message.contains("Skipping LogicalModel"));
779 assert!(message.contains("test-logical-model"));
780 } else {
781 panic!("Expected CodegenError::Generation for LogicalModel");
782 }
783 }
784
785 #[test]
786 fn test_nested_struct_generation() {
787 use crate::fhir_types::{
788 ElementDefinition, ElementType, StructureDefinition, StructureDefinitionDifferential,
789 };
790
791 let config = CodegenConfig::default();
792 let mut generator = CodeGenerator::new(config);
793
794 let bundle_structure = StructureDefinition {
796 resource_type: "StructureDefinition".to_string(),
797 id: "Bundle".to_string(),
798 url: "http://hl7.org/fhir/StructureDefinition/Bundle".to_string(),
799 name: "Bundle".to_string(),
800 title: Some("Bundle".to_string()),
801 status: "active".to_string(),
802 kind: "resource".to_string(),
803 is_abstract: false,
804 description: Some("A container for a collection of resources".to_string()),
805 purpose: None,
806 base_type: "Bundle".to_string(),
807 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Resource".to_string()),
808 version: None,
809 differential: Some(StructureDefinitionDifferential {
810 element: vec![
811 ElementDefinition {
812 id: Some("Bundle.entry".to_string()),
813 path: "Bundle.entry".to_string(),
814 short: Some("Entry in the bundle".to_string()),
815 definition: None,
816 min: Some(0),
817 max: Some("*".to_string()),
818 element_type: Some(vec![ElementType {
819 code: Some("BackboneElement".to_string()),
820 target_profile: None,
821 }]),
822 fixed: None,
823 pattern: None,
824 binding: None,
825 constraint: None,
826 },
827 ElementDefinition {
828 id: Some("Bundle.entry.resource".to_string()),
829 path: "Bundle.entry.resource".to_string(),
830 short: Some("A resource in the bundle".to_string()),
831 definition: None,
832 min: Some(0),
833 max: Some("1".to_string()),
834 element_type: Some(vec![ElementType {
835 code: Some("Resource".to_string()),
836 target_profile: None,
837 }]),
838 fixed: None,
839 pattern: None,
840 binding: None,
841 constraint: None,
842 },
843 ],
844 }),
845 snapshot: None,
846 };
847
848 let result = generator.generate_struct(&bundle_structure);
850 assert!(result.is_ok());
851
852 let bundle_struct = result.unwrap();
853
854 assert_eq!(bundle_struct.name, "Bundle");
856
857 let entry_field = bundle_struct.fields.iter().find(|f| f.name == "entry");
859 assert!(entry_field.is_some(), "Bundle should have an entry field");
860
861 assert!(
863 generator.type_cache_snapshot().contains_key("BundleEntry"),
864 "BundleEntry struct should be generated"
865 );
866
867 let bundle_entry_struct = generator
868 .type_cache_snapshot()
869 .get("BundleEntry")
870 .unwrap()
871 .clone();
872 assert_eq!(bundle_entry_struct.name, "BundleEntry");
873
874 let resource_field = bundle_entry_struct
876 .fields
877 .iter()
878 .find(|f| f.name == "resource");
879 assert!(
880 resource_field.is_some(),
881 "BundleEntry should have a resource field"
882 );
883 }
884
885 #[test]
886 fn test_primitive_type_naming() {
887 use crate::fhir_types::StructureDefinition;
888
889 let primitive_structure = StructureDefinition {
891 resource_type: "StructureDefinition".to_string(),
892 id: "string".to_string(),
893 url: "http://hl7.org/fhir/StructureDefinition/string".to_string(),
894 name: "string".to_string(),
895 title: Some("string".to_string()),
896 status: "active".to_string(),
897 kind: "primitive-type".to_string(),
898 is_abstract: false,
899 description: Some("A sequence of Unicode characters".to_string()),
900 purpose: None,
901 base_type: "string".to_string(),
902 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Element".to_string()),
903 version: None,
904 differential: None,
905 snapshot: None,
906 };
907
908 let struct_name = crate::naming::Naming::struct_name(&primitive_structure);
910 assert_eq!(
911 struct_name, "string",
912 "Primitive type 'string' should not be capitalized"
913 );
914
915 let filename = crate::naming::Naming::filename(&primitive_structure);
916 assert_eq!(
917 filename, "string.rs",
918 "Primitive type filename should not be capitalized"
919 );
920
921 let boolean_structure = StructureDefinition {
923 resource_type: "StructureDefinition".to_string(),
924 id: "boolean".to_string(),
925 url: "http://hl7.org/fhir/StructureDefinition/boolean".to_string(),
926 name: "boolean".to_string(),
927 title: Some("boolean".to_string()),
928 status: "active".to_string(),
929 kind: "primitive-type".to_string(),
930 is_abstract: false,
931 description: Some("Value of 'true' or 'false'".to_string()),
932 purpose: None,
933 base_type: "boolean".to_string(),
934 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Element".to_string()),
935 version: None,
936 differential: None,
937 snapshot: None,
938 };
939
940 let struct_name = crate::naming::Naming::struct_name(&boolean_structure);
941 assert_eq!(
942 struct_name, "boolean",
943 "Primitive type 'boolean' should not be capitalized"
944 );
945
946 let complex_structure = StructureDefinition {
948 resource_type: "StructureDefinition".to_string(),
949 id: "Period".to_string(),
950 url: "http://hl7.org/fhir/StructureDefinition/Period".to_string(),
951 name: "Period".to_string(),
952 title: Some("Period".to_string()),
953 status: "active".to_string(),
954 kind: "complex-type".to_string(),
955 is_abstract: false,
956 description: Some("A time period defined by a start and end date".to_string()),
957 purpose: None,
958 base_type: "Period".to_string(),
959 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Element".to_string()),
960 version: None,
961 differential: None,
962 snapshot: None,
963 };
964
965 let struct_name = crate::naming::Naming::struct_name(&complex_structure);
966 assert_eq!(
967 struct_name, "Period",
968 "Complex type 'Period' should be capitalized"
969 );
970 }
971
972 #[test]
973 fn test_primitive_type_generation() {
974 use crate::fhir_types::StructureDefinition;
975 use crate::rust_types::RustType;
976
977 let config = CodegenConfig::default();
978 let mut generator = CodeGenerator::new(config);
979
980 let uri_structure = StructureDefinition {
982 resource_type: "StructureDefinition".to_string(),
983 id: "uri".to_string(),
984 url: "http://hl7.org/fhir/StructureDefinition/uri".to_string(),
985 name: "uri".to_string(),
986 title: Some("uri".to_string()),
987 status: "active".to_string(),
988 kind: "primitive-type".to_string(),
989 is_abstract: false,
990 description: Some(
991 "String of characters used to identify a name or a resource".to_string(),
992 ),
993 purpose: None,
994 base_type: "uri".to_string(),
995 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Element".to_string()),
996 version: None,
997 differential: None,
998 snapshot: None,
999 };
1000
1001 let type_alias_result = generator.generate_primitive_type_alias(&uri_structure);
1003 assert!(
1004 type_alias_result.is_ok(),
1005 "Should generate primitive type alias successfully"
1006 );
1007
1008 let uri_type_alias = type_alias_result.unwrap();
1009
1010 assert_eq!(
1012 uri_type_alias.name, "UriType",
1013 "Primitive type alias should use PascalCase with Type suffix"
1014 );
1015
1016 if let RustType::String = uri_type_alias.target_type {
1018 } else {
1020 panic!(
1021 "URI primitive type alias should target String, got: {:?}",
1022 uri_type_alias.target_type
1023 );
1024 }
1025
1026 let boolean_structure = StructureDefinition {
1028 resource_type: "StructureDefinition".to_string(),
1029 id: "boolean".to_string(),
1030 url: "http://hl7.org/fhir/StructureDefinition/boolean".to_string(),
1031 name: "boolean".to_string(),
1032 title: Some("boolean".to_string()),
1033 status: "active".to_string(),
1034 kind: "primitive-type".to_string(),
1035 is_abstract: false,
1036 description: Some("Value of 'true' or 'false'".to_string()),
1037 purpose: None,
1038 base_type: "boolean".to_string(),
1039 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Element".to_string()),
1040 version: None,
1041 differential: None,
1042 snapshot: None,
1043 };
1044
1045 let type_alias_result = generator.generate_primitive_type_alias(&boolean_structure);
1046 assert!(
1047 type_alias_result.is_ok(),
1048 "Should generate boolean primitive type alias successfully"
1049 );
1050
1051 let boolean_type_alias = type_alias_result.unwrap();
1052
1053 if let RustType::Boolean = boolean_type_alias.target_type {
1055 } else {
1057 panic!(
1058 "Boolean primitive type alias should target bool, got: {:?}",
1059 boolean_type_alias.target_type
1060 );
1061 }
1062
1063 let element_struct = generator.generate_primitive_element_struct(&uri_structure);
1065 assert!(
1066 element_struct.is_ok(),
1067 "Should generate companion Element struct successfully"
1068 );
1069
1070 let element_struct = element_struct.unwrap();
1071 assert_eq!(
1072 element_struct.name, "_uri",
1073 "Companion Element struct should be named '_uri'"
1074 );
1075 assert_eq!(
1076 element_struct.base_definition,
1077 Some("Element".to_string()),
1078 "Companion Element struct should inherit from Element"
1079 );
1080 }
1081
1082 #[test]
1083 fn test_trait_generation() {
1084 use crate::fhir_types::{
1085 ElementDefinition, ElementType, StructureDefinition, StructureDefinitionDifferential,
1086 };
1087
1088 let config = CodegenConfig::default();
1089 let mut generator = CodeGenerator::new(config);
1090
1091 let patient_structure = StructureDefinition {
1093 resource_type: "StructureDefinition".to_string(),
1094 id: "Patient".to_string(),
1095 url: "http://hl7.org/fhir/StructureDefinition/Patient".to_string(),
1096 name: "Patient".to_string(),
1097 title: Some("Patient".to_string()),
1098 status: "active".to_string(),
1099 kind: "resource".to_string(),
1100 is_abstract: false,
1101 description: Some("Demographics and other administrative information about an individual receiving care.".to_string()),
1102 purpose: None,
1103 base_type: "DomainResource".to_string(),
1104 base_definition: Some("http://hl7.org/fhir/StructureDefinition/DomainResource".to_string()),
1105 version: None,
1106 differential: Some(StructureDefinitionDifferential {
1107 element: vec![
1108 ElementDefinition {
1109 id: Some("Patient.active".to_string()),
1110 path: "Patient.active".to_string(),
1111 short: Some("Whether this patient record is in active use".to_string()),
1112 definition: Some("Whether this patient record is in active use".to_string()),
1113 min: Some(0),
1114 max: Some("1".to_string()),
1115 element_type: Some(vec![ElementType {
1116 code: Some("boolean".to_string()),
1117 target_profile: None,
1118 }]),
1119 fixed: None,
1120 pattern: None,
1121 binding: None,
1122 constraint: None,
1123 },
1124 ElementDefinition {
1125 id: Some("Patient.name".to_string()),
1126 path: "Patient.name".to_string(),
1127 short: Some("A name associated with the patient".to_string()),
1128 definition: Some("A name associated with the patient".to_string()),
1129 min: Some(0),
1130 max: Some("*".to_string()),
1131 element_type: Some(vec![ElementType {
1132 code: Some("HumanName".to_string()),
1133 target_profile: None,
1134 }]),
1135 fixed: None,
1136 pattern: None,
1137 binding: None,
1138 constraint: None,
1139 },
1140 ],
1141 }),
1142 snapshot: None,
1143 };
1144
1145 let result = generator.generate_trait(&patient_structure);
1147 assert!(result.is_ok(), "Should generate Patient trait successfully");
1148
1149 let patient_traits = result.unwrap();
1150 let patient_trait = patient_traits
1151 .iter()
1152 .find(|t| t.name == "PatientAccessors")
1153 .expect("PatientAccessors trait not found");
1154
1155 assert_eq!(
1156 patient_trait.name, "PatientAccessors",
1157 "Trait should be named 'PatientAccessors'"
1158 );
1159
1160 assert!(
1162 patient_trait
1163 .super_traits
1164 .contains(&"DomainResourceAccessors".to_string()),
1165 "Patient trait should inherit from DomainResourceAccessors"
1166 );
1167
1168 let has_extensions = patient_trait.methods.iter().any(|m| m.name == "extensions");
1170 assert!(
1171 !has_extensions,
1172 "Patient trait should NOT have extensions method - it should be inherited from Resource"
1173 );
1174
1175 let has_narrative = patient_trait.methods.iter().any(|m| m.name == "narrative");
1176 assert!(
1177 !has_narrative,
1178 "Patient trait should NOT have narrative method - it should be inherited from DomainResource"
1179 );
1180
1181 let has_id = patient_trait.methods.iter().any(|m| m.name == "id");
1182 assert!(
1183 !has_id,
1184 "Patient trait should NOT have id method - it should be inherited from Resource"
1185 );
1186
1187 }
1190
1191 #[test]
1192 fn test_filename_generation() {
1193 let patient_structure = StructureDefinition {
1195 resource_type: "StructureDefinition".to_string(),
1196 id: "Patient".to_string(),
1197 url: "http://hl7.org/fhir/StructureDefinition/Patient".to_string(),
1198 name: "Patient".to_string(),
1199 title: Some("Patient".to_string()),
1200 status: "active".to_string(),
1201 kind: "resource".to_string(),
1202 is_abstract: false,
1203 description: Some("A patient resource".to_string()),
1204 purpose: None,
1205 base_type: "DomainResource".to_string(),
1206 base_definition: Some(
1207 "http://hl7.org/fhir/StructureDefinition/DomainResource".to_string(),
1208 ),
1209 version: None,
1210 differential: None,
1211 snapshot: None,
1212 };
1213
1214 let observation_structure = StructureDefinition {
1215 resource_type: "StructureDefinition".to_string(),
1216 id: "Observation".to_string(),
1217 url: "http://hl7.org/fhir/StructureDefinition/Observation".to_string(),
1218 name: "Observation".to_string(),
1219 title: Some("Observation".to_string()),
1220 status: "active".to_string(),
1221 kind: "resource".to_string(),
1222 is_abstract: false,
1223 description: Some("An observation resource".to_string()),
1224 purpose: None,
1225 base_type: "DomainResource".to_string(),
1226 base_definition: Some(
1227 "http://hl7.org/fhir/StructureDefinition/DomainResource".to_string(),
1228 ),
1229 version: None,
1230 differential: None,
1231 snapshot: None,
1232 };
1233
1234 let patient_struct_name = crate::naming::Naming::struct_name(&patient_structure);
1236 assert_eq!(patient_struct_name, "Patient");
1237
1238 let observation_struct_name = crate::naming::Naming::struct_name(&observation_structure);
1239 assert_eq!(observation_struct_name, "Observation");
1240
1241 let patient_filename = crate::naming::Naming::filename(&patient_structure);
1243 assert_eq!(patient_filename, "patient.rs");
1244
1245 let observation_filename = crate::naming::Naming::filename(&observation_structure);
1246 assert_eq!(observation_filename, "observation.rs");
1247
1248 let structure_definition = StructureDefinition {
1250 resource_type: "StructureDefinition".to_string(),
1251 id: "StructureDefinition".to_string(),
1252 url: "http://hl7.org/fhir/StructureDefinition/StructureDefinition".to_string(),
1253 name: "StructureDefinition".to_string(),
1254 title: Some("StructureDefinition".to_string()),
1255 status: "active".to_string(),
1256 kind: "resource".to_string(),
1257 is_abstract: false,
1258 description: Some("A structure definition".to_string()),
1259 purpose: None,
1260 base_type: "DomainResource".to_string(),
1261 base_definition: Some(
1262 "http://hl7.org/fhir/StructureDefinition/DomainResource".to_string(),
1263 ),
1264 version: None,
1265 differential: None,
1266 snapshot: None,
1267 };
1268
1269 let struct_def_struct_name = crate::naming::Naming::struct_name(&structure_definition);
1270 assert_eq!(struct_def_struct_name, "StructureDefinition");
1271
1272 let struct_def_filename = crate::naming::Naming::filename(&structure_definition);
1273 assert_eq!(struct_def_filename, "structure_definition.rs");
1274
1275 let enum_filename = crate::naming::Naming::enum_filename("AdministrativeGender");
1277 assert_eq!(enum_filename, "administrative_gender.rs");
1278
1279 let enum_module_name = crate::naming::Naming::module_name("AdministrativeGender");
1280 assert_eq!(enum_module_name, "administrative_gender");
1281 }
1282
1283 #[test]
1284 fn test_import_classification() {
1285 assert!(ImportManager::is_fhir_resource_type("DomainResource"));
1287 assert!(ImportManager::is_fhir_resource_type("Patient"));
1288 assert!(ImportManager::is_fhir_resource_type("ActivityDefinition"));
1289 assert!(!ImportManager::is_fhir_resource_type("Identifier"));
1290
1291 assert!(ImportManager::is_fhir_datatype("Identifier"));
1293 assert!(ImportManager::is_fhir_datatype("CodeableConcept"));
1294 assert!(ImportManager::is_fhir_datatype("Reference"));
1295 assert!(!ImportManager::is_fhir_datatype("DomainResource"));
1296
1297 assert_eq!(
1299 ImportManager::get_import_path_for_type("DomainResource"),
1300 "crate::resources::domain_resource::DomainResource"
1301 );
1302 assert_eq!(
1303 ImportManager::get_import_path_for_type("Identifier"),
1304 "crate::datatypes::identifier::Identifier"
1305 );
1306 assert_eq!(
1307 ImportManager::get_import_path_for_type("PublicationStatus"),
1308 "crate::bindings::publication_status::PublicationStatus"
1309 );
1310 }
1311}