1use 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
14pub 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 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 pub fn generate_struct(
37 &mut self,
38 structure_def: &StructureDefinition,
39 ) -> CodegenResult<RustStruct> {
40 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 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 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 let struct_name = crate::naming::Naming::struct_name(structure_def);
73
74 if let Some(cached_struct) = self.type_cache.get(&struct_name) {
76 return Ok(cached_struct.clone());
77 }
78
79 let mut rust_struct = RustStruct::new(struct_name.clone());
81 rust_struct.doc_comment =
82 DocumentationGenerator::generate_struct_documentation(structure_def);
83
84 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 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 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 if structure_def.kind == "primitive-type" {
112 return self.generate_primitive_type_struct(structure_def, rust_struct);
113 }
114
115 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); };
123
124 let mut nested_structs_info = HashMap::new();
126 let mut direct_fields = Vec::new();
127
128 for element in elements {
129 if element.path == structure_def.name || element.path == structure_def.base_type {
131 continue;
132 }
133
134 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 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 direct_fields.push(element.clone());
165 }
166 }
167
168 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 self.type_cache
181 .insert(nested_struct.name.clone(), nested_struct.clone());
182
183 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 for element in direct_fields {
195 let fields = self.create_fields_from_element(&element)?;
196 for mut field in fields {
197 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 self.type_cache.insert(struct_name, rust_struct.clone());
206
207 Ok(rust_struct)
208 }
209
210 pub fn generate_primitive_type_struct(
212 &mut self,
213 structure_def: &StructureDefinition,
214 mut rust_struct: RustStruct,
215 ) -> CodegenResult<RustStruct> {
216 rust_struct.base_definition = None;
218
219 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, _ => RustType::String, };
229
230 let value_field = RustField::new("value".to_string(), rust_primitive_type);
233 rust_struct.add_field(value_field);
234
235 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 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 let nested_struct_name = format!(
252 "{}{}",
253 parent_struct_name,
254 crate::naming::Naming::to_pascal_case(nested_field_name)
255 );
256
257 if self.type_cache.contains_key(&nested_struct_name) {
259 return Ok(None);
260 }
261
262 let mut nested_struct = RustStruct::new(nested_struct_name.clone());
264
265 nested_struct.doc_comment = Some(
267 DocumentationGenerator::generate_nested_struct_documentation(
268 parent_struct_name,
269 nested_field_name,
270 ),
271 );
272
273 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 nested_struct.base_definition = Some("BackboneElement".to_string());
282
283 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 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 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 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 sub_nested_structs
333 .entry(field_path.to_string())
334 .or_insert_with(Vec::new)
335 .push(element.clone());
336 } else {
337 direct_fields.push(element.clone());
339 }
340 }
341 }
342
343 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 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 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 let sub_base_path = format!("{base_path}.{sub_nested_field_name}");
376
377 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 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 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 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 sub_sub_nested_structs
423 .entry(sub_field_path.to_string())
424 .or_default()
425 .push(element.clone());
426 } else {
427 sub_direct_fields.push(element.clone());
429 }
430 }
431 }
432
433 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 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 self.type_cache
459 .insert(sub_nested_struct_name.clone(), sub_nested_struct);
460
461 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 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 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 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 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 let base_path = format!("{parent_base_path}.{field_name}");
530
531 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 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 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 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 sub_nested_structs
581 .entry(field_path.to_string())
582 .or_default()
583 .push(element.clone());
584 } else {
585 direct_fields.push(element.clone());
587 }
588 }
589 }
590
591 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 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 self.type_cache
614 .insert(nested_struct_name.clone(), nested_struct);
615
616 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 fn extract_root_parent_resource(nested_struct_name: &str) -> String {
633 crate::generators::type_registry::TypeRegistry::extract_parent_from_name(nested_struct_name)
635 .unwrap_or_else(|| nested_struct_name.to_string())
636 }
637
638 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 if let Some(first_type) = element_types.first() {
646 if let Some(code) = &first_type.code {
647 if code == "BackboneElement" {
648 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 return true;
656 }
657 }
658 }
659 }
660 false
661 }
662
663 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 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 fn apply_box_for_circular_dependencies(
695 field_type: RustType,
696 current_struct_name: &str,
697 ) -> RustType {
698 let circular_dependencies = [("Identifier", "Reference"), ("Reference", "Identifier")];
700
701 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 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 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}