rh_codegen/generators/
accessor_trait_generator.rs1use crate::fhir_types::{ElementDefinition, ElementType, StructureDefinition};
3use crate::rust_types::{RustTrait, RustTraitMethod, RustType};
4use crate::type_mapper::TypeMapper;
5use crate::value_sets::ValueSetManager;
6use crate::CodegenResult;
7
8#[derive(Default)]
9pub struct AccessorTraitGenerator {}
10
11impl AccessorTraitGenerator {
12 pub fn new() -> Self {
13 Self::default()
14 }
15
16 pub fn add_accessor_methods(
17 &self,
18 rust_trait: &mut RustTrait,
19 structure_def: &StructureDefinition,
20 ) -> CodegenResult<()> {
21 let config = crate::config::CodegenConfig::default();
23 let mut value_set_manager = ValueSetManager::new();
24 let mut type_mapper = TypeMapper::new(&config, &mut value_set_manager);
25
26 let elements = structure_def
27 .differential
28 .as_ref()
29 .map_or(Vec::new(), |d| d.element.clone());
30
31 if elements.is_empty() {
32 if let Some(snapshot) = &structure_def.snapshot {
33 let snapshot_elements = snapshot.element.clone();
34 for element in &snapshot_elements {
35 if self.should_generate_accessor(element, structure_def) {
36 if let Some(method) =
37 self.create_accessor_method(element, &mut type_mapper)?
38 {
39 rust_trait.add_method(method);
40 }
41 }
42 }
43 }
44 } else {
45 for element in &elements {
46 if self.should_generate_accessor(element, structure_def) {
47 if let Some(method) = self.create_accessor_method(element, &mut type_mapper)? {
48 rust_trait.add_method(method);
49 }
50 }
51 }
52 }
53
54 self.add_choice_type_accessor_methods(rust_trait, structure_def)?;
55
56 Ok(())
57 }
58
59 fn should_generate_accessor(
60 &self,
61 element: &ElementDefinition,
62 structure_def: &StructureDefinition,
63 ) -> bool {
64 let field_path = &element.path;
65 let base_name = &structure_def.name;
66
67 if !field_path.starts_with(base_name) {
69 return false;
70 }
71
72 let path_parts: Vec<&str> = field_path.split('.').collect();
75 if path_parts.len() != 2 {
76 return false;
77 }
78
79 if path_parts[0] != base_name {
81 return false;
82 }
83
84 let field_name = path_parts[1];
86 !field_name.ends_with("[x]")
87 }
88
89 fn create_accessor_method(
90 &self,
91 element: &ElementDefinition,
92 type_mapper: &mut TypeMapper,
93 ) -> CodegenResult<Option<RustTraitMethod>> {
94 let path_parts: Vec<&str> = element.path.split('.').collect();
95 let field_name = path_parts.last().unwrap().to_string();
96 let rust_field_name = crate::naming::Naming::field_name(&field_name);
97
98 let is_optional = element.min.unwrap_or(0) == 0;
99 let is_array = element.max.as_deref() == Some("*")
100 || element
101 .max
102 .as_deref()
103 .unwrap_or("1")
104 .parse::<i32>()
105 .unwrap_or(1)
106 > 1;
107
108 let Some(element_types) = element.element_type.as_ref() else {
109 return Ok(None);
110 };
111
112 let rust_type = if self.is_backbone_element(element_types) {
114 self.get_nested_type_for_backbone_element(element, is_array)
115 } else {
116 type_mapper.map_fhir_type_with_binding(
118 element_types,
119 element.binding.as_ref(),
120 is_array,
121 )
122 };
123
124 let return_type = match rust_type {
126 RustType::Custom(_) => {
127 rust_type.clone()
130 }
131 RustType::Vec(inner) => RustType::Slice(inner),
132 other => other,
133 };
134
135 let method = RustTraitMethod::new(rust_field_name)
136 .with_doc(format!("Returns a reference to the {field_name} field."))
137 .with_return_type(if is_optional && !is_array {
138 return_type.clone().wrap_in_option()
139 } else {
140 return_type.clone()
141 })
142 .with_body(format!("self.{field_name}"));
143
144 Ok(Some(method))
145 }
146
147 fn is_backbone_element(&self, element_types: &[ElementType]) -> bool {
149 element_types
150 .iter()
151 .any(|et| et.code.as_deref() == Some("BackboneElement"))
152 }
153
154 fn get_nested_type_for_backbone_element(
156 &self,
157 element: &ElementDefinition,
158 is_array: bool,
159 ) -> RustType {
160 let path_parts: Vec<&str> = element.path.split('.').collect();
161
162 if path_parts.len() == 2 {
163 let resource_name = path_parts[0];
164 let field_name = path_parts[1];
165
166 let field_name_pascal = crate::naming::Naming::to_pascal_case(field_name);
168 let nested_type_name = format!("{resource_name}{field_name_pascal}");
169
170 let rust_type = RustType::Custom(nested_type_name);
171
172 if is_array {
173 RustType::Vec(Box::new(rust_type))
174 } else {
175 rust_type
176 }
177 } else {
178 let rust_type = RustType::Custom("BackboneElement".to_string());
180 if is_array {
181 RustType::Vec(Box::new(rust_type))
182 } else {
183 rust_type
184 }
185 }
186 }
187
188 fn add_choice_type_accessor_methods(
189 &self,
190 _rust_trait: &mut RustTrait,
191 _structure_def: &StructureDefinition,
192 ) -> CodegenResult<()> {
193 Ok(())
195 }
196}