Skip to main content

rh_codegen/generators/
struct_generator.rs

1//! Struct generation functionality
2//!
3//! This module handles the generation of Rust structs from FHIR StructureDefinitions.
4
5use std::collections::HashMap;
6
7use crate::config::CodegenConfig;
8use crate::fhir_types::StructureDefinition;
9use crate::generators::{DocumentationGenerator, FieldGenerator, TypeUtilities};
10use crate::rust_types::{RustField, RustStruct, RustType};
11use crate::value_sets::ValueSetManager;
12use crate::{CodegenError, CodegenResult};
13
14/// Struct generator for FHIR StructureDefinitions
15pub struct StructGenerator<'a> {
16    config: &'a CodegenConfig,
17    type_cache: &'a mut HashMap<String, RustStruct>,
18    value_set_manager: &'a mut ValueSetManager,
19}
20
21impl<'a> StructGenerator<'a> {
22    /// Create a new struct generator
23    pub fn new(
24        config: &'a CodegenConfig,
25        type_cache: &'a mut HashMap<String, RustStruct>,
26        value_set_manager: &'a mut ValueSetManager,
27    ) -> Self {
28        Self {
29            config,
30            type_cache,
31            value_set_manager,
32        }
33    }
34
35    /// Generate a Rust struct from a FHIR StructureDefinition
36    pub fn generate_struct(
37        &mut self,
38        structure_def: &StructureDefinition,
39    ) -> CodegenResult<RustStruct> {
40        // Skip LogicalModels as they are conceptual models, not implementable types
41        if structure_def.kind == "logical" {
42            return Err(CodegenError::Generation {
43                message: format!(
44                    "Skipping LogicalModel '{}' - logical models are not generated as Rust types",
45                    structure_def.name
46                ),
47            });
48        }
49
50        // Skip sample/example profiles such as `example-composition`, but do not skip
51        // real FHIR types whose names contain "Example" (for example, ExampleScenario).
52        if TypeUtilities::is_example_structure_definition(structure_def) {
53            return Err(CodegenError::Generation {
54                message: format!(
55                    "Skipping example StructureDefinition '{}'",
56                    structure_def.name
57                ),
58            });
59        }
60
61        // Skip files that begin with underscore (auto-generated/temporary files)
62        if TypeUtilities::should_skip_underscore_prefixed(structure_def) {
63            return Err(CodegenError::Generation {
64                message: format!(
65                    "Skipping underscore-prefixed StructureDefinition '{}' - underscore prefixed files are ignored",
66                    structure_def.name
67                ),
68            });
69        }
70
71        // Generate struct name from URL or ID, not the name field
72        let struct_name = crate::naming::Naming::struct_name(structure_def);
73
74        // Check if we've already generated this type
75        if let Some(cached_struct) = self.type_cache.get(&struct_name) {
76            return Ok(cached_struct.clone());
77        }
78
79        // Create the struct with enhanced documentation
80        let mut rust_struct = RustStruct::new(struct_name.clone());
81        rust_struct.doc_comment =
82            DocumentationGenerator::generate_struct_documentation(structure_def);
83
84        // Use config to determine derives
85        let mut derives = vec!["Debug".to_string(), "Clone".to_string()];
86        if self.config.with_serde {
87            derives.extend(vec!["Serialize".to_string(), "Deserialize".to_string()]);
88        }
89
90        // Add Default derive for profiles since they only have a base field that implements Default
91        // Check using TypeRegistry to see if this is a profile
92        let is_profile = crate::generators::type_registry::TypeRegistry::is_profile(structure_def);
93        if is_profile {
94            derives.push("Default".to_string());
95        }
96
97        rust_struct.derives = derives;
98
99        // Set the base definition for inheritance if present
100        if let Some(base_def) = &structure_def.base_definition {
101            rust_struct.base_definition = Some(
102                base_def
103                    .split('/')
104                    .next_back()
105                    .unwrap_or(base_def)
106                    .to_string(),
107            );
108        }
109
110        // Handle primitive types specially
111        if structure_def.kind == "primitive-type" {
112            return self.generate_primitive_type_struct(structure_def, rust_struct);
113        }
114
115        // Extract element definitions from differential only (preferred FHIR approach)
116        let elements = if let Some(differential) = &structure_def.differential {
117            &differential.element
118        } else if let Some(snapshot) = &structure_def.snapshot {
119            &snapshot.element
120        } else {
121            return Ok(rust_struct); // No elements to process
122        };
123
124        // Process each element definition to create struct fields and nested structs
125        let mut nested_structs_info = HashMap::new();
126        let mut direct_fields = Vec::new();
127
128        for element in elements {
129            // Skip the root element
130            if element.path == structure_def.name || element.path == structure_def.base_type {
131                continue;
132            }
133
134            // Only process elements that belong to this struct
135            let base_path = &structure_def.name;
136            if !element.path.starts_with(&format!("{base_path}.")) {
137                continue;
138            }
139
140            let field_path = element
141                .path
142                .strip_prefix(&format!("{base_path}."))
143                .unwrap_or_else(|| {
144                    panic!(
145                        "codegen bug: element path '{}' does not start with '{base_path}.'",
146                        element.path
147                    )
148                });
149
150            if field_path.contains('.') {
151                // This is a nested field - collect it for nested struct generation
152                let nested_field_name = field_path.split('.').next().unwrap_or_else(|| {
153                    panic!(
154                        "codegen bug: field path '{}' contains '.' but has no prefix segment",
155                        field_path
156                    )
157                });
158                nested_structs_info
159                    .entry(nested_field_name.to_string())
160                    .or_insert_with(Vec::new)
161                    .push(element.clone());
162            } else {
163                // This is a direct field of this struct
164                direct_fields.push(element.clone());
165            }
166        }
167
168        // First pass: Generate nested structs for BackboneElements
169        let mut nested_structs: Vec<_> = nested_structs_info.iter().collect();
170        nested_structs.sort_by_key(|(left_name, _)| *left_name);
171
172        for (nested_field_name, nested_elements) in nested_structs {
173            if let Some(nested_struct) = self.generate_nested_struct(
174                &struct_name,
175                nested_field_name,
176                nested_elements,
177                structure_def,
178            )? {
179                // Store the nested struct in cache for later use
180                self.type_cache
181                    .insert(nested_struct.name.clone(), nested_struct.clone());
182
183                // Register the nested struct in TypeRegistry with proper classification
184                crate::generators::type_registry::TypeRegistry::register_type_classification_only(
185                    &nested_struct.name,
186                    crate::generators::type_registry::TypeClassification::NestedStructure {
187                        parent_resource: struct_name.clone(),
188                    },
189                );
190            }
191        }
192
193        // Second pass: Process direct fields (now nested structs are available)
194        for element in direct_fields {
195            let fields = self.create_fields_from_element(&element)?;
196            for mut field in fields {
197                // Apply Box wrapper for circular dependencies
198                field.field_type =
199                    Self::apply_box_for_circular_dependencies(field.field_type, &rust_struct.name);
200                rust_struct.add_field(field);
201            }
202        }
203
204        // Cache the generated struct for future use
205        self.type_cache.insert(struct_name, rust_struct.clone());
206
207        Ok(rust_struct)
208    }
209
210    /// Generate a primitive type struct with special FHIR primitive type semantics
211    pub fn generate_primitive_type_struct(
212        &mut self,
213        structure_def: &StructureDefinition,
214        mut rust_struct: RustStruct,
215    ) -> CodegenResult<RustStruct> {
216        // For primitive types, don't inherit from Element
217        rust_struct.base_definition = None;
218
219        // Map FHIR primitive types to Rust types
220        let rust_primitive_type = match structure_def.name.as_str() {
221            "boolean" => RustType::Boolean,
222            "integer" | "positiveInt" | "unsignedInt" => RustType::Integer,
223            "decimal" => RustType::Float,
224            "string" | "code" | "id" | "markdown" | "uri" | "url" | "canonical" | "oid"
225            | "uuid" | "base64Binary" | "xhtml" => RustType::String,
226            "date" | "dateTime" | "time" | "instant" => RustType::String, // Could use chrono types later
227            _ => RustType::String, // Default to String for unknown primitive types
228        };
229
230        // The primitive type is just a type alias or newtype wrapper around the Rust primitive
231        // For now, we'll create a struct with a single `value` field
232        let value_field = RustField::new("value".to_string(), rust_primitive_type);
233        rust_struct.add_field(value_field);
234
235        // Cache the generated struct for future use
236        let struct_name = rust_struct.name.clone();
237        self.type_cache.insert(struct_name, rust_struct.clone());
238
239        Ok(rust_struct)
240    }
241
242    /// Generate a nested struct for BackboneElements
243    pub fn generate_nested_struct(
244        &mut self,
245        parent_struct_name: &str,
246        nested_field_name: &str,
247        nested_elements: &[crate::fhir_types::ElementDefinition],
248        parent_structure_def: &StructureDefinition,
249    ) -> CodegenResult<Option<RustStruct>> {
250        // Generate the nested struct name (e.g., BundleEntry, BundleLink)
251        let nested_struct_name = format!(
252            "{}{}",
253            parent_struct_name,
254            crate::naming::Naming::to_pascal_case(nested_field_name)
255        );
256
257        // Check if we've already generated this nested struct
258        if self.type_cache.contains_key(&nested_struct_name) {
259            return Ok(None);
260        }
261
262        // Create the nested struct
263        let mut nested_struct = RustStruct::new(nested_struct_name.clone());
264
265        // Add documentation
266        nested_struct.doc_comment = Some(
267            DocumentationGenerator::generate_nested_struct_documentation(
268                parent_struct_name,
269                nested_field_name,
270            ),
271        );
272
273        // Use config to determine derives
274        let mut derives = vec!["Debug".to_string(), "Clone".to_string()];
275        if self.config.with_serde {
276            derives.extend(vec!["Serialize".to_string(), "Deserialize".to_string()]);
277        }
278        nested_struct.derives = derives;
279
280        // Set base as BackboneElement since these are typically BackboneElements
281        nested_struct.base_definition = Some("BackboneElement".to_string());
282
283        // Process the nested elements
284        let base_path = format!("{}.{}", parent_structure_def.name, nested_field_name);
285        let mut sub_nested_structs = HashMap::new();
286        let mut direct_fields = Vec::new();
287
288        for element in nested_elements {
289            if !element.path.starts_with(&base_path) {
290                continue;
291            }
292
293            // Skip the BackboneElement definition itself (where path == base_path)
294            if element.path == base_path {
295                continue;
296            }
297
298            let field_path = element
299                .path
300                .strip_prefix(&format!("{base_path}."))
301                .unwrap_or_else(|| {
302                    panic!(
303                        "codegen bug: element path '{}' does not start with '{base_path}.'",
304                        element.path
305                    )
306                });
307
308            if field_path.contains('.') {
309                // This is a sub-nested field - collect it for recursive nested struct generation
310                let sub_nested_field_name = field_path.split('.').next().unwrap_or_else(|| {
311                    panic!(
312                        "codegen bug: field path '{}' contains '.' but has no prefix segment",
313                        field_path
314                    )
315                });
316                sub_nested_structs
317                    .entry(sub_nested_field_name.to_string())
318                    .or_insert_with(Vec::new)
319                    .push(element.clone());
320            } else {
321                // Check if this direct field is itself a BackboneElement that needs a nested struct
322                let is_backbone_element = element
323                    .element_type
324                    .as_ref()
325                    .and_then(|types| types.first())
326                    .and_then(|t| t.code.as_ref())
327                    .map(|code| code == "BackboneElement")
328                    .unwrap_or(false);
329
330                if is_backbone_element {
331                    // Treat this as a sub-nested struct even though it appears as a direct field
332                    sub_nested_structs
333                        .entry(field_path.to_string())
334                        .or_insert_with(Vec::new)
335                        .push(element.clone());
336                } else {
337                    // This is a direct field of this nested struct
338                    direct_fields.push(element.clone());
339                }
340            }
341        }
342
343        // First, generate any sub-nested structs
344        let mut sorted_sub_nested_structs: Vec<_> = sub_nested_structs.iter().collect();
345        sorted_sub_nested_structs.sort_by_key(|(left_name, _)| *left_name);
346
347        for (sub_nested_field_name, sub_nested_elements) in sorted_sub_nested_structs {
348            // For recursive calls, we need to create a modified context
349            // The base path for sub-nested structs should be the current nested struct's path
350            let sub_nested_struct_name = format!(
351                "{}{}",
352                nested_struct_name,
353                crate::naming::Naming::to_pascal_case(sub_nested_field_name)
354            );
355
356            if !self.type_cache.contains_key(&sub_nested_struct_name) {
357                let mut sub_nested_struct = RustStruct::new(sub_nested_struct_name.clone());
358
359                sub_nested_struct.doc_comment = Some(
360                    DocumentationGenerator::generate_sub_nested_struct_documentation(
361                        &nested_struct_name,
362                        sub_nested_field_name,
363                    ),
364                );
365
366                // Use config to determine derives
367                let mut derives = vec!["Debug".to_string(), "Clone".to_string()];
368                if self.config.with_serde {
369                    derives.extend(vec!["Serialize".to_string(), "Deserialize".to_string()]);
370                }
371                sub_nested_struct.derives = derives;
372                sub_nested_struct.base_definition = Some("BackboneElement".to_string());
373
374                // Process the sub-nested elements with full recursive support
375                let sub_base_path = format!("{base_path}.{sub_nested_field_name}");
376
377                // Separate sub-nested elements into direct fields and further sub-nested structures
378                let mut sub_direct_fields = Vec::new();
379                let mut sub_sub_nested_structs: HashMap<
380                    String,
381                    Vec<crate::fhir_types::ElementDefinition>,
382                > = HashMap::new();
383
384                for element in sub_nested_elements {
385                    if !element.path.starts_with(&sub_base_path) {
386                        continue;
387                    }
388
389                    // Skip the BackboneElement definition itself (where path == base_path)
390                    if element.path == sub_base_path {
391                        continue;
392                    }
393
394                    let sub_field_path = element
395                        .path
396                        .strip_prefix(&format!("{sub_base_path}."))
397                        .unwrap_or_else(|| {
398                            panic!("codegen bug: element path '{}' does not start with '{sub_base_path}.'", element.path)
399                        });
400
401                    if sub_field_path.contains('.') {
402                        // This is a further sub-nested field - collect it for recursive generation
403                        let sub_sub_nested_field_name = sub_field_path.split('.').next().unwrap_or_else(|| {
404                            panic!("codegen bug: field path '{}' contains '.' but has no prefix segment", sub_field_path)
405                        });
406                        sub_sub_nested_structs
407                            .entry(sub_sub_nested_field_name.to_string())
408                            .or_default()
409                            .push(element.clone());
410                    } else {
411                        // Check if this direct field is itself a BackboneElement that needs a nested struct
412                        let is_backbone_element = element
413                            .element_type
414                            .as_ref()
415                            .and_then(|types| types.first())
416                            .and_then(|t| t.code.as_ref())
417                            .map(|code| code == "BackboneElement")
418                            .unwrap_or(false);
419
420                        if is_backbone_element {
421                            // Treat this as a further sub-nested struct even though it appears as a direct field
422                            sub_sub_nested_structs
423                                .entry(sub_field_path.to_string())
424                                .or_default()
425                                .push(element.clone());
426                        } else {
427                            // This is a direct field of this sub-nested struct
428                            sub_direct_fields.push(element.clone());
429                        }
430                    }
431                }
432
433                // First, recursively generate any further sub-nested structs
434                let mut sorted_sub_sub_nested_structs: Vec<_> =
435                    sub_sub_nested_structs.iter().collect();
436                sorted_sub_sub_nested_structs.sort_by_key(|(left_name, _)| *left_name);
437
438                for (sub_sub_nested_field_name, sub_sub_nested_elements) in
439                    sorted_sub_sub_nested_structs
440                {
441                    self.generate_deeply_nested_struct(
442                        &sub_nested_struct_name,
443                        sub_sub_nested_field_name,
444                        sub_sub_nested_elements,
445                        &sub_base_path,
446                    )?;
447                }
448
449                // Then, process direct fields (now further sub-nested structs are available)
450                for element in sub_direct_fields {
451                    let fields = self.create_fields_from_element(&element)?;
452                    for field in fields {
453                        sub_nested_struct.add_field(field);
454                    }
455                }
456
457                // Store the sub-nested struct in cache
458                self.type_cache
459                    .insert(sub_nested_struct_name.clone(), sub_nested_struct);
460
461                // Register the sub-nested struct in TypeRegistry with proper classification
462                // Use the parent struct name (first level nested struct's parent is the resource)
463                crate::generators::type_registry::TypeRegistry::register_type_classification_only(
464                    &sub_nested_struct_name,
465                    crate::generators::type_registry::TypeClassification::NestedStructure {
466                        parent_resource: parent_struct_name.to_string(),
467                    },
468                );
469            }
470
471            // Now create a field in the parent struct that references this sub-nested struct
472            // Find the BackboneElement definition for this field
473            let backbone_element_def = sub_nested_elements
474                .iter()
475                .find(|e| e.path == format!("{base_path}.{sub_nested_field_name}"));
476
477            if let Some(element) = backbone_element_def {
478                let fields = self.create_fields_from_element(element)?;
479                for field in fields {
480                    nested_struct.add_field(field);
481                }
482            }
483        }
484
485        // Then, process direct fields (now sub-nested structs are available)
486        for element in direct_fields {
487            let fields = self.create_fields_from_element(&element)?;
488            for field in fields {
489                nested_struct.add_field(field);
490            }
491        }
492
493        Ok(Some(nested_struct))
494    }
495
496    /// Recursively generate deeply nested structs (for arbitrary nesting levels)
497    fn generate_deeply_nested_struct(
498        &mut self,
499        parent_nested_struct_name: &str,
500        field_name: &str,
501        elements: &[crate::fhir_types::ElementDefinition],
502        parent_base_path: &str,
503    ) -> CodegenResult<()> {
504        let nested_struct_name = format!(
505            "{}{}",
506            parent_nested_struct_name,
507            crate::naming::Naming::to_pascal_case(field_name)
508        );
509
510        if !self.type_cache.contains_key(&nested_struct_name) {
511            let mut nested_struct = RustStruct::new(nested_struct_name.clone());
512
513            nested_struct.doc_comment = Some(
514                DocumentationGenerator::generate_sub_nested_struct_documentation(
515                    parent_nested_struct_name,
516                    field_name,
517                ),
518            );
519
520            // Use config to determine derives
521            let mut derives = vec!["Debug".to_string(), "Clone".to_string()];
522            if self.config.with_serde {
523                derives.extend(vec!["Serialize".to_string(), "Deserialize".to_string()]);
524            }
525            nested_struct.derives = derives;
526            nested_struct.base_definition = Some("BackboneElement".to_string());
527
528            // Process the nested elements with full recursive support
529            let base_path = format!("{parent_base_path}.{field_name}");
530
531            // Separate nested elements into direct fields and further nested structures
532            let mut direct_fields = Vec::new();
533            let mut sub_nested_structs: HashMap<String, Vec<crate::fhir_types::ElementDefinition>> =
534                HashMap::new();
535
536            for element in elements {
537                if !element.path.starts_with(&base_path) {
538                    continue;
539                }
540
541                // Skip the BackboneElement definition itself (where path == base_path)
542                if element.path == base_path {
543                    continue;
544                }
545
546                let field_path = element
547                    .path
548                    .strip_prefix(&format!("{base_path}."))
549                    .unwrap_or_else(|| {
550                        panic!(
551                            "codegen bug: element path '{}' does not start with '{base_path}.'",
552                            element.path
553                        )
554                    });
555
556                if field_path.contains('.') {
557                    // This is a further nested field - collect it for recursive generation
558                    let sub_nested_field_name = field_path.split('.').next().unwrap_or_else(|| {
559                        panic!(
560                            "codegen bug: field path '{}' contains '.' but has no prefix segment",
561                            field_path
562                        )
563                    });
564                    sub_nested_structs
565                        .entry(sub_nested_field_name.to_string())
566                        .or_default()
567                        .push(element.clone());
568                } else {
569                    // Check if this direct field is itself a BackboneElement that needs a nested struct
570                    let is_backbone_element = element
571                        .element_type
572                        .as_ref()
573                        .and_then(|types| types.first())
574                        .and_then(|t| t.code.as_ref())
575                        .map(|code| code == "BackboneElement")
576                        .unwrap_or(false);
577
578                    if is_backbone_element {
579                        // Treat this as a further nested struct even though it appears as a direct field
580                        sub_nested_structs
581                            .entry(field_path.to_string())
582                            .or_default()
583                            .push(element.clone());
584                    } else {
585                        // This is a direct field of this nested struct
586                        direct_fields.push(element.clone());
587                    }
588                }
589            }
590
591            // First, recursively generate any further nested structs
592            let mut sorted_sub_nested_structs: Vec<_> = sub_nested_structs.iter().collect();
593            sorted_sub_nested_structs.sort_by_key(|(left_name, _)| *left_name);
594
595            for (sub_nested_field_name, sub_nested_elements) in sorted_sub_nested_structs {
596                self.generate_deeply_nested_struct(
597                    &nested_struct_name,
598                    sub_nested_field_name,
599                    sub_nested_elements,
600                    &base_path,
601                )?;
602            }
603
604            // Then, process direct fields (now further nested structs are available)
605            for element in direct_fields {
606                let fields = self.create_fields_from_element(&element)?;
607                for field in fields {
608                    nested_struct.add_field(field);
609                }
610            }
611
612            // Store the nested struct in cache
613            self.type_cache
614                .insert(nested_struct_name.clone(), nested_struct);
615
616            // Register the deeply nested struct in TypeRegistry with proper classification
617            // Extract the root parent resource name (e.g., "MedicationKnowledge" from "MedicationKnowledgeAdministrationguidelinesDosage")
618            let root_parent_resource = Self::extract_root_parent_resource(&nested_struct_name);
619            crate::generators::type_registry::TypeRegistry::register_type_classification_only(
620                &nested_struct_name,
621                crate::generators::type_registry::TypeClassification::NestedStructure {
622                    parent_resource: root_parent_resource,
623                },
624            );
625        }
626
627        Ok(())
628    }
629
630    /// Extract the root parent resource name from a deeply nested struct name
631    /// For example: "MedicationKnowledgeAdministrationguidelinesDosage" -> "MedicationKnowledge"
632    fn extract_root_parent_resource(nested_struct_name: &str) -> String {
633        // Use the TypeRegistry's method for consistency
634        crate::generators::type_registry::TypeRegistry::extract_parent_from_name(nested_struct_name)
635            .unwrap_or_else(|| nested_struct_name.to_string())
636    }
637
638    /// Check if a field should use a nested struct type instead of BackboneElement
639    pub fn should_use_nested_struct_type(
640        &self,
641        element: &crate::fhir_types::ElementDefinition,
642        element_types: &[crate::fhir_types::ElementType],
643    ) -> bool {
644        // Check if this element is a BackboneElement and we have nested elements for it
645        if let Some(first_type) = element_types.first() {
646            if let Some(code) = &first_type.code {
647                if code == "BackboneElement" {
648                    // Extract parent struct name and field name from the path
649                    let path_parts: Vec<&str> = element.path.split('.').collect();
650                    if path_parts.len() >= 2 {
651                        let _parent_name = path_parts[0];
652                        let _field_name = path_parts[1];
653                        // We would check here if we have nested elements for this field
654                        // For now, always return true for BackboneElements
655                        return true;
656                    }
657                }
658            }
659        }
660        false
661    }
662
663    /// Create a RustField from an ElementDefinition
664    pub fn create_field_from_element(
665        &mut self,
666        element: &crate::fhir_types::ElementDefinition,
667    ) -> CodegenResult<Option<RustField>> {
668        let mut field_generator =
669            FieldGenerator::new(self.config, self.type_cache, self.value_set_manager);
670        field_generator.create_field_from_element(element)
671    }
672
673    /// Create RustField(s) from an ElementDefinition (supports choice types)
674    pub fn create_fields_from_element(
675        &mut self,
676        element: &crate::fhir_types::ElementDefinition,
677    ) -> CodegenResult<Vec<RustField>> {
678        let mut field_generator =
679            FieldGenerator::new(self.config, self.type_cache, self.value_set_manager);
680        field_generator.create_fields_from_element(element)
681    }
682
683    /// Apply `Box` wrapper to field types that form circular dependencies.
684    ///
685    /// FHIR's `Identifier.assigner` is `Reference`, and `Reference.identifier` is `Identifier`,
686    /// forming a direct cycle. Rust structs cannot contain themselves (even indirectly) without
687    /// indirection, so we wrap these cross-references in `Box<T>` to break the cycle.
688    ///
689    /// Currently the only known cycle is `Identifier ↔ Reference`. If future FHIR versions
690    /// introduce additional cycles, add them to the `circular_dependencies` table below.
691    ///
692    /// The recursive handling of `Option<T>` and `Vec<T>` ensures that `Option<Box<Reference>>`
693    /// and `Vec<Box<Reference>>` are emitted correctly rather than `Box<Option<Reference>>`.
694    fn apply_box_for_circular_dependencies(
695        field_type: RustType,
696        current_struct_name: &str,
697    ) -> RustType {
698        // Known circular dependency pairs that need Box wrapping
699        let circular_dependencies = [("Identifier", "Reference"), ("Reference", "Identifier")];
700
701        // Check if we need to wrap this type in Box
702        match &field_type {
703            RustType::Custom(type_name) => {
704                for (struct_a, struct_b) in &circular_dependencies {
705                    if (current_struct_name == *struct_a && type_name == *struct_b)
706                        || (current_struct_name == *struct_b && type_name == *struct_a)
707                    {
708                        return RustType::Box(Box::new(field_type));
709                    }
710                }
711                field_type
712            }
713            RustType::Option(inner) => {
714                let boxed_inner = Self::apply_box_for_circular_dependencies(
715                    (**inner).clone(),
716                    current_struct_name,
717                );
718                if let RustType::Box(_) = boxed_inner {
719                    RustType::Option(Box::new(boxed_inner))
720                } else {
721                    field_type
722                }
723            }
724            RustType::Vec(inner) => {
725                let boxed_inner = Self::apply_box_for_circular_dependencies(
726                    (**inner).clone(),
727                    current_struct_name,
728                );
729                if let RustType::Box(_) = boxed_inner {
730                    RustType::Vec(Box::new(boxed_inner))
731                } else {
732                    field_type
733                }
734            }
735            _ => field_type,
736        }
737    }
738}
739
740#[cfg(test)]
741mod tests {
742    use super::*;
743    use crate::config::CodegenConfig;
744
745    #[test]
746    fn test_underscore_prefixed_structure_skipping() {
747        let config = CodegenConfig::default();
748        let mut type_cache = HashMap::new();
749        let mut value_set_manager = ValueSetManager::new();
750        let mut generator = StructGenerator::new(&config, &mut type_cache, &mut value_set_manager);
751
752        // Test structure with underscore prefixed name
753        let underscore_structure = StructureDefinition {
754            resource_type: "StructureDefinition".to_string(),
755            id: "normal-id".to_string(),
756            url: "http://hl7.org/fhir/StructureDefinition/_11179object_class".to_string(),
757            name: "_11179object_class".to_string(),
758            title: Some("Auto-generated class".to_string()),
759            status: "active".to_string(),
760            kind: "resource".to_string(),
761            is_abstract: false,
762            description: Some("An auto-generated resource".to_string()),
763            purpose: None,
764            base_type: "DomainResource".to_string(),
765            base_definition: Some(
766                "http://hl7.org/fhir/StructureDefinition/DomainResource".to_string(),
767            ),
768            version: None,
769            differential: None,
770            snapshot: None,
771        };
772
773        // Test that underscore-prefixed structures are rejected
774        let result = generator.generate_struct(&underscore_structure);
775        assert!(result.is_err());
776
777        if let Err(CodegenError::Generation { message }) = result {
778            assert!(message.contains("underscore prefixed files are ignored"));
779            assert!(message.contains("_11179object_class"));
780        } else {
781            panic!("Expected CodegenError::Generation for underscore-prefixed structure");
782        }
783    }
784}