Skip to main content

rh_codegen/
generator.rs

1//! FHIR type generation functionality
2//!
3//! This module contains the main code generator that orchestrates the generation of Rust types
4//! from FHIR StructureDefinitions using specialized sub-generators.
5
6#![allow(dead_code)] // Allow unused code during refactoring transition
7
8use 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
24// Re-export from file_generator for backward compatibility
25pub use crate::generators::file_generator::FhirTypeCategory;
26
27/// Main code generator struct that orchestrates specialized generators
28pub struct CodeGenerator {
29    config: CodegenConfig,
30    /// Cache of previously generated types to avoid regenerating the same struct
31    type_cache: HashMap<String, RustStruct>,
32    /// Cache of generated enums for value set bindings
33    enum_cache: HashMap<String, RustEnum>,
34    /// ValueSet manager for handling ValueSet operations
35    value_set_manager: ValueSetManager,
36    /// Token generator for generating Rust code
37    token_generator: TokenGenerator,
38}
39
40impl CodeGenerator {
41    #[allow(dead_code)] // Methods below are preserved during refactoring transition
42    /// Create a new code generator with the given configuration
43    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    /// Create a new code generator with a ValueSet directory
57    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    /// Load and parse a FHIR StructureDefinition from a JSON file
74    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    /// Generate a Rust struct from a FHIR StructureDefinition
82    pub fn generate_struct(
83        &mut self,
84        structure_def: &StructureDefinition,
85    ) -> CodegenResult<RustStruct> {
86        // Register the type in the TypeRegistry based on its StructureDefinition
87        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    /// Generate traits for a structure definition
100    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    /// Generate a primitive type struct with special FHIR primitive type semantics
123    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    /// Generate a type alias for primitive types
133    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    /// Generate the companion Element struct for a primitive type
143    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    /// Generate a nested struct for BackboneElements
152    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    /// Create a RustField from an ElementDefinition
173    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    /// Convert a FHIR field name to a valid Rust field name
183    fn to_rust_field_name(&self, name: &str) -> String {
184        crate::naming::Naming::field_name(name)
185    }
186
187    /// Generate a Rust struct and write it to the appropriate directory based on FHIR type classification
188    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    /// Generate traits and write them to the traits directory
207    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    /// Classify a FHIR StructureDefinition into the appropriate category
223    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    /// Check if a type name represents a known FHIR data type
232    fn is_fhir_datatype(&self, name: &str) -> bool {
233        TypeUtilities::is_fhir_datatype(name)
234    }
235
236    /// Generate a Rust struct and write it to a file
237    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            // For primitive types, use empty placeholder values
244            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            // Generate the main struct for non-primitive types
255            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    /// Generate a Rust trait and write it to a file
270    pub fn generate_trait_to_file<P: AsRef<Path>>(
271        &mut self,
272        structure_def: &StructureDefinition,
273        output_path: P,
274    ) -> CodegenResult<()> {
275        // Generate the trait first
276        let rust_traits = self.generate_trait(structure_def)?;
277
278        // Create FileIoManager and delegate
279        let file_io_manager = FileIoManager::new(&self.config, &self.token_generator);
280
281        // Use generate_traits_to_file to write all traits to the same file
282        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    /// Pre-scan and register all ValueSet enums in the TypeRegistry
292    /// This should be called before processing resources to ensure correct import paths
293    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        // Scan for ValueSet JSON files in the package directory
300        if !package_path.exists() {
301            return Ok(()); // Nothing to scan
302        }
303
304        let entries = match std::fs::read_dir(package_path) {
305            Ok(entries) => entries,
306            Err(_) => return Ok(()), // Can't read directory, skip
307        };
308
309        for entry in entries {
310            let entry = match entry {
311                Ok(entry) => entry,
312                Err(_) => continue, // Skip problematic entries
313            };
314
315            let path = entry.path();
316            if !path.is_file() || path.extension().is_none_or(|ext| ext != "json") {
317                continue;
318            }
319
320            // Try to read and parse as ValueSet
321            let content = match std::fs::read_to_string(&path) {
322                Ok(content) => content,
323                Err(_) => continue, // Skip unreadable files
324            };
325
326            let json_value: serde_json::Value = match serde_json::from_str(&content) {
327                Ok(value) => value,
328                Err(_) => continue, // Skip invalid JSON
329            };
330
331            // Check if this is a ValueSet resource
332            if json_value.get("resourceType").and_then(|v| v.as_str()) != Some("ValueSet") {
333                continue;
334            }
335
336            // Get the ValueSet URL to generate enum name
337            if let Some(url) = json_value.get("url").and_then(|v| v.as_str()) {
338                // Generate enum name using the same logic as the enum generator
339                let enum_name = self.value_set_manager.generate_enum_name(url);
340
341                // Pre-register this enum in the TypeRegistry
342                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    /// Generate all ValueSet enums to separate files in the specified directory
353    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    /// Generate a mod.rs file that re-exports all the enum modules
362    pub fn generate_enums_mod_file<P: AsRef<Path>>(&self, enums_dir: P) -> CodegenResult<()> {
363        // Create an EnumGenerator with access to the cached enums
364        let mut value_set_manager = self.value_set_manager.clone(); // Need to clone for borrow checker
365        let mut enum_cache = self.enum_cache.clone();
366        let enum_generator = EnumGenerator::new(&mut value_set_manager, &mut enum_cache);
367
368        // Create FileGenerator and delegate
369        let file_generator = FileGenerator::new(&self.config, &self.token_generator);
370        file_generator.generate_enums_mod_file(enums_dir, &enum_generator)
371    }
372
373    /// Generate an enum for a value set binding
374    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    /// Check if any ValueSet enums have been generated
386    pub fn has_cached_enums(&self) -> bool {
387        TypeUtilities::has_cached_enums(&self.value_set_manager)
388    }
389
390    /// Convert a FHIR resource type name to filename using snake_case
391    pub fn to_filename(&self, structure_def: &StructureDefinition) -> String {
392        crate::naming::Naming::filename(structure_def)
393    }
394
395    /// Generate a comprehensive Resource trait with common FHIR resource methods
396    // pub fn generate_resource_trait(&mut self) -> CodegenResult<String> {
397    //     let mut trait_generator = TraitGenerator::new();
398    //     trait_generator.generate_resource_trait()
399    // }
400    /// Generate a trait file directly from a RustTrait object
401    pub fn generate_trait_file_from_trait<P: AsRef<Path>>(
402        &self,
403        rust_trait: &RustTrait,
404        output_path: P,
405    ) -> CodegenResult<()> {
406        // Create FileGenerator and delegate
407        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    // Generate an impl block for the Resource trait
412    // pub fn generate_resource_impl(&self) -> String {
413    //     let trait_generator = TraitGenerator::new();
414    //     trait_generator.generate_resource_impl()
415    // }
416}
417
418#[cfg(test)]
419mod tests {
420    use super::*;
421
422    #[test]
423    fn test_to_valid_rust_identifier_conversion() {
424        // Test FHIR resource names that should preserve original case
425        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        // Test names that need conversion due to special characters
443        assert_eq!(
444            crate::naming::Naming::to_rust_identifier("patient"),
445            "patient"
446        );
447
448        // Test names with spaces
449        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        // Test names with dashes and underscores
459        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        // Test mixed separators
469        assert_eq!(
470            crate::naming::Naming::to_rust_identifier("some-complex_name with.spaces"),
471            "SomeComplexNameWithSpaces"
472        );
473
474        // Test empty and edge cases
475        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        // Create a test LogicalModel StructureDefinition
488        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        // Test that LogicalModels are rejected
507        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        // Create a simplified Bundle StructureDefinition with nested entry
528        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        // Generate the struct
582        let result = generator.generate_struct(&bundle_structure);
583        assert!(result.is_ok());
584
585        let bundle_struct = result.unwrap();
586
587        // Check that the main Bundle struct was generated
588        assert_eq!(bundle_struct.name, "Bundle");
589
590        // Check that an entry field exists
591        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        // Check that the nested BundleEntry struct was generated and cached
595        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        // Check that BundleEntry has the expected fields
604        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        // Test primitive type - should not be capitalized
619        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        // Test that primitive types are not capitalized
638        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        // Test another primitive type
651        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        // Test complex type - should be capitalized
676        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        // Test primitive type generation
710        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        // Test that primitive type alias is generated correctly
731        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        // Check that the type alias name follows the new PascalCase Type suffix convention
740        assert_eq!(
741            uri_type_alias.name, "UriType",
742            "Primitive type alias should use PascalCase with Type suffix"
743        );
744
745        // Check that the type alias target is String for uri
746        if let RustType::String = uri_type_alias.target_type {
747            // Expected
748        } else {
749            panic!(
750                "URI primitive type alias should target String, got: {:?}",
751                uri_type_alias.target_type
752            );
753        }
754
755        // Test boolean primitive type
756        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        // Check that the boolean type alias targets bool
783        if let RustType::Boolean = boolean_type_alias.target_type {
784            // Expected
785        } else {
786            panic!(
787                "Boolean primitive type alias should target bool, got: {:?}",
788                boolean_type_alias.target_type
789            );
790        }
791
792        // Test that the companion Element struct is generated
793        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        // Create a simplified Patient StructureDefinition for trait generation
821        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        // Generate the trait
875        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        // Check that the Patient trait properly inherits from DomainResource
890        assert!(
891            patient_trait
892                .super_traits
893                .contains(&"DomainResourceAccessors".to_string()),
894            "Patient trait should inherit from DomainResourceAccessors"
895        );
896
897        // The Patient trait should NOT have methods that are inherited from parent traits
898        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        // Note: The new trait generator focuses on resource-level methods rather than
917        // field-specific methods like 'active' and 'name', which are handled by struct implementations
918    }
919
920    #[test]
921    fn test_filename_generation() {
922        // Test PascalCase struct names generate snake_case filenames
923        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        // Test that struct names remain PascalCase
964        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        // Test that filenames are snake_case
971        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        // Test more complex PascalCase names
978        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        // Test enum filename generation
1005        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        // Test resource classification
1015        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        // Test datatype classification
1021        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        // Test import path generation
1027        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}