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