Skip to main content

rh_codegen/generators/
crate_generator.rs

1//! FHIR Crate Generation
2//!
3//! This module provides functionality to generate complete Rust crates from FHIR packages,
4//! including Cargo.toml, lib.rs, README.md, and proper module structure.
5
6use std::fs;
7use std::path::Path;
8
9use anyhow::Result;
10use chrono::Local;
11
12// use crate::quality::{run_quality_checks, QualityConfig};
13
14/// Parameters for crate generation
15#[derive(Debug, Clone)]
16pub struct CrateGenerationParams<'a> {
17    /// Output directory for the crate
18    pub output: &'a Path,
19    /// Package name (e.g., "hl7.fhir.r4.core")
20    pub package: &'a str,
21    /// Package version (e.g., "4.0.1")
22    pub version: &'a str,
23    /// Canonical URL from package.json
24    pub canonical_url: &'a str,
25    /// Author from package.json
26    pub author: &'a str,
27    /// Description from package.json
28    pub description: &'a str,
29    /// Command that was invoked to generate this crate
30    pub command_invoked: &'a str,
31    /// Optional override for the generated crate name (e.g., "rh-hl7-fhir-r4-core").
32    /// When None, the name is auto-derived from the FHIR package name.
33    pub crate_name: Option<&'a str>,
34}
35
36/// Statistics about generated crate content
37#[derive(Debug, Clone)]
38pub struct CrateStatistics {
39    /// Number of generated structs
40    pub num_structs: usize,
41    /// Number of generated enums
42    pub num_enums: usize,
43    /// Total number of types
44    pub total_types: usize,
45    /// Canonical URL
46    pub canonical_url: String,
47}
48
49/// Generate a complete Rust crate structure with idiomatic directory organization
50pub fn generate_crate_structure(params: CrateGenerationParams) -> Result<()> {
51    // Create the src directory structure
52    let src_dir = params.output.join("src");
53    fs::create_dir_all(&src_dir)?;
54
55    // Create subdirectories
56    let resource_dir = src_dir.join("resources");
57    let datatypes_dir = src_dir.join("datatypes");
58    let extensions_dir = src_dir.join("extensions");
59    let primitives_dir = src_dir.join("primitives");
60    let traits_dir = src_dir.join("traits");
61    let bindings_dir = src_dir.join("bindings");
62    let profiles_dir = src_dir.join("profiles");
63
64    fs::create_dir_all(&resource_dir)?;
65    fs::create_dir_all(&datatypes_dir)?;
66    fs::create_dir_all(&extensions_dir)?;
67    fs::create_dir_all(&primitives_dir)?;
68    fs::create_dir_all(&traits_dir)?;
69    fs::create_dir_all(&bindings_dir)?;
70    fs::create_dir_all(&profiles_dir)?;
71
72    // Generate statistics by counting organized files (if any exist from organized generation)
73    let stats = generate_crate_statistics_from_organized_dirs(
74        &resource_dir,
75        &datatypes_dir,
76        &extensions_dir,
77        &primitives_dir,
78    )?;
79
80    // Generate Cargo.toml
81    let cargo_toml_content = generate_cargo_toml(
82        params.package,
83        params.version,
84        params.output,
85        params.crate_name,
86    );
87    let cargo_toml_path = params.output.join("Cargo.toml");
88    fs::write(&cargo_toml_path, cargo_toml_content)?;
89
90    // Generate lib.rs with new structure
91    let lib_rs_content = generate_lib_rs_idiomatic()?;
92    let lib_rs_path = src_dir.join("lib.rs");
93    fs::write(&lib_rs_path, lib_rs_content)?;
94
95    // Generate macros.rs with FHIR primitive macros
96    let macros_content = include_str!("../macros.rs");
97    let macros_path = src_dir.join("macros.rs");
98    fs::write(&macros_path, macros_content)?;
99
100    // Generate validation.rs module with ValidatableResource trait
101    let validation_content =
102        crate::generators::ValidationTraitGenerator::generate_validation_module();
103    let validation_path = src_dir.join("validation.rs");
104    fs::write(&validation_path, validation_content)?;
105
106    // Generate prelude.rs module with commonly used trait re-exports
107    let prelude_content = generate_prelude_module();
108    let prelude_path = src_dir.join("prelude.rs");
109    fs::write(&prelude_path, prelude_content)?;
110
111    // Generate mod.rs files for each module
112    generate_module_files(
113        &resource_dir,
114        &datatypes_dir,
115        &extensions_dir,
116        &primitives_dir,
117        &traits_dir,
118        &bindings_dir,
119        &profiles_dir,
120    )?;
121
122    // Generate README.md
123    let readme_content = generate_readme_md(
124        params.package,
125        params.version,
126        params.canonical_url,
127        params.author,
128        params.description,
129        params.command_invoked,
130        &stats,
131        params.crate_name,
132    );
133    let readme_path = params.output.join("README.md");
134    fs::write(&readme_path, readme_content)?;
135
136    // Run quality checks as a final step
137    //let quality_config = QualityConfig::default();
138    //run_quality_checks(params.output, &quality_config)
139    //   .map_err(|e| anyhow::anyhow!("Quality checks failed: {e}"))?;
140
141    Ok(())
142}
143
144/// Generate Cargo.toml content for the FHIR crate
145fn generate_cargo_toml(
146    package: &str,
147    version: &str,
148    _output_dir: &Path,
149    crate_name_override: Option<&str>,
150) -> String {
151    // Derive the package name: use override if provided, else convert FHIR package name
152    let derived = package.replace(['.', '-'], "_");
153    let crate_name = crate_name_override.unwrap_or(&derived);
154    // Lib name must be a valid Rust identifier (no hyphens)
155    let lib_name = crate_name.replace('-', "_");
156
157    // Try to find rh-foundation crate path relative to output directory
158    let rh_foundation_path = if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
159        // We're in the rh-codegen crate, so rh-foundation is at ../rh-foundation
160        let workspace_root = Path::new(&manifest_dir).parent().and_then(|p| p.parent());
161        if let Some(root) = workspace_root {
162            let foundation_path = root.join("crates/rh-foundation");
163            if foundation_path.exists() {
164                // Use absolute path for reliability
165                foundation_path.display().to_string()
166            } else {
167                // Fallback to relative path assumption
168                "../../rh/crates/rh-foundation".to_string()
169            }
170        } else {
171            "../../rh/crates/rh-foundation".to_string()
172        }
173    } else {
174        "../../rh/crates/rh-foundation".to_string()
175    };
176
177    format!(
178        r#"[package]
179name = "{crate_name}"
180version = "0.1.0"
181edition = "2021"
182description = "Generated FHIR types from {package} package version {version}"
183authors = ["FHIR Code Generator"]
184license = "MIT OR Apache-2.0"
185
186[dependencies]
187serde = {{ version = "1.0", features = ["derive"] }}
188serde_json = "1.0"
189phf = {{ version = "0.11", features = ["macros"] }}
190once_cell = "1.19"
191rh-foundation = {{ path = "{rh_foundation_path}" }}
192
193[lib]
194name = "{lib_name}"
195path = "src/lib.rs"
196"#
197    )
198}
199
200/// Generate lib.rs content with idiomatic module structure
201fn generate_lib_rs_idiomatic() -> Result<String> {
202    let lib_content = r#"//! Generated FHIR Rust bindings
203//!
204//! This crate contains Rust types and traits for FHIR resources and data types.
205//! It includes macros for primitive field generation and maintains FHIR compliance.
206
207// Allow clippy lint for derivable Default implementations
208//
209// TODO: Future optimization - derive Default when possible instead of manual impl
210//
211// Currently, we generate explicit Default implementations for all structs.
212// Many of these could use #[derive(Default)] instead, which would be more idiomatic.
213//
214// Pros of deriving Default:
215// - More idiomatic Rust code
216// - Less generated code (no manual impl blocks)
217// - Clearer intent (all fields use Default::default())
218//
219// Cons of current approach (manual impl):
220// - Clippy warns about 1,100+ derivable implementations
221// - More verbose generated code
222//
223// Pros of current approach:
224// - Explicit and predictable behavior
225// - Handles mixed initialization patterns consistently
226// - Simpler code generation logic
227//
228// To implement derive-based approach would require:
229// 1. Analyze all field types to ensure they implement Default
230// 2. Detect required fields with non-Default initializations (String::new(), Vec::new(), etc.)
231// 3. Add "Default" to struct derives only when ALL fields can use Default::default()
232// 4. Skip manual impl generation for those structs
233//
234#![allow(clippy::derivable_impls)]
235
236pub mod macros;
237pub mod metadata;
238pub mod primitives;
239pub mod datatypes;
240pub mod extensions;
241pub mod resources;
242pub mod profiles;
243pub mod traits;
244pub mod bindings;
245pub mod validation;
246pub mod prelude;
247
248pub use serde::{Deserialize, Serialize};
249"#;
250
251    Ok(lib_content.to_string())
252}
253/// Generate mod.rs files for each module directory
254pub fn generate_module_files(
255    resource_dir: &Path,
256    datatypes_dir: &Path,
257    extensions_dir: &Path,
258    primitives_dir: &Path,
259    traits_dir: &Path,
260    bindings_dir: &Path,
261    profiles_dir: &Path,
262) -> Result<()> {
263    // Generate resource/mod.rs
264    let resource_mod_content = generate_mod_rs_for_directory(resource_dir, "FHIR resource types")?;
265    fs::write(resource_dir.join("mod.rs"), resource_mod_content)?;
266
267    // Generate datatypes/mod.rs
268    let datatypes_mod_content = generate_mod_rs_for_directory(datatypes_dir, "FHIR data types")?;
269    fs::write(datatypes_dir.join("mod.rs"), datatypes_mod_content)?;
270
271    // Generate extensions/mod.rs
272    let extensions_mod_content =
273        generate_mod_rs_for_directory(extensions_dir, "FHIR extension types")?;
274    fs::write(extensions_dir.join("mod.rs"), extensions_mod_content)?;
275
276    // Generate primitives/mod.rs
277    let primitives_mod_content =
278        generate_mod_rs_for_directory(primitives_dir, "FHIR primitive types")?;
279    fs::write(primitives_dir.join("mod.rs"), primitives_mod_content)?;
280
281    // Generate traits/mod.rs (placeholder for now, but don't overwrite existing content)
282    let traits_mod_path = traits_dir.join("mod.rs");
283    if !traits_mod_path.exists() || fs::read_to_string(&traits_mod_path)?.len() < 500 {
284        // If file is small, it's likely just placeholder
285        let traits_mod_content = generate_traits_mod_rs()?;
286        fs::write(traits_mod_path, traits_mod_content)?;
287    }
288
289    // Generate bindings/mod.rs for ValueSet enums
290    let bindings_mod_content =
291        generate_mod_rs_for_directory(bindings_dir, "FHIR ValueSet bindings and enums")?;
292    fs::write(bindings_dir.join("mod.rs"), bindings_mod_content)?;
293
294    // Generate profiles/mod.rs for FHIR profiles
295    let profiles_mod_content =
296        generate_mod_rs_for_directory(profiles_dir, "FHIR profiles derived from core resources")?;
297    fs::write(profiles_dir.join("mod.rs"), profiles_mod_content)?;
298
299    Ok(())
300}
301
302/// Generate mod.rs content for a specific directory
303fn generate_mod_rs_for_directory(dir: &Path, description: &str) -> Result<String> {
304    let mut content = String::new();
305    content.push_str(&format!("//! {description}\n\n"));
306
307    // Get all .rs files in the directory
308    let mut rs_files = Vec::new();
309    if dir.exists() {
310        for entry in fs::read_dir(dir)? {
311            let entry = entry?;
312            let path = entry.path();
313            if path.is_file() && path.extension().is_some_and(|ext| ext == "rs") {
314                if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
315                    if stem != "mod" {
316                        rs_files.push(stem.to_string());
317                    }
318                }
319            }
320        }
321    }
322
323    // Sort module names for consistency
324    rs_files.sort();
325
326    // Generate module declarations
327    for module_name in &rs_files {
328        content.push_str(&format!("pub mod {module_name};\n"));
329    }
330
331    // Note: No glob re-exports to avoid ambiguous re-export warnings
332    // Individual types can be imported explicitly when needed
333
334    Ok(content)
335}
336
337/// Generate traits/mod.rs with placeholder content
338fn generate_traits_mod_rs() -> Result<String> {
339    let content = r#"//! FHIR traits for common functionality
340//!
341//! This module contains traits that define common interfaces for FHIR types.
342
343// Placeholder traits - these would be generated based on FHIR structure definitions
344
345/// Trait for types that have extensions
346pub trait HasExtensions {
347    /// Get the extensions for this type
348    fn extensions(&self) -> &[crate::datatypes::extension::Extension];
349}
350
351/// Trait for FHIR resources
352pub trait Resource {
353    /// Get the resource type name
354    fn resource_type(&self) -> &'static str;
355    
356    /// Get the logical id of this resource
357    fn id(&self) -> Option<&str>;
358    
359    /// Get the metadata about this resource
360    fn meta(&self) -> Option<&crate::datatypes::meta::Meta>;
361}
362
363/// Trait for domain resources (resources that can have narrative)
364pub trait DomainResource: Resource + HasExtensions {
365    /// Get the narrative text for this domain resource
366    fn narrative(&self) -> Option<&crate::datatypes::narrative::Narrative>;
367}
368"#;
369
370    Ok(content.to_string())
371}
372
373/// Generate statistics from organized directories
374fn generate_crate_statistics_from_organized_dirs(
375    resource_dir: &Path,
376    datatypes_dir: &Path,
377    extensions_dir: &Path,
378    primitives_dir: &Path,
379) -> Result<CrateStatistics> {
380    let mut num_structs = 0;
381    let num_enums = 0; // Enums would be handled separately
382
383    // Count files in each directory
384    for dir in [resource_dir, datatypes_dir, extensions_dir, primitives_dir] {
385        if dir.exists() {
386            for entry in fs::read_dir(dir)? {
387                let entry = entry?;
388                let path = entry.path();
389                if path.is_file() && path.extension().is_some_and(|ext| ext == "rs") {
390                    if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
391                        if stem != "mod" {
392                            num_structs += 1;
393
394                            // Count additional structs within the file (nested types)
395                            if let Ok(content) = fs::read_to_string(&path) {
396                                num_structs +=
397                                    content.matches("pub struct ").count().saturating_sub(1);
398                            }
399                        }
400                    }
401                }
402            }
403        }
404    }
405
406    let total_types = num_structs + num_enums;
407
408    Ok(CrateStatistics {
409        num_structs,
410        num_enums,
411        total_types,
412        canonical_url: "Unknown".to_string(),
413    })
414}
415
416/// Generate README.md content with package information and statistics
417#[allow(clippy::too_many_arguments)]
418fn generate_readme_md(
419    package: &str,
420    version: &str,
421    canonical_url: &str,
422    author: &str,
423    description: &str,
424    command_invoked: &str,
425    stats: &CrateStatistics,
426    crate_name_override: Option<&str>,
427) -> String {
428    let derived = package.replace(['.', '-'], "_");
429    // Use lib name form (underscores) for Rust `use` statements in README
430    let crate_name = crate_name_override.unwrap_or(&derived).replace('-', "_");
431    let mut content = String::new();
432
433    content.push_str(&format!("# {crate_name}\n\n"));
434    content.push_str(&format!("**Generated FHIR Types for {package}**\n\n"));
435    content.push_str(&format!("This crate contains automatically generated Rust types for FHIR (Fast Healthcare Interoperability Resources) based on the `{package}` package.\n\n"));
436
437    content.push_str("## Important Notice\n\n");
438    content
439        .push_str("**This crate was automatically generated using the RH codegen CLI tool.**\n\n");
440    content.push_str(&format!(
441        "- **Generator command**:\n```bash\n{command_invoked}\n```\n\n"
442    ));
443    content.push_str(&format!("- **Generation timestamp**: {}\n\n", Local::now()));
444
445    content.push_str("## Package Information\n\n");
446
447    content.push_str(&format!("* **Package Name** {package}\n"));
448    content.push_str(&format!("* **Package Author** {author}\n"));
449    content.push_str(&format!("* **Version** {version}\n"));
450    content.push_str(&format!("* **Canonical URL** `{canonical_url}`\n\n"));
451
452    content.push_str(&format!(
453        "**Statistics: {} structs, {} enums, {} total types**\n\n",
454        stats.num_structs, stats.num_enums, stats.total_types
455    ));
456
457    content.push_str(&format!("## Description\n\n{description}"));
458
459    content.push_str("\n\n");
460
461    content.push_str("## Features\n\n");
462    content.push_str(
463        "- **Complete FHIR type definitions** - All resources, datatypes, and primitives\n",
464    );
465    content.push_str(
466        "- **Serde serialization** - Built-in JSON serialization/deserialization support\n",
467    );
468    content.push_str(
469        "- **Type metadata** - Compile-time metadata for field types and path resolution\n",
470    );
471    content.push_str(
472        "- **Idiomatic Rust** - Clean, organized module structure with proper naming conventions\n",
473    );
474    content.push_str("- **Zero-cost abstractions** - PHF (perfect hash function) maps for O(1) metadata lookups\n\n");
475
476    content.push_str("## Usage\n\n");
477    content.push_str("Add this crate to your `Cargo.toml`:\n\n");
478    content.push_str("```toml\n");
479    content.push_str("[dependencies]\n");
480    content.push_str(&format!("{crate_name} = \"0.1.0\"\n"));
481    content.push_str("```\n\n");
482
483    content.push_str("### Deserializing FHIR Resources\n\n");
484    content.push_str("```rust\n");
485    content.push_str(&format!("use {crate_name}::resources::patient::Patient;\n"));
486    content.push_str("use serde_json;\n\n");
487    content.push_str("let json_data = r#\"{\\\"resourceType\\\": \\\"Patient\\\", \\\"id\\\": \\\"example\\\"}\"#;\n");
488    content.push_str("let patient: Patient = serde_json::from_str(json_data)?;\n\n");
489    content.push_str("println!(\"Patient ID: {}\", patient.id.unwrap_or_default());\n");
490    content.push_str("```\n\n");
491
492    content.push_str("### Creating Resources Programmatically\n\n");
493    content.push_str("This crate provides two idiomatic ways to work with FHIR resources using builder traits:\n\n");
494
495    content.push_str("#### Option 1: Resource Module with Re-exported Traits (Recommended)\n\n");
496    content.push_str("Each resource module re-exports its associated traits for convenience:\n\n");
497    content.push_str("```rust\n");
498    content.push_str("// Import resource with its traits - all in one place!\n");
499    content.push_str(&format!(
500        "use {crate_name}::resources::patient::{{Patient, PatientMutators}};\n"
501    ));
502    content.push_str(&format!(
503        "use {crate_name}::prelude::*;  // Gets base traits (ResourceMutators, etc.)\n"
504    ));
505    content.push_str(&format!(
506        "use {crate_name}::datatypes::human_name::HumanName;\n\n"
507    ));
508    content.push_str("// Build a patient using the builder pattern\n");
509    content.push_str("let patient = <Patient as PatientMutators>::new()\n");
510    content.push_str("    .set_id(\"patient-123\".to_string())\n");
511    content.push_str("    .set_active(true)\n");
512    content.push_str("    .add_name(HumanName {\n");
513    content.push_str("        family: Some(\"Doe\".to_string()),\n");
514    content.push_str("        given: vec![\"John\".to_string()],\n");
515    content.push_str("        ..Default::default()\n");
516    content.push_str("    })\n");
517    content.push_str("    .set_gender(Some(AdministrativeGender::Male))\n");
518    content.push_str("    .set_birth_date(\"1990-01-15\".to_string());\n");
519    content.push_str("```\n\n");
520
521    content.push_str("#### Option 2: Prelude Module\n\n");
522    content.push_str("For common base traits, use the prelude module:\n\n");
523    content.push_str("```rust\n");
524    content.push_str(&format!(
525        "use {crate_name}::prelude::*;  // ValidatableResource, ResourceMutators, etc.\n"
526    ));
527    content.push_str(&format!(
528        "use {crate_name}::resources::patient::{{Patient, PatientMutators}};\n\n"
529    ));
530    content.push_str("let patient = <Patient as PatientMutators>::new()\n");
531    content.push_str("    .set_id(\"example\".to_string());\n");
532    content.push_str("```\n\n");
533    content.push_str("The prelude includes:\n");
534    content.push_str("- `ValidatableResource` - Access invariants and validation rules\n");
535    content.push_str("- `ResourceMutators` - Builder methods for all resources\n");
536    content.push_str("- `DomainResourceMutators` - Builder methods for domain resources\n\n");
537
538    content.push_str("#### Direct Struct Construction\n\n");
539    content.push_str("You can also construct resources directly:\n\n");
540    content.push_str("```rust\n");
541    content.push_str(&format!("use {crate_name}::resources::patient::Patient;\n"));
542    content.push_str(&format!(
543        "use {crate_name}::datatypes::human_name::HumanName;\n"
544    ));
545    content.push_str(&format!(
546        "use {crate_name}::bindings::administrative_gender::AdministrativeGender;\n\n"
547    ));
548    content.push_str("let patient = Patient {\n");
549    content.push_str("    id: Some(\"patient-123\".to_string()),\n");
550    content.push_str("    active: Some(true),\n");
551    content.push_str("    name: vec![HumanName {\n");
552    content.push_str("        family: Some(\"Doe\".to_string()),\n");
553    content.push_str("        given: vec![\"John\".to_string()],\n");
554    content.push_str("        ..Default::default()\n");
555    content.push_str("    }],\n");
556    content.push_str("    gender: Some(AdministrativeGender::Male),\n");
557    content.push_str("    birth_date: Some(\"1990-01-15\".to_string()),\n");
558    content.push_str("    ..Default::default()\n");
559    content.push_str("};\n");
560    content.push_str("```\n\n");
561
562    content.push_str("### Using Type Metadata\n\n");
563    content.push_str("This crate includes compile-time metadata for all FHIR types, enabling runtime type introspection and path resolution:\n\n");
564    content.push_str("```rust\n");
565    content.push_str(&format!("use {crate_name}::metadata::{{resolve_path, get_field_info, FhirFieldType, FhirPrimitiveType}};\n\n"));
566    content.push_str("// Resolve nested paths to their FHIR types\n");
567    content.push_str("if let Some(field_type) = resolve_path(\"Patient.birthDate\") {\n");
568    content.push_str("    match field_type {\n");
569    content.push_str("        FhirFieldType::Primitive(FhirPrimitiveType::Date) => {\n");
570    content.push_str("            println!(\"birthDate is a FHIR date type\");\n");
571    content.push_str("        }\n");
572    content.push_str("        _ => {}\n");
573    content.push_str("    }\n");
574    content.push_str("}\n\n");
575    content.push_str("// Resolve complex nested paths\n");
576    content.push_str("if let Some(field_type) = resolve_path(\"Patient.name.given\") {\n");
577    content.push_str("    match field_type {\n");
578    content.push_str("        FhirFieldType::Primitive(FhirPrimitiveType::String) => {\n");
579    content.push_str("            println!(\"name.given is a string array\");\n");
580    content.push_str("        }\n");
581    content.push_str("        _ => {}\n");
582    content.push_str("    }\n");
583    content.push_str("}\n\n");
584    content.push_str("// Get field information directly\n");
585    content.push_str("if let Some(field_info) = get_field_info(\"Patient\", \"active\") {\n");
586    content.push_str("    println!(\"Min cardinality: {}\", field_info.min);\n");
587    content.push_str("    println!(\"Max cardinality: {:?}\", field_info.max);\n");
588    content.push_str("    println!(\"Is choice type: {}\", field_info.is_choice_type);\n");
589    content.push_str("}\n");
590    content.push_str("```\n\n");
591
592    content.push_str("The metadata system enables:\n");
593    content.push_str("- **Path resolution** - Navigate nested paths like `Patient.name.given`\n");
594    content.push_str("- **Type introspection** - Determine field types at runtime\n");
595    content.push_str("- **Cardinality information** - Min/max occurrence constraints\n");
596    content.push_str("- **Choice type detection** - Identify polymorphic fields\n");
597    content
598        .push_str("- **Zero runtime cost** - All lookups use compile-time perfect hash maps\n\n");
599
600    content.push_str("## Structure\n\n");
601    content.push_str("This crate organizes FHIR types into logical modules:\n\n");
602    content.push_str("- **resources/** - All FHIR resources (Patient, Observation, etc.)\n");
603    content.push_str("- **profiles/** - FHIR profiles (Vitalsigns, BodyHeight, etc.)\n");
604    content.push_str(
605        "- **datatypes/** - Complex and primitive datatypes (HumanName, Address, etc.)\n",
606    );
607    content.push_str("- **bindings/** - ValueSet enumerations (AdministrativeGender, etc.)\n");
608    content.push_str("- **primitives/** - Base primitive types (DateType, DateTimeType, etc.)\n");
609    content.push_str("- **traits/** - Mutator, accessor, and existence traits for all types\n");
610    content.push_str(
611        "- **prelude.rs** - Commonly used traits (ValidatableResource, ResourceMutators, etc.)\n",
612    );
613    content.push_str("- **metadata.rs** - Type metadata and path resolution functions\n\n");
614
615    content.push_str("## Regenerating This Crate\n\n");
616    content.push_str("To regenerate this crate with updated FHIR definitions:\n\n");
617    content.push_str("```bash\n");
618    content.push_str(command_invoked);
619    content.push_str("\n```\n\n");
620
621    content.push_str("## License\n\n");
622    content.push_str("This generated crate is provided under MIT OR Apache-2.0 license.\n\n");
623
624    content.push_str("## Related Links\n\n");
625    content.push_str("- [FHIR Specification](https://hl7.org/fhir/)\n");
626    content.push_str("- [FHIR Package Registry](https://packages.fhir.org/)\n");
627    content.push_str("- [RH Project](https://github.com/reasonhealth/rh)\n\n");
628    content.push_str("---\n\n");
629    content.push_str(&format!(
630        "*Generated by RH codegen tool at {}*\n",
631        Local::now()
632    ));
633
634    content
635}
636
637/// Generate a prelude module with commonly used trait re-exports
638fn generate_prelude_module() -> String {
639    r#"//! Prelude module - commonly used traits for convenience
640//!
641//! This module re-exports the most commonly used traits for working with
642//! FHIR resources. Import this module to avoid having to import individual
643//! traits from the `traits` module.
644//!
645//! # Example
646//!
647//! ```ignore
648//! use hl7_fhir_r4_core::prelude::*;
649//! use hl7_fhir_r4_core::resources::patient::Patient;
650//!
651//! // All mutator traits are now in scope
652//! let patient = <Patient as PatientMutators>::new()
653//!     .set_id("example".to_string())
654//!     .set_active(true);
655//! ```
656
657// Resource mutator traits - for building resources with method chaining
658pub use crate::traits::resource::ResourceMutators;
659pub use crate::traits::domain_resource::DomainResourceMutators;
660
661// Note: Individual resource mutator traits (PatientMutators, ObservationMutators, etc.)
662// are re-exported from their respective resource modules for convenience.
663// For example: use hl7_fhir_r4_core::resources::patient::PatientMutators;
664
665// Validation trait
666pub use crate::validation::ValidatableResource;
667"#
668    .to_string()
669}
670
671/// Parse package.json to extract metadata for crate generation
672pub fn parse_package_metadata(package_json_path: &Path) -> Result<(String, String, String)> {
673    let package_json_content = fs::read_to_string(package_json_path)?;
674    let package_json: serde_json::Value = serde_json::from_str(&package_json_content)?;
675
676    let canonical = package_json
677        .get("canonical")
678        .and_then(|v| v.as_str())
679        .unwrap_or("Unknown")
680        .to_string();
681
682    let author = package_json
683        .get("author")
684        .and_then(|v| v.as_str())
685        .unwrap_or("FHIR Code Generator")
686        .to_string();
687
688    let description = package_json
689        .get("description")
690        .and_then(|v| v.as_str())
691        .unwrap_or("Generated FHIR types crate.")
692        .to_string();
693
694    Ok((canonical, author, description))
695}