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.last().unwrap().to_string();
102 let rust_field_name = crate::naming::Naming::field_name(&field_name);
103
104 let _is_optional = element.min.unwrap_or(0) == 0;
105 let is_array = element.max.as_deref() == Some("*")
106 || element
107 .max
108 .as_deref()
109 .unwrap_or("1")
110 .parse::<i32>()
111 .unwrap_or(1)
112 > 1;
113
114 let rust_type = self.get_field_rust_type(element, &field_name)?;
116
117 self.add_set_method(
119 rust_trait,
120 &rust_field_name,
121 &field_name,
122 &rust_type,
123 is_array,
124 )?;
125
126 if is_array {
128 self.add_add_method(rust_trait, &rust_field_name, &field_name, &rust_type)?;
129 }
130
131 Ok(())
132 }
133
134 fn add_set_method(
135 &self,
136 rust_trait: &mut RustTrait,
137 rust_field_name: &str,
138 field_name: &str,
139 rust_type: &RustType,
140 is_array: bool,
141 ) -> CodegenResult<()> {
142 let method_name = format!("set_{rust_field_name}");
143
144 let parameter_type = if is_array {
145 RustType::Vec(Box::new(rust_type.clone()))
147 } else {
148 rust_type.clone()
149 };
150
151 let method = RustTraitMethod::new(method_name)
152 .with_doc(format!(
153 "Sets the {field_name} field and returns self for chaining."
154 ))
155 .with_parameter("value".to_string(), parameter_type)
156 .with_return_type(RustType::Custom("Self".to_string()))
157 .with_body(format!("self.{field_name} = value; self"))
158 .with_self_param(Some("self".to_string())); rust_trait.add_method(method);
161 Ok(())
162 }
163
164 fn add_add_method(
165 &self,
166 rust_trait: &mut RustTrait,
167 rust_field_name: &str,
168 field_name: &str,
169 rust_type: &RustType,
170 ) -> CodegenResult<()> {
171 let method_name = format!("add_{rust_field_name}");
172
173 let method = RustTraitMethod::new(method_name)
174 .with_doc(format!(
175 "Adds an item to the {field_name} field and returns self for chaining."
176 ))
177 .with_parameter("item".to_string(), rust_type.clone())
178 .with_return_type(RustType::Custom("Self".to_string()))
179 .with_body(format!("self.{field_name}.push(item); self"))
180 .with_self_param(Some("self".to_string())); rust_trait.add_method(method);
183 Ok(())
184 }
185
186 fn add_choice_type_mutator_methods(
187 &self,
188 _rust_trait: &mut RustTrait,
189 _structure_def: &StructureDefinition,
190 ) -> CodegenResult<()> {
191 Ok(())
193 }
194
195 fn add_constructor_method(
197 &self,
198 rust_trait: &mut RustTrait,
199 structure_def: &StructureDefinition,
200 ) -> CodegenResult<()> {
201 let struct_name = crate::naming::Naming::struct_name(structure_def);
202
203 let is_profile = crate::generators::type_registry::TypeRegistry::is_profile(structure_def);
205 let module = if is_profile { "profiles" } else { "resources" };
206 let snake_name = crate::naming::Naming::to_snake_case(&struct_name);
207 let struct_import = format!(
208 "{crate_name}::{module}::{snake_name}::{struct_name}",
209 crate_name = &self.crate_name
210 );
211 let trait_import = format!(
212 "{crate_name}::traits::{snake_name}::{struct_name}Mutators",
213 crate_name = &self.crate_name
214 );
215
216 let new_method = RustTraitMethod::new("new".to_string())
218 .with_doc(format!(
219 "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```"
220 ))
221 .with_return_type(RustType::Custom("Self".to_string()))
222 .with_self_param(None); rust_trait.add_method(new_method);
225
226 Ok(())
227 }
228
229 fn get_field_rust_type(
233 &self,
234 element: &ElementDefinition,
235 field_name: &str,
236 ) -> CodegenResult<RustType> {
237 let Some(element_type) = element.element_type.as_ref().and_then(|t| t.first()) else {
238 return Ok(RustType::String);
239 };
240
241 let Some(code) = &element_type.code else {
242 return Ok(RustType::String);
243 };
244
245 if code == "code" {
247 if let Some(binding) = &element.binding {
248 if binding.strength == "required" {
249 if let Some(value_set_url) = &binding.value_set {
250 if let Some(enum_name) =
253 self.extract_enum_name_from_value_set(value_set_url)
254 {
255 return Ok(RustType::Custom(enum_name));
256 }
257 }
258 }
259 }
260 }
261
262 TypeUtilities::map_fhir_type_to_rust(element_type, field_name, &element.path)
264 }
265
266 fn extract_enum_name_from_value_set(&self, url: &str) -> Option<String> {
269 let url_without_version = url.split('|').next().unwrap_or(url);
271
272 let value_set_name = url_without_version.split('/').next_back()?;
274
275 let name = value_set_name
278 .split(&['-', '.'][..])
279 .filter(|part| !part.is_empty())
280 .map(|part| {
281 let mut chars = part.chars();
282 match chars.next() {
283 None => String::new(),
284 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
285 }
286 })
287 .collect::<String>();
288
289 if name.chars().next().unwrap_or('0').is_ascii_digit() {
291 Some(format!("ValueSet{name}"))
292 } else {
293 Some(name)
294 }
295 }
296}