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