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 structure_def.url.to_lowercase().contains("example") {
52 return Err(CodegenError::Generation {
53 message: format!(
54 "Skipping example StructureDefinition '{}'",
55 structure_def.name
56 ),
57 });
58 }
59
60 if TypeUtilities::should_skip_underscore_prefixed(structure_def) {
62 return Err(CodegenError::Generation {
63 message: format!(
64 "Skipping underscore-prefixed StructureDefinition '{}' - underscore prefixed files are ignored",
65 structure_def.name
66 ),
67 });
68 }
69
70 let struct_name = crate::naming::Naming::struct_name(structure_def);
72
73 if let Some(cached_struct) = self.type_cache.get(&struct_name) {
75 return Ok(cached_struct.clone());
76 }
77
78 let mut rust_struct = RustStruct::new(struct_name.clone());
80 rust_struct.doc_comment =
81 DocumentationGenerator::generate_struct_documentation(structure_def);
82
83 let mut derives = vec!["Debug".to_string(), "Clone".to_string()];
85 if self.config.with_serde {
86 derives.extend(vec!["Serialize".to_string(), "Deserialize".to_string()]);
87 }
88
89 let is_profile = crate::generators::type_registry::TypeRegistry::is_profile(structure_def);
92 if is_profile {
93 derives.push("Default".to_string());
94 }
95
96 rust_struct.derives = derives;
97
98 if let Some(base_def) = &structure_def.base_definition {
100 rust_struct.base_definition = Some(
101 base_def
102 .split('/')
103 .next_back()
104 .unwrap_or(base_def)
105 .to_string(),
106 );
107 }
108
109 if structure_def.kind == "primitive-type" {
111 return self.generate_primitive_type_struct(structure_def, rust_struct);
112 }
113
114 let elements = if let Some(differential) = &structure_def.differential {
116 &differential.element
117 } else if let Some(snapshot) = &structure_def.snapshot {
118 &snapshot.element
119 } else {
120 return Ok(rust_struct); };
122
123 let mut nested_structs_info = HashMap::new();
125 let mut direct_fields = Vec::new();
126
127 for element in elements {
128 if element.path == structure_def.name || element.path == structure_def.base_type {
130 continue;
131 }
132
133 let base_path = &structure_def.name;
135 if !element.path.starts_with(&format!("{base_path}.")) {
136 continue;
137 }
138
139 let field_path = element.path.strip_prefix(&format!("{base_path}.")).unwrap();
140
141 if field_path.contains('.') {
142 let nested_field_name = field_path.split('.').next().unwrap();
144 nested_structs_info
145 .entry(nested_field_name.to_string())
146 .or_insert_with(Vec::new)
147 .push(element.clone());
148 } else {
149 direct_fields.push(element.clone());
151 }
152 }
153
154 for (nested_field_name, nested_elements) in &nested_structs_info {
156 if let Some(nested_struct) = self.generate_nested_struct(
157 &struct_name,
158 nested_field_name,
159 nested_elements,
160 structure_def,
161 )? {
162 self.type_cache
164 .insert(nested_struct.name.clone(), nested_struct.clone());
165
166 crate::generators::type_registry::TypeRegistry::register_type_classification_only(
168 &nested_struct.name,
169 crate::generators::type_registry::TypeClassification::NestedStructure {
170 parent_resource: struct_name.clone(),
171 },
172 );
173 }
174 }
175
176 for element in direct_fields {
178 let fields = self.create_fields_from_element(&element)?;
179 for mut field in fields {
180 field.field_type =
182 Self::apply_box_for_circular_dependencies(field.field_type, &rust_struct.name);
183 rust_struct.add_field(field);
184 }
185 }
186
187 self.type_cache.insert(struct_name, rust_struct.clone());
189
190 Ok(rust_struct)
191 }
192
193 pub fn generate_primitive_type_struct(
195 &mut self,
196 structure_def: &StructureDefinition,
197 mut rust_struct: RustStruct,
198 ) -> CodegenResult<RustStruct> {
199 rust_struct.base_definition = None;
201
202 let rust_primitive_type = match structure_def.name.as_str() {
204 "boolean" => RustType::Boolean,
205 "integer" | "positiveInt" | "unsignedInt" => RustType::Integer,
206 "decimal" => RustType::Float,
207 "string" | "code" | "id" | "markdown" | "uri" | "url" | "canonical" | "oid"
208 | "uuid" | "base64Binary" | "xhtml" => RustType::String,
209 "date" | "dateTime" | "time" | "instant" => RustType::String, _ => RustType::String, };
212
213 let value_field = RustField::new("value".to_string(), rust_primitive_type);
216 rust_struct.add_field(value_field);
217
218 let struct_name = rust_struct.name.clone();
220 self.type_cache.insert(struct_name, rust_struct.clone());
221
222 Ok(rust_struct)
223 }
224
225 pub fn generate_nested_struct(
227 &mut self,
228 parent_struct_name: &str,
229 nested_field_name: &str,
230 nested_elements: &[crate::fhir_types::ElementDefinition],
231 parent_structure_def: &StructureDefinition,
232 ) -> CodegenResult<Option<RustStruct>> {
233 let nested_struct_name = format!(
235 "{}{}",
236 parent_struct_name,
237 crate::naming::Naming::to_pascal_case(nested_field_name)
238 );
239
240 if self.type_cache.contains_key(&nested_struct_name) {
242 return Ok(None);
243 }
244
245 let mut nested_struct = RustStruct::new(nested_struct_name.clone());
247
248 nested_struct.doc_comment = Some(
250 DocumentationGenerator::generate_nested_struct_documentation(
251 parent_struct_name,
252 nested_field_name,
253 ),
254 );
255
256 let mut derives = vec!["Debug".to_string(), "Clone".to_string()];
258 if self.config.with_serde {
259 derives.extend(vec!["Serialize".to_string(), "Deserialize".to_string()]);
260 }
261 nested_struct.derives = derives;
262
263 nested_struct.base_definition = Some("BackboneElement".to_string());
265
266 let base_path = format!("{}.{}", parent_structure_def.name, nested_field_name);
268 let mut sub_nested_structs = HashMap::new();
269 let mut direct_fields = Vec::new();
270
271 for element in nested_elements {
272 if !element.path.starts_with(&base_path) {
273 continue;
274 }
275
276 if element.path == base_path {
278 continue;
279 }
280
281 let field_path = element.path.strip_prefix(&format!("{base_path}.")).unwrap();
282
283 if field_path.contains('.') {
284 let sub_nested_field_name = field_path.split('.').next().unwrap();
286 sub_nested_structs
287 .entry(sub_nested_field_name.to_string())
288 .or_insert_with(Vec::new)
289 .push(element.clone());
290 } else {
291 let is_backbone_element = element
293 .element_type
294 .as_ref()
295 .and_then(|types| types.first())
296 .and_then(|t| t.code.as_ref())
297 .map(|code| code == "BackboneElement")
298 .unwrap_or(false);
299
300 if is_backbone_element {
301 sub_nested_structs
303 .entry(field_path.to_string())
304 .or_insert_with(Vec::new)
305 .push(element.clone());
306 } else {
307 direct_fields.push(element.clone());
309 }
310 }
311 }
312
313 for (sub_nested_field_name, sub_nested_elements) in &sub_nested_structs {
315 let sub_nested_struct_name = format!(
318 "{}{}",
319 nested_struct_name,
320 crate::naming::Naming::to_pascal_case(sub_nested_field_name)
321 );
322
323 if !self.type_cache.contains_key(&sub_nested_struct_name) {
324 let mut sub_nested_struct = RustStruct::new(sub_nested_struct_name.clone());
325
326 sub_nested_struct.doc_comment = Some(
327 DocumentationGenerator::generate_sub_nested_struct_documentation(
328 &nested_struct_name,
329 sub_nested_field_name,
330 ),
331 );
332
333 let mut derives = vec!["Debug".to_string(), "Clone".to_string()];
335 if self.config.with_serde {
336 derives.extend(vec!["Serialize".to_string(), "Deserialize".to_string()]);
337 }
338 sub_nested_struct.derives = derives;
339 sub_nested_struct.base_definition = Some("BackboneElement".to_string());
340
341 let sub_base_path = format!("{base_path}.{sub_nested_field_name}");
343
344 let mut sub_direct_fields = Vec::new();
346 let mut sub_sub_nested_structs: HashMap<
347 String,
348 Vec<crate::fhir_types::ElementDefinition>,
349 > = HashMap::new();
350
351 for element in sub_nested_elements {
352 if !element.path.starts_with(&sub_base_path) {
353 continue;
354 }
355
356 if element.path == sub_base_path {
358 continue;
359 }
360
361 let sub_field_path = element
362 .path
363 .strip_prefix(&format!("{sub_base_path}."))
364 .unwrap();
365
366 if sub_field_path.contains('.') {
367 let sub_sub_nested_field_name = sub_field_path.split('.').next().unwrap();
369 sub_sub_nested_structs
370 .entry(sub_sub_nested_field_name.to_string())
371 .or_default()
372 .push(element.clone());
373 } else {
374 let is_backbone_element = element
376 .element_type
377 .as_ref()
378 .and_then(|types| types.first())
379 .and_then(|t| t.code.as_ref())
380 .map(|code| code == "BackboneElement")
381 .unwrap_or(false);
382
383 if is_backbone_element {
384 sub_sub_nested_structs
386 .entry(sub_field_path.to_string())
387 .or_default()
388 .push(element.clone());
389 } else {
390 sub_direct_fields.push(element.clone());
392 }
393 }
394 }
395
396 for (sub_sub_nested_field_name, sub_sub_nested_elements) in &sub_sub_nested_structs
398 {
399 self.generate_deeply_nested_struct(
400 &sub_nested_struct_name,
401 sub_sub_nested_field_name,
402 sub_sub_nested_elements,
403 &sub_base_path,
404 )?;
405 }
406
407 for element in sub_direct_fields {
409 let fields = self.create_fields_from_element(&element)?;
410 for field in fields {
411 sub_nested_struct.add_field(field);
412 }
413 }
414
415 self.type_cache
417 .insert(sub_nested_struct_name.clone(), sub_nested_struct);
418
419 crate::generators::type_registry::TypeRegistry::register_type_classification_only(
422 &sub_nested_struct_name,
423 crate::generators::type_registry::TypeClassification::NestedStructure {
424 parent_resource: parent_struct_name.to_string(),
425 },
426 );
427 }
428
429 let backbone_element_def = sub_nested_elements
432 .iter()
433 .find(|e| e.path == format!("{base_path}.{sub_nested_field_name}"));
434
435 if let Some(element) = backbone_element_def {
436 let fields = self.create_fields_from_element(element)?;
437 for field in fields {
438 nested_struct.add_field(field);
439 }
440 }
441 }
442
443 for element in direct_fields {
445 let fields = self.create_fields_from_element(&element)?;
446 for field in fields {
447 nested_struct.add_field(field);
448 }
449 }
450
451 Ok(Some(nested_struct))
452 }
453
454 fn generate_deeply_nested_struct(
456 &mut self,
457 parent_nested_struct_name: &str,
458 field_name: &str,
459 elements: &[crate::fhir_types::ElementDefinition],
460 parent_base_path: &str,
461 ) -> CodegenResult<()> {
462 let nested_struct_name = format!(
463 "{}{}",
464 parent_nested_struct_name,
465 crate::naming::Naming::to_pascal_case(field_name)
466 );
467
468 if !self.type_cache.contains_key(&nested_struct_name) {
469 let mut nested_struct = RustStruct::new(nested_struct_name.clone());
470
471 nested_struct.doc_comment = Some(
472 DocumentationGenerator::generate_sub_nested_struct_documentation(
473 parent_nested_struct_name,
474 field_name,
475 ),
476 );
477
478 let mut derives = vec!["Debug".to_string(), "Clone".to_string()];
480 if self.config.with_serde {
481 derives.extend(vec!["Serialize".to_string(), "Deserialize".to_string()]);
482 }
483 nested_struct.derives = derives;
484 nested_struct.base_definition = Some("BackboneElement".to_string());
485
486 let base_path = format!("{parent_base_path}.{field_name}");
488
489 let mut direct_fields = Vec::new();
491 let mut sub_nested_structs: HashMap<String, Vec<crate::fhir_types::ElementDefinition>> =
492 HashMap::new();
493
494 for element in elements {
495 if !element.path.starts_with(&base_path) {
496 continue;
497 }
498
499 if element.path == base_path {
501 continue;
502 }
503
504 let field_path = element.path.strip_prefix(&format!("{base_path}.")).unwrap();
505
506 if field_path.contains('.') {
507 let sub_nested_field_name = field_path.split('.').next().unwrap();
509 sub_nested_structs
510 .entry(sub_nested_field_name.to_string())
511 .or_default()
512 .push(element.clone());
513 } else {
514 let is_backbone_element = element
516 .element_type
517 .as_ref()
518 .and_then(|types| types.first())
519 .and_then(|t| t.code.as_ref())
520 .map(|code| code == "BackboneElement")
521 .unwrap_or(false);
522
523 if is_backbone_element {
524 sub_nested_structs
526 .entry(field_path.to_string())
527 .or_default()
528 .push(element.clone());
529 } else {
530 direct_fields.push(element.clone());
532 }
533 }
534 }
535
536 for (sub_nested_field_name, sub_nested_elements) in &sub_nested_structs {
538 self.generate_deeply_nested_struct(
539 &nested_struct_name,
540 sub_nested_field_name,
541 sub_nested_elements,
542 &base_path,
543 )?;
544 }
545
546 for element in direct_fields {
548 let fields = self.create_fields_from_element(&element)?;
549 for field in fields {
550 nested_struct.add_field(field);
551 }
552 }
553
554 self.type_cache
556 .insert(nested_struct_name.clone(), nested_struct);
557
558 let root_parent_resource = Self::extract_root_parent_resource(&nested_struct_name);
561 crate::generators::type_registry::TypeRegistry::register_type_classification_only(
562 &nested_struct_name,
563 crate::generators::type_registry::TypeClassification::NestedStructure {
564 parent_resource: root_parent_resource,
565 },
566 );
567 }
568
569 Ok(())
570 }
571
572 fn extract_root_parent_resource(nested_struct_name: &str) -> String {
575 crate::generators::type_registry::TypeRegistry::extract_parent_from_name(nested_struct_name)
577 .unwrap_or_else(|| nested_struct_name.to_string())
578 }
579
580 pub fn should_use_nested_struct_type(
582 &self,
583 element: &crate::fhir_types::ElementDefinition,
584 element_types: &[crate::fhir_types::ElementType],
585 ) -> bool {
586 if let Some(first_type) = element_types.first() {
588 if let Some(code) = &first_type.code {
589 if code == "BackboneElement" {
590 let path_parts: Vec<&str> = element.path.split('.').collect();
592 if path_parts.len() >= 2 {
593 let _parent_name = path_parts[0];
594 let _field_name = path_parts[1];
595 return true;
598 }
599 }
600 }
601 }
602 false
603 }
604
605 pub fn create_field_from_element(
607 &mut self,
608 element: &crate::fhir_types::ElementDefinition,
609 ) -> CodegenResult<Option<RustField>> {
610 let mut field_generator =
611 FieldGenerator::new(self.config, self.type_cache, self.value_set_manager);
612 field_generator.create_field_from_element(element)
613 }
614
615 pub fn create_fields_from_element(
617 &mut self,
618 element: &crate::fhir_types::ElementDefinition,
619 ) -> CodegenResult<Vec<RustField>> {
620 let mut field_generator =
621 FieldGenerator::new(self.config, self.type_cache, self.value_set_manager);
622 field_generator.create_fields_from_element(element)
623 }
624
625 fn apply_box_for_circular_dependencies(
627 field_type: RustType,
628 current_struct_name: &str,
629 ) -> RustType {
630 let circular_dependencies = [("Identifier", "Reference"), ("Reference", "Identifier")];
632
633 match &field_type {
635 RustType::Custom(type_name) => {
636 for (struct_a, struct_b) in &circular_dependencies {
637 if (current_struct_name == *struct_a && type_name == *struct_b)
638 || (current_struct_name == *struct_b && type_name == *struct_a)
639 {
640 return RustType::Box(Box::new(field_type));
641 }
642 }
643 field_type
644 }
645 RustType::Option(inner) => {
646 let boxed_inner = Self::apply_box_for_circular_dependencies(
647 (**inner).clone(),
648 current_struct_name,
649 );
650 if let RustType::Box(_) = boxed_inner {
651 RustType::Option(Box::new(boxed_inner))
652 } else {
653 field_type
654 }
655 }
656 RustType::Vec(inner) => {
657 let boxed_inner = Self::apply_box_for_circular_dependencies(
658 (**inner).clone(),
659 current_struct_name,
660 );
661 if let RustType::Box(_) = boxed_inner {
662 RustType::Vec(Box::new(boxed_inner))
663 } else {
664 field_type
665 }
666 }
667 _ => field_type,
668 }
669 }
670}
671
672#[cfg(test)]
673mod tests {
674 use super::*;
675 use crate::config::CodegenConfig;
676
677 #[test]
678 fn test_underscore_prefixed_structure_skipping() {
679 let config = CodegenConfig::default();
680 let mut type_cache = HashMap::new();
681 let mut value_set_manager = ValueSetManager::new();
682 let mut generator = StructGenerator::new(&config, &mut type_cache, &mut value_set_manager);
683
684 let underscore_structure = StructureDefinition {
686 resource_type: "StructureDefinition".to_string(),
687 id: "normal-id".to_string(),
688 url: "http://hl7.org/fhir/StructureDefinition/_11179object_class".to_string(),
689 name: "_11179object_class".to_string(),
690 title: Some("Auto-generated class".to_string()),
691 status: "active".to_string(),
692 kind: "resource".to_string(),
693 is_abstract: false,
694 description: Some("An auto-generated resource".to_string()),
695 purpose: None,
696 base_type: "DomainResource".to_string(),
697 base_definition: Some(
698 "http://hl7.org/fhir/StructureDefinition/DomainResource".to_string(),
699 ),
700 version: None,
701 differential: None,
702 snapshot: None,
703 };
704
705 let result = generator.generate_struct(&underscore_structure);
707 assert!(result.is_err());
708
709 if let Err(CodegenError::Generation { message }) = result {
710 assert!(message.contains("underscore prefixed files are ignored"));
711 assert!(message.contains("_11179object_class"));
712 } else {
713 panic!("Expected CodegenError::Generation for underscore-prefixed structure");
714 }
715 }
716}