Skip to main content

rh_codegen/generators/
nested_struct_generator.rs

1//! Nested struct generation functionality for FHIR BackboneElements
2//!
3//! This module provides functionality for generating nested structs from FHIR BackboneElements,
4//! which are complex structures that appear within other FHIR structures.
5
6use std::collections::HashMap;
7
8use crate::config::CodegenConfig;
9use crate::fhir_types::{ElementDefinition, StructureDefinition};
10use crate::generators::{DocumentationGenerator, FieldGenerator, TypeRegistry};
11use crate::naming::Naming;
12use crate::rust_types::RustStruct;
13use crate::CodegenResult;
14
15/// Generator for nested struct types from FHIR BackboneElements
16pub struct NestedStructGenerator<'a> {
17    config: &'a CodegenConfig,
18    type_cache: &'a mut HashMap<String, RustStruct>,
19    value_set_manager: &'a mut crate::value_sets::ValueSetManager,
20}
21
22impl<'a> NestedStructGenerator<'a> {
23    /// Create a new nested struct generator
24    pub fn new(
25        config: &'a CodegenConfig,
26        type_cache: &'a mut HashMap<String, RustStruct>,
27        value_set_manager: &'a mut crate::value_sets::ValueSetManager,
28    ) -> Self {
29        Self {
30            config,
31            type_cache,
32            value_set_manager,
33        }
34    }
35
36    /// Generate a nested struct for BackboneElements
37    pub fn generate_nested_struct(
38        &mut self,
39        parent_struct_name: &str,
40        nested_field_name: &str,
41        nested_elements: &[ElementDefinition],
42        parent_structure_def: &StructureDefinition,
43    ) -> CodegenResult<Option<RustStruct>> {
44        // Generate the nested struct name (e.g., BundleEntry, BundleLink)
45        let nested_struct_name = format!(
46            "{}{}",
47            parent_struct_name,
48            Naming::to_pascal_case(nested_field_name)
49        );
50
51        // Check if we've already generated this nested struct
52        if self.type_cache.contains_key(&nested_struct_name) {
53            return Ok(None);
54        }
55
56        // Create the nested struct
57        let mut nested_struct = RustStruct::new(nested_struct_name.clone());
58
59        // Add documentation
60        nested_struct.doc_comment = Some(
61            DocumentationGenerator::generate_nested_struct_documentation(
62                parent_struct_name,
63                nested_field_name,
64            ),
65        );
66
67        // Use config to determine derives
68        let mut derives = vec!["Debug".to_string(), "Clone".to_string()];
69        if self.config.with_serde {
70            derives.extend(vec!["Serialize".to_string(), "Deserialize".to_string()]);
71        }
72        nested_struct.derives = derives;
73
74        // Set base as BackboneElement since these are typically BackboneElements
75        nested_struct.base_definition = Some("BackboneElement".to_string());
76
77        // Process the nested elements
78        let base_path = format!("{}.{}", parent_structure_def.name, nested_field_name);
79        let mut sub_nested_structs = HashMap::new();
80        let mut direct_fields = Vec::new();
81
82        for element in nested_elements {
83            if !element.path.starts_with(&base_path) {
84                continue;
85            }
86
87            let field_path = element
88                .path
89                .strip_prefix(&format!("{base_path}."))
90                .unwrap_or_else(|| {
91                    panic!(
92                        "codegen bug: element path '{}' does not start with '{base_path}.'",
93                        element.path
94                    )
95                });
96
97            if field_path.contains('.') {
98                // This is a sub-nested field - collect it for recursive nested struct generation
99                let sub_nested_field_name = field_path.split('.').next().unwrap_or_else(|| {
100                    panic!(
101                        "codegen bug: field path '{}' contains '.' but has no prefix segment",
102                        field_path
103                    )
104                });
105                sub_nested_structs
106                    .entry(sub_nested_field_name.to_string())
107                    .or_insert_with(Vec::new)
108                    .push(element.clone());
109            } else {
110                // Check if this direct field is itself a BackboneElement that needs a nested struct
111                let is_backbone_element = element
112                    .element_type
113                    .as_ref()
114                    .and_then(|types| types.first())
115                    .and_then(|t| t.code.as_ref())
116                    .map(|code| code == "BackboneElement")
117                    .unwrap_or(false);
118
119                if is_backbone_element {
120                    // Treat this as a sub-nested struct even though it appears as a direct field
121                    sub_nested_structs
122                        .entry(field_path.to_string())
123                        .or_insert_with(Vec::new)
124                        .push(element.clone());
125                } else {
126                    // This is a direct field of this nested struct
127                    direct_fields.push(element.clone());
128                }
129            }
130        }
131
132        // First, generate any sub-nested structs
133        for (sub_nested_field_name, sub_nested_elements) in &sub_nested_structs {
134            self.generate_sub_nested_struct(
135                &nested_struct_name,
136                sub_nested_field_name,
137                sub_nested_elements,
138                &base_path,
139            )?;
140        }
141
142        // Then, process direct fields (now sub-nested structs are available)
143        for element in direct_fields {
144            if let Some(field) = self.create_field_from_element(&element)? {
145                nested_struct.add_field(field);
146            }
147        }
148
149        // Store the nested struct in cache before returning
150        self.type_cache
151            .insert(nested_struct_name.clone(), nested_struct.clone());
152
153        // Register the nested struct in TypeRegistry with proper classification
154        // Get the parent resource name from the parent struct name
155        let parent_resource = parent_struct_name.to_string();
156        TypeRegistry::register_type_classification_only(
157            &nested_struct_name,
158            crate::generators::type_registry::TypeClassification::NestedStructure {
159                parent_resource,
160            },
161        );
162
163        Ok(Some(nested_struct))
164    }
165
166    /// Generate a sub-nested struct (nested within a nested struct)
167    fn generate_sub_nested_struct(
168        &mut self,
169        nested_struct_name: &str,
170        sub_nested_field_name: &str,
171        sub_nested_elements: &[ElementDefinition],
172        base_path: &str,
173    ) -> CodegenResult<()> {
174        // For recursive calls, we need to create a modified context
175        // The base path for sub-nested structs should be the current nested struct's path
176        let sub_nested_struct_name = format!(
177            "{}{}",
178            nested_struct_name,
179            Naming::to_pascal_case(sub_nested_field_name)
180        );
181
182        if !self.type_cache.contains_key(&sub_nested_struct_name) {
183            let mut sub_nested_struct = RustStruct::new(sub_nested_struct_name.clone());
184
185            sub_nested_struct.doc_comment = Some(
186                DocumentationGenerator::generate_sub_nested_struct_documentation(
187                    nested_struct_name,
188                    sub_nested_field_name,
189                ),
190            );
191
192            // Use config to determine derives
193            let mut derives = vec!["Debug".to_string(), "Clone".to_string()];
194            if self.config.with_serde {
195                derives.extend(vec!["Serialize".to_string(), "Deserialize".to_string()]);
196            }
197            sub_nested_struct.derives = derives;
198            sub_nested_struct.base_definition = Some("BackboneElement".to_string());
199
200            // Process the sub-nested elements with full recursive support
201            let sub_base_path = format!("{base_path}.{sub_nested_field_name}");
202
203            // Separate sub-nested elements into direct fields and further sub-nested structures
204            let mut direct_fields = Vec::new();
205            let mut sub_sub_nested_structs: HashMap<String, Vec<ElementDefinition>> =
206                HashMap::new();
207
208            for element in sub_nested_elements {
209                if !element.path.starts_with(&sub_base_path) {
210                    continue;
211                }
212
213                let field_path = element
214                    .path
215                    .strip_prefix(&format!("{sub_base_path}."))
216                    .unwrap_or_else(|| {
217                        panic!(
218                            "codegen bug: element path '{}' does not start with '{sub_base_path}.'",
219                            element.path
220                        )
221                    });
222
223                if field_path.contains('.') {
224                    // This is a further sub-nested field - collect it for recursive generation
225                    let sub_sub_nested_field_name = field_path.split('.').next().unwrap_or_else(|| {
226                        panic!("codegen bug: field path '{}' contains '.' but has no prefix segment", field_path)
227                    });
228                    sub_sub_nested_structs
229                        .entry(sub_sub_nested_field_name.to_string())
230                        .or_default()
231                        .push(element.clone());
232                } else {
233                    // Check if this direct field is itself a BackboneElement that needs a nested struct
234                    let is_backbone_element = element
235                        .element_type
236                        .as_ref()
237                        .and_then(|types| types.first())
238                        .and_then(|t| t.code.as_ref())
239                        .map(|code| code == "BackboneElement")
240                        .unwrap_or(false);
241
242                    if is_backbone_element {
243                        // Treat this as a further sub-nested struct even though it appears as a direct field
244                        sub_sub_nested_structs
245                            .entry(field_path.to_string())
246                            .or_default()
247                            .push(element.clone());
248                    } else {
249                        // This is a direct field of this sub-nested struct
250                        direct_fields.push(element.clone());
251                    }
252                }
253            }
254
255            // First, generate any further sub-nested structs recursively
256            for (sub_sub_nested_field_name, sub_sub_nested_elements) in &sub_sub_nested_structs {
257                self.generate_sub_nested_struct(
258                    &sub_nested_struct_name,
259                    sub_sub_nested_field_name,
260                    sub_sub_nested_elements,
261                    &sub_base_path,
262                )?;
263            } // Then, process direct fields (now further sub-nested structs are available)
264            for element in direct_fields {
265                if let Some(field) = self.create_field_from_element(&element)? {
266                    sub_nested_struct.add_field(field);
267                }
268            }
269
270            // Store the sub-nested struct in cache
271            self.type_cache
272                .insert(sub_nested_struct_name.clone(), sub_nested_struct);
273
274            // Register the sub-nested struct in TypeRegistry with proper classification
275            // Extract the parent resource from the nested_struct_name
276            // Use a simple approach: extract from the beginning of nested_struct_name
277            let parent_resource = Self::extract_parent_resource_name(nested_struct_name);
278
279            TypeRegistry::register_type_classification_only(
280                &sub_nested_struct_name,
281                crate::generators::type_registry::TypeClassification::NestedStructure {
282                    parent_resource,
283                },
284            );
285        }
286
287        Ok(())
288    }
289
290    /// Extract parent resource name from a nested structure name
291    /// For example: "ActivityDefinitionParticipant" -> "ActivityDefinition"
292    fn extract_parent_resource_name(nested_struct_name: &str) -> String {
293        // Use the TypeRegistry's method for consistency
294        crate::generators::type_registry::TypeRegistry::extract_parent_from_name(nested_struct_name)
295            .unwrap_or_else(|| nested_struct_name.to_string())
296    }
297
298    /// Create a RustField from an ElementDefinition
299    fn create_field_from_element(
300        &mut self,
301        element: &ElementDefinition,
302    ) -> CodegenResult<Option<crate::rust_types::RustField>> {
303        let mut field_generator =
304            FieldGenerator::new(self.config, self.type_cache, self.value_set_manager);
305        field_generator.create_field_from_element(element)
306    }
307}
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312    use crate::config::CodegenConfig;
313    use crate::fhir_types::{
314        ElementDefinition, ElementType, StructureDefinition, StructureDefinitionDifferential,
315    };
316    use crate::value_sets::ValueSetManager;
317
318    #[test]
319    fn test_nested_struct_generation() {
320        let config = CodegenConfig::default();
321        let mut type_cache = HashMap::new();
322        let mut value_set_manager = ValueSetManager::new();
323        let mut generator =
324            NestedStructGenerator::new(&config, &mut type_cache, &mut value_set_manager);
325
326        // Create a simplified Bundle StructureDefinition with nested entry
327        let bundle_structure = StructureDefinition {
328            resource_type: "StructureDefinition".to_string(),
329            id: "Bundle".to_string(),
330            url: "http://hl7.org/fhir/StructureDefinition/Bundle".to_string(),
331            name: "Bundle".to_string(),
332            title: Some("Bundle".to_string()),
333            status: "active".to_string(),
334            kind: "resource".to_string(),
335            is_abstract: false,
336            description: Some("A container for a collection of resources".to_string()),
337            purpose: None,
338            base_type: "Bundle".to_string(),
339            base_definition: Some("http://hl7.org/fhir/StructureDefinition/Resource".to_string()),
340            version: None,
341            differential: Some(StructureDefinitionDifferential {
342                element: vec![
343                    ElementDefinition {
344                        id: Some("Bundle.entry".to_string()),
345                        path: "Bundle.entry".to_string(),
346                        short: Some("Entry in the bundle".to_string()),
347                        definition: None,
348                        min: Some(0),
349                        max: Some("*".to_string()),
350                        element_type: Some(vec![ElementType {
351                            code: Some("BackboneElement".to_string()),
352                            target_profile: None,
353                        }]),
354                        fixed: None,
355                        pattern: None,
356                        binding: None,
357                        constraint: None,
358                    },
359                    ElementDefinition {
360                        id: Some("Bundle.entry.resource".to_string()),
361                        path: "Bundle.entry.resource".to_string(),
362                        short: Some("A resource in the bundle".to_string()),
363                        definition: None,
364                        min: Some(0),
365                        max: Some("1".to_string()),
366                        element_type: Some(vec![ElementType {
367                            code: Some("Resource".to_string()),
368                            target_profile: None,
369                        }]),
370                        fixed: None,
371                        pattern: None,
372                        binding: None,
373                        constraint: None,
374                    },
375                ],
376            }),
377            snapshot: None,
378        };
379
380        let nested_elements = bundle_structure
381            .differential
382            .as_ref()
383            .unwrap()
384            .element
385            .clone();
386
387        // Filter to only include the sub-elements of Bundle.entry (not Bundle.entry itself)
388        let filtered_elements: Vec<_> = nested_elements
389            .into_iter()
390            .filter(|e| e.path.starts_with("Bundle.entry."))
391            .collect();
392
393        // Generate the nested struct
394        let result = generator.generate_nested_struct(
395            "Bundle",
396            "entry",
397            &filtered_elements,
398            &bundle_structure,
399        );
400        assert!(result.is_ok(), "Should generate nested struct successfully");
401
402        let bundle_entry_struct = result.unwrap();
403        assert!(
404            bundle_entry_struct.is_some(),
405            "Should return a nested struct"
406        );
407
408        let bundle_entry_struct = bundle_entry_struct.unwrap();
409        assert_eq!(bundle_entry_struct.name, "BundleEntry");
410        assert_eq!(
411            bundle_entry_struct.base_definition,
412            Some("BackboneElement".to_string())
413        );
414
415        // Check that the struct was added to the type cache
416        assert!(
417            type_cache.contains_key("BundleEntry"),
418            "BundleEntry should be cached"
419        );
420
421        // Check that the nested struct has the expected fields
422        let resource_field = bundle_entry_struct
423            .fields
424            .iter()
425            .find(|f| f.name == "resource");
426        assert!(
427            resource_field.is_some(),
428            "BundleEntry should have a resource field"
429        );
430    }
431
432    #[test]
433    fn test_nested_struct_caching() {
434        let config = CodegenConfig::default();
435        let mut type_cache = HashMap::new();
436        let mut value_set_manager = ValueSetManager::new();
437
438        // Create a dummy nested struct and add it to cache
439        let existing_struct = RustStruct::new("BundleEntry".to_string());
440        type_cache.insert("BundleEntry".to_string(), existing_struct);
441
442        let mut generator =
443            NestedStructGenerator::new(&config, &mut type_cache, &mut value_set_manager);
444
445        let bundle_structure = StructureDefinition {
446            resource_type: "StructureDefinition".to_string(),
447            id: "Bundle".to_string(),
448            url: "http://hl7.org/fhir/StructureDefinition/Bundle".to_string(),
449            name: "Bundle".to_string(),
450            title: Some("Bundle".to_string()),
451            status: "active".to_string(),
452            kind: "resource".to_string(),
453            is_abstract: false,
454            description: Some("A container for a collection of resources".to_string()),
455            purpose: None,
456            base_type: "Bundle".to_string(),
457            base_definition: Some("http://hl7.org/fhir/StructureDefinition/Resource".to_string()),
458            version: None,
459            differential: None,
460            snapshot: None,
461        };
462
463        let nested_elements = vec![];
464
465        // Generate the nested struct - should return None due to caching
466        let result = generator.generate_nested_struct(
467            "Bundle",
468            "entry",
469            &nested_elements,
470            &bundle_structure,
471        );
472        assert!(result.is_ok(), "Should handle cached struct successfully");
473
474        let bundle_entry_struct = result.unwrap();
475        assert!(
476            bundle_entry_struct.is_none(),
477            "Should return None for cached struct"
478        );
479    }
480
481    #[test]
482    fn test_nested_struct_documentation() {
483        let config = CodegenConfig::default();
484        let mut type_cache = HashMap::new();
485        let mut value_set_manager = ValueSetManager::new();
486        let mut generator =
487            NestedStructGenerator::new(&config, &mut type_cache, &mut value_set_manager);
488
489        let bundle_structure = StructureDefinition {
490            resource_type: "StructureDefinition".to_string(),
491            id: "Bundle".to_string(),
492            url: "http://hl7.org/fhir/StructureDefinition/Bundle".to_string(),
493            name: "Bundle".to_string(),
494            title: Some("Bundle".to_string()),
495            status: "active".to_string(),
496            kind: "resource".to_string(),
497            is_abstract: false,
498            description: Some("A container for a collection of resources".to_string()),
499            purpose: None,
500            base_type: "Bundle".to_string(),
501            base_definition: Some("http://hl7.org/fhir/StructureDefinition/Resource".to_string()),
502            version: None,
503            differential: None,
504            snapshot: None,
505        };
506
507        let nested_elements = vec![];
508
509        // Generate the nested struct
510        let result = generator.generate_nested_struct(
511            "Bundle",
512            "entry",
513            &nested_elements,
514            &bundle_structure,
515        );
516        assert!(result.is_ok(), "Should generate nested struct successfully");
517
518        let bundle_entry_struct = result.unwrap().unwrap();
519
520        // Check documentation was generated
521        assert!(
522            bundle_entry_struct.doc_comment.is_some(),
523            "Should have documentation"
524        );
525        let doc = bundle_entry_struct.doc_comment.unwrap();
526        assert!(
527            doc.contains("Bundle"),
528            "Documentation should mention parent struct"
529        );
530        assert!(
531            doc.contains("entry"),
532            "Documentation should mention field name"
533        );
534    }
535
536    #[test]
537    fn test_nested_struct_derives() {
538        let config = CodegenConfig {
539            with_serde: true,
540            ..Default::default()
541        };
542        let mut type_cache = HashMap::new();
543        let mut value_set_manager = ValueSetManager::new();
544        let mut generator =
545            NestedStructGenerator::new(&config, &mut type_cache, &mut value_set_manager);
546
547        let bundle_structure = StructureDefinition {
548            resource_type: "StructureDefinition".to_string(),
549            id: "Bundle".to_string(),
550            url: "http://hl7.org/fhir/StructureDefinition/Bundle".to_string(),
551            name: "Bundle".to_string(),
552            title: Some("Bundle".to_string()),
553            status: "active".to_string(),
554            kind: "resource".to_string(),
555            is_abstract: false,
556            description: Some("A container for a collection of resources".to_string()),
557            purpose: None,
558            base_type: "Bundle".to_string(),
559            base_definition: Some("http://hl7.org/fhir/StructureDefinition/Resource".to_string()),
560            version: None,
561            differential: None,
562            snapshot: None,
563        };
564
565        let nested_elements = vec![];
566
567        // Generate the nested struct
568        let result = generator.generate_nested_struct(
569            "Bundle",
570            "entry",
571            &nested_elements,
572            &bundle_structure,
573        );
574        assert!(result.is_ok(), "Should generate nested struct successfully");
575
576        let bundle_entry_struct = result.unwrap().unwrap();
577
578        // Check derives include serde when enabled
579        assert!(bundle_entry_struct.derives.contains(&"Debug".to_string()));
580        assert!(bundle_entry_struct.derives.contains(&"Clone".to_string()));
581        assert!(bundle_entry_struct
582            .derives
583            .contains(&"Serialize".to_string()));
584        assert!(bundle_entry_struct
585            .derives
586            .contains(&"Deserialize".to_string()));
587    }
588}