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.path.strip_prefix(&format!("{base_path}.")).unwrap();
88
89 if field_path.contains('.') {
90 let sub_nested_field_name = field_path.split('.').next().unwrap();
92 sub_nested_structs
93 .entry(sub_nested_field_name.to_string())
94 .or_insert_with(Vec::new)
95 .push(element.clone());
96 } else {
97 let is_backbone_element = element
99 .element_type
100 .as_ref()
101 .and_then(|types| types.first())
102 .and_then(|t| t.code.as_ref())
103 .map(|code| code == "BackboneElement")
104 .unwrap_or(false);
105
106 if is_backbone_element {
107 sub_nested_structs
109 .entry(field_path.to_string())
110 .or_insert_with(Vec::new)
111 .push(element.clone());
112 } else {
113 direct_fields.push(element.clone());
115 }
116 }
117 }
118
119 for (sub_nested_field_name, sub_nested_elements) in &sub_nested_structs {
121 self.generate_sub_nested_struct(
122 &nested_struct_name,
123 sub_nested_field_name,
124 sub_nested_elements,
125 &base_path,
126 )?;
127 }
128
129 for element in direct_fields {
131 if let Some(field) = self.create_field_from_element(&element)? {
132 nested_struct.add_field(field);
133 }
134 }
135
136 self.type_cache
138 .insert(nested_struct_name.clone(), nested_struct.clone());
139
140 let parent_resource = parent_struct_name.to_string();
143 TypeRegistry::register_type_classification_only(
144 &nested_struct_name,
145 crate::generators::type_registry::TypeClassification::NestedStructure {
146 parent_resource,
147 },
148 );
149
150 Ok(Some(nested_struct))
151 }
152
153 fn generate_sub_nested_struct(
155 &mut self,
156 nested_struct_name: &str,
157 sub_nested_field_name: &str,
158 sub_nested_elements: &[ElementDefinition],
159 base_path: &str,
160 ) -> CodegenResult<()> {
161 let sub_nested_struct_name = format!(
164 "{}{}",
165 nested_struct_name,
166 Naming::to_pascal_case(sub_nested_field_name)
167 );
168
169 if !self.type_cache.contains_key(&sub_nested_struct_name) {
170 let mut sub_nested_struct = RustStruct::new(sub_nested_struct_name.clone());
171
172 sub_nested_struct.doc_comment = Some(
173 DocumentationGenerator::generate_sub_nested_struct_documentation(
174 nested_struct_name,
175 sub_nested_field_name,
176 ),
177 );
178
179 let mut derives = vec!["Debug".to_string(), "Clone".to_string()];
181 if self.config.with_serde {
182 derives.extend(vec!["Serialize".to_string(), "Deserialize".to_string()]);
183 }
184 sub_nested_struct.derives = derives;
185 sub_nested_struct.base_definition = Some("BackboneElement".to_string());
186
187 let sub_base_path = format!("{base_path}.{sub_nested_field_name}");
189
190 let mut direct_fields = Vec::new();
192 let mut sub_sub_nested_structs: HashMap<String, Vec<ElementDefinition>> =
193 HashMap::new();
194
195 for element in sub_nested_elements {
196 if !element.path.starts_with(&sub_base_path) {
197 continue;
198 }
199
200 let field_path = element
201 .path
202 .strip_prefix(&format!("{sub_base_path}."))
203 .unwrap();
204
205 if field_path.contains('.') {
206 let sub_sub_nested_field_name = field_path.split('.').next().unwrap();
208 sub_sub_nested_structs
209 .entry(sub_sub_nested_field_name.to_string())
210 .or_default()
211 .push(element.clone());
212 } else {
213 let is_backbone_element = element
215 .element_type
216 .as_ref()
217 .and_then(|types| types.first())
218 .and_then(|t| t.code.as_ref())
219 .map(|code| code == "BackboneElement")
220 .unwrap_or(false);
221
222 if is_backbone_element {
223 sub_sub_nested_structs
225 .entry(field_path.to_string())
226 .or_default()
227 .push(element.clone());
228 } else {
229 direct_fields.push(element.clone());
231 }
232 }
233 }
234
235 for (sub_sub_nested_field_name, sub_sub_nested_elements) in &sub_sub_nested_structs {
237 self.generate_sub_nested_struct(
238 &sub_nested_struct_name,
239 sub_sub_nested_field_name,
240 sub_sub_nested_elements,
241 &sub_base_path,
242 )?;
243 } for element in direct_fields {
245 if let Some(field) = self.create_field_from_element(&element)? {
246 sub_nested_struct.add_field(field);
247 }
248 }
249
250 self.type_cache
252 .insert(sub_nested_struct_name.clone(), sub_nested_struct);
253
254 let parent_resource = Self::extract_parent_resource_name(nested_struct_name);
258
259 TypeRegistry::register_type_classification_only(
260 &sub_nested_struct_name,
261 crate::generators::type_registry::TypeClassification::NestedStructure {
262 parent_resource,
263 },
264 );
265 }
266
267 Ok(())
268 }
269
270 fn extract_parent_resource_name(nested_struct_name: &str) -> String {
273 crate::generators::type_registry::TypeRegistry::extract_parent_from_name(nested_struct_name)
275 .unwrap_or_else(|| nested_struct_name.to_string())
276 }
277
278 fn create_field_from_element(
280 &mut self,
281 element: &ElementDefinition,
282 ) -> CodegenResult<Option<crate::rust_types::RustField>> {
283 let mut field_generator =
284 FieldGenerator::new(self.config, self.type_cache, self.value_set_manager);
285 field_generator.create_field_from_element(element)
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292 use crate::config::CodegenConfig;
293 use crate::fhir_types::{
294 ElementDefinition, ElementType, StructureDefinition, StructureDefinitionDifferential,
295 };
296 use crate::value_sets::ValueSetManager;
297
298 #[test]
299 fn test_nested_struct_generation() {
300 let config = CodegenConfig::default();
301 let mut type_cache = HashMap::new();
302 let mut value_set_manager = ValueSetManager::new();
303 let mut generator =
304 NestedStructGenerator::new(&config, &mut type_cache, &mut value_set_manager);
305
306 let bundle_structure = StructureDefinition {
308 resource_type: "StructureDefinition".to_string(),
309 id: "Bundle".to_string(),
310 url: "http://hl7.org/fhir/StructureDefinition/Bundle".to_string(),
311 name: "Bundle".to_string(),
312 title: Some("Bundle".to_string()),
313 status: "active".to_string(),
314 kind: "resource".to_string(),
315 is_abstract: false,
316 description: Some("A container for a collection of resources".to_string()),
317 purpose: None,
318 base_type: "Bundle".to_string(),
319 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Resource".to_string()),
320 version: None,
321 differential: Some(StructureDefinitionDifferential {
322 element: vec![
323 ElementDefinition {
324 id: Some("Bundle.entry".to_string()),
325 path: "Bundle.entry".to_string(),
326 short: Some("Entry in the bundle".to_string()),
327 definition: None,
328 min: Some(0),
329 max: Some("*".to_string()),
330 element_type: Some(vec![ElementType {
331 code: Some("BackboneElement".to_string()),
332 target_profile: None,
333 }]),
334 fixed: None,
335 pattern: None,
336 binding: None,
337 constraint: None,
338 },
339 ElementDefinition {
340 id: Some("Bundle.entry.resource".to_string()),
341 path: "Bundle.entry.resource".to_string(),
342 short: Some("A resource in the bundle".to_string()),
343 definition: None,
344 min: Some(0),
345 max: Some("1".to_string()),
346 element_type: Some(vec![ElementType {
347 code: Some("Resource".to_string()),
348 target_profile: None,
349 }]),
350 fixed: None,
351 pattern: None,
352 binding: None,
353 constraint: None,
354 },
355 ],
356 }),
357 snapshot: None,
358 };
359
360 let nested_elements = bundle_structure
361 .differential
362 .as_ref()
363 .unwrap()
364 .element
365 .clone();
366
367 let filtered_elements: Vec<_> = nested_elements
369 .into_iter()
370 .filter(|e| e.path.starts_with("Bundle.entry."))
371 .collect();
372
373 let result = generator.generate_nested_struct(
375 "Bundle",
376 "entry",
377 &filtered_elements,
378 &bundle_structure,
379 );
380 assert!(result.is_ok(), "Should generate nested struct successfully");
381
382 let bundle_entry_struct = result.unwrap();
383 assert!(
384 bundle_entry_struct.is_some(),
385 "Should return a nested struct"
386 );
387
388 let bundle_entry_struct = bundle_entry_struct.unwrap();
389 assert_eq!(bundle_entry_struct.name, "BundleEntry");
390 assert_eq!(
391 bundle_entry_struct.base_definition,
392 Some("BackboneElement".to_string())
393 );
394
395 assert!(
397 type_cache.contains_key("BundleEntry"),
398 "BundleEntry should be cached"
399 );
400
401 let resource_field = bundle_entry_struct
403 .fields
404 .iter()
405 .find(|f| f.name == "resource");
406 assert!(
407 resource_field.is_some(),
408 "BundleEntry should have a resource field"
409 );
410 }
411
412 #[test]
413 fn test_nested_struct_caching() {
414 let config = CodegenConfig::default();
415 let mut type_cache = HashMap::new();
416 let mut value_set_manager = ValueSetManager::new();
417
418 let existing_struct = RustStruct::new("BundleEntry".to_string());
420 type_cache.insert("BundleEntry".to_string(), existing_struct);
421
422 let mut generator =
423 NestedStructGenerator::new(&config, &mut type_cache, &mut value_set_manager);
424
425 let bundle_structure = StructureDefinition {
426 resource_type: "StructureDefinition".to_string(),
427 id: "Bundle".to_string(),
428 url: "http://hl7.org/fhir/StructureDefinition/Bundle".to_string(),
429 name: "Bundle".to_string(),
430 title: Some("Bundle".to_string()),
431 status: "active".to_string(),
432 kind: "resource".to_string(),
433 is_abstract: false,
434 description: Some("A container for a collection of resources".to_string()),
435 purpose: None,
436 base_type: "Bundle".to_string(),
437 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Resource".to_string()),
438 version: None,
439 differential: None,
440 snapshot: None,
441 };
442
443 let nested_elements = vec![];
444
445 let result = generator.generate_nested_struct(
447 "Bundle",
448 "entry",
449 &nested_elements,
450 &bundle_structure,
451 );
452 assert!(result.is_ok(), "Should handle cached struct successfully");
453
454 let bundle_entry_struct = result.unwrap();
455 assert!(
456 bundle_entry_struct.is_none(),
457 "Should return None for cached struct"
458 );
459 }
460
461 #[test]
462 fn test_nested_struct_documentation() {
463 let config = CodegenConfig::default();
464 let mut type_cache = HashMap::new();
465 let mut value_set_manager = ValueSetManager::new();
466 let mut generator =
467 NestedStructGenerator::new(&config, &mut type_cache, &mut value_set_manager);
468
469 let bundle_structure = StructureDefinition {
470 resource_type: "StructureDefinition".to_string(),
471 id: "Bundle".to_string(),
472 url: "http://hl7.org/fhir/StructureDefinition/Bundle".to_string(),
473 name: "Bundle".to_string(),
474 title: Some("Bundle".to_string()),
475 status: "active".to_string(),
476 kind: "resource".to_string(),
477 is_abstract: false,
478 description: Some("A container for a collection of resources".to_string()),
479 purpose: None,
480 base_type: "Bundle".to_string(),
481 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Resource".to_string()),
482 version: None,
483 differential: None,
484 snapshot: None,
485 };
486
487 let nested_elements = vec![];
488
489 let result = generator.generate_nested_struct(
491 "Bundle",
492 "entry",
493 &nested_elements,
494 &bundle_structure,
495 );
496 assert!(result.is_ok(), "Should generate nested struct successfully");
497
498 let bundle_entry_struct = result.unwrap().unwrap();
499
500 assert!(
502 bundle_entry_struct.doc_comment.is_some(),
503 "Should have documentation"
504 );
505 let doc = bundle_entry_struct.doc_comment.unwrap();
506 assert!(
507 doc.contains("Bundle"),
508 "Documentation should mention parent struct"
509 );
510 assert!(
511 doc.contains("entry"),
512 "Documentation should mention field name"
513 );
514 }
515
516 #[test]
517 fn test_nested_struct_derives() {
518 let config = CodegenConfig {
519 with_serde: true,
520 ..Default::default()
521 };
522 let mut type_cache = HashMap::new();
523 let mut value_set_manager = ValueSetManager::new();
524 let mut generator =
525 NestedStructGenerator::new(&config, &mut type_cache, &mut value_set_manager);
526
527 let bundle_structure = StructureDefinition {
528 resource_type: "StructureDefinition".to_string(),
529 id: "Bundle".to_string(),
530 url: "http://hl7.org/fhir/StructureDefinition/Bundle".to_string(),
531 name: "Bundle".to_string(),
532 title: Some("Bundle".to_string()),
533 status: "active".to_string(),
534 kind: "resource".to_string(),
535 is_abstract: false,
536 description: Some("A container for a collection of resources".to_string()),
537 purpose: None,
538 base_type: "Bundle".to_string(),
539 base_definition: Some("http://hl7.org/fhir/StructureDefinition/Resource".to_string()),
540 version: None,
541 differential: None,
542 snapshot: None,
543 };
544
545 let nested_elements = vec![];
546
547 let result = generator.generate_nested_struct(
549 "Bundle",
550 "entry",
551 &nested_elements,
552 &bundle_structure,
553 );
554 assert!(result.is_ok(), "Should generate nested struct successfully");
555
556 let bundle_entry_struct = result.unwrap().unwrap();
557
558 assert!(bundle_entry_struct.derives.contains(&"Debug".to_string()));
560 assert!(bundle_entry_struct.derives.contains(&"Clone".to_string()));
561 assert!(bundle_entry_struct
562 .derives
563 .contains(&"Serialize".to_string()));
564 assert!(bundle_entry_struct
565 .derives
566 .contains(&"Deserialize".to_string()));
567 }
568}