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 if structure_def.name == "DomainResource" || structure_def.name == "Element" {
58 rust_trait.add_method(
59 RustTraitMethod::new("extension_by_url".to_string())
60 .with_return_type(RustType::Custom("Option<&crate::datatypes::extension::Extension>".to_string()))
61 .with_parameter("url".to_string(), RustType::Custom("&str".to_string()))
62 .with_default_implementation(
63 "self.extension().iter().find(|e| e.url == url)".to_string(),
64 )
65 .with_doc("Find an extension by its URL. Returns the first matching extension, or None if not found.".to_string()),
66 );
67 }
68
69 Ok(())
70 }
71
72 fn should_generate_accessor(
73 &self,
74 element: &ElementDefinition,
75 structure_def: &StructureDefinition,
76 ) -> bool {
77 let field_path = &element.path;
78 let base_name = &structure_def.name;
79
80 if !field_path.starts_with(base_name) {
82 return false;
83 }
84
85 let path_parts: Vec<&str> = field_path.split('.').collect();
88 if path_parts.len() != 2 {
89 return false;
90 }
91
92 if path_parts[0] != base_name {
94 return false;
95 }
96
97 let field_name = path_parts[1];
99 !field_name.ends_with("[x]")
100 }
101
102 fn create_accessor_method(
103 &self,
104 element: &ElementDefinition,
105 type_mapper: &mut TypeMapper,
106 ) -> CodegenResult<Option<RustTraitMethod>> {
107 let path_parts: Vec<&str> = element.path.split('.').collect();
108 let field_name = path_parts
109 .last()
110 .ok_or_else(|| crate::CodegenError::MissingField {
111 field: format!("element path has no field segment: {}", element.path),
112 })?
113 .to_string();
114 let rust_field_name = crate::naming::Naming::field_name(&field_name);
115
116 let is_optional = element.min.unwrap_or(0) == 0;
117 let is_array = element.max.as_deref() == Some("*")
118 || element
119 .max
120 .as_deref()
121 .unwrap_or("1")
122 .parse::<i32>()
123 .unwrap_or(1)
124 > 1;
125
126 let Some(element_types) = element.element_type.as_ref() else {
127 return Ok(None);
128 };
129
130 let rust_type = if self.is_backbone_element(element_types) {
132 self.get_nested_type_for_backbone_element(element, is_array)
133 } else if field_name == "language" && element.path.ends_with(".language") {
134 if is_array {
138 RustType::Vec(Box::new(RustType::Custom("StringType".to_string())))
139 } else {
140 RustType::Custom("StringType".to_string())
141 }
142 } else {
143 let resolved = type_mapper.map_fhir_type_with_binding(
145 element_types,
146 element.binding.as_ref(),
147 is_array,
148 );
149 let resource_name = element.path.split('.').next().unwrap_or("");
152 let collides = matches!(&resolved, RustType::Custom(n) if n == resource_name);
153 if collides {
154 if is_array {
155 RustType::Vec(Box::new(RustType::Custom("StringType".to_string())))
156 } else {
157 RustType::Custom("StringType".to_string())
158 }
159 } else {
160 resolved
161 }
162 };
163
164 let return_type = match rust_type {
166 RustType::Custom(_) => {
167 rust_type.clone()
170 }
171 RustType::Vec(inner) => RustType::Slice(inner),
172 other => other,
173 };
174
175 let method = RustTraitMethod::new(rust_field_name)
176 .with_doc(format!("Returns a reference to the {field_name} field."))
177 .with_return_type(if is_optional && !is_array {
178 return_type.clone().wrap_in_option()
179 } else {
180 return_type.clone()
181 })
182 .with_body(format!("self.{field_name}"));
183
184 Ok(Some(method))
185 }
186
187 fn is_backbone_element(&self, element_types: &[ElementType]) -> bool {
189 element_types
190 .iter()
191 .any(|et| et.code.as_deref() == Some("BackboneElement"))
192 }
193
194 fn get_nested_type_for_backbone_element(
196 &self,
197 element: &ElementDefinition,
198 is_array: bool,
199 ) -> RustType {
200 let path_parts: Vec<&str> = element.path.split('.').collect();
201
202 if path_parts.len() == 2 {
203 let resource_name = path_parts[0];
204 let field_name = path_parts[1];
205
206 let field_name_pascal = crate::naming::Naming::to_pascal_case(field_name);
208 let nested_type_name = format!("{resource_name}{field_name_pascal}");
209
210 let rust_type = RustType::Custom(nested_type_name);
211
212 if is_array {
213 RustType::Vec(Box::new(rust_type))
214 } else {
215 rust_type
216 }
217 } else {
218 let rust_type = RustType::Custom("BackboneElement".to_string());
220 if is_array {
221 RustType::Vec(Box::new(rust_type))
222 } else {
223 rust_type
224 }
225 }
226 }
227
228 fn add_choice_type_accessor_methods(
229 &self,
230 _rust_trait: &mut RustTrait,
231 _structure_def: &StructureDefinition,
232 ) -> CodegenResult<()> {
233 Ok(())
235 }
236}