Skip to main content

rh_codegen/generators/
file_generator.rs

1//! File generation and organization functionality
2//!
3//! This module handles writing generated code to files and organizing the output structure.
4//!
5//! ## Ergonomic Improvements for Trait Usage
6//!
7//! This module implements two key ergonomic improvements for generated FHIR resources:
8//!
9//! ### 1. Trait Re-exports in Resource Modules
10//!
11//! Each generated resource module (e.g., `resources::patient`) automatically re-exports its
12//! associated traits (`PatientMutators`, `PatientAccessors`, `PatientExistence`). This allows
13//! users to import just the resource module and get all necessary traits:
14//!
15//! ```ignore
16//! // Before: Required importing from multiple modules
17//! use hl7_fhir_r4_core::resources::patient::Patient;
18//! use hl7_fhir_r4_core::traits::patient::PatientMutators;
19//! use hl7_fhir_r4_core::traits::domain_resource::DomainResourceMutators;
20//! use hl7_fhir_r4_core::traits::resource::ResourceMutators;
21//!
22//! // After: Single import gets everything needed
23//! use hl7_fhir_r4_core::resources::patient::{Patient, PatientMutators};
24//! // Note: Parent traits (ResourceMutators, DomainResourceMutators) are trait bounds,
25//! // so they're brought into scope automatically when PatientMutators is used
26//! ```
27//!
28//! ### 2. Prelude Module
29//!
30//! A `prelude` module is generated that re-exports commonly used base traits:
31//!
32//! ```ignore
33//! use hl7_fhir_r4_core::prelude::*;
34//! use hl7_fhir_r4_core::resources::patient::{Patient, PatientMutators};
35//!
36//! // Now all base traits are in scope
37//! let patient = <Patient as PatientMutators>::new()
38//!     .set_id("example".to_string())  // from ResourceMutators
39//!     .set_active(true);               // from PatientMutators
40//! ```
41//!
42//! These improvements follow idiomatic Rust patterns used by popular crates like
43//! `serde`, `tokio`, and `diesel`, making the generated code more ergonomic and
44//! reducing the cognitive load on users.
45
46use std::collections::HashSet;
47use std::fs;
48use std::path::Path;
49
50use quote::{format_ident, quote};
51
52use crate::config::CodegenConfig;
53use crate::fhir_types::StructureDefinition;
54use crate::generators::binding_generator::BindingGenerator;
55use crate::generators::enum_generator::EnumGenerator;
56use crate::generators::import_manager::ImportManager;
57use crate::generators::primitive_generator::PrimitiveGenerator;
58use crate::generators::token_generator::TokenGenerator;
59use crate::generators::trait_impl_generator::TraitImplGenerator;
60use crate::generators::utils::GeneratorUtils;
61use crate::rust_types::{RustStruct, RustTrait};
62use crate::{CodegenError, CodegenResult};
63
64/// Classification of FHIR types for organizing into appropriate directories
65#[derive(Debug, Clone, PartialEq)]
66pub enum FhirTypeCategory {
67    Resource,
68    Profile,
69    DataType,
70    Extension,
71    Primitive,
72}
73
74/// File generator for organizing and writing generated code
75pub struct FileGenerator<'a> {
76    config: &'a CodegenConfig,
77    token_generator: &'a TokenGenerator,
78}
79
80impl<'a> FileGenerator<'a> {
81    /// Create a new file generator
82    pub fn new(config: &'a CodegenConfig, token_generator: &'a TokenGenerator) -> Self {
83        Self {
84            config,
85            token_generator,
86        }
87    }
88
89    /// Generate a macros.rs file with all FHIR primitive macros
90    pub fn generate_macros_file<P: AsRef<Path>>(&self, output_path: P) -> CodegenResult<()> {
91        let macros_content = include_str!("../macros.rs");
92
93        // Parse and reformat the macros content to ensure proper formatting
94        let syntax_tree =
95            syn::parse_file(macros_content).map_err(|e| CodegenError::Generation {
96                message: format!("Failed to parse macros file: {e}"),
97            })?;
98
99        let formatted_code = prettyplease::unparse(&syntax_tree);
100
101        // Write to file
102        fs::write(output_path, formatted_code)?;
103
104        Ok(())
105    }
106
107    /// Generate a lib.rs file for the generated crate
108    pub fn generate_lib_file<P: AsRef<Path>>(&self, output_path: P) -> CodegenResult<()> {
109        let lib_tokens = quote! {
110            //! Generated FHIR Rust bindings
111            //!
112            //! This crate contains Rust types and traits for FHIR resources and data types.
113            //! It includes macros for primitive field generation and maintains FHIR compliance.
114
115            // Allow clippy lint for derivable Default implementations
116            //
117            // TODO: Future optimization - derive Default when possible instead of manual impl
118            //
119            // Currently, we generate explicit Default implementations for all structs.
120            // Many of these could use #[derive(Default)] instead, which would be more idiomatic.
121            //
122            // Pros of deriving Default:
123            // - More idiomatic Rust code
124            // - Less generated code (no manual impl blocks)
125            // - Clearer intent (all fields use Default::default())
126            //
127            // Cons of current approach (manual impl):
128            // - Clippy warns about 1,100+ derivable implementations
129            // - More verbose generated code
130            //
131            // Pros of current approach:
132            // - Explicit and predictable behavior
133            // - Handles mixed initialization patterns consistently
134            // - Simpler code generation logic
135            //
136            // To implement derive-based approach would require:
137            // 1. Analyze all field types to ensure they implement Default
138            // 2. Detect required fields with non-Default initializations (String::new(), Vec::new(), etc.)
139            // 3. Add "Default" to struct derives only when ALL fields can use Default::default()
140            // 4. Skip manual impl generation for those structs
141            //
142            #![allow(clippy::derivable_impls)]
143
144            pub mod macros;
145            pub mod primitives;
146            pub mod datatypes;
147            pub mod extensions;
148            pub mod resources;
149            pub mod traits;
150            pub mod bindings;
151
152            // Re-export macros and serde traits for convenience
153            pub use macros::*;
154            pub use serde::{Deserialize, Serialize};
155        };
156
157        // Parse the tokens into a syntax tree and format it
158        let syntax_tree = syn::parse2(lib_tokens).map_err(|e| CodegenError::Generation {
159            message: format!("Failed to parse generated lib tokens: {e}"),
160        })?;
161
162        let formatted_code = prettyplease::unparse(&syntax_tree);
163
164        // Write to file
165        fs::write(output_path, formatted_code)?;
166
167        Ok(())
168    }
169
170    /// Generate module files (mod.rs) for organized directories
171    pub fn generate_module_file<P: AsRef<Path>>(
172        &self,
173        module_dir: P,
174        module_names: &[String],
175    ) -> CodegenResult<()> {
176        let module_dir = module_dir.as_ref();
177        let mod_file_path = module_dir.join("mod.rs");
178
179        let mut mod_tokens = proc_macro2::TokenStream::new();
180
181        // Add module declarations only (no re-exports to avoid conflicts)
182        for module_name in module_names {
183            let mod_ident = format_ident!("{}", module_name);
184            mod_tokens.extend(quote! {
185                pub mod #mod_ident;
186            });
187        }
188
189        // Parse the tokens into a syntax tree and format it
190        let syntax_tree = syn::parse2(mod_tokens).map_err(|e| CodegenError::Generation {
191            message: format!("Failed to parse generated mod tokens: {e}"),
192        })?;
193
194        let formatted_code = prettyplease::unparse(&syntax_tree);
195
196        // Write to file
197        fs::write(mod_file_path, formatted_code)?;
198
199        Ok(())
200    }
201
202    /// Generate a combined primitives.rs file with all FHIR primitive type aliases
203    pub fn generate_combined_primitives_file<P: AsRef<Path>>(
204        &self,
205        primitive_structure_defs: &[StructureDefinition],
206        output_path: P,
207    ) -> CodegenResult<()> {
208        let mut all_tokens = proc_macro2::TokenStream::new();
209
210        // Add file-level documentation
211        let doc_comment = quote! {
212            //! FHIR Primitive Types
213            //!
214            //! This module contains type aliases for all FHIR primitive types.
215            //! Companion elements for primitive fields use the base Element type.
216        };
217        all_tokens.extend(doc_comment);
218
219        // Generate all primitive type aliases
220        let mut type_cache = std::collections::HashMap::new();
221        let primitive_generator = PrimitiveGenerator::new(self.config, &mut type_cache);
222        let type_aliases =
223            primitive_generator.generate_all_primitive_type_aliases(primitive_structure_defs)?;
224
225        for type_alias in type_aliases {
226            let type_alias_tokens = self.token_generator.generate_type_alias(&type_alias);
227            all_tokens.extend(type_alias_tokens);
228        }
229
230        // Parse the tokens into a syntax tree and format it
231        let syntax_tree = syn::parse2(all_tokens).map_err(|e| CodegenError::Generation {
232            message: format!("Failed to parse generated primitive tokens: {e}"),
233        })?;
234
235        let formatted_code = prettyplease::unparse(&syntax_tree);
236
237        // Write to file
238        fs::write(output_path, formatted_code)?;
239
240        Ok(())
241    }
242
243    /// Generate a Rust struct and write it to the appropriate directory based on FHIR type classification
244    pub fn generate_to_organized_directories<P: AsRef<Path>>(
245        &self,
246        structure_def: &StructureDefinition,
247        base_output_dir: P,
248        rust_struct: &RustStruct,
249        nested_structs: &[RustStruct],
250    ) -> CodegenResult<()> {
251        let base_dir = base_output_dir.as_ref();
252
253        // Determine the appropriate subdirectory based on FHIR type
254        // Also check if the main struct has Extension base (for nested structs)
255        let mut category = self.classify_fhir_structure_def(structure_def);
256        if category != FhirTypeCategory::Extension && Self::has_extension_base(rust_struct) {
257            category = FhirTypeCategory::Extension;
258        }
259
260        let target_dir = match category {
261            FhirTypeCategory::Resource => base_dir.join("src").join("resource"),
262            FhirTypeCategory::Profile => base_dir.join("src").join("profiles"),
263            FhirTypeCategory::DataType => base_dir.join("src").join("datatypes"),
264            FhirTypeCategory::Extension => base_dir.join("src").join("extensions"),
265            FhirTypeCategory::Primitive => base_dir.join("src").join("primitives"),
266        };
267
268        // Ensure the target directory exists
269        std::fs::create_dir_all(&target_dir)?;
270
271        // Separate nested structs that are extensions from those that should remain
272        // embedded in the parent file. Extension-like nested structs should be
273        // emitted into the shared `extensions` module instead of duplicated in
274        // resource files.
275        let mut embedded_nested: Vec<RustStruct> = Vec::new();
276        let mut external_extensions: Vec<RustStruct> = Vec::new();
277
278        for nested in nested_structs {
279            if Self::has_extension_base(nested) {
280                external_extensions.push(nested.clone());
281            } else {
282                embedded_nested.push(nested.clone());
283            }
284        }
285
286        // Generate the main file in the appropriate directory (including only
287        // the nested structs that belong with the parent)
288        let filename = crate::naming::Naming::filename(structure_def);
289        let output_path = target_dir.join(filename);
290
291        let result =
292            self.generate_to_file(structure_def, output_path, rust_struct, &embedded_nested);
293
294        // If there are nested extension structs discovered, write each to the
295        // extensions directory as standalone files to avoid duplicate definitions
296        // across resource files.
297        if !external_extensions.is_empty() {
298            let extensions_dir = base_dir.join("src").join("extensions");
299            std::fs::create_dir_all(&extensions_dir)?;
300
301            for ext in external_extensions {
302                // Write each extension struct to its own file. If a file already
303                // exists, it will be overwritten (consistent with existing behavior).
304                self.write_struct_only_file(&ext, &extensions_dir)?;
305            }
306        }
307
308        result
309    }
310
311    /// Generate a trait and write it to the traits directory
312    pub fn generate_trait_to_organized_directory<P: AsRef<Path>>(
313        &self,
314        structure_def: &StructureDefinition,
315        base_output_dir: P,
316        rust_trait: &RustTrait,
317    ) -> CodegenResult<()> {
318        let traits_dir = base_output_dir.as_ref().join("src").join("traits");
319
320        // Ensure the traits directory exists
321        std::fs::create_dir_all(&traits_dir)?;
322
323        // Generate the trait file
324        let struct_name = crate::naming::Naming::struct_name(structure_def);
325        let snake_case_name = crate::naming::Naming::to_snake_case(&struct_name);
326        let filename = format!("{snake_case_name}.rs");
327        let output_path = traits_dir.join(filename);
328
329        self.generate_trait_to_file(structure_def, output_path, rust_trait)
330    }
331
332    /// Check if a RustStruct has Extension as its base type
333    fn has_extension_base(rust_struct: &RustStruct) -> bool {
334        rust_struct.fields.iter().any(|field| {
335            field.name == "base" && matches!(&field.field_type, crate::rust_types::RustType::Custom(type_name) if type_name == "Extension")
336        })
337    }
338
339    /// Classify a FHIR StructureDefinition into the appropriate category
340    pub fn classify_fhir_structure_def(
341        &self,
342        structure_def: &StructureDefinition,
343    ) -> FhirTypeCategory {
344        // Check if this is a profile first (derives from a core FHIR resource)
345        if crate::generators::type_registry::TypeRegistry::is_profile(structure_def) {
346            return FhirTypeCategory::Profile;
347        }
348
349        // Check if it's a primitive type
350        if structure_def.kind == "primitive-type" {
351            return FhirTypeCategory::Primitive;
352        }
353
354        // Check for known data types first (including core Extension)
355        if GeneratorUtils::is_fhir_datatype(&structure_def.name)
356            || structure_def.base_type == "Element"
357            || structure_def.base_type == "BackboneElement"
358            || structure_def.base_type == "DataType"
359            || structure_def.name == "Extension"
360        {
361            return FhirTypeCategory::DataType;
362        }
363
364        // Check if it's an Extension-based type (but not the core Extension itself)
365        if structure_def.base_type == "Extension" {
366            return FhirTypeCategory::Extension;
367        }
368
369        // Check for resources
370        if structure_def.kind == "resource"
371            || structure_def.base_type == "Resource"
372            || structure_def.base_type == "DomainResource"
373        {
374            return FhirTypeCategory::Resource;
375        }
376
377        // Default to data type for complex types
378        if structure_def.kind == "complex-type" {
379            return FhirTypeCategory::DataType;
380        }
381
382        // Default to resource for unknown types
383        FhirTypeCategory::Resource
384    }
385
386    /// Generate a Rust struct and write it to a file
387    pub fn generate_to_file<P: AsRef<Path>>(
388        &self,
389        structure_def: &StructureDefinition,
390        output_path: P,
391        rust_struct: &RustStruct,
392        nested_structs: &[RustStruct],
393    ) -> CodegenResult<()> {
394        // Collect all imports needed for this file
395        let mut imports = HashSet::new();
396
397        // Always include serde if enabled, but exclude primitive types
398        if self.config.with_serde && structure_def.kind != "primitive-type" {
399            imports.insert("serde::{Deserialize, Serialize}".to_string());
400        }
401
402        // Note: We use fully qualified `rh_foundation::Invariant` in INVARIANTS declarations
403        // and ValidatableResource trait implementations, so no import is needed here.
404
405        // Check if any struct contains macro calls and add necessary imports
406        let has_macro_calls = rust_struct
407            .fields
408            .iter()
409            .any(|field| field.macro_call.is_some())
410            || nested_structs
411                .iter()
412                .any(|s| s.fields.iter().any(|field| field.macro_call.is_some()));
413
414        if has_macro_calls {
415            // Add the macro imports from the current crate
416            imports.insert("crate::{primitive_string, primitive_boolean, primitive_integer, primitive_decimal, primitive_datetime, primitive_date, primitive_time, primitive_uri, primitive_canonical, primitive_base64binary, primitive_instant, primitive_positiveint, primitive_unsignedint, primitive_id, primitive_oid, primitive_uuid, primitive_code, primitive_markdown, primitive_url}".to_string());
417        }
418
419        let mut all_tokens = proc_macro2::TokenStream::new();
420
421        if structure_def.kind == "primitive-type" {
422            // Generate type alias for primitive types
423            let mut type_cache = std::collections::HashMap::new();
424            let primitive_generator = PrimitiveGenerator::new(self.config, &mut type_cache);
425            let type_alias = primitive_generator.generate_primitive_type_alias(structure_def)?;
426            let type_alias_tokens = self.token_generator.generate_type_alias(&type_alias);
427            all_tokens.extend(type_alias_tokens);
428
429            // Note: No longer generating individual companion Element structs
430            // All companion fields now use the base Element type directly
431        } else {
432            // Generate the main struct for non-primitive types
433            let mut all_structs = vec![rust_struct.clone()];
434            all_structs.extend(nested_structs.iter().cloned());
435
436            // Collect all struct names that will be in this file
437            let structs_in_file: HashSet<String> =
438                all_structs.iter().map(|s| s.name.clone()).collect();
439
440            // Collect custom types from all structs, excluding types that are in the same file
441            for struct_def in &all_structs {
442                ImportManager::collect_custom_types_from_struct(
443                    struct_def,
444                    &mut imports,
445                    &structs_in_file,
446                );
447            }
448
449            for struct_def in all_structs {
450                let struct_tokens = self.token_generator.generate_struct(&struct_def);
451                all_tokens.extend(struct_tokens);
452            }
453        }
454
455        // Generate import tokens AFTER collecting all custom types
456        let mut import_tokens = proc_macro2::TokenStream::new();
457        for import in &imports {
458            let import_token: proc_macro2::TokenStream = format!("use {import};")
459                .parse()
460                .expect("Invalid import statement");
461            import_tokens.extend(import_token);
462        }
463
464        // Prepend imports to the tokens
465        let mut final_tokens = proc_macro2::TokenStream::new();
466        final_tokens.extend(import_tokens);
467        final_tokens.extend(all_tokens);
468
469        // Parse the tokens into a syntax tree and format it
470        let syntax_tree = syn::parse2(final_tokens).map_err(|e| CodegenError::Generation {
471            message: format!("Failed to parse generated tokens: {e}"),
472        })?;
473
474        let mut formatted_code = prettyplease::unparse(&syntax_tree);
475
476        // Add Default implementation if needed (for non-profile resources)
477        if structure_def.kind == "resource" || structure_def.kind == "complex-type" {
478            let default_impl = self.generate_default_implementation(structure_def, rust_struct);
479            if !default_impl.is_empty() {
480                formatted_code.push_str("\n\n");
481                formatted_code.push_str(&default_impl);
482            }
483
484            // Also generate Default implementations for nested structs
485            for nested in nested_structs {
486                let nested_default_impl =
487                    self.generate_nested_struct_default_implementation(structure_def, nested);
488                if !nested_default_impl.is_empty() {
489                    formatted_code.push_str("\n\n");
490                    formatted_code.push_str(&nested_default_impl);
491                }
492            }
493        }
494
495        // Add invariants constant for resources and complex types
496        if structure_def.kind == "resource" || structure_def.kind == "complex-type" {
497            let invariants_const =
498                crate::generators::InvariantGenerator::generate_invariants_constant(structure_def);
499            if !invariants_const.is_empty() {
500                formatted_code.push_str("\n\n");
501                formatted_code.push_str(&invariants_const);
502            }
503        }
504
505        // Add bindings constant for resources and complex types
506        if structure_def.kind == "resource" || structure_def.kind == "complex-type" {
507            let bindings_const = BindingGenerator::generate_bindings_constant(structure_def);
508            if !bindings_const.is_empty() {
509                formatted_code.push_str("\n\n");
510                formatted_code.push_str(&bindings_const);
511            }
512        }
513
514        // Add cardinalities constant for resources and complex types
515        if structure_def.kind == "resource" || structure_def.kind == "complex-type" {
516            let cardinalities_const =
517                crate::generators::cardinality_generator::CardinalityGenerator::generate_cardinalities_constant(
518                    structure_def,
519                );
520            if !cardinalities_const.is_empty() {
521                formatted_code.push_str("\n\n");
522                formatted_code.push_str(&cardinalities_const);
523            }
524        }
525
526        // Add trait implementations for FHIR resources
527        if structure_def.kind == "resource" {
528            formatted_code.push_str("\n\n");
529            formatted_code.push_str(&self.generate_trait_implementations(structure_def));
530        }
531
532        // Add ValidatableResource trait implementation
533        if structure_def.kind == "resource" || structure_def.kind == "complex-type" {
534            let validation_impl =
535                crate::generators::ValidationTraitGenerator::generate_trait_impl(structure_def);
536            if !validation_impl.is_empty() {
537                formatted_code.push_str("\n\n");
538                formatted_code.push_str(&validation_impl);
539            }
540        }
541
542        // Re-export the mutator traits for convenience (resources only)
543        if structure_def.kind == "resource" {
544            formatted_code.push_str("\n\n");
545            formatted_code.push_str(&self.generate_trait_reexports(structure_def));
546        }
547
548        // Add Resource trait impl if this is the Resource struct (legacy)
549        if structure_def.name == "Resource" {
550            formatted_code.push_str("\n\n");
551            // let trait_impl = crate::generators::TraitGenerator::new().generate_resource_impl();
552            // formatted_code.push_str(&trait_impl);
553        }
554
555        // Check for file collision and warn if overwriting
556        if output_path.as_ref().exists() {
557            eprintln!(
558                "Warning: File '{}' already exists and will be overwritten. This may indicate a naming collision between FHIR StructureDefinitions.",
559                output_path.as_ref().display()
560            );
561        }
562
563        // Write to file
564        fs::write(output_path.as_ref(), formatted_code)?;
565
566        Ok(())
567    }
568
569    /// Generate a Rust trait and write it to a file
570    pub fn generate_trait_to_file<P: AsRef<Path>>(
571        &self,
572        _structure_def: &StructureDefinition,
573        output_path: P,
574        rust_trait: &RustTrait,
575    ) -> CodegenResult<()> {
576        // Generate import tokens
577        let mut all_tokens = proc_macro2::TokenStream::new();
578
579        // Collect imports needed for this trait
580        let mut imports = std::collections::HashSet::new();
581        ImportManager::collect_custom_types_from_trait(rust_trait, &mut imports);
582
583        // Add import statements
584        for import_path in imports {
585            let import_stmt = format!("use {import_path};");
586            let import_tokens: proc_macro2::TokenStream =
587                import_stmt.parse().map_err(|e| CodegenError::Generation {
588                    message: format!("Failed to parse import statement '{import_stmt}': {e}"),
589                })?;
590            all_tokens.extend(import_tokens);
591        }
592
593        // Generate the trait
594        let trait_tokens = self.token_generator.generate_trait(rust_trait);
595        all_tokens.extend(trait_tokens);
596
597        // Debug: Print the tokens before parsing
598        if std::env::var("DEBUG_TOKENS").is_ok() {
599            eprintln!(
600                "DEBUG: Generated tokens for trait '{}': {}",
601                rust_trait.name, all_tokens
602            );
603        }
604
605        // Parse the tokens into a syntax tree and format it
606        let syntax_tree = syn::parse2(all_tokens).map_err(|e| CodegenError::Generation {
607            message: format!(
608                "Failed to parse generated trait tokens for '{}': {e}",
609                rust_trait.name
610            ),
611        })?;
612
613        let formatted_code = prettyplease::unparse(&syntax_tree);
614
615        // Check for file collision and warn if overwriting
616        if output_path.as_ref().exists() {
617            eprintln!(
618                "Warning: Trait file '{}' already exists and will be overwritten.",
619                output_path.as_ref().display()
620            );
621        }
622
623        // Write to file
624        fs::write(output_path.as_ref(), formatted_code)?;
625
626        Ok(())
627    }
628
629    /// Generate multiple traits to a single file
630    pub fn generate_traits_to_file<P: AsRef<Path>>(
631        &self,
632        _structure_def: &StructureDefinition,
633        output_path: P,
634        rust_traits: &[&RustTrait],
635    ) -> CodegenResult<()> {
636        // Generate import tokens
637        let mut all_tokens = proc_macro2::TokenStream::new();
638
639        // Collect imports needed for all traits
640        let mut imports = std::collections::HashSet::new();
641        for rust_trait in rust_traits {
642            ImportManager::collect_custom_types_from_trait(rust_trait, &mut imports);
643        }
644
645        // Add import statements
646        for import_path in imports {
647            let import_stmt = format!("use {import_path};");
648            let import_tokens: proc_macro2::TokenStream =
649                import_stmt.parse().map_err(|e| CodegenError::Generation {
650                    message: format!("Failed to parse import statement '{import_stmt}': {e}"),
651                })?;
652            all_tokens.extend(import_tokens);
653        }
654
655        // Generate all traits
656        for rust_trait in rust_traits {
657            let trait_tokens = self.token_generator.generate_trait(rust_trait);
658            all_tokens.extend(trait_tokens);
659        }
660
661        // Debug: Print the tokens before parsing
662        if std::env::var("DEBUG_TOKENS").is_ok() {
663            let trait_names: Vec<&str> = rust_traits.iter().map(|t| t.name.as_str()).collect();
664            eprintln!(
665                "DEBUG: Generated tokens for traits [{}]: {}",
666                trait_names.join(", "),
667                all_tokens
668            );
669        }
670
671        // Parse the tokens into a syntax tree and format it
672        let syntax_tree = syn::parse2(all_tokens).map_err(|e| CodegenError::Generation {
673            message: format!("Failed to parse generated trait tokens: {e}"),
674        })?;
675
676        let formatted_code = prettyplease::unparse(&syntax_tree);
677
678        // Check for file collision and warn if overwriting
679        if output_path.as_ref().exists() {
680            eprintln!(
681                "Warning: Trait file '{}' already exists and will be overwritten.",
682                output_path.as_ref().display()
683            );
684        }
685
686        // Write to file
687        fs::write(output_path.as_ref(), formatted_code)?;
688
689        Ok(())
690    }
691
692    /// Generate all ValueSet enums to separate files in the specified directory
693    pub fn generate_enum_files<P: AsRef<Path>>(
694        &self,
695        enums_dir: P,
696        enum_generator: &EnumGenerator,
697    ) -> CodegenResult<()> {
698        let enums_dir = enums_dir.as_ref();
699
700        // Create the enums directory if it doesn't exist
701        if !enums_dir.exists() {
702            fs::create_dir_all(enums_dir)?;
703        }
704
705        // Generate a file for each cached enum
706        for (enum_name, rust_enum) in enum_generator.get_cached_enums() {
707            let enum_filename = EnumGenerator::enum_name_to_filename(enum_name);
708            let enum_file_path = enums_dir.join(enum_filename);
709
710            // Generate tokens for this enum with imports
711            let import_tokens = quote! {
712                use serde::{Deserialize, Serialize};
713            };
714            let enum_tokens = self.token_generator.generate_enum(rust_enum);
715            let combined_tokens = quote! {
716                #import_tokens
717
718                #enum_tokens
719            };
720
721            // Parse the tokens into a syntax tree and format it
722            let syntax_tree =
723                syn::parse2(combined_tokens).map_err(|e| CodegenError::Generation {
724                    message: format!("Failed to parse generated enum tokens for {enum_name}: {e}"),
725                })?;
726
727            let formatted_code = prettyplease::unparse(&syntax_tree);
728
729            // Check for file collision and warn if overwriting
730            if enum_file_path.exists() {
731                eprintln!(
732                    "Warning: Enum file '{}' already exists and will be overwritten.",
733                    enum_file_path.display()
734                );
735            }
736
737            // Write enum to its own file
738            fs::write(&enum_file_path, formatted_code)?;
739        }
740
741        Ok(())
742    }
743
744    /// Generate a mod.rs file that re-exports all the enum modules
745    pub fn generate_enums_mod_file<P: AsRef<Path>>(
746        &self,
747        enums_dir: P,
748        enum_generator: &EnumGenerator,
749    ) -> CodegenResult<()> {
750        let enums_dir = enums_dir.as_ref();
751        let mod_file_path = enums_dir.join("mod.rs");
752
753        let mut mod_content = vec![
754            "//! FHIR ValueSet enums".to_string(),
755            "//!".to_string(),
756            "//! This module contains all the generated enums from FHIR ValueSets.".to_string(),
757            "//! Each enum represents a specific ValueSet and its allowed codes.".to_string(),
758            "".to_string(),
759        ];
760
761        // Sort enum names for consistent output
762        let mut enum_names: Vec<_> = enum_generator.get_cached_enums().keys().collect();
763        enum_names.sort();
764
765        // Generate module declarations and re-exports
766        for enum_name in enum_names {
767            let module_name = EnumGenerator::enum_name_to_module_name(enum_name);
768            mod_content.push(format!("pub mod {module_name};"));
769        }
770
771        let final_content = mod_content.join("\n") + "\n";
772
773        // Check for file collision and warn if overwriting
774        if mod_file_path.exists() {
775            eprintln!(
776                "Warning: Enum mod file '{}' already exists and will be overwritten.",
777                mod_file_path.display()
778            );
779        }
780
781        fs::write(&mod_file_path, final_content)?;
782
783        Ok(())
784    }
785
786    /// Generate a trait file directly from a RustTrait object
787    pub fn generate_trait_file_from_trait<P: AsRef<Path>>(
788        &self,
789        rust_trait: &RustTrait,
790        output_path: P,
791    ) -> CodegenResult<()> {
792        // Generate import tokens
793        let mut all_tokens = proc_macro2::TokenStream::new();
794
795        // Collect imports needed for this trait
796        let mut imports = std::collections::HashSet::new();
797        ImportManager::collect_custom_types_from_trait(rust_trait, &mut imports);
798
799        // Add import statements
800        for import_path in imports {
801            let import_stmt = format!("use {import_path};");
802            let import_tokens: proc_macro2::TokenStream =
803                import_stmt.parse().map_err(|e| CodegenError::Generation {
804                    message: format!("Failed to parse import statement '{import_stmt}': {e}"),
805                })?;
806            all_tokens.extend(import_tokens);
807        }
808
809        // Generate the trait tokens
810        let trait_tokens = self.token_generator.generate_trait(rust_trait);
811        all_tokens.extend(trait_tokens);
812
813        // Parse the tokens into a syntax tree and format it
814        let syntax_tree = syn::parse2(all_tokens).map_err(|e| CodegenError::Generation {
815            message: format!("Failed to parse generated trait tokens: {e}"),
816        })?;
817
818        let formatted_code = prettyplease::unparse(&syntax_tree);
819
820        // Check for file collision and warn if overwriting
821        if output_path.as_ref().exists() {
822            eprintln!(
823                "Warning: Trait file '{}' already exists and will be overwritten.",
824                output_path.as_ref().display()
825            );
826        }
827
828        // Write to file
829        fs::write(output_path.as_ref(), formatted_code)?;
830
831        Ok(())
832    }
833
834    /// Write a single struct as its own file into the given directory.
835    /// This is used to emit extension nested structs into the `extensions` module
836    /// to avoid duplicating their definitions inside resource files.
837    fn write_struct_only_file<P: AsRef<Path>>(
838        &self,
839        rust_struct: &RustStruct,
840        dir: P,
841    ) -> CodegenResult<()> {
842        let dir = dir.as_ref();
843
844        // Prepare imports for this struct
845        let mut imports = HashSet::new();
846        if self.config.with_serde {
847            imports.insert("serde::{Deserialize, Serialize}".to_string());
848        }
849
850        // Collect custom types referenced by this struct
851        let mut structs_in_file = HashSet::new();
852        structs_in_file.insert(rust_struct.name.clone());
853        ImportManager::collect_custom_types_from_struct(
854            rust_struct,
855            &mut imports,
856            &structs_in_file,
857        );
858
859        // Generate tokens
860        let mut all_tokens = proc_macro2::TokenStream::new();
861
862        // Add imports
863        for import in &imports {
864            let import_token: proc_macro2::TokenStream =
865                format!("use {import};").parse().expect("Invalid import");
866            all_tokens.extend(import_token);
867        }
868
869        // Add the struct tokens
870        all_tokens.extend(self.token_generator.generate_struct(rust_struct));
871
872        // Parse and format
873        let syntax_tree = syn::parse2(all_tokens).map_err(|e| CodegenError::Generation {
874            message: format!(
875                "Failed to parse generated tokens for {}: {e}",
876                rust_struct.name
877            ),
878        })?;
879
880        let formatted_code = prettyplease::unparse(&syntax_tree);
881
882        // Determine filename
883        let filename = format!(
884            "{}.rs",
885            crate::naming::Naming::to_snake_case(&rust_struct.name)
886        );
887        let output_path = dir.join(filename);
888
889        // Write file
890        std::fs::write(output_path, formatted_code)?;
891
892        Ok(())
893    }
894
895    /// Generate trait implementations for a FHIR resource
896    fn generate_trait_implementations(&self, structure_def: &StructureDefinition) -> String {
897        let trait_impl_generator = TraitImplGenerator::new();
898        let trait_impls = match trait_impl_generator.generate_trait_impls(structure_def) {
899            Ok(impls) => impls,
900            Err(e) => {
901                eprintln!(
902                    "Warning: Failed to generate trait implementations for {}: {}",
903                    structure_def.name, e
904                );
905                return String::new();
906            }
907        };
908
909        let mut implementations = Vec::new();
910
911        for trait_impl in trait_impls {
912            let impl_tokens = self.token_generator.generate_trait_impl(&trait_impl);
913
914            // Parse and format the implementation
915            match syn::parse2(impl_tokens.clone()) {
916                Ok(syntax_tree) => {
917                    let formatted_impl = prettyplease::unparse(&syntax_tree);
918                    implementations.push(formatted_impl);
919                }
920                Err(e) => {
921                    eprintln!(
922                        "Warning: Failed to parse trait implementation for {}: {}",
923                        trait_impl.struct_name, e
924                    );
925                    eprintln!("Generated tokens:\n{impl_tokens}");
926                }
927            }
928        }
929
930        if implementations.is_empty() {
931            String::new()
932        } else {
933            format!("// Trait implementations\n{}", implementations.join("\n\n"))
934        }
935    }
936
937    /// Generate re-exports for mutator traits for convenient importing
938    ///
939    /// This generates `pub use` statements that re-export the resource's associated traits
940    /// from the traits module. This allows users to import just the resource module and
941    /// get all the traits they need for working with that resource.
942    ///
943    /// For example, for the Patient resource, this generates:
944    ///
945    /// ```ignore
946    /// pub use crate::traits::patient::{
947    ///     PatientMutators,
948    ///     PatientAccessors,
949    ///     PatientExistence,
950    /// };
951    /// ```
952    ///
953    /// This enables the idiomatic Rust pattern:
954    ///
955    /// ```ignore
956    /// use hl7_fhir_r4_core::resources::patient::{Patient, PatientMutators};
957    /// // Now PatientMutators is in scope without importing from traits module
958    /// ```
959    fn generate_trait_reexports(&self, structure_def: &StructureDefinition) -> String {
960        // For profiles, the trait file is named based on struct_name (from baseDefinition),
961        // not the profile ID. For regular resources, use the structure name.
962        let is_profile = crate::generators::type_registry::TypeRegistry::is_profile(structure_def);
963
964        let (trait_module_name, trait_prefix) = if is_profile {
965            // For profiles, get the struct name (e.g., "Vitalsigns" from baseDefinition)
966            let struct_name = crate::naming::Naming::struct_name(structure_def);
967            let snake_module = crate::naming::Naming::to_rust_identifier(
968                &crate::naming::Naming::to_snake_case(&struct_name),
969            );
970            (snake_module, struct_name)
971        } else {
972            // For regular resources, use the structure name
973            let resource_name = crate::naming::Naming::to_rust_identifier(&structure_def.name);
974            let snake_name = crate::naming::Naming::to_rust_identifier(
975                &crate::naming::Naming::to_snake_case(&resource_name),
976            );
977            (snake_name, resource_name)
978        };
979
980        format!(
981            r#"// Re-export traits for convenient importing
982// This allows users to just import the resource module and get all associated traits
983pub use crate::traits::{trait_module_name}::{{
984    {trait_prefix}Mutators,
985    {trait_prefix}Accessors,
986    {trait_prefix}Existence,
987}};"#
988        )
989    }
990
991    /// Generate Default implementation for a struct if needed
992    fn generate_default_implementation(
993        &self,
994        structure_def: &StructureDefinition,
995        rust_struct: &RustStruct,
996    ) -> String {
997        // Skip if this is a profile - profiles already have Default derived
998        let is_profile = crate::generators::type_registry::TypeRegistry::is_profile(structure_def);
999        if is_profile {
1000            return String::new();
1001        }
1002
1003        // Get the struct name
1004        let struct_name = &rust_struct.name;
1005
1006        // Check if the struct has Default derive already
1007        if rust_struct.derives.iter().any(|d| d == "Default") {
1008            return String::new();
1009        }
1010
1011        // Generate Default implementation using StructureDefinition as source of truth
1012        let elements = if let Some(differential) = &structure_def.differential {
1013            &differential.element
1014        } else if let Some(snapshot) = &structure_def.snapshot {
1015            &snapshot.element
1016        } else {
1017            // No elements - struct likely only has base field, but we still need Default
1018            &Vec::new()
1019        };
1020
1021        // Collect required fields (min >= 1)
1022        let mut required_fields = Vec::new();
1023        for element in elements {
1024            let path_parts: Vec<&str> = element.path.split('.').collect();
1025            if path_parts.len() == 2 && path_parts[0] == structure_def.name {
1026                let field_name = path_parts[1];
1027                if let Some(min) = element.min {
1028                    if min >= 1 && !field_name.ends_with("[x]") {
1029                        required_fields.push((field_name, element.clone()));
1030                    }
1031                }
1032            }
1033        }
1034
1035        // If no required fields, we could derive Default, but we'll generate it anyway for consistency
1036        // Build the Default implementation
1037        let mut field_inits = Vec::new();
1038
1039        // First, handle the base field if it exists (base fields are added by TokenGenerator, not in rust_struct.fields)
1040        if let Some(base_def) = &rust_struct.base_definition {
1041            // Extract the base type name (e.g., "DomainResource" from a URL or just "DomainResource")
1042            let base_type = base_def.split('/').next_back().unwrap_or(base_def);
1043            let base_type = crate::naming::Naming::to_rust_identifier(base_type);
1044            let proper_base_type = if base_type
1045                .chars()
1046                .next()
1047                .map(|c| c.is_lowercase())
1048                .unwrap_or(false)
1049            {
1050                crate::naming::Naming::capitalize_first(&base_type)
1051            } else {
1052                base_type
1053            };
1054            field_inits.push(format!("base: {proper_base_type}::default()"));
1055            // Base field uses ::default(), so we can potentially derive
1056        }
1057
1058        // Then, process other fields from the struct
1059        for field in &rust_struct.fields {
1060            let field_name = &field.name;
1061
1062            // Check if this is a required field
1063            let is_required = required_fields.iter().any(|(name, _)| {
1064                let snake_name = crate::naming::Naming::to_snake_case(name);
1065                snake_name == *field_name
1066            });
1067
1068            if is_required {
1069                // Generate appropriate default for required field based on type
1070                let default_value = match field.field_type.to_string().as_str() {
1071                    // Handle enums - use Default::default() if available
1072                    s if s.contains("::") && !s.contains("Option") && !s.contains("Vec") => {
1073                        format!("{s}::default()")
1074                    }
1075                    // Handle String
1076                    "String" => "String::new()".to_string(),
1077                    // Handle primitives
1078                    "i32" | "i64" | "u32" | "u64" => "0".to_string(),
1079                    "f32" | "f64" => "0.0".to_string(),
1080                    "bool" => "false".to_string(),
1081                    // Handle Vec
1082                    s if s.starts_with("Vec<") => "Vec::new()".to_string(),
1083                    // For unknown types, try Default::default()
1084                    _ => format!("{}::default()", field.field_type.to_string()),
1085                };
1086                field_inits.push(format!("{field_name}: {default_value}"));
1087            } else {
1088                // Optional field - use Default
1089                field_inits.push(format!("{field_name}: Default::default()"));
1090            }
1091        }
1092
1093        // Generate the impl block
1094        let impl_block = format!(
1095            r#"impl Default for {} {{
1096    fn default() -> Self {{
1097        Self {{
1098            {}
1099        }}
1100    }}
1101}}"#,
1102            struct_name,
1103            field_inits.join(",\n            ")
1104        );
1105
1106        impl_block
1107    }
1108
1109    /// Generate Default implementation for a nested struct
1110    /// Nested structs are BackboneElements within a parent resource, so we need to
1111    /// extract the relevant elements from the parent StructureDefinition using the
1112    /// nested struct's base path (e.g., "AuditEvent.source" for AuditEventSource)
1113    fn generate_nested_struct_default_implementation(
1114        &self,
1115        parent_structure_def: &StructureDefinition,
1116        nested_struct: &RustStruct,
1117    ) -> String {
1118        // Get the struct name
1119        let struct_name = &nested_struct.name;
1120
1121        // Check if the struct has Default derive already
1122        if nested_struct.derives.iter().any(|d| d == "Default") {
1123            return String::new();
1124        }
1125
1126        // Determine the base path for this nested struct from the parent StructureDefinition
1127        // Example: "AuditEventSource" -> "AuditEvent.source"
1128        let parent_name = &parent_structure_def.name;
1129        let nested_field_name = if struct_name.starts_with(parent_name) {
1130            let suffix = &struct_name[parent_name.len()..];
1131            crate::naming::Naming::to_snake_case(suffix)
1132        } else {
1133            // Fallback - should not happen in practice
1134            return String::new();
1135        };
1136
1137        let base_path = format!("{parent_name}.{nested_field_name}");
1138
1139        // Get elements from the parent StructureDefinition
1140        let elements = if let Some(differential) = &parent_structure_def.differential {
1141            &differential.element
1142        } else if let Some(snapshot) = &parent_structure_def.snapshot {
1143            &snapshot.element
1144        } else {
1145            &Vec::new()
1146        };
1147
1148        // Collect required fields for this nested struct (elements under base_path with min >= 1)
1149        let mut required_fields = Vec::new();
1150        for element in elements {
1151            // Match elements like "AuditEvent.source.observer"
1152            if element.path.starts_with(&format!("{base_path}.")) {
1153                let field_path = element.path.strip_prefix(&format!("{base_path}.")).unwrap();
1154                // Only direct fields (no dots in remaining path)
1155                if !field_path.contains('.') && !field_path.ends_with("[x]") {
1156                    if let Some(min) = element.min {
1157                        if min >= 1 {
1158                            required_fields.push((field_path, element.clone()));
1159                        }
1160                    }
1161                }
1162            }
1163        }
1164
1165        // Build the Default implementation
1166        let mut field_inits = Vec::new();
1167
1168        // First, handle the base field (use the actual base type from the struct definition)
1169        if let Some(base_def) = &nested_struct.base_definition {
1170            // Extract the base type name (e.g., "BackboneElement", "Element", "Extension")
1171            let base_type = base_def.split('/').next_back().unwrap_or(base_def);
1172            let base_type = crate::naming::Naming::to_rust_identifier(base_type);
1173            let proper_base_type = if base_type
1174                .chars()
1175                .next()
1176                .map(|c| c.is_lowercase())
1177                .unwrap_or(false)
1178            {
1179                crate::naming::Naming::capitalize_first(&base_type)
1180            } else {
1181                base_type
1182            };
1183            field_inits.push(format!("base: {proper_base_type}::default()"));
1184        }
1185
1186        // Then, process other fields from the struct
1187        for field in &nested_struct.fields {
1188            let field_name = &field.name;
1189
1190            // Check if this is a required field
1191            let is_required = required_fields.iter().any(|(name, _)| {
1192                let snake_name = crate::naming::Naming::to_snake_case(name);
1193                snake_name == *field_name
1194            });
1195
1196            if is_required {
1197                // Generate appropriate default for required field based on type
1198                let default_value = match field.field_type.to_string().as_str() {
1199                    // Handle enums - use Default::default() if available
1200                    s if s.contains("::") && !s.contains("Option") && !s.contains("Vec") => {
1201                        format!("{s}::default()")
1202                    }
1203                    // Handle String
1204                    "String" => "String::new()".to_string(),
1205                    // Handle primitives
1206                    "i32" | "i64" | "u32" | "u64" => "0".to_string(),
1207                    "f32" | "f64" => "0.0".to_string(),
1208                    "bool" => "false".to_string(),
1209                    // Handle Vec
1210                    s if s.starts_with("Vec<") => "Vec::new()".to_string(),
1211                    // For unknown types, try Default::default()
1212                    _ => format!("{}::default()", field.field_type.to_string()),
1213                };
1214                field_inits.push(format!("{field_name}: {default_value}"));
1215            } else {
1216                // Optional field - use Default
1217                field_inits.push(format!("{field_name}: Default::default()"));
1218            }
1219        }
1220
1221        // Generate the impl block
1222        let impl_block = format!(
1223            r#"impl Default for {} {{
1224    fn default() -> Self {{
1225        Self {{
1226            {}
1227        }}
1228    }}
1229}}"#,
1230            struct_name,
1231            field_inits.join(",\n            ")
1232        );
1233
1234        impl_block
1235    }
1236
1237    /// Generate a complete crate structure with all necessary files and modules
1238    pub fn generate_complete_crate<P: AsRef<Path>>(
1239        &self,
1240        output_dir: P,
1241        crate_name: &str,
1242        _structures: &[StructureDefinition],
1243    ) -> CodegenResult<()> {
1244        let output_dir = output_dir.as_ref();
1245
1246        // Create main directories
1247        let src_dir = output_dir.join("src");
1248        fs::create_dir_all(&src_dir)?;
1249
1250        // Create module directories
1251        let primitives_dir = src_dir.join("primitives");
1252        let datatypes_dir = src_dir.join("datatypes");
1253        let extensions_dir = src_dir.join("extensions");
1254        let resource_dir = src_dir.join("resource");
1255        let traits_dir = src_dir.join("traits");
1256
1257        fs::create_dir_all(&primitives_dir)?;
1258        fs::create_dir_all(&datatypes_dir)?;
1259        fs::create_dir_all(&extensions_dir)?;
1260        fs::create_dir_all(&resource_dir)?;
1261        fs::create_dir_all(&traits_dir)?;
1262
1263        // Generate main lib.rs
1264        self.generate_lib_file(src_dir.join("lib.rs"))?;
1265
1266        // Generate macros.rs
1267        self.generate_macros_file(src_dir.join("macros.rs"))?;
1268
1269        // Generate combined primitives.rs file
1270        // For now, use an empty array since we're focusing on macro inclusion
1271        self.generate_combined_primitives_file(&[], primitives_dir.join("mod.rs"))?;
1272
1273        // Generate a basic Cargo.toml if it doesn't exist
1274        let cargo_toml_path = output_dir.join("Cargo.toml");
1275        if !cargo_toml_path.exists() {
1276            self.generate_cargo_toml(&cargo_toml_path, crate_name)?;
1277        }
1278
1279        // Generate module files for datatypes, extensions, resource, and traits directories
1280        // These will be populated with actual generated types later
1281        self.generate_module_file(&datatypes_dir, &[])?;
1282        self.generate_module_file(&extensions_dir, &[])?;
1283        self.generate_module_file(&resource_dir, &[])?;
1284        self.generate_module_file(&traits_dir, &[])?;
1285
1286        Ok(())
1287    }
1288
1289    /// Generate a basic Cargo.toml for the generated crate
1290    fn generate_cargo_toml<P: AsRef<Path>>(
1291        &self,
1292        cargo_path: P,
1293        crate_name: &str,
1294    ) -> CodegenResult<()> {
1295        let cargo_content = format!(
1296            r#"[package]
1297name = "{crate_name}"
1298version = "0.1.0"
1299edition = "2021"
1300
1301[dependencies]
1302serde = {{ version = "1.0", features = ["derive"] }}
1303serde_json = "1.0"
1304"#
1305        );
1306
1307        fs::write(cargo_path, cargo_content)?;
1308        Ok(())
1309    }
1310}
1311
1312#[cfg(test)]
1313mod tests {
1314    use super::*;
1315    use crate::config::CodegenConfig;
1316    use crate::generators::token_generator::TokenGenerator;
1317    use std::fs;
1318    use tempfile::TempDir;
1319
1320    #[test]
1321    fn test_generate_macros_file() {
1322        let temp_dir = TempDir::new().unwrap();
1323        let macros_path = temp_dir.path().join("macros.rs");
1324
1325        let config = CodegenConfig::default();
1326        let token_generator = TokenGenerator::new();
1327        let file_generator = FileGenerator::new(&config, &token_generator);
1328
1329        file_generator.generate_macros_file(&macros_path).unwrap();
1330
1331        assert!(macros_path.exists());
1332        let content = fs::read_to_string(&macros_path).unwrap();
1333
1334        // Check that the file contains our macro definitions
1335        assert!(content.contains("macro_rules! primitive_string"));
1336        assert!(content.contains("macro_rules! primitive_boolean"));
1337        assert!(content.contains("macro_rules! primitive_id"));
1338    }
1339
1340    #[test]
1341    fn test_generate_lib_file() {
1342        let temp_dir = TempDir::new().unwrap();
1343        let lib_path = temp_dir.path().join("lib.rs");
1344
1345        let config = CodegenConfig::default();
1346        let token_generator = TokenGenerator::new();
1347        let file_generator = FileGenerator::new(&config, &token_generator);
1348
1349        file_generator.generate_lib_file(&lib_path).unwrap();
1350
1351        assert!(lib_path.exists());
1352        let content = fs::read_to_string(&lib_path).unwrap();
1353
1354        // Check that the file contains module declarations
1355        assert!(content.contains("pub mod macros;"));
1356        assert!(content.contains("pub mod primitives;"));
1357        assert!(content.contains("pub mod datatypes;"));
1358        assert!(content.contains("pub mod resources;"));
1359        assert!(content.contains("pub mod traits;"));
1360        assert!(content.contains("pub mod bindings;"));
1361
1362        // Check selective re-exports (only macros and serde)
1363        assert!(content.contains("pub use macros::*;"));
1364        assert!(content.contains("pub use serde::{Deserialize, Serialize};"));
1365
1366        // Should NOT have glob re-exports for other modules
1367        assert!(!content.contains("pub use primitives::*;"));
1368        assert!(!content.contains("pub use datatypes::*;"));
1369        assert!(!content.contains("pub use resource::*;"));
1370        assert!(!content.contains("pub use traits::*;"));
1371        assert!(!content.contains("pub use bindings::*;"));
1372    }
1373
1374    #[test]
1375    fn test_generate_complete_crate() {
1376        let temp_dir = TempDir::new().unwrap();
1377        let crate_path = temp_dir.path().join("test-crate");
1378
1379        let config = CodegenConfig::default();
1380        let token_generator = TokenGenerator::new();
1381        let file_generator = FileGenerator::new(&config, &token_generator);
1382
1383        file_generator
1384            .generate_complete_crate(
1385                &crate_path,
1386                "test-crate",
1387                &[], // Empty structure definitions
1388            )
1389            .unwrap();
1390
1391        // Check that all required files and directories exist
1392        assert!(crate_path.join("Cargo.toml").exists());
1393        assert!(crate_path.join("src").is_dir());
1394        assert!(crate_path.join("src/lib.rs").exists());
1395        assert!(crate_path.join("src/macros.rs").exists());
1396        assert!(crate_path.join("src/primitives").is_dir());
1397        assert!(crate_path.join("src/primitives/mod.rs").exists());
1398        assert!(crate_path.join("src/datatypes").is_dir());
1399        assert!(crate_path.join("src/datatypes/mod.rs").exists());
1400        assert!(crate_path.join("src/resource").is_dir());
1401        assert!(crate_path.join("src/resource/mod.rs").exists());
1402        assert!(crate_path.join("src/traits").is_dir());
1403        assert!(crate_path.join("src/traits/mod.rs").exists());
1404
1405        // Check Cargo.toml content
1406        let cargo_content = fs::read_to_string(crate_path.join("Cargo.toml")).unwrap();
1407        assert!(cargo_content.contains("name = \"test-crate\""));
1408        assert!(cargo_content.contains("edition = \"2021\""));
1409        assert!(cargo_content.contains("serde"));
1410        assert!(!cargo_content.contains("paste")); // paste should NOT be in dependencies
1411    }
1412}