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