1#![allow(dead_code)] use std::collections::HashMap;
9use std::path::Path;
10
11use crate::config::CodegenConfig;
12use crate::fhir_types::StructureDefinition;
13use crate::generators::token_generator::TokenGenerator;
14#[cfg(test)]
15use crate::generators::ImportManager;
16use crate::generators::{
17 EnumGenerator, FieldGenerator, FileGenerator, FileIoManager, NestedStructGenerator,
18 PrimitiveGenerator, StructGenerator, TraitGenerator, TypeRegistry, TypeUtilities,
19};
20use crate::rust_types::{RustEnum, RustStruct, RustTrait};
21use crate::value_sets::ValueSetManager;
22use crate::CodegenResult;
23
24pub use crate::generators::file_generator::FhirTypeCategory;
26
27pub struct CodeGenerator {
29 config: CodegenConfig,
30 type_cache: HashMap<String, RustStruct>,
32 enum_cache: HashMap<String, RustEnum>,
34 value_set_manager: ValueSetManager,
36 token_generator: TokenGenerator,
38}
39
40impl CodeGenerator {
41 pub fn new(config: CodegenConfig) -> Self {
43 let value_set_manager = ValueSetManager::new();
44 let token_generator = TokenGenerator::new();
45
46 Self {
47 config,
48 type_cache: HashMap::new(),
49 enum_cache: HashMap::new(),
50 value_set_manager,
51 token_generator,
52 }
53 }
54
55 pub fn new_with_value_set_directory<P: AsRef<Path>>(
57 config: CodegenConfig,
58 value_set_dir: P,
59 ) -> Self {
60 let value_set_manager = ValueSetManager::new_with_directory(value_set_dir);
61 let token_generator = TokenGenerator::new();
62
63 Self {
64 config,
65 type_cache: HashMap::new(),
66 enum_cache: HashMap::new(),
67 value_set_manager,
68 token_generator,
69 }
70 }
71
72 pub fn load_structure_definition<P: AsRef<Path>>(
74 &self,
75 path: P,
76 ) -> CodegenResult<StructureDefinition> {
77 FileIoManager::load_structure_definition(path)
78 }
79
80 pub fn generate_struct(
82 &mut self,
83 structure_def: &StructureDefinition,
84 ) -> CodegenResult<RustStruct> {
85 TypeRegistry::register_from_structure_definition(structure_def);
87
88 let mut struct_generator = StructGenerator::new(
89 &self.config,
90 &mut self.type_cache,
91 &mut self.value_set_manager,
92 );
93 let rust_struct = struct_generator.generate_struct(structure_def)?;
94
95 Ok(rust_struct)
96 }
97
98 pub fn generate_trait(
100 &mut self,
101 structure_def: &StructureDefinition,
102 ) -> CodegenResult<Vec<RustTrait>> {
103 let crate_lib_name = self
104 .config
105 .crate_name
106 .as_deref()
107 .map(|n| n.replace('-', "_"))
108 .unwrap_or_else(|| "hl7_fhir_r4_core".to_string());
109 let mut trait_generator = TraitGenerator::new_with_crate_name(crate_lib_name);
110 let mut traits = Vec::new();
111 let categories = ["Accessors", "Mutators", "Existence"];
112
113 for category in &categories {
114 let rust_trait = trait_generator.generate_trait(structure_def, category)?;
115 traits.push(rust_trait);
116 }
117
118 Ok(traits)
119 }
120
121 fn generate_primitive_type_struct(
123 &mut self,
124 structure_def: &StructureDefinition,
125 rust_struct: RustStruct,
126 ) -> CodegenResult<RustStruct> {
127 let mut primitive_generator = PrimitiveGenerator::new(&self.config, &mut self.type_cache);
128 primitive_generator.generate_primitive_type_struct(structure_def, rust_struct)
129 }
130
131 fn generate_primitive_type_alias(
133 &self,
134 structure_def: &StructureDefinition,
135 ) -> CodegenResult<crate::rust_types::RustTypeAlias> {
136 let mut temp_cache = HashMap::new();
137 let primitive_generator = PrimitiveGenerator::new(&self.config, &mut temp_cache);
138 primitive_generator.generate_primitive_type_alias(structure_def)
139 }
140
141 fn generate_primitive_element_struct(
143 &mut self,
144 structure_def: &StructureDefinition,
145 ) -> CodegenResult<RustStruct> {
146 let mut primitive_generator = PrimitiveGenerator::new(&self.config, &mut self.type_cache);
147 primitive_generator.generate_primitive_element_struct(structure_def)
148 }
149
150 fn generate_nested_struct(
152 &mut self,
153 parent_struct_name: &str,
154 nested_field_name: &str,
155 nested_elements: &[crate::fhir_types::ElementDefinition],
156 parent_structure_def: &StructureDefinition,
157 ) -> CodegenResult<Option<crate::rust_types::RustStruct>> {
158 let mut nested_struct_generator = NestedStructGenerator::new(
159 &self.config,
160 &mut self.type_cache,
161 &mut self.value_set_manager,
162 );
163 nested_struct_generator.generate_nested_struct(
164 parent_struct_name,
165 nested_field_name,
166 nested_elements,
167 parent_structure_def,
168 )
169 }
170
171 fn create_field_from_element(
173 &mut self,
174 element: &crate::fhir_types::ElementDefinition,
175 ) -> CodegenResult<Option<crate::rust_types::RustField>> {
176 let mut field_generator =
177 FieldGenerator::new(&self.config, &self.type_cache, &mut self.value_set_manager);
178 field_generator.create_field_from_element(element)
179 }
180
181 fn to_rust_field_name(&self, name: &str) -> String {
183 crate::naming::Naming::field_name(name)
184 }
185
186 pub fn generate_to_organized_directories<P: AsRef<Path>>(
188 &mut self,
189 structure_def: &StructureDefinition,
190 base_output_dir: P,
191 ) -> CodegenResult<()> {
192 let rust_struct = self.generate_struct(structure_def)?;
193 let nested_structs =
194 FileIoManager::collect_nested_structs(&rust_struct.name, &self.type_cache);
195
196 let file_io_manager = FileIoManager::new(&self.config, &self.token_generator);
197 file_io_manager.generate_to_organized_directories(
198 structure_def,
199 base_output_dir,
200 &rust_struct,
201 &nested_structs,
202 )
203 }
204
205 pub fn generate_trait_to_organized_directory<P: AsRef<Path>>(
207 &mut self,
208 structure_def: &StructureDefinition,
209 base_output_dir: P,
210 ) -> CodegenResult<()> {
211 let rust_traits = self.generate_trait(structure_def)?;
212
213 let file_io_manager = FileIoManager::new(&self.config, &self.token_generator);
214 file_io_manager.generate_traits_to_organized_directory(
215 structure_def,
216 base_output_dir.as_ref(),
217 &rust_traits,
218 )
219 }
220
221 pub fn classify_fhir_structure_def(
223 &self,
224 structure_def: &StructureDefinition,
225 ) -> FhirTypeCategory {
226 let file_generator = FileGenerator::new(&self.config, &self.token_generator);
227 file_generator.classify_fhir_structure_def(structure_def)
228 }
229
230 fn is_fhir_datatype(&self, name: &str) -> bool {
232 TypeUtilities::is_fhir_datatype(name)
233 }
234
235 pub fn generate_to_file<P: AsRef<Path>>(
237 &mut self,
238 structure_def: &StructureDefinition,
239 output_path: P,
240 ) -> CodegenResult<()> {
241 if structure_def.kind == "primitive-type" {
242 let empty_struct = RustStruct::new("".to_string());
244 let nested_structs = vec![];
245 let file_io_manager = FileIoManager::new(&self.config, &self.token_generator);
246 file_io_manager.generate_to_file(
247 structure_def,
248 output_path,
249 &empty_struct,
250 &nested_structs,
251 )
252 } else {
253 let rust_struct = self.generate_struct(structure_def)?;
255 let nested_structs =
256 FileIoManager::collect_nested_structs(&rust_struct.name, &self.type_cache);
257 let file_io_manager = FileIoManager::new(&self.config, &self.token_generator);
258
259 file_io_manager.generate_to_file(
260 structure_def,
261 output_path,
262 &rust_struct,
263 &nested_structs,
264 )
265 }
266 }
267
268 pub fn generate_trait_to_file<P: AsRef<Path>>(
270 &mut self,
271 structure_def: &StructureDefinition,
272 output_path: P,
273 ) -> CodegenResult<()> {
274 let rust_traits = self.generate_trait(structure_def)?;
276
277 let file_io_manager = FileIoManager::new(&self.config, &self.token_generator);
279
280 file_io_manager.generate_traits_to_file(
282 structure_def,
283 output_path.as_ref(),
284 &rust_traits,
285 )?;
286
287 Ok(())
288 }
289
290 pub fn pre_register_value_set_enums<P: AsRef<Path>>(
293 &mut self,
294 package_dir: P,
295 ) -> CodegenResult<()> {
296 let package_path = package_dir.as_ref();
297
298 if !package_path.exists() {
300 return Ok(()); }
302
303 let entries = match std::fs::read_dir(package_path) {
304 Ok(entries) => entries,
305 Err(_) => return Ok(()), };
307
308 for entry in entries {
309 let entry = match entry {
310 Ok(entry) => entry,
311 Err(_) => continue, };
313
314 let path = entry.path();
315 if !path.is_file() || path.extension().is_none_or(|ext| ext != "json") {
316 continue;
317 }
318
319 let content = match std::fs::read_to_string(&path) {
321 Ok(content) => content,
322 Err(_) => continue, };
324
325 let json_value: serde_json::Value = match serde_json::from_str(&content) {
326 Ok(value) => value,
327 Err(_) => continue, };
329
330 if json_value.get("resourceType").and_then(|v| v.as_str()) != Some("ValueSet") {
332 continue;
333 }
334
335 if let Some(url) = json_value.get("url").and_then(|v| v.as_str()) {
337 let enum_name = self.value_set_manager.generate_enum_name(url);
339
340 crate::generators::type_registry::TypeRegistry::register_type_classification_only(
342 &enum_name,
343 crate::generators::type_registry::TypeClassification::ValueSetEnum,
344 );
345 }
346 }
347
348 Ok(())
349 }
350
351 pub fn generate_enum_files<P: AsRef<Path>>(&mut self, enums_dir: P) -> CodegenResult<()> {
353 let enum_generator = EnumGenerator::new(&mut self.value_set_manager, &mut self.enum_cache);
354 let token_generator = crate::generators::token_generator::TokenGenerator::new();
355 let file_generator = FileGenerator::new(&self.config, &token_generator);
356
357 file_generator.generate_enum_files(enums_dir, &enum_generator)
358 }
359
360 pub fn generate_enums_mod_file<P: AsRef<Path>>(&self, enums_dir: P) -> CodegenResult<()> {
362 let mut value_set_manager = self.value_set_manager.clone(); let mut enum_cache = self.enum_cache.clone();
365 let enum_generator = EnumGenerator::new(&mut value_set_manager, &mut enum_cache);
366
367 let file_generator = FileGenerator::new(&self.config, &self.token_generator);
369 file_generator.generate_enums_mod_file(enums_dir, &enum_generator)
370 }
371
372 pub fn generate_enum_for_value_set(
374 &mut self,
375 value_set_url: &str,
376 ) -> CodegenResult<Option<RustEnum>> {
377 let mut enum_generator =
378 EnumGenerator::new(&mut self.value_set_manager, &mut self.enum_cache);
379 let result = enum_generator.generate_enum_for_value_set(value_set_url)?;
380
381 Ok(result)
382 }
383
384 pub fn has_cached_enums(&self) -> bool {
386 TypeUtilities::has_cached_enums(&self.value_set_manager)
387 }
388
389 pub fn to_filename(&self, structure_def: &StructureDefinition) -> String {
391 crate::naming::Naming::filename(structure_def)
392 }
393
394 pub fn generate_trait_file_from_trait<P: AsRef<Path>>(
396 &self,
397 rust_trait: &RustTrait,
398 output_path: P,
399 ) -> CodegenResult<()> {
400 let file_generator = FileGenerator::new(&self.config, &self.token_generator);
402 file_generator.generate_trait_file_from_trait(rust_trait, output_path)
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409
410 #[test]
411 fn test_to_valid_rust_identifier_conversion() {
412 assert_eq!(
414 crate::naming::Naming::to_rust_identifier("StructureDefinition"),
415 "StructureDefinition"
416 );
417 assert_eq!(
418 crate::naming::Naming::to_rust_identifier("Patient"),
419 "Patient"
420 );
421 assert_eq!(
422 crate::naming::Naming::to_rust_identifier("Observation"),
423 "Observation"
424 );
425 assert_eq!(
426 crate::naming::Naming::to_rust_identifier("CodeSystem"),
427 "CodeSystem"
428 );
429
430 assert_eq!(
432 crate::naming::Naming::to_rust_identifier("patient"),
433 "patient"
434 );
435
436 assert_eq!(
438 crate::naming::Naming::to_rust_identifier("Relative Date Criteria"),
439 "RelativeDateCriteria"
440 );
441 assert_eq!(
442 crate::naming::Naming::to_rust_identifier("Care Plan"),
443 "CarePlan"
444 );
445
446 assert_eq!(
448 crate::naming::Naming::to_rust_identifier("patient-name"),
449 "PatientName"
450 );
451 assert_eq!(
452 crate::naming::Naming::to_rust_identifier("patient_name"),
453 "patient_name"
454 );
455
456 assert_eq!(
458 crate::naming::Naming::to_rust_identifier("some-complex_name with.spaces"),
459 "SomeComplexNameWithSpaces"
460 );
461
462 assert_eq!(crate::naming::Naming::to_rust_identifier(""), "_");
464 assert_eq!(crate::naming::Naming::to_rust_identifier(" "), "_");
465 assert_eq!(crate::naming::Naming::to_rust_identifier("a"), "a");
466 }
467
468 #[test]
469 fn test_logical_model_skipping() {
470 use crate::fhir_types::StructureDefinition;
471
472 let config = CodegenConfig::default();
473 let mut generator = CodeGenerator::new(config);
474
475 let logical_model = StructureDefinition {
477 resource_type: "StructureDefinition".to_string(),
478 id: "test-logical-model".to_string(),
479 url: "http://example.org/fhir/StructureDefinition/test-logical-model".to_string(),
480 name: "test-logical-model".to_string(),
481 title: Some("Test Logical Model".to_string()),
482 status: "active".to_string(),
483 kind: "logical".to_string(),
484 is_abstract: false,
485 description: Some("A test logical model".to_string()),
486 purpose: None,
487 base_type: "Base".to_string(),
488 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Base".to_string()),
489 version: None,
490 differential: None,
491 snapshot: None,
492 };
493
494 let result = generator.generate_struct(&logical_model);
496 assert!(result.is_err());
497
498 if let Err(crate::CodegenError::Generation { message }) = result {
499 assert!(message.contains("Skipping LogicalModel"));
500 assert!(message.contains("test-logical-model"));
501 } else {
502 panic!("Expected CodegenError::Generation for LogicalModel");
503 }
504 }
505
506 #[test]
507 fn test_nested_struct_generation() {
508 use crate::fhir_types::{
509 ElementDefinition, ElementType, StructureDefinition, StructureDefinitionDifferential,
510 };
511
512 let config = CodegenConfig::default();
513 let mut generator = CodeGenerator::new(config);
514
515 let bundle_structure = StructureDefinition {
517 resource_type: "StructureDefinition".to_string(),
518 id: "Bundle".to_string(),
519 url: "http://hl7.org/fhir/StructureDefinition/Bundle".to_string(),
520 name: "Bundle".to_string(),
521 title: Some("Bundle".to_string()),
522 status: "active".to_string(),
523 kind: "resource".to_string(),
524 is_abstract: false,
525 description: Some("A container for a collection of resources".to_string()),
526 purpose: None,
527 base_type: "Bundle".to_string(),
528 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Resource".to_string()),
529 version: None,
530 differential: Some(StructureDefinitionDifferential {
531 element: vec![
532 ElementDefinition {
533 id: Some("Bundle.entry".to_string()),
534 path: "Bundle.entry".to_string(),
535 short: Some("Entry in the bundle".to_string()),
536 definition: None,
537 min: Some(0),
538 max: Some("*".to_string()),
539 element_type: Some(vec![ElementType {
540 code: Some("BackboneElement".to_string()),
541 target_profile: None,
542 }]),
543 fixed: None,
544 pattern: None,
545 binding: None,
546 constraint: None,
547 },
548 ElementDefinition {
549 id: Some("Bundle.entry.resource".to_string()),
550 path: "Bundle.entry.resource".to_string(),
551 short: Some("A resource in the bundle".to_string()),
552 definition: None,
553 min: Some(0),
554 max: Some("1".to_string()),
555 element_type: Some(vec![ElementType {
556 code: Some("Resource".to_string()),
557 target_profile: None,
558 }]),
559 fixed: None,
560 pattern: None,
561 binding: None,
562 constraint: None,
563 },
564 ],
565 }),
566 snapshot: None,
567 };
568
569 let result = generator.generate_struct(&bundle_structure);
571 assert!(result.is_ok());
572
573 let bundle_struct = result.unwrap();
574
575 assert_eq!(bundle_struct.name, "Bundle");
577
578 let entry_field = bundle_struct.fields.iter().find(|f| f.name == "entry");
580 assert!(entry_field.is_some(), "Bundle should have an entry field");
581
582 assert!(
584 generator.type_cache.contains_key("BundleEntry"),
585 "BundleEntry struct should be generated"
586 );
587
588 let bundle_entry_struct = generator.type_cache.get("BundleEntry").unwrap();
589 assert_eq!(bundle_entry_struct.name, "BundleEntry");
590
591 let resource_field = bundle_entry_struct
593 .fields
594 .iter()
595 .find(|f| f.name == "resource");
596 assert!(
597 resource_field.is_some(),
598 "BundleEntry should have a resource field"
599 );
600 }
601
602 #[test]
603 fn test_primitive_type_naming() {
604 use crate::fhir_types::StructureDefinition;
605
606 let primitive_structure = StructureDefinition {
608 resource_type: "StructureDefinition".to_string(),
609 id: "string".to_string(),
610 url: "http://hl7.org/fhir/StructureDefinition/string".to_string(),
611 name: "string".to_string(),
612 title: Some("string".to_string()),
613 status: "active".to_string(),
614 kind: "primitive-type".to_string(),
615 is_abstract: false,
616 description: Some("A sequence of Unicode characters".to_string()),
617 purpose: None,
618 base_type: "string".to_string(),
619 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Element".to_string()),
620 version: None,
621 differential: None,
622 snapshot: None,
623 };
624
625 let struct_name = crate::naming::Naming::struct_name(&primitive_structure);
627 assert_eq!(
628 struct_name, "string",
629 "Primitive type 'string' should not be capitalized"
630 );
631
632 let filename = crate::naming::Naming::filename(&primitive_structure);
633 assert_eq!(
634 filename, "string.rs",
635 "Primitive type filename should not be capitalized"
636 );
637
638 let boolean_structure = StructureDefinition {
640 resource_type: "StructureDefinition".to_string(),
641 id: "boolean".to_string(),
642 url: "http://hl7.org/fhir/StructureDefinition/boolean".to_string(),
643 name: "boolean".to_string(),
644 title: Some("boolean".to_string()),
645 status: "active".to_string(),
646 kind: "primitive-type".to_string(),
647 is_abstract: false,
648 description: Some("Value of 'true' or 'false'".to_string()),
649 purpose: None,
650 base_type: "boolean".to_string(),
651 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Element".to_string()),
652 version: None,
653 differential: None,
654 snapshot: None,
655 };
656
657 let struct_name = crate::naming::Naming::struct_name(&boolean_structure);
658 assert_eq!(
659 struct_name, "boolean",
660 "Primitive type 'boolean' should not be capitalized"
661 );
662
663 let complex_structure = StructureDefinition {
665 resource_type: "StructureDefinition".to_string(),
666 id: "Period".to_string(),
667 url: "http://hl7.org/fhir/StructureDefinition/Period".to_string(),
668 name: "Period".to_string(),
669 title: Some("Period".to_string()),
670 status: "active".to_string(),
671 kind: "complex-type".to_string(),
672 is_abstract: false,
673 description: Some("A time period defined by a start and end date".to_string()),
674 purpose: None,
675 base_type: "Period".to_string(),
676 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Element".to_string()),
677 version: None,
678 differential: None,
679 snapshot: None,
680 };
681
682 let struct_name = crate::naming::Naming::struct_name(&complex_structure);
683 assert_eq!(
684 struct_name, "Period",
685 "Complex type 'Period' should be capitalized"
686 );
687 }
688
689 #[test]
690 fn test_primitive_type_generation() {
691 use crate::fhir_types::StructureDefinition;
692 use crate::rust_types::RustType;
693
694 let config = CodegenConfig::default();
695 let mut generator = CodeGenerator::new(config);
696
697 let uri_structure = StructureDefinition {
699 resource_type: "StructureDefinition".to_string(),
700 id: "uri".to_string(),
701 url: "http://hl7.org/fhir/StructureDefinition/uri".to_string(),
702 name: "uri".to_string(),
703 title: Some("uri".to_string()),
704 status: "active".to_string(),
705 kind: "primitive-type".to_string(),
706 is_abstract: false,
707 description: Some(
708 "String of characters used to identify a name or a resource".to_string(),
709 ),
710 purpose: None,
711 base_type: "uri".to_string(),
712 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Element".to_string()),
713 version: None,
714 differential: None,
715 snapshot: None,
716 };
717
718 let type_alias_result = generator.generate_primitive_type_alias(&uri_structure);
720 assert!(
721 type_alias_result.is_ok(),
722 "Should generate primitive type alias successfully"
723 );
724
725 let uri_type_alias = type_alias_result.unwrap();
726
727 assert_eq!(
729 uri_type_alias.name, "UriType",
730 "Primitive type alias should use PascalCase with Type suffix"
731 );
732
733 if let RustType::String = uri_type_alias.target_type {
735 } else {
737 panic!(
738 "URI primitive type alias should target String, got: {:?}",
739 uri_type_alias.target_type
740 );
741 }
742
743 let boolean_structure = StructureDefinition {
745 resource_type: "StructureDefinition".to_string(),
746 id: "boolean".to_string(),
747 url: "http://hl7.org/fhir/StructureDefinition/boolean".to_string(),
748 name: "boolean".to_string(),
749 title: Some("boolean".to_string()),
750 status: "active".to_string(),
751 kind: "primitive-type".to_string(),
752 is_abstract: false,
753 description: Some("Value of 'true' or 'false'".to_string()),
754 purpose: None,
755 base_type: "boolean".to_string(),
756 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Element".to_string()),
757 version: None,
758 differential: None,
759 snapshot: None,
760 };
761
762 let type_alias_result = generator.generate_primitive_type_alias(&boolean_structure);
763 assert!(
764 type_alias_result.is_ok(),
765 "Should generate boolean primitive type alias successfully"
766 );
767
768 let boolean_type_alias = type_alias_result.unwrap();
769
770 if let RustType::Boolean = boolean_type_alias.target_type {
772 } else {
774 panic!(
775 "Boolean primitive type alias should target bool, got: {:?}",
776 boolean_type_alias.target_type
777 );
778 }
779
780 let element_struct = generator.generate_primitive_element_struct(&uri_structure);
782 assert!(
783 element_struct.is_ok(),
784 "Should generate companion Element struct successfully"
785 );
786
787 let element_struct = element_struct.unwrap();
788 assert_eq!(
789 element_struct.name, "_uri",
790 "Companion Element struct should be named '_uri'"
791 );
792 assert_eq!(
793 element_struct.base_definition,
794 Some("Element".to_string()),
795 "Companion Element struct should inherit from Element"
796 );
797 }
798
799 #[test]
800 fn test_trait_generation() {
801 use crate::fhir_types::{
802 ElementDefinition, ElementType, StructureDefinition, StructureDefinitionDifferential,
803 };
804
805 let config = CodegenConfig::default();
806 let mut generator = CodeGenerator::new(config);
807
808 let patient_structure = StructureDefinition {
810 resource_type: "StructureDefinition".to_string(),
811 id: "Patient".to_string(),
812 url: "http://hl7.org/fhir/StructureDefinition/Patient".to_string(),
813 name: "Patient".to_string(),
814 title: Some("Patient".to_string()),
815 status: "active".to_string(),
816 kind: "resource".to_string(),
817 is_abstract: false,
818 description: Some("Demographics and other administrative information about an individual receiving care.".to_string()),
819 purpose: None,
820 base_type: "DomainResource".to_string(),
821 base_definition: Some("http://hl7.org/fhir/StructureDefinition/DomainResource".to_string()),
822 version: None,
823 differential: Some(StructureDefinitionDifferential {
824 element: vec![
825 ElementDefinition {
826 id: Some("Patient.active".to_string()),
827 path: "Patient.active".to_string(),
828 short: Some("Whether this patient record is in active use".to_string()),
829 definition: Some("Whether this patient record is in active use".to_string()),
830 min: Some(0),
831 max: Some("1".to_string()),
832 element_type: Some(vec![ElementType {
833 code: Some("boolean".to_string()),
834 target_profile: None,
835 }]),
836 fixed: None,
837 pattern: None,
838 binding: None,
839 constraint: None,
840 },
841 ElementDefinition {
842 id: Some("Patient.name".to_string()),
843 path: "Patient.name".to_string(),
844 short: Some("A name associated with the patient".to_string()),
845 definition: Some("A name associated with the patient".to_string()),
846 min: Some(0),
847 max: Some("*".to_string()),
848 element_type: Some(vec![ElementType {
849 code: Some("HumanName".to_string()),
850 target_profile: None,
851 }]),
852 fixed: None,
853 pattern: None,
854 binding: None,
855 constraint: None,
856 },
857 ],
858 }),
859 snapshot: None,
860 };
861
862 let result = generator.generate_trait(&patient_structure);
864 assert!(result.is_ok(), "Should generate Patient trait successfully");
865
866 let patient_traits = result.unwrap();
867 let patient_trait = patient_traits
868 .iter()
869 .find(|t| t.name == "PatientAccessors")
870 .expect("PatientAccessors trait not found");
871
872 assert_eq!(
873 patient_trait.name, "PatientAccessors",
874 "Trait should be named 'PatientAccessors'"
875 );
876
877 assert!(
879 patient_trait
880 .super_traits
881 .contains(&"DomainResourceAccessors".to_string()),
882 "Patient trait should inherit from DomainResourceAccessors"
883 );
884
885 let has_extensions = patient_trait.methods.iter().any(|m| m.name == "extensions");
887 assert!(
888 !has_extensions,
889 "Patient trait should NOT have extensions method - it should be inherited from Resource"
890 );
891
892 let has_narrative = patient_trait.methods.iter().any(|m| m.name == "narrative");
893 assert!(
894 !has_narrative,
895 "Patient trait should NOT have narrative method - it should be inherited from DomainResource"
896 );
897
898 let has_id = patient_trait.methods.iter().any(|m| m.name == "id");
899 assert!(
900 !has_id,
901 "Patient trait should NOT have id method - it should be inherited from Resource"
902 );
903
904 }
907
908 #[test]
909 fn test_filename_generation() {
910 let patient_structure = StructureDefinition {
912 resource_type: "StructureDefinition".to_string(),
913 id: "Patient".to_string(),
914 url: "http://hl7.org/fhir/StructureDefinition/Patient".to_string(),
915 name: "Patient".to_string(),
916 title: Some("Patient".to_string()),
917 status: "active".to_string(),
918 kind: "resource".to_string(),
919 is_abstract: false,
920 description: Some("A patient resource".to_string()),
921 purpose: None,
922 base_type: "DomainResource".to_string(),
923 base_definition: Some(
924 "http://hl7.org/fhir/StructureDefinition/DomainResource".to_string(),
925 ),
926 version: None,
927 differential: None,
928 snapshot: None,
929 };
930
931 let observation_structure = StructureDefinition {
932 resource_type: "StructureDefinition".to_string(),
933 id: "Observation".to_string(),
934 url: "http://hl7.org/fhir/StructureDefinition/Observation".to_string(),
935 name: "Observation".to_string(),
936 title: Some("Observation".to_string()),
937 status: "active".to_string(),
938 kind: "resource".to_string(),
939 is_abstract: false,
940 description: Some("An observation resource".to_string()),
941 purpose: None,
942 base_type: "DomainResource".to_string(),
943 base_definition: Some(
944 "http://hl7.org/fhir/StructureDefinition/DomainResource".to_string(),
945 ),
946 version: None,
947 differential: None,
948 snapshot: None,
949 };
950
951 let patient_struct_name = crate::naming::Naming::struct_name(&patient_structure);
953 assert_eq!(patient_struct_name, "Patient");
954
955 let observation_struct_name = crate::naming::Naming::struct_name(&observation_structure);
956 assert_eq!(observation_struct_name, "Observation");
957
958 let patient_filename = crate::naming::Naming::filename(&patient_structure);
960 assert_eq!(patient_filename, "patient.rs");
961
962 let observation_filename = crate::naming::Naming::filename(&observation_structure);
963 assert_eq!(observation_filename, "observation.rs");
964
965 let structure_definition = StructureDefinition {
967 resource_type: "StructureDefinition".to_string(),
968 id: "StructureDefinition".to_string(),
969 url: "http://hl7.org/fhir/StructureDefinition/StructureDefinition".to_string(),
970 name: "StructureDefinition".to_string(),
971 title: Some("StructureDefinition".to_string()),
972 status: "active".to_string(),
973 kind: "resource".to_string(),
974 is_abstract: false,
975 description: Some("A structure definition".to_string()),
976 purpose: None,
977 base_type: "DomainResource".to_string(),
978 base_definition: Some(
979 "http://hl7.org/fhir/StructureDefinition/DomainResource".to_string(),
980 ),
981 version: None,
982 differential: None,
983 snapshot: None,
984 };
985
986 let struct_def_struct_name = crate::naming::Naming::struct_name(&structure_definition);
987 assert_eq!(struct_def_struct_name, "StructureDefinition");
988
989 let struct_def_filename = crate::naming::Naming::filename(&structure_definition);
990 assert_eq!(struct_def_filename, "structure_definition.rs");
991
992 let enum_filename = crate::naming::Naming::enum_filename("AdministrativeGender");
994 assert_eq!(enum_filename, "administrative_gender.rs");
995
996 let enum_module_name = crate::naming::Naming::module_name("AdministrativeGender");
997 assert_eq!(enum_module_name, "administrative_gender");
998 }
999
1000 #[test]
1001 fn test_import_classification() {
1002 assert!(ImportManager::is_fhir_resource_type("DomainResource"));
1004 assert!(ImportManager::is_fhir_resource_type("Patient"));
1005 assert!(ImportManager::is_fhir_resource_type("ActivityDefinition"));
1006 assert!(!ImportManager::is_fhir_resource_type("Identifier"));
1007
1008 assert!(ImportManager::is_fhir_datatype("Identifier"));
1010 assert!(ImportManager::is_fhir_datatype("CodeableConcept"));
1011 assert!(ImportManager::is_fhir_datatype("Reference"));
1012 assert!(!ImportManager::is_fhir_datatype("DomainResource"));
1013
1014 assert_eq!(
1016 ImportManager::get_import_path_for_type("DomainResource"),
1017 "crate::resources::domain_resource::DomainResource"
1018 );
1019 assert_eq!(
1020 ImportManager::get_import_path_for_type("Identifier"),
1021 "crate::datatypes::identifier::Identifier"
1022 );
1023 assert_eq!(
1024 ImportManager::get_import_path_for_type("PublicationStatus"),
1025 "crate::bindings::publication_status::PublicationStatus"
1026 );
1027 }
1028}