1use 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
15pub 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 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 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 let nested_struct_name = format!(
46 "{}{}",
47 parent_struct_name,
48 Naming::to_pascal_case(nested_field_name)
49 );
50
51 if self.type_cache.contains_key(&nested_struct_name) {
53 return Ok(None);
54 }
55
56 let mut nested_struct = RustStruct::new(nested_struct_name.clone());
58
59 nested_struct.doc_comment = Some(
61 DocumentationGenerator::generate_nested_struct_documentation(
62 parent_struct_name,
63 nested_field_name,
64 ),
65 );
66
67 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 nested_struct.base_definition = Some("BackboneElement".to_string());
76
77 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 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 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 sub_nested_structs
122 .entry(field_path.to_string())
123 .or_insert_with(Vec::new)
124 .push(element.clone());
125 } else {
126 direct_fields.push(element.clone());
128 }
129 }
130 }
131
132 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 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 self.type_cache
151 .insert(nested_struct_name.clone(), nested_struct.clone());
152
153 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 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 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 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 let sub_base_path = format!("{base_path}.{sub_nested_field_name}");
202
203 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 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 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 sub_sub_nested_structs
245 .entry(field_path.to_string())
246 .or_default()
247 .push(element.clone());
248 } else {
249 direct_fields.push(element.clone());
251 }
252 }
253 }
254
255 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 } 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 self.type_cache
272 .insert(sub_nested_struct_name.clone(), sub_nested_struct);
273
274 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 fn extract_parent_resource_name(nested_struct_name: &str) -> String {
293 crate::generators::type_registry::TypeRegistry::extract_parent_from_name(nested_struct_name)
295 .unwrap_or_else(|| nested_struct_name.to_string())
296 }
297
298 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 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 let filtered_elements: Vec<_> = nested_elements
389 .into_iter()
390 .filter(|e| e.path.starts_with("Bundle.entry."))
391 .collect();
392
393 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 assert!(
417 type_cache.contains_key("BundleEntry"),
418 "BundleEntry should be cached"
419 );
420
421 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 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 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 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 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 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 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}