1use super::utils::GeneratorUtils;
6use crate::fhir_types::StructureDefinition;
7use crate::naming::Naming;
8use crate::rust_types::{RustTraitImpl, RustTraitImplMethod};
9use crate::CodegenResult;
10
11pub struct TraitImplGenerator;
13
14#[allow(dead_code)]
15impl TraitImplGenerator {
16 pub fn new() -> Self {
18 Self
19 }
20
21 fn extract_base_resource_type(base_definition: &str) -> Option<String> {
24 if base_definition.starts_with("http://hl7.org/fhir/StructureDefinition/") {
27 if let Some(last_segment) = base_definition.split('/').next_back() {
28 return Some(last_segment.to_string());
29 }
30 }
31 None
32 }
33
34 fn is_core_resource(base_definition: &str) -> bool {
37 matches!(
38 base_definition,
39 "http://hl7.org/fhir/StructureDefinition/Resource"
40 | "http://hl7.org/fhir/StructureDefinition/DomainResource"
41 )
42 }
43
44 fn resolve_to_core_resource_type(
47 base_resource_type: &str,
48 _base_definition_url: &str,
49 ) -> String {
50 match base_resource_type.to_lowercase().as_str() {
53 "vitalsigns" => "Observation".to_string(),
55 "bodyweight" | "bodyheight" | "bmi" | "bodytemp" | "heartrate" | "resprate"
57 | "oxygensat" => "Observation".to_string(),
58 _ => {
60 if GeneratorUtils::is_fhir_resource_type(base_resource_type) {
62 base_resource_type.to_string()
63 } else {
64 base_resource_type.to_string()
66 }
67 }
68 }
69 }
70
71 fn get_resource_type_for_struct(
74 struct_name: &str,
75 structure_def: &StructureDefinition,
76 ) -> String {
77 if let Some(base_def) = &structure_def.base_definition {
79 if Self::is_core_resource(base_def) {
81 return struct_name.to_string();
82 }
83
84 if let Some(base_resource_type) = Self::extract_base_resource_type(base_def) {
86 let core_resource_type =
88 Self::resolve_to_core_resource_type(&base_resource_type, base_def);
89
90 if GeneratorUtils::is_fhir_resource_type(&core_resource_type) {
92 return core_resource_type;
93 }
94 }
95 }
96
97 struct_name.to_string()
99 }
100
101 pub fn generate_trait_impls(
103 &self,
104 structure_def: &StructureDefinition,
105 ) -> CodegenResult<Vec<RustTraitImpl>> {
106 let mut trait_impls = Vec::new();
107
108 if structure_def.kind != "resource" {
110 return Ok(trait_impls);
111 }
112
113 let struct_name = Naming::struct_name(structure_def);
114
115 trait_impls.push(self.generate_resource_trait_impl(&struct_name, structure_def));
117 trait_impls.push(self.generate_resource_mutators_trait_impl(&struct_name, structure_def));
118 trait_impls.push(self.generate_resource_existence_trait_impl(&struct_name, structure_def));
119
120 if let Some(base_def) = &structure_def.base_definition {
122 if base_def.contains("DomainResource") {
123 trait_impls.push(self.generate_domain_resource_trait_impl(&struct_name));
124 trait_impls.push(self.generate_domain_resource_mutators_trait_impl(&struct_name));
125 trait_impls.push(self.generate_domain_resource_existence_trait_impl(&struct_name));
126 }
127 }
128
129 if struct_name != "Resource" {
132 let specific_trait_impl =
133 self.generate_specific_resource_trait_impl(&struct_name, structure_def);
134
135 if !specific_trait_impl.is_empty() {
137 trait_impls.push(specific_trait_impl);
138 }
139
140 let specific_mutators_trait_impl =
142 self.generate_specific_resource_mutators_trait_impl(&struct_name, structure_def);
143
144 if !specific_mutators_trait_impl.is_empty() {
145 trait_impls.push(specific_mutators_trait_impl);
146 }
147
148 let specific_existence_trait_impl =
150 self.generate_specific_resource_existence_trait_impl(&struct_name, structure_def);
151
152 if !specific_existence_trait_impl.is_empty() {
153 trait_impls.push(specific_existence_trait_impl);
154 }
155 }
156
157 Ok(trait_impls)
158 }
159
160 fn generate_resource_trait_impl(
162 &self,
163 struct_name: &str,
164 structure_def: &StructureDefinition,
165 ) -> RustTraitImpl {
166 let mut trait_impl = RustTraitImpl::new(
167 "crate::traits::resource::ResourceAccessors".to_string(),
168 struct_name.to_string(),
169 );
170
171 let (base_access, use_trait_methods) =
173 self.get_resource_base_access(struct_name, structure_def);
174
175 let id_method = RustTraitImplMethod::new("id".to_string())
177 .with_return_type("Option<String>".to_string())
178 .with_body(if use_trait_methods {
179 format!("{base_access}.id()")
180 } else {
181 format!("{base_access}.id.clone()")
182 });
183 trait_impl.add_method(id_method);
184
185 let meta_method = RustTraitImplMethod::new("meta".to_string())
187 .with_return_type("Option<crate::datatypes::meta::Meta>".to_string())
188 .with_body(if use_trait_methods {
189 format!("{base_access}.meta()")
190 } else {
191 format!("{base_access}.meta.clone()")
192 });
193 trait_impl.add_method(meta_method);
194
195 let implicit_rules_method = RustTraitImplMethod::new("implicit_rules".to_string())
197 .with_return_type("Option<String>".to_string())
198 .with_body(if use_trait_methods {
199 format!("{base_access}.implicit_rules()")
200 } else {
201 format!("{base_access}.implicit_rules.clone()")
202 });
203 trait_impl.add_method(implicit_rules_method);
204
205 let language_method = RustTraitImplMethod::new("language".to_string())
207 .with_return_type("Option<String>".to_string())
208 .with_body(if use_trait_methods {
209 format!("{base_access}.language()")
210 } else {
211 format!("{base_access}.language.clone()")
212 });
213 trait_impl.add_method(language_method);
214
215 trait_impl
216 }
217
218 fn get_resource_base_access(
220 &self,
221 struct_name: &str,
222 structure_def: &StructureDefinition,
223 ) -> (String, bool) {
224 if struct_name == "Resource" {
225 ("self".to_string(), false)
227 } else if struct_name == "DomainResource" {
228 ("self.base".to_string(), false)
230 } else if let Some(base_def) = &structure_def.base_definition {
231 if base_def.contains("DomainResource") {
232 ("self.base.base".to_string(), false)
234 } else if base_def.contains("Resource") && struct_name != "DomainResource" {
235 ("self.base".to_string(), false)
237 } else if base_def.starts_with("http://hl7.org/fhir/StructureDefinition/") {
238 ("self.base".to_string(), true)
240 } else {
241 ("self.base.base".to_string(), false)
243 }
244 } else {
245 ("self.base.base".to_string(), false)
247 }
248 }
249
250 fn generate_domain_resource_trait_impl(&self, struct_name: &str) -> RustTraitImpl {
252 let mut trait_impl = RustTraitImpl::new(
253 "crate::traits::domain_resource::DomainResourceAccessors".to_string(),
254 struct_name.to_string(),
255 );
256
257 let text_method = RustTraitImplMethod::new("text".to_string())
259 .with_return_type("Option<crate::datatypes::narrative::Narrative>".to_string())
260 .with_body("self.base.text.clone()".to_string());
261 trait_impl.add_method(text_method);
262
263 let contained_method = RustTraitImplMethod::new("contained".to_string())
265 .with_return_type("&[crate::resources::resource::Resource]".to_string())
266 .with_body("self.base.contained.as_deref().unwrap_or(&[])".to_string());
267 trait_impl.add_method(contained_method);
268
269 let extension_method = RustTraitImplMethod::new("extension".to_string())
271 .with_return_type("&[crate::datatypes::extension::Extension]".to_string())
272 .with_body("self.base.extension.as_deref().unwrap_or(&[])".to_string());
273 trait_impl.add_method(extension_method);
274
275 let modifier_extension_method = RustTraitImplMethod::new("modifier_extension".to_string())
277 .with_return_type("&[crate::datatypes::extension::Extension]".to_string())
278 .with_body("self.base.modifier_extension.as_deref().unwrap_or(&[])".to_string());
279 trait_impl.add_method(modifier_extension_method);
280
281 trait_impl
282 }
283
284 fn generate_resource_mutators_trait_impl(
286 &self,
287 struct_name: &str,
288 structure_def: &StructureDefinition,
289 ) -> RustTraitImpl {
290 let mut trait_impl = RustTraitImpl::new(
291 "crate::traits::resource::ResourceMutators".to_string(),
292 struct_name.to_string(),
293 );
294
295 let (base_access, use_trait_methods) =
297 self.get_resource_base_access(struct_name, structure_def);
298
299 let resource_access = if base_access == "self" {
304 String::new()
305 } else {
306 base_access
307 .strip_prefix("self.")
308 .unwrap_or(&base_access)
309 .to_string()
310 };
311
312 let new_method = RustTraitImplMethod::new("new".to_string())
314 .with_return_type("Self".to_string())
315 .with_body("Self::default()".to_string())
316 .with_self_param(None); trait_impl.add_method(new_method);
318
319 if use_trait_methods {
322 let set_id_method = RustTraitImplMethod::new("set_id".to_string())
324 .with_param(crate::rust_types::RustMethodParam::new(
325 "value".to_string(),
326 crate::rust_types::RustType::String,
327 ))
328 .with_return_type("Self".to_string())
329 .with_body("let mut resource = self.clone();\n resource.base = resource.base.set_id(value);\n resource".to_string())
330 .with_self_param(Some("self".to_string()));
331 trait_impl.add_method(set_id_method);
332
333 let set_meta_method = RustTraitImplMethod::new("set_meta".to_string())
335 .with_param(crate::rust_types::RustMethodParam::new(
336 "value".to_string(),
337 crate::rust_types::RustType::Custom("crate::datatypes::meta::Meta".to_string()),
338 ))
339 .with_return_type("Self".to_string())
340 .with_body("let mut resource = self.clone();\n resource.base = resource.base.set_meta(value);\n resource".to_string())
341 .with_self_param(Some("self".to_string()));
342 trait_impl.add_method(set_meta_method);
343
344 let set_implicit_rules_method = RustTraitImplMethod::new("set_implicit_rules".to_string())
346 .with_param(crate::rust_types::RustMethodParam::new(
347 "value".to_string(),
348 crate::rust_types::RustType::String,
349 ))
350 .with_return_type("Self".to_string())
351 .with_body("let mut resource = self.clone();\n resource.base = resource.base.set_implicit_rules(value);\n resource".to_string())
352 .with_self_param(Some("self".to_string()));
353 trait_impl.add_method(set_implicit_rules_method);
354
355 let set_language_method = RustTraitImplMethod::new("set_language".to_string())
357 .with_param(crate::rust_types::RustMethodParam::new(
358 "value".to_string(),
359 crate::rust_types::RustType::String,
360 ))
361 .with_return_type("Self".to_string())
362 .with_body("let mut resource = self.clone();\n resource.base = resource.base.set_language(value);\n resource".to_string())
363 .with_self_param(Some("self".to_string()));
364 trait_impl.add_method(set_language_method);
365 } else {
366 let set_id_method = RustTraitImplMethod::new("set_id".to_string())
368 .with_param(crate::rust_types::RustMethodParam::new(
369 "value".to_string(),
370 crate::rust_types::RustType::String,
371 ))
372 .with_return_type("Self".to_string())
373 .with_body(if resource_access.is_empty() {
374 "let mut resource = self.clone();\n resource.id = Some(value);\n resource".to_string()
375 } else {
376 format!(
377 "let mut resource = self.clone();\n resource.{resource_access}.id = Some(value);\n resource"
378 )
379 })
380 .with_self_param(Some("self".to_string())); trait_impl.add_method(set_id_method);
382
383 let set_meta_method = RustTraitImplMethod::new("set_meta".to_string())
385 .with_param(crate::rust_types::RustMethodParam::new(
386 "value".to_string(),
387 crate::rust_types::RustType::Custom("crate::datatypes::meta::Meta".to_string()),
388 ))
389 .with_return_type("Self".to_string())
390 .with_body(if resource_access.is_empty() {
391 "let mut resource = self.clone();\n resource.meta = Some(value);\n resource".to_string()
392 } else {
393 format!(
394 "let mut resource = self.clone();\n resource.{resource_access}.meta = Some(value);\n resource"
395 )
396 })
397 .with_self_param(Some("self".to_string()));
398 trait_impl.add_method(set_meta_method);
399
400 let set_implicit_rules_method = RustTraitImplMethod::new("set_implicit_rules".to_string())
402 .with_param(crate::rust_types::RustMethodParam::new(
403 "value".to_string(),
404 crate::rust_types::RustType::String,
405 ))
406 .with_return_type("Self".to_string())
407 .with_body(if resource_access.is_empty() {
408 "let mut resource = self.clone();\n resource.implicit_rules = Some(value);\n resource".to_string()
409 } else {
410 format!(
411 "let mut resource = self.clone();\n resource.{resource_access}.implicit_rules = Some(value);\n resource"
412 )
413 })
414 .with_self_param(Some("self".to_string()));
415 trait_impl.add_method(set_implicit_rules_method);
416
417 let set_language_method = RustTraitImplMethod::new("set_language".to_string())
419 .with_param(crate::rust_types::RustMethodParam::new(
420 "value".to_string(),
421 crate::rust_types::RustType::String,
422 ))
423 .with_return_type("Self".to_string())
424 .with_body(if resource_access.is_empty() {
425 "let mut resource = self.clone();\n resource.language = Some(value);\n resource".to_string()
426 } else {
427 format!(
428 "let mut resource = self.clone();\n resource.{resource_access}.language = Some(value);\n resource"
429 )
430 })
431 .with_self_param(Some("self".to_string()));
432 trait_impl.add_method(set_language_method);
433 }
434
435 trait_impl
436 }
437
438 fn generate_resource_existence_trait_impl(
440 &self,
441 struct_name: &str,
442 structure_def: &StructureDefinition,
443 ) -> RustTraitImpl {
444 let mut trait_impl = RustTraitImpl::new(
445 "crate::traits::resource::ResourceExistence".to_string(),
446 struct_name.to_string(),
447 );
448
449 let (base_access, use_trait_methods) =
451 self.get_resource_base_access(struct_name, structure_def);
452
453 let has_id_method = RustTraitImplMethod::new("has_id".to_string())
455 .with_return_type("bool".to_string())
456 .with_body(if use_trait_methods {
457 format!("{base_access}.has_id()")
458 } else {
459 format!("{base_access}.id.is_some()")
460 });
461 trait_impl.add_method(has_id_method);
462
463 let has_meta_method = RustTraitImplMethod::new("has_meta".to_string())
465 .with_return_type("bool".to_string())
466 .with_body(if use_trait_methods {
467 format!("{base_access}.has_meta()")
468 } else {
469 format!("{base_access}.meta.is_some()")
470 });
471 trait_impl.add_method(has_meta_method);
472
473 let has_implicit_rules_method = RustTraitImplMethod::new("has_implicit_rules".to_string())
475 .with_return_type("bool".to_string())
476 .with_body(if use_trait_methods {
477 format!("{base_access}.has_implicit_rules()")
478 } else {
479 format!("{base_access}.implicit_rules.is_some()")
480 });
481 trait_impl.add_method(has_implicit_rules_method);
482
483 let has_language_method = RustTraitImplMethod::new("has_language".to_string())
485 .with_return_type("bool".to_string())
486 .with_body(if use_trait_methods {
487 format!("{base_access}.has_language()")
488 } else {
489 format!("{base_access}.language.is_some()")
490 });
491 trait_impl.add_method(has_language_method);
492
493 trait_impl
494 }
495
496 fn generate_domain_resource_mutators_trait_impl(&self, struct_name: &str) -> RustTraitImpl {
498 let mut trait_impl = RustTraitImpl::new(
499 "crate::traits::domain_resource::DomainResourceMutators".to_string(),
500 struct_name.to_string(),
501 );
502
503 let new_method = RustTraitImplMethod::new("new".to_string())
505 .with_return_type("Self".to_string())
506 .with_body("Self::default()".to_string())
507 .with_self_param(None); trait_impl.add_method(new_method);
509
510 let set_text_method = RustTraitImplMethod::new("set_text".to_string())
512 .with_param(crate::rust_types::RustMethodParam::new(
513 "value".to_string(),
514 crate::rust_types::RustType::Custom("crate::datatypes::narrative::Narrative".to_string()),
515 ))
516 .with_return_type("Self".to_string())
517 .with_body("let mut resource = self.clone();\n resource.base.text = Some(value);\n resource".to_string())
518 .with_self_param(Some("self".to_string()));
519 trait_impl.add_method(set_text_method);
520
521 let set_contained_method = RustTraitImplMethod::new("set_contained".to_string())
523 .with_param(crate::rust_types::RustMethodParam::new(
524 "value".to_string(),
525 crate::rust_types::RustType::Vec(Box::new(crate::rust_types::RustType::Custom("crate::resources::resource::Resource".to_string()))),
526 ))
527 .with_return_type("Self".to_string())
528 .with_body("let mut resource = self.clone();\n resource.base.contained = Some(value);\n resource".to_string())
529 .with_self_param(Some("self".to_string()));
530 trait_impl.add_method(set_contained_method);
531
532 let add_contained_method = RustTraitImplMethod::new("add_contained".to_string())
534 .with_param(crate::rust_types::RustMethodParam::new(
535 "item".to_string(),
536 crate::rust_types::RustType::Custom("crate::resources::resource::Resource".to_string()),
537 ))
538 .with_return_type("Self".to_string())
539 .with_body("let mut resource = self.clone();\n resource.base.contained.get_or_insert_with(Vec::new).push(item);\n resource".to_string())
540 .with_self_param(Some("self".to_string()));
541 trait_impl.add_method(add_contained_method);
542
543 let set_extension_method = RustTraitImplMethod::new("set_extension".to_string())
545 .with_param(crate::rust_types::RustMethodParam::new(
546 "value".to_string(),
547 crate::rust_types::RustType::Vec(Box::new(crate::rust_types::RustType::Custom("crate::datatypes::extension::Extension".to_string()))),
548 ))
549 .with_return_type("Self".to_string())
550 .with_body("let mut resource = self.clone();\n resource.base.extension = Some(value);\n resource".to_string())
551 .with_self_param(Some("self".to_string()));
552 trait_impl.add_method(set_extension_method);
553
554 let add_extension_method = RustTraitImplMethod::new("add_extension".to_string())
556 .with_param(crate::rust_types::RustMethodParam::new(
557 "item".to_string(),
558 crate::rust_types::RustType::Custom("crate::datatypes::extension::Extension".to_string()),
559 ))
560 .with_return_type("Self".to_string())
561 .with_body("let mut resource = self.clone();\n resource.base.extension.get_or_insert_with(Vec::new).push(item);\n resource".to_string())
562 .with_self_param(Some("self".to_string()));
563 trait_impl.add_method(add_extension_method);
564
565 let set_modifier_extension_method = RustTraitImplMethod::new("set_modifier_extension".to_string())
567 .with_param(crate::rust_types::RustMethodParam::new(
568 "value".to_string(),
569 crate::rust_types::RustType::Vec(Box::new(crate::rust_types::RustType::Custom("crate::datatypes::extension::Extension".to_string()))),
570 ))
571 .with_return_type("Self".to_string())
572 .with_body("let mut resource = self.clone();\n resource.base.modifier_extension = Some(value);\n resource".to_string())
573 .with_self_param(Some("self".to_string()));
574 trait_impl.add_method(set_modifier_extension_method);
575
576 let add_modifier_extension_method =
578 RustTraitImplMethod::new("add_modifier_extension".to_string())
579 .with_param(crate::rust_types::RustMethodParam::new(
580 "item".to_string(),
581 crate::rust_types::RustType::Custom("crate::datatypes::extension::Extension".to_string()),
582 ))
583 .with_return_type("Self".to_string())
584 .with_body("let mut resource = self.clone();\n resource.base.modifier_extension.get_or_insert_with(Vec::new).push(item);\n resource".to_string())
585 .with_self_param(Some("self".to_string()));
586 trait_impl.add_method(add_modifier_extension_method);
587
588 trait_impl
589 }
590
591 fn generate_domain_resource_existence_trait_impl(&self, struct_name: &str) -> RustTraitImpl {
593 let mut trait_impl = RustTraitImpl::new(
594 "crate::traits::domain_resource::DomainResourceExistence".to_string(),
595 struct_name.to_string(),
596 );
597
598 let has_id_method = RustTraitImplMethod::new("has_id".to_string())
601 .with_return_type("bool".to_string())
602 .with_body("self.base.base.id.is_some()".to_string());
603 trait_impl.add_method(has_id_method);
604
605 let has_meta_method = RustTraitImplMethod::new("has_meta".to_string())
607 .with_return_type("bool".to_string())
608 .with_body("self.base.base.meta.is_some()".to_string());
609 trait_impl.add_method(has_meta_method);
610
611 let has_implicit_rules_method = RustTraitImplMethod::new("has_implicit_rules".to_string())
613 .with_return_type("bool".to_string())
614 .with_body("self.base.base.implicit_rules.is_some()".to_string());
615 trait_impl.add_method(has_implicit_rules_method);
616
617 let has_language_method = RustTraitImplMethod::new("has_language".to_string())
619 .with_return_type("bool".to_string())
620 .with_body("self.base.base.language.is_some()".to_string());
621 trait_impl.add_method(has_language_method);
622
623 let has_text_method = RustTraitImplMethod::new("has_text".to_string())
625 .with_return_type("bool".to_string())
626 .with_body("self.base.text.is_some()".to_string());
627 trait_impl.add_method(has_text_method);
628
629 let has_contained_method = RustTraitImplMethod::new("has_contained".to_string())
631 .with_return_type("bool".to_string())
632 .with_body("self.base.contained.as_ref().is_some_and(|c| !c.is_empty())".to_string());
633 trait_impl.add_method(has_contained_method);
634
635 let has_extension_method = RustTraitImplMethod::new("has_extension".to_string())
637 .with_return_type("bool".to_string())
638 .with_body("self.base.extension.as_ref().is_some_and(|e| !e.is_empty())".to_string());
639 trait_impl.add_method(has_extension_method);
640
641 let has_modifier_extension_method =
643 RustTraitImplMethod::new("has_modifier_extension".to_string())
644 .with_return_type("bool".to_string())
645 .with_body(
646 "self.base.modifier_extension.as_ref().is_some_and(|m| !m.is_empty())"
647 .to_string(),
648 );
649 trait_impl.add_method(has_modifier_extension_method);
650
651 trait_impl
652 }
653
654 fn generate_specific_resource_trait_impl(
656 &self,
657 struct_name: &str,
658 structure_def: &StructureDefinition,
659 ) -> RustTraitImpl {
660 let trait_name = format!(
661 "crate::traits::{}::{}Accessors",
662 crate::naming::Naming::to_snake_case(struct_name),
663 struct_name
664 );
665
666 let mut trait_impl = RustTraitImpl::new(trait_name, struct_name.to_string());
667
668 let elements = if let Some(differential) = &structure_def.differential {
670 &differential.element
671 } else if let Some(snapshot) = &structure_def.snapshot {
672 &snapshot.element
673 } else {
674 return trait_impl; };
676
677 for element in elements {
679 if self.should_generate_accessor_impl(element, structure_def) {
680 if let Some(method) = self.generate_field_accessor_method(element) {
681 trait_impl.add_method(method);
682 }
683 }
684 }
685
686 trait_impl
687 }
688
689 fn generate_specific_resource_mutators_trait_impl(
691 &self,
692 struct_name: &str,
693 structure_def: &StructureDefinition,
694 ) -> RustTraitImpl {
695 let trait_name = format!(
696 "crate::traits::{}::{}Mutators",
697 crate::naming::Naming::to_snake_case(struct_name),
698 struct_name
699 );
700
701 let mut trait_impl = RustTraitImpl::new(trait_name, struct_name.to_string());
702
703 let new_method = RustTraitImplMethod::new("new".to_string())
705 .with_return_type("Self".to_string())
706 .with_body("Self::default()".to_string())
707 .with_self_param(None); trait_impl.add_method(new_method);
709
710 let elements = if let Some(differential) = &structure_def.differential {
712 &differential.element
713 } else if let Some(snapshot) = &structure_def.snapshot {
714 &snapshot.element
715 } else {
716 return trait_impl; };
718
719 for element in elements {
721 if self.should_generate_accessor_impl(element, structure_def) {
722 if let Some(methods) = self.generate_field_mutator_methods(element) {
723 for method in methods {
724 trait_impl.add_method(method);
725 }
726 }
727 }
728 }
729
730 trait_impl
731 }
732
733 fn generate_specific_resource_existence_trait_impl(
735 &self,
736 struct_name: &str,
737 structure_def: &StructureDefinition,
738 ) -> RustTraitImpl {
739 let trait_name = format!(
740 "crate::traits::{}::{}Existence",
741 crate::naming::Naming::to_snake_case(struct_name),
742 struct_name
743 );
744
745 let mut trait_impl = RustTraitImpl::new(trait_name, struct_name.to_string());
746
747 let is_profile = crate::generators::type_registry::TypeRegistry::is_profile(structure_def);
749
750 let extends_domain_resource = structure_def
752 .base_definition
753 .as_ref()
754 .map(|base| base.ends_with("/DomainResource"))
755 .unwrap_or(false);
756
757 let extends_resource_directly = structure_def
759 .base_definition
760 .as_ref()
761 .map(|base| base.ends_with("/Resource") && !base.ends_with("/DomainResource"))
762 .unwrap_or(false);
763
764 if !is_profile {
767 let (id_access, meta_access, implicit_rules_access, language_access) =
769 if extends_resource_directly {
770 (
772 "self.base.id.is_some()".to_string(),
773 "self.base.meta.is_some()".to_string(),
774 "self.base.implicit_rules.is_some()".to_string(),
775 "self.base.language.is_some()".to_string(),
776 )
777 } else {
778 (
780 "self.base.base.id.is_some()".to_string(),
781 "self.base.base.meta.is_some()".to_string(),
782 "self.base.base.implicit_rules.is_some()".to_string(),
783 "self.base.base.language.is_some()".to_string(),
784 )
785 };
786
787 let has_id_method = RustTraitImplMethod::new("has_id".to_string())
790 .with_return_type("bool".to_string())
791 .with_body(id_access);
792 trait_impl.add_method(has_id_method);
793
794 let has_meta_method = RustTraitImplMethod::new("has_meta".to_string())
796 .with_return_type("bool".to_string())
797 .with_body(meta_access);
798 trait_impl.add_method(has_meta_method);
799
800 let has_implicit_rules_method =
802 RustTraitImplMethod::new("has_implicit_rules".to_string())
803 .with_return_type("bool".to_string())
804 .with_body(implicit_rules_access);
805 trait_impl.add_method(has_implicit_rules_method);
806
807 let has_language_method = RustTraitImplMethod::new("has_language".to_string())
809 .with_return_type("bool".to_string())
810 .with_body(language_access);
811 trait_impl.add_method(has_language_method);
812
813 if extends_domain_resource {
816 let has_text_method = RustTraitImplMethod::new("has_text".to_string())
819 .with_return_type("bool".to_string())
820 .with_body("self.base.text.is_some()".to_string());
821 trait_impl.add_method(has_text_method);
822
823 let has_contained_method = RustTraitImplMethod::new("has_contained".to_string())
825 .with_return_type("bool".to_string())
826 .with_body(
827 "self.base.contained.as_ref().is_some_and(|c| !c.is_empty())".to_string(),
828 );
829 trait_impl.add_method(has_contained_method);
830
831 let has_extension_method = RustTraitImplMethod::new("has_extension".to_string())
833 .with_return_type("bool".to_string())
834 .with_body(
835 "self.base.extension.as_ref().is_some_and(|e| !e.is_empty())".to_string(),
836 );
837 trait_impl.add_method(has_extension_method);
838
839 let has_modifier_extension_method =
841 RustTraitImplMethod::new("has_modifier_extension".to_string())
842 .with_return_type("bool".to_string())
843 .with_body(
844 "self.base.modifier_extension.as_ref().is_some_and(|m| !m.is_empty())"
845 .to_string(),
846 );
847 trait_impl.add_method(has_modifier_extension_method);
848 }
849 }
850
851 let elements = if let Some(differential) = &structure_def.differential {
853 &differential.element
854 } else if let Some(snapshot) = &structure_def.snapshot {
855 &snapshot.element
856 } else {
857 return trait_impl; };
859
860 let mut choice_fields = std::collections::HashSet::new();
862 for element in elements {
863 let path_parts: Vec<&str> = element.path.split('.').collect();
864 if path_parts.len() == 2 && path_parts[0] == structure_def.name {
865 let field_name = path_parts[1];
866 if field_name.ends_with("[x]") {
867 choice_fields.insert(field_name.trim_end_matches("[x]").to_string());
868 }
869 }
870 }
871
872 for choice_field in &choice_fields {
874 let choice_path = format!("{}.{}[x]", structure_def.name, choice_field);
876 if let Some(choice_element) = elements.iter().find(|e| e.path == choice_path) {
877 if let Some(method) =
878 self.generate_choice_type_existence_method(choice_field, choice_element)
879 {
880 trait_impl.add_method(method);
881 }
882 }
883 }
884
885 for element in elements {
887 let path_parts: Vec<&str> = element.path.split('.').collect();
888 if path_parts.len() == 2 && path_parts[0] == structure_def.name {
889 let field_name = path_parts[1];
890 if !field_name.ends_with("[x]")
892 && self.should_generate_accessor_impl(element, structure_def)
893 {
894 if let Some(method) = self.generate_field_existence_method(element) {
895 trait_impl.add_method(method);
896 }
897 }
898 }
899 }
900
901 trait_impl
902 }
903
904 fn should_generate_accessor_impl(
907 &self,
908 element: &crate::fhir_types::ElementDefinition,
909 structure_def: &StructureDefinition,
910 ) -> bool {
911 let field_path = &element.path;
912 let base_name = &structure_def.name;
913
914 if !field_path.starts_with(base_name) {
916 return false;
917 }
918
919 let path_parts: Vec<&str> = field_path.split('.').collect();
922 if path_parts.len() != 2 {
923 return false;
924 }
925
926 if path_parts[0] != base_name {
928 return false;
929 }
930
931 let field_name = path_parts[1];
933 !field_name.ends_with("[x]")
934 }
935
936 fn generate_field_accessor_method(
938 &self,
939 element: &crate::fhir_types::ElementDefinition,
940 ) -> Option<RustTraitImplMethod> {
941 use crate::config::CodegenConfig;
942 use crate::type_mapper::TypeMapper;
943 use crate::value_sets::ValueSetManager;
944
945 let path_parts: Vec<&str> = element.path.split('.').collect();
946 let field_name = path_parts.last()?.to_string();
947 let rust_field_name = crate::naming::Naming::field_name(&field_name);
948
949 let is_array = element.max.as_deref() == Some("*")
950 || element
951 .max
952 .as_deref()
953 .unwrap_or("1")
954 .parse::<i32>()
955 .unwrap_or(1)
956 > 1;
957
958 let is_optional = element.min.unwrap_or(0) == 0;
960
961 let config = CodegenConfig::default();
963 let mut value_set_manager = ValueSetManager::new();
964 let mut type_mapper = TypeMapper::new(&config, &mut value_set_manager);
965
966 let fhir_types = element.element_type.as_ref()?;
968
969 let rust_type = if self.is_backbone_element(fhir_types) {
971 self.get_nested_type_for_backbone_element(element, is_array)
972 } else {
973 type_mapper.map_fhir_type_with_binding(fhir_types, element.binding.as_ref(), is_array)
975 };
976
977 let (return_type, body) = if is_array {
979 let inner_type = match &rust_type {
981 crate::rust_types::RustType::Vec(inner) => inner.to_string(),
982 crate::rust_types::RustType::Option(inner) => {
983 if let crate::rust_types::RustType::Vec(vec_inner) = inner.as_ref() {
984 vec_inner.to_string()
985 } else {
986 inner.to_string()
987 }
988 }
989 _ => rust_type.to_string(),
990 };
991
992 let return_type = format!("&[{inner_type}]");
993 let body = if is_optional {
994 format!("self.{rust_field_name}.as_deref().unwrap_or(&[])")
996 } else {
997 format!("&self.{rust_field_name}")
999 };
1000 (return_type, body)
1001 } else {
1002 if is_optional {
1004 let inner_type = match &rust_type {
1006 crate::rust_types::RustType::Option(inner) => inner.to_string(),
1007 _ => rust_type.to_string(),
1008 };
1009 let return_type = format!("Option<{inner_type}>");
1010
1011 let body = if self.is_copy_type(&rust_type) {
1013 format!("self.{rust_field_name}")
1014 } else {
1015 format!("self.{rust_field_name}.clone()")
1016 };
1017
1018 (return_type, body)
1019 } else {
1020 let return_type = match &rust_type {
1022 crate::rust_types::RustType::Option(inner) => inner.to_string(),
1023 _ => rust_type.to_string(),
1024 };
1025
1026 let body = if self.is_copy_type(&rust_type) {
1028 format!("self.{rust_field_name}")
1029 } else {
1030 format!("self.{rust_field_name}.clone()")
1031 };
1032
1033 (return_type, body)
1034 }
1035 };
1036
1037 Some(
1038 RustTraitImplMethod::new(rust_field_name)
1039 .with_return_type(return_type)
1040 .with_body(body),
1041 )
1042 }
1043
1044 fn generate_field_mutator_methods(
1046 &self,
1047 element: &crate::fhir_types::ElementDefinition,
1048 ) -> Option<Vec<RustTraitImplMethod>> {
1049 let path_parts: Vec<&str> = element.path.split('.').collect();
1050 let field_name = path_parts.last()?.to_string();
1051 let rust_field_name = crate::naming::Naming::field_name(&field_name);
1052
1053 let is_array = element.max.as_deref() == Some("*")
1054 || element
1055 .max
1056 .as_deref()
1057 .unwrap_or("1")
1058 .parse::<i32>()
1059 .unwrap_or(1)
1060 > 1;
1061
1062 let is_optional = element.min.unwrap_or(0) == 0;
1064
1065 let rust_type = self.get_field_rust_type(element, &field_name).ok()?;
1067
1068 let mut methods = Vec::new();
1069
1070 if is_array {
1072 let inner_type = match &rust_type {
1074 crate::rust_types::RustType::Vec(inner) => inner.to_string(),
1075 crate::rust_types::RustType::Option(inner) => {
1076 if let crate::rust_types::RustType::Vec(vec_inner) = inner.as_ref() {
1077 vec_inner.to_string()
1078 } else {
1079 inner.to_string()
1080 }
1081 }
1082 _ => rust_type.to_string(),
1083 };
1084
1085 let set_method_name = format!("set_{rust_field_name}");
1087 let set_body = if is_optional {
1088 format!(
1089 "let mut resource = self.clone();\n resource.{rust_field_name} = Some(value);\n resource"
1090 )
1091 } else {
1092 format!(
1093 "let mut resource = self.clone();\n resource.{rust_field_name} = value;\n resource"
1094 )
1095 };
1096
1097 methods.push(
1098 RustTraitImplMethod::new(set_method_name)
1099 .with_param(crate::rust_types::RustMethodParam::new(
1100 "value".to_string(),
1101 crate::rust_types::RustType::Vec(Box::new(
1102 crate::rust_types::RustType::Custom(inner_type.clone()),
1103 )),
1104 ))
1105 .with_return_type("Self".to_string())
1106 .with_body(set_body)
1107 .with_self_param(Some("self".to_string())),
1108 );
1109
1110 let add_method_name = format!("add_{rust_field_name}");
1112 let add_body = if is_optional {
1113 format!(
1114 "let mut resource = self.clone();\n resource.{rust_field_name}.get_or_insert_with(Vec::new).push(item);\n resource"
1115 )
1116 } else {
1117 format!(
1118 "let mut resource = self.clone();\n resource.{rust_field_name}.push(item);\n resource"
1119 )
1120 };
1121
1122 methods.push(
1123 RustTraitImplMethod::new(add_method_name)
1124 .with_param(crate::rust_types::RustMethodParam::new(
1125 "item".to_string(),
1126 crate::rust_types::RustType::Custom(inner_type),
1127 ))
1128 .with_return_type("Self".to_string())
1129 .with_body(add_body)
1130 .with_self_param(Some("self".to_string())),
1131 );
1132 } else {
1133 let method_name = format!("set_{rust_field_name}");
1135 let inner_type = match &rust_type {
1136 crate::rust_types::RustType::Option(inner) => inner.to_string(),
1137 _ => rust_type.to_string(),
1138 };
1139
1140 let body = if is_optional {
1142 format!(
1144 "let mut resource = self.clone();\n resource.{rust_field_name} = Some(value);\n resource"
1145 )
1146 } else {
1147 format!(
1149 "let mut resource = self.clone();\n resource.{rust_field_name} = value;\n resource"
1150 )
1151 };
1152
1153 methods.push(
1154 RustTraitImplMethod::new(method_name)
1155 .with_param(crate::rust_types::RustMethodParam::new(
1156 "value".to_string(),
1157 crate::rust_types::RustType::Custom(inner_type),
1158 ))
1159 .with_return_type("Self".to_string())
1160 .with_body(body)
1161 .with_self_param(Some("self".to_string())),
1162 );
1163 }
1164
1165 Some(methods)
1166 }
1167
1168 fn generate_field_existence_method(
1170 &self,
1171 element: &crate::fhir_types::ElementDefinition,
1172 ) -> Option<RustTraitImplMethod> {
1173 let path_parts: Vec<&str> = element.path.split('.').collect();
1174 let field_name = path_parts.last()?.to_string();
1175 let rust_field_name = crate::naming::Naming::field_name(&field_name);
1176
1177 let is_array = element.max.as_deref() == Some("*")
1178 || element
1179 .max
1180 .as_deref()
1181 .unwrap_or("1")
1182 .parse::<i32>()
1183 .unwrap_or(1)
1184 > 1;
1185
1186 let is_optional = element.min.unwrap_or(0) == 0;
1188
1189 let method_name = format!("has_{rust_field_name}");
1190
1191 let body = if is_array {
1192 if is_optional {
1193 format!("self.{rust_field_name}.as_ref().is_some_and(|v| !v.is_empty())")
1195 } else {
1196 format!("!self.{rust_field_name}.is_empty()")
1198 }
1199 } else if is_optional {
1200 format!("self.{rust_field_name}.is_some()")
1202 } else {
1203 "true".to_string()
1205 };
1206
1207 Some(
1208 RustTraitImplMethod::new(method_name)
1209 .with_return_type("bool".to_string())
1210 .with_body(body),
1211 )
1212 }
1213
1214 fn generate_choice_type_existence_method(
1218 &self,
1219 choice_field: &str,
1220 choice_element: &crate::fhir_types::ElementDefinition,
1221 ) -> Option<RustTraitImplMethod> {
1222 let types = choice_element.element_type.as_ref()?;
1224
1225 if types.is_empty() {
1226 return None;
1227 }
1228
1229 let is_optional = choice_element.min.unwrap_or(0) == 0;
1231
1232 let mut variants = Vec::new();
1236
1237 for type_def in types {
1238 if let Some(type_code) = &type_def.code {
1239 let type_suffix = Naming::type_suffix(type_code);
1242 let field_name = format!("{choice_field}_{type_suffix}");
1245 let rust_field_name = Naming::field_name(&field_name);
1246 variants.push(rust_field_name);
1247 }
1248 }
1249
1250 if variants.is_empty() {
1251 return None;
1252 }
1253
1254 let method_name = format!("has_{}", Naming::to_snake_case(choice_field));
1255
1256 let body = if is_optional {
1258 variants
1260 .iter()
1261 .map(|v| format!("self.{v}.is_some()"))
1262 .collect::<Vec<_>>()
1263 .join(" || ")
1264 } else {
1265 "true".to_string()
1271 };
1272
1273 Some(
1274 RustTraitImplMethod::new(method_name)
1275 .with_return_type("bool".to_string())
1276 .with_body(body),
1277 )
1278 }
1279 fn get_inner_type_for_slice(&self, rust_type: &crate::rust_types::RustType) -> String {
1281 match rust_type {
1282 crate::rust_types::RustType::Vec(inner) => inner.to_string(),
1283 crate::rust_types::RustType::Option(inner) => {
1284 if let crate::rust_types::RustType::Vec(vec_inner) = inner.as_ref() {
1285 vec_inner.to_string()
1286 } else {
1287 inner.to_string()
1288 }
1289 }
1290 _ => rust_type.to_string(),
1291 }
1292 }
1293
1294 fn get_type_for_option(&self, rust_type: &crate::rust_types::RustType) -> String {
1296 match rust_type {
1297 crate::rust_types::RustType::Option(inner) => inner.to_string(),
1298 _ => rust_type.to_string(),
1299 }
1300 }
1301
1302 fn is_copy_type(&self, rust_type: &crate::rust_types::RustType) -> bool {
1305 match rust_type {
1306 crate::rust_types::RustType::Boolean
1308 | crate::rust_types::RustType::Integer
1309 | crate::rust_types::RustType::Float => true,
1310
1311 crate::rust_types::RustType::Option(inner) => self.is_copy_type(inner),
1313
1314 crate::rust_types::RustType::Custom(type_name) => {
1316 self.is_copy_primitive_type(type_name)
1317 }
1318
1319 _ => false,
1321 }
1322 }
1323
1324 fn is_copy_primitive_type(&self, type_name: &str) -> bool {
1327 matches!(
1328 type_name,
1329 "BooleanType"
1330 | "IntegerType"
1331 | "UnsignedIntType"
1332 | "PositiveIntType"
1333 | "DecimalType"
1334 | "Integer64Type"
1335 )
1336 }
1337
1338 fn is_enum_type(&self, rust_type: &crate::rust_types::RustType) -> bool {
1340 match rust_type {
1341 crate::rust_types::RustType::Custom(type_name) => self.is_enum_type_name(type_name),
1342 _ => false,
1343 }
1344 }
1345
1346 fn is_enum_type_name(&self, type_name: &str) -> bool {
1348 type_name.ends_with("Status")
1350 || type_name.ends_with("Kind")
1351 || type_name.ends_with("Code")
1352 || type_name.ends_with("Codes")
1353 || type_name.ends_with("Priority")
1354 || type_name.ends_with("Intent")
1355 || matches!(
1356 type_name,
1357 "PublicationStatus"
1358 | "CapabilityStatementKind"
1359 | "CodeSearchSupport"
1360 | "FmStatus"
1361 | "ReportStatusCodes"
1362 | "ReportResultCodes"
1363 | "VerificationresultStatus"
1364 | "TaskStatus"
1365 | "TaskIntent"
1366 | "RequestPriority"
1367 | "SupplydeliveryStatus"
1368 | "SupplyrequestStatus"
1369 )
1370 }
1371
1372 fn determine_method_return_type(
1374 &self,
1375 element: &crate::fhir_types::ElementDefinition,
1376 ) -> String {
1377 let is_optional = element.min.unwrap_or(0) == 0;
1381
1382 let is_array = element
1384 .max
1385 .as_ref()
1386 .is_some_and(|max| max == "*" || max.parse::<u32>().unwrap_or(1) > 1);
1387
1388 let base_type = if let Some(element_types) = &element.element_type {
1390 if let Some(first_type) = element_types.first() {
1391 if let Some(code) = &first_type.code {
1392 match code.as_str() {
1393 "string" | "code" | "id" | "markdown" | "uri" | "url" | "canonical"
1394 | "dateTime" | "date" | "time" | "instant" | "base64Binary" | "oid"
1395 | "uuid" => "String".to_string(),
1396 "boolean" => "bool".to_string(),
1397 "integer" | "positiveInt" | "unsignedInt" => "i32".to_string(),
1398 "decimal" => "f64".to_string(),
1399 "Reference" => "crate::datatypes::reference::Reference".to_string(),
1400 "Identifier" => "crate::datatypes::identifier::Identifier".to_string(),
1401 "CodeableConcept" => {
1402 "crate::datatypes::codeable_concept::CodeableConcept".to_string()
1403 }
1404 "Coding" => "crate::datatypes::coding::Coding".to_string(),
1405 "Address" => "crate::datatypes::address::Address".to_string(),
1406 "HumanName" => "crate::datatypes::human_name::HumanName".to_string(),
1407 "ContactPoint" => {
1408 "crate::datatypes::contact_point::ContactPoint".to_string()
1409 }
1410 "Attachment" => "crate::datatypes::attachment::Attachment".to_string(),
1411 "Annotation" => "crate::datatypes::annotation::Annotation".to_string(),
1412 "BackboneElement" => {
1413 "crate::datatypes::backbone_element::BackboneElement".to_string()
1414 }
1415 _ => "String".to_string(), }
1417 } else {
1418 "String".to_string()
1419 }
1420 } else {
1421 "String".to_string()
1422 }
1423 } else {
1424 "String".to_string()
1425 };
1426
1427 if is_array {
1429 if is_optional {
1430 format!("Option<Vec<{base_type}>>")
1431 } else {
1432 format!("Vec<{base_type}>")
1433 }
1434 } else if is_optional {
1435 format!("Option<{base_type}>")
1436 } else {
1437 base_type
1438 }
1439 }
1440
1441 fn generate_method_body(
1443 &self,
1444 field_name: &str,
1445 element: &crate::fhir_types::ElementDefinition,
1446 ) -> String {
1447 let rust_field_name = if field_name == "type" {
1449 "type_".to_string()
1450 } else {
1451 crate::naming::Naming::field_name(field_name)
1452 };
1453
1454 let field_access = format!("self.{rust_field_name}");
1455
1456 let is_optional = element.min.unwrap_or(0) == 0;
1458 let is_array = element
1459 .max
1460 .as_ref()
1461 .is_some_and(|max| max == "*" || max.parse::<u32>().unwrap_or(1) > 1);
1462
1463 if is_array {
1464 format!("{field_access}.clone()")
1466 } else if let Some(type_def) = element
1467 .element_type
1468 .as_ref()
1469 .and_then(|types| types.first())
1470 {
1471 if let Some(code) = &type_def.code {
1472 match code.as_str() {
1473 "string" | "code" | "id" | "markdown" | "uri" | "url" | "canonical"
1474 | "dateTime" | "date" | "time" | "instant" | "base64Binary" | "oid"
1475 | "uuid" => {
1476 if is_optional {
1477 format!("{field_access}.as_ref().map(|s| s.to_string())")
1478 } else {
1479 format!("{field_access}.to_string()")
1480 }
1481 }
1482 "boolean" => {
1483 if is_optional {
1484 format!("{field_access}.map(|b| b.into())")
1485 } else {
1486 format!("{field_access}.into()")
1487 }
1488 }
1489 "integer" | "positiveInt" | "unsignedInt" => {
1490 if is_optional {
1491 format!("{field_access}.map(|i| i.into())")
1492 } else {
1493 format!("{field_access}.into()")
1494 }
1495 }
1496 "decimal" => {
1497 if is_optional {
1498 format!("{field_access}.map(|d| d.into())")
1499 } else {
1500 format!("{field_access}.into()")
1501 }
1502 }
1503 "CodeableConcept" | "Reference" | "Identifier" | "Coding" | "Address"
1504 | "HumanName" | "ContactPoint" | "Attachment" | "Annotation"
1505 | "BackboneElement" => {
1506 format!("{field_access}.clone()")
1508 }
1509 _ => {
1510 if is_optional {
1512 format!("{field_access}.as_ref().map(|v| format!(\"{{:?}}\", v))")
1513 } else {
1514 format!("format!(\"{{:?}}\", {field_access})")
1515 }
1516 }
1517 }
1518 } else {
1519 format!("{field_access}.clone()")
1520 }
1521 } else {
1522 format!("{field_access}.clone()")
1523 }
1524 }
1525
1526 fn is_backbone_element(&self, element_types: &[crate::fhir_types::ElementType]) -> bool {
1528 element_types
1529 .iter()
1530 .any(|et| et.code.as_deref() == Some("BackboneElement"))
1531 }
1532
1533 fn get_nested_type_for_backbone_element(
1535 &self,
1536 element: &crate::fhir_types::ElementDefinition,
1537 is_array: bool,
1538 ) -> crate::rust_types::RustType {
1539 let path_parts: Vec<&str> = element.path.split('.').collect();
1540
1541 if path_parts.len() == 2 {
1542 let resource_name = path_parts[0];
1543 let field_name = path_parts[1];
1544
1545 let field_name_pascal = crate::naming::Naming::to_pascal_case(field_name);
1547 let nested_type_name = format!("{resource_name}{field_name_pascal}");
1548
1549 let rust_type = crate::rust_types::RustType::Custom(nested_type_name);
1550
1551 if is_array {
1552 crate::rust_types::RustType::Vec(Box::new(rust_type))
1553 } else {
1554 rust_type
1555 }
1556 } else {
1557 let rust_type = crate::rust_types::RustType::Custom("BackboneElement".to_string());
1559 if is_array {
1560 crate::rust_types::RustType::Vec(Box::new(rust_type))
1561 } else {
1562 rust_type
1563 }
1564 }
1565 }
1566
1567 fn get_field_rust_type(
1571 &self,
1572 element: &crate::fhir_types::ElementDefinition,
1573 field_name: &str,
1574 ) -> CodegenResult<crate::rust_types::RustType> {
1575 use crate::rust_types::RustType;
1576
1577 let Some(element_type) = element.element_type.as_ref().and_then(|t| t.first()) else {
1578 return Ok(RustType::String);
1579 };
1580
1581 let Some(code) = &element_type.code else {
1582 return Ok(RustType::String);
1583 };
1584
1585 if code == "code" {
1587 if let Some(binding) = &element.binding {
1588 if binding.strength == "required" {
1589 if let Some(value_set_url) = &binding.value_set {
1590 if let Some(enum_name) =
1592 self.extract_enum_name_from_value_set(value_set_url)
1593 {
1594 return Ok(RustType::Custom(enum_name));
1595 }
1596 }
1597 }
1598 }
1599 }
1600
1601 use crate::generators::TypeUtilities;
1603 TypeUtilities::map_fhir_type_to_rust(element_type, field_name, &element.path)
1604 }
1605
1606 fn extract_enum_name_from_value_set(&self, url: &str) -> Option<String> {
1609 let url_without_version = url.split('|').next().unwrap_or(url);
1611
1612 let value_set_name = url_without_version.split('/').next_back()?;
1614
1615 let name = value_set_name
1618 .split(&['-', '.'][..])
1619 .filter(|part| !part.is_empty())
1620 .map(|part| {
1621 let mut chars = part.chars();
1622 match chars.next() {
1623 None => String::new(),
1624 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
1625 }
1626 })
1627 .collect::<String>();
1628
1629 if name.chars().next().unwrap_or('0').is_ascii_digit() {
1631 Some(format!("ValueSet{name}"))
1632 } else {
1633 Some(name)
1634 }
1635 }
1636}
1637
1638impl Default for TraitImplGenerator {
1639 fn default() -> Self {
1640 Self::new()
1641 }
1642}
1643
1644#[cfg(test)]
1645mod tests {
1646 use super::*;
1647 use crate::fhir_types::StructureDefinitionDifferential;
1648
1649 fn create_test_structure_definition(
1650 name: &str,
1651 base_definition: Option<&str>,
1652 ) -> StructureDefinition {
1653 StructureDefinition {
1654 resource_type: "StructureDefinition".to_string(),
1655 id: name.to_lowercase(),
1656 url: format!("http://test.com/{name}"),
1657 version: Some("1.0.0".to_string()),
1658 name: name.to_string(),
1659 title: Some(name.to_string()),
1660 status: "active".to_string(),
1661 description: None,
1662 purpose: None,
1663 kind: "resource".to_string(),
1664 is_abstract: false,
1665 base_type: "Resource".to_string(),
1666 base_definition: base_definition.map(|s| s.to_string()),
1667 differential: None,
1668 snapshot: None,
1669 }
1670 }
1671
1672 #[test]
1673 fn test_resource_type_for_core_resource() {
1674 let patient = create_test_structure_definition(
1675 "Patient",
1676 Some("http://hl7.org/fhir/StructureDefinition/DomainResource"),
1677 );
1678
1679 let result = TraitImplGenerator::get_resource_type_for_struct("Patient", &patient);
1680 assert_eq!(
1681 result, "Patient",
1682 "Core resource should return its own name"
1683 );
1684 }
1685
1686 #[test]
1687 fn test_resource_type_for_group_profile() {
1688 let group_definition = create_test_structure_definition(
1689 "GroupDefinition",
1690 Some("http://hl7.org/fhir/StructureDefinition/Group"),
1691 );
1692
1693 let result =
1694 TraitImplGenerator::get_resource_type_for_struct("GroupDefinition", &group_definition);
1695 assert_eq!(result, "Group", "Group profile should return 'Group'");
1696 }
1697
1698 #[test]
1699 fn test_resource_type_for_observation_profile() {
1700 let vital_signs = create_test_structure_definition(
1701 "VitalSigns",
1702 Some("http://hl7.org/fhir/StructureDefinition/Observation"),
1703 );
1704
1705 let result = TraitImplGenerator::get_resource_type_for_struct("VitalSigns", &vital_signs);
1706 assert_eq!(
1707 result, "Observation",
1708 "Observation profile should return 'Observation'"
1709 );
1710 }
1711
1712 #[test]
1713 fn test_resource_type_for_profile_on_profile() {
1714 let bmi = create_test_structure_definition(
1715 "BMI",
1716 Some("http://hl7.org/fhir/StructureDefinition/vitalsigns"),
1717 );
1718
1719 let result = TraitImplGenerator::get_resource_type_for_struct("BMI", &bmi);
1720 assert_eq!(
1722 result, "Observation",
1723 "BMI profile should resolve to 'Observation' via vitalsigns"
1724 );
1725 }
1726
1727 #[test]
1728 fn test_resource_type_without_base_definition() {
1729 let custom_resource = create_test_structure_definition("CustomResource", None);
1730
1731 let result =
1732 TraitImplGenerator::get_resource_type_for_struct("CustomResource", &custom_resource);
1733 assert_eq!(
1734 result, "CustomResource",
1735 "Resource without baseDefinition should return struct name"
1736 );
1737 }
1738
1739 #[test]
1740 fn test_is_core_resource() {
1741 assert!(TraitImplGenerator::is_core_resource(
1742 "http://hl7.org/fhir/StructureDefinition/Resource"
1743 ));
1744 assert!(TraitImplGenerator::is_core_resource(
1745 "http://hl7.org/fhir/StructureDefinition/DomainResource"
1746 ));
1747 assert!(!TraitImplGenerator::is_core_resource(
1748 "http://hl7.org/fhir/StructureDefinition/Patient"
1749 ));
1750 assert!(!TraitImplGenerator::is_core_resource(
1751 "http://hl7.org/fhir/StructureDefinition/Group"
1752 ));
1753 }
1754
1755 #[test]
1756 fn test_extract_base_resource_type() {
1757 assert_eq!(
1758 TraitImplGenerator::extract_base_resource_type(
1759 "http://hl7.org/fhir/StructureDefinition/Group"
1760 ),
1761 Some("Group".to_string())
1762 );
1763 assert_eq!(
1764 TraitImplGenerator::extract_base_resource_type(
1765 "http://hl7.org/fhir/StructureDefinition/Observation"
1766 ),
1767 Some("Observation".to_string())
1768 );
1769 assert_eq!(
1770 TraitImplGenerator::extract_base_resource_type(
1771 "http://hl7.org/fhir/StructureDefinition/vitalsigns"
1772 ),
1773 Some("vitalsigns".to_string())
1774 );
1775 assert_eq!(
1776 TraitImplGenerator::extract_base_resource_type("invalid-url"),
1777 None
1778 );
1779 }
1780
1781 #[test]
1782 fn test_resolve_to_core_resource_type() {
1783 assert_eq!(
1785 TraitImplGenerator::resolve_to_core_resource_type(
1786 "vitalsigns",
1787 "http://hl7.org/fhir/StructureDefinition/vitalsigns"
1788 ),
1789 "Observation"
1790 );
1791
1792 assert_eq!(
1794 TraitImplGenerator::resolve_to_core_resource_type(
1795 "Patient",
1796 "http://hl7.org/fhir/StructureDefinition/Patient"
1797 ),
1798 "Patient"
1799 );
1800 assert_eq!(
1801 TraitImplGenerator::resolve_to_core_resource_type(
1802 "Group",
1803 "http://hl7.org/fhir/StructureDefinition/Group"
1804 ),
1805 "Group"
1806 );
1807
1808 assert_eq!(
1810 TraitImplGenerator::resolve_to_core_resource_type(
1811 "bmi",
1812 "http://hl7.org/fhir/StructureDefinition/bmi"
1813 ),
1814 "Observation"
1815 );
1816
1817 assert_eq!(
1819 TraitImplGenerator::resolve_to_core_resource_type(
1820 "UnknownProfile",
1821 "http://hl7.org/fhir/StructureDefinition/UnknownProfile"
1822 ),
1823 "UnknownProfile"
1824 );
1825 }
1826
1827 #[test]
1828 fn test_empty_trait_implementations_are_filtered() {
1829 let generator = TraitImplGenerator::new();
1830
1831 let mut structure_def = create_test_structure_definition("EmptyProfile", None);
1833 structure_def.differential = Some(StructureDefinitionDifferential { element: vec![] });
1834
1835 let trait_impls = generator.generate_trait_impls(&structure_def).unwrap();
1837
1838 assert!(
1840 !trait_impls.is_empty(),
1841 "Should have at least Resource trait impl"
1842 );
1843
1844 let specific_trait_name = format!(
1846 "crate::traits::{}::{}Accessors",
1847 crate::naming::Naming::to_snake_case("EmptyProfile"),
1848 "EmptyProfile"
1849 );
1850
1851 let has_empty_specific_impl = trait_impls
1852 .iter()
1853 .any(|impl_| impl_.trait_name == specific_trait_name && impl_.is_empty());
1854
1855 assert!(
1856 !has_empty_specific_impl,
1857 "Should not include empty specific trait implementations"
1858 );
1859 }
1860}