rh_codegen/generators/
mutator_trait_generator.rs1use crate::fhir_types::{ElementDefinition, StructureDefinition};
3use crate::generators::TypeUtilities;
4use crate::rust_types::{RustTrait, RustTraitMethod, RustType};
5use crate::CodegenResult;
6
7pub struct MutatorTraitGenerator {
8 crate_name: String,
9}
10
11impl Default for MutatorTraitGenerator {
12 fn default() -> Self {
13 Self {
14 crate_name: "hl7_fhir_r4_core".to_string(),
15 }
16 }
17}
18
19impl MutatorTraitGenerator {
20 pub fn new() -> Self {
21 Self::default()
22 }
23
24 pub fn with_crate_name(crate_name: impl Into<String>) -> Self {
25 Self {
26 crate_name: crate_name.into(),
27 }
28 }
29
30 pub fn add_mutator_methods(
31 &self,
32 rust_trait: &mut RustTrait,
33 structure_def: &StructureDefinition,
34 ) -> CodegenResult<()> {
35 self.add_constructor_method(rust_trait, structure_def)?;
37
38 let elements = structure_def
39 .differential
40 .as_ref()
41 .map_or(Vec::new(), |d| d.element.clone());
42
43 if elements.is_empty() {
44 if let Some(snapshot) = &structure_def.snapshot {
45 let snapshot_elements = snapshot.element.clone();
46 for element in &snapshot_elements {
47 if self.should_generate_mutator(element, structure_def) {
48 self.add_mutator_methods_for_element(rust_trait, element)?;
49 }
50 }
51 }
52 } else {
53 for element in &elements {
54 if self.should_generate_mutator(element, structure_def) {
55 self.add_mutator_methods_for_element(rust_trait, element)?;
56 }
57 }
58 }
59
60 self.add_choice_type_mutator_methods(rust_trait, structure_def)?;
61
62 Ok(())
63 }
64
65 fn should_generate_mutator(
66 &self,
67 element: &ElementDefinition,
68 structure_def: &StructureDefinition,
69 ) -> bool {
70 let field_path = &element.path;
71 let base_name = &structure_def.name;
72
73 if !field_path.starts_with(base_name) {
75 return false;
76 }
77
78 let path_parts: Vec<&str> = field_path.split('.').collect();
81 if path_parts.len() != 2 {
82 return false;
83 }
84
85 if path_parts[0] != base_name {
87 return false;
88 }
89
90 let field_name = path_parts[1];
92 !field_name.ends_with("[x]")
93 }
94
95 fn add_mutator_methods_for_element(
96 &self,
97 rust_trait: &mut RustTrait,
98 element: &ElementDefinition,
99 ) -> CodegenResult<()> {
100 let path_parts: Vec<&str> = element.path.split('.').collect();
101 let field_name = path_parts
102 .last()
103 .ok_or_else(|| crate::CodegenError::MissingField {
104 field: format!("element path has no field segment: {}", element.path),
105 })?
106 .to_string();
107 let rust_field_name = crate::naming::Naming::field_name(&field_name);
108
109 let _is_optional = element.min.unwrap_or(0) == 0;
110 let is_array = element.max.as_deref() == Some("*")
111 || element
112 .max
113 .as_deref()
114 .unwrap_or("1")
115 .parse::<i32>()
116 .unwrap_or(1)
117 > 1;
118
119 let rust_type = self.get_field_rust_type(element, &field_name)?;
121
122 self.add_set_method(
124 rust_trait,
125 &rust_field_name,
126 &field_name,
127 &rust_type,
128 is_array,
129 )?;
130
131 if is_array {
133 self.add_add_method(rust_trait, &rust_field_name, &field_name, &rust_type)?;
134 }
135
136 Ok(())
137 }
138
139 fn add_set_method(
140 &self,
141 rust_trait: &mut RustTrait,
142 rust_field_name: &str,
143 field_name: &str,
144 rust_type: &RustType,
145 is_array: bool,
146 ) -> CodegenResult<()> {
147 let method_name = format!("set_{rust_field_name}");
148
149 let parameter_type = if is_array {
150 RustType::Vec(Box::new(rust_type.clone()))
152 } else {
153 rust_type.clone()
154 };
155
156 let method = RustTraitMethod::new(method_name)
157 .with_doc(format!(
158 "Sets the {field_name} field and returns self for chaining."
159 ))
160 .with_parameter("value".to_string(), parameter_type)
161 .with_return_type(RustType::Custom("Self".to_string()))
162 .with_body(format!("self.{field_name} = value; self"))
163 .with_self_param(Some("self".to_string())); rust_trait.add_method(method);
166 Ok(())
167 }
168
169 fn add_add_method(
170 &self,
171 rust_trait: &mut RustTrait,
172 rust_field_name: &str,
173 field_name: &str,
174 rust_type: &RustType,
175 ) -> CodegenResult<()> {
176 let method_name = format!("add_{rust_field_name}");
177
178 let method = RustTraitMethod::new(method_name)
179 .with_doc(format!(
180 "Adds an item to the {field_name} field and returns self for chaining."
181 ))
182 .with_parameter("item".to_string(), rust_type.clone())
183 .with_return_type(RustType::Custom("Self".to_string()))
184 .with_body(format!("self.{field_name}.push(item); self"))
185 .with_self_param(Some("self".to_string())); rust_trait.add_method(method);
188 Ok(())
189 }
190
191 fn add_choice_type_mutator_methods(
192 &self,
193 _rust_trait: &mut RustTrait,
194 _structure_def: &StructureDefinition,
195 ) -> CodegenResult<()> {
196 Ok(())
198 }
199
200 fn add_constructor_method(
202 &self,
203 rust_trait: &mut RustTrait,
204 structure_def: &StructureDefinition,
205 ) -> CodegenResult<()> {
206 let struct_name = crate::naming::Naming::struct_name(structure_def);
207
208 let is_profile = crate::generators::type_registry::TypeRegistry::is_profile(structure_def);
210 let module = if is_profile { "profiles" } else { "resources" };
211 let snake_name = crate::naming::Naming::to_snake_case(&struct_name);
212 let struct_import = format!(
213 "{crate_name}::{module}::{snake_name}::{struct_name}",
214 crate_name = &self.crate_name
215 );
216 let trait_import = format!(
217 "{crate_name}::traits::{snake_name}::{struct_name}Mutators",
218 crate_name = &self.crate_name
219 );
220
221 let new_method = RustTraitMethod::new("new".to_string())
223 .with_doc(format!(
224 "Create a new {struct_name} with default/empty values.\n\nAll optional fields will be set to None and array fields will be empty vectors.\nSupports method chaining with set_xxx() and add_xxx() methods.\n\n# Example\n```rust\nuse {struct_import};\nuse {trait_import};\n\nlet resource = {struct_name}::new();\n// Can be used with method chaining:\n// resource.set_field(value).add_item(item);\n```"
225 ))
226 .with_return_type(RustType::Custom("Self".to_string()))
227 .with_self_param(None); rust_trait.add_method(new_method);
230
231 Ok(())
232 }
233
234 fn get_field_rust_type(
238 &self,
239 element: &ElementDefinition,
240 field_name: &str,
241 ) -> CodegenResult<RustType> {
242 let Some(element_type) = element.element_type.as_ref().and_then(|t| t.first()) else {
243 return Ok(RustType::String);
244 };
245
246 let Some(code) = &element_type.code else {
247 return Ok(RustType::String);
248 };
249
250 if code == "code" {
252 if field_name == "language" && element.path.ends_with(".language") {
254 return TypeUtilities::map_fhir_type_to_rust(
255 element_type,
256 field_name,
257 &element.path,
258 );
259 }
260 if let Some(binding) = &element.binding {
261 if binding.strength == "required" {
262 if let Some(value_set_url) = &binding.value_set {
263 if let Some(enum_name) =
266 self.extract_enum_name_from_value_set(value_set_url)
267 {
268 let resource_name = element.path.split('.').next().unwrap_or("");
271 if enum_name == resource_name {
272 return TypeUtilities::map_fhir_type_to_rust(
273 element_type,
274 field_name,
275 &element.path,
276 );
277 }
278 return Ok(RustType::Custom(enum_name));
279 }
280 }
281 }
282 }
283 }
284
285 TypeUtilities::map_fhir_type_to_rust(element_type, field_name, &element.path)
287 }
288
289 fn extract_enum_name_from_value_set(&self, url: &str) -> Option<String> {
292 let url_without_version = url.split('|').next().unwrap_or(url);
294
295 let value_set_name = url_without_version.split('/').next_back()?;
297
298 let name = value_set_name
301 .split(&['-', '.'][..])
302 .filter(|part| !part.is_empty())
303 .map(|part| {
304 let mut chars = part.chars();
305 match chars.next() {
306 None => String::new(),
307 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
308 }
309 })
310 .collect::<String>();
311
312 if name.chars().next().unwrap_or('0').is_ascii_digit() {
314 Some(format!("ValueSet{name}"))
315 } else {
316 Some(name)
317 }
318 }
319}