1use std::collections::HashMap;
7
8use crate::config::CodegenConfig;
9use crate::fhir_types::{ElementDefinition, ElementType};
10use crate::generators::DocumentationGenerator;
11use crate::naming::Naming;
12use crate::rust_types::{RustField, RustStruct, RustType};
13use crate::type_mapper::TypeMapper;
14use crate::value_sets::ValueSetManager;
15use crate::CodegenResult;
16
17pub struct FieldGenerator<'a> {
19 config: &'a CodegenConfig,
20 type_cache: &'a HashMap<String, RustStruct>,
21 value_set_manager: &'a mut ValueSetManager,
22}
23
24impl<'a> FieldGenerator<'a> {
25 pub fn new(
27 config: &'a CodegenConfig,
28 type_cache: &'a HashMap<String, RustStruct>,
29 value_set_manager: &'a mut ValueSetManager,
30 ) -> Self {
31 Self {
32 config,
33 type_cache,
34 value_set_manager,
35 }
36 }
37
38 pub fn create_fields_from_element(
41 &mut self,
42 element: &ElementDefinition,
43 ) -> CodegenResult<Vec<RustField>> {
44 let field_name = element.path.split('.').next_back().unwrap_or("unknown");
46
47 if field_name.ends_with("[x]") {
49 return self.create_choice_type_fields(element);
50 }
51
52 if self.config.use_macro_calls {
54 if let Some(primitive_field) = self.create_primitive_field_macro_call(element)? {
55 let companion_field = self.create_companion_extension_field(field_name)?;
57 return Ok(vec![primitive_field, companion_field]);
58 }
59 }
60
61 let mut fields = Vec::new();
63 if let Some(field) = self.create_single_field_from_element(element)? {
64 fields.push(field);
65
66 if let Some(companion_field) = self.create_companion_field_if_primitive(element)? {
68 fields.push(companion_field);
69 }
70 }
71 Ok(fields)
72 }
73
74 fn create_choice_type_fields(
76 &mut self,
77 element: &ElementDefinition,
78 ) -> CodegenResult<Vec<RustField>> {
79 let mut fields = Vec::new();
80 let field_name = element.path.split('.').next_back().unwrap_or("unknown");
81 let base_name = field_name.strip_suffix("[x]").unwrap_or(field_name);
82
83 let is_optional = element.min.unwrap_or(0) == 0;
85
86 let is_array = element
88 .max
89 .as_ref()
90 .is_some_and(|max| max == "*" || max.parse::<u32>().unwrap_or(1) > 1);
91
92 if let Some(element_types) = &element.element_type {
93 for element_type in element_types {
94 if let Some(type_code) = &element_type.code {
95 let type_suffix = crate::naming::Naming::type_suffix(type_code);
97 let field_name = format!("{base_name}_{type_suffix}");
98 let rust_field_name = crate::naming::Naming::field_name(&field_name);
99
100 let mut type_mapper = TypeMapper::new(self.config, self.value_set_manager);
102 let field_type = type_mapper.map_fhir_type_with_binding(
103 std::slice::from_ref(element_type),
104 element.binding.as_ref(),
105 is_array,
106 );
107
108 let mut field = RustField::new(rust_field_name.clone(), field_type);
110 field.is_optional = is_optional;
111
112 field.doc_comment =
114 DocumentationGenerator::generate_choice_field_documentation_with_binding(
115 element,
116 type_code,
117 self.value_set_manager,
118 );
119
120 let serde_name = format!(
122 "{base_name}{type_code_capitalized}",
123 type_code_capitalized = Self::capitalize_first_char(type_code)
124 );
125 field = field.with_serde_rename(serde_name);
126
127 fields.push(field);
128 }
129 }
130 }
131
132 Ok(fields)
133 }
134
135 fn create_single_field_from_element(
137 &mut self,
138 element: &ElementDefinition,
139 ) -> CodegenResult<Option<RustField>> {
140 let field_name = element.path.split('.').next_back().unwrap_or("unknown");
142 let rust_field_name = crate::naming::Naming::field_name(field_name);
143
144 let is_optional = element.min.unwrap_or(0) == 0;
146
147 let is_array = element
149 .max
150 .as_ref()
151 .is_some_and(|max| max == "*" || max.parse::<u32>().unwrap_or(1) > 1);
152
153 let field_type = if let Some(element_types) = &element.element_type {
155 if self.should_use_nested_struct_type(element, element_types) {
157 self.build_nested_struct_type(element, is_array)
158 } else {
159 let mut type_mapper = TypeMapper::new(self.config, self.value_set_manager);
160 type_mapper.map_fhir_type_with_binding(
161 element_types,
162 element.binding.as_ref(),
163 is_array,
164 )
165 }
166 } else {
167 if is_array {
169 RustType::Vec(Box::new(RustType::Custom("StringType".to_string())))
170 } else {
171 RustType::Custom("StringType".to_string())
172 }
173 };
174
175 let mut field = RustField::new(rust_field_name.clone(), field_type);
177 field.is_optional = is_optional;
178
179 field.doc_comment = DocumentationGenerator::generate_field_documentation_with_binding(
181 element,
182 self.value_set_manager,
183 );
184
185 if rust_field_name != field_name {
187 field = field.with_serde_rename(field_name.to_string());
188 }
189
190 Ok(Some(field))
191 }
192
193 pub fn create_field_from_element(
195 &mut self,
196 element: &ElementDefinition,
197 ) -> CodegenResult<Option<RustField>> {
198 let fields = self.create_fields_from_element(element)?;
199 Ok(fields.into_iter().next())
200 }
201
202 pub fn to_rust_field_name(name: &str) -> String {
204 let clean_name = if name.ends_with("[x]") {
206 name.strip_suffix("[x]").unwrap_or(name)
207 } else {
208 name
209 };
210
211 let conflict_resolved_name = if clean_name == "base" {
213 "base_definition"
215 } else {
216 clean_name
217 };
218
219 let snake_case = conflict_resolved_name
221 .chars()
222 .enumerate()
223 .map(|(i, c)| {
224 if c.is_uppercase() && i > 0 {
225 format!("_{c}", c = c.to_lowercase())
226 } else {
227 c.to_lowercase().to_string()
228 }
229 })
230 .collect::<String>();
231
232 Self::handle_rust_keywords(&snake_case)
234 }
235
236 fn handle_rust_keywords(name: &str) -> String {
238 match name {
239 "type" => "type_".to_string(),
240 "use" => "use_".to_string(),
241 "ref" => "ref_".to_string(),
242 "mod" => "mod_".to_string(),
243 "fn" => "fn_".to_string(),
244 "let" => "let_".to_string(),
245 "const" => "const_".to_string(),
246 "static" => "static_".to_string(),
247 "struct" => "struct_".to_string(),
248 "enum" => "enum_".to_string(),
249 "impl" => "impl_".to_string(),
250 "trait" => "trait_".to_string(),
251 "for" => "for_".to_string(),
252 "if" => "if_".to_string(),
253 "else" => "else_".to_string(),
254 "while" => "while_".to_string(),
255 "loop" => "loop_".to_string(),
256 "match" => "match_".to_string(),
257 "return" => "return_".to_string(),
258 "where" => "where_".to_string(),
259 "abstract" => "abstract_".to_string(),
260 _ => name.to_string(),
261 }
262 }
263
264 pub fn type_code_to_snake_case(type_code: &str) -> String {
266 type_code
267 .chars()
268 .enumerate()
269 .map(|(i, c)| {
270 if c.is_uppercase() && i > 0 {
271 format!("_{c}", c = c.to_lowercase())
272 } else {
273 c.to_lowercase().to_string()
274 }
275 })
276 .collect()
277 }
278
279 fn capitalize_first_char(s: &str) -> String {
281 let mut chars = s.chars();
282 match chars.next() {
283 None => String::new(),
284 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
285 }
286 }
287
288 fn should_use_nested_struct_type(
290 &self,
291 element: &ElementDefinition,
292 element_types: &[ElementType],
293 ) -> bool {
294 if let Some(first_type) = element_types.first() {
296 if let Some(code) = &first_type.code {
297 if code == "BackboneElement" {
298 let path_parts: Vec<&str> = element.path.split('.').collect();
300 if path_parts.len() >= 2 {
301 let parent_struct_name = Naming::to_rust_identifier(path_parts[0]);
302 let field_name = path_parts[path_parts.len() - 1];
303
304 let nested_struct_name = if path_parts.len() == 2 {
307 format!(
309 "{parent_struct_name}{field_pascal}",
310 field_pascal = Naming::to_pascal_case(field_name)
311 )
312 } else {
313 let mut nested_name = parent_struct_name;
315 for part in path_parts.iter().skip(1) {
316 nested_name.push_str(&Naming::to_pascal_case(part));
317 }
318 nested_name
319 };
320
321 return self.type_cache.contains_key(&nested_struct_name);
323 }
324 }
325 }
326 }
327 false
328 }
329
330 fn build_nested_struct_type(&self, element: &ElementDefinition, is_array: bool) -> RustType {
332 let path_parts: Vec<&str> = element.path.split('.').collect();
333 let nested_struct_name = if path_parts.len() >= 2 {
334 let parent_struct_name = Naming::to_rust_identifier(path_parts[0]);
335 if path_parts.len() == 2 {
336 format!(
338 "{parent_struct_name}{part_pascal}",
339 part_pascal = Naming::to_pascal_case(path_parts[1])
340 )
341 } else {
342 let mut nested_name = parent_struct_name;
344 for part in path_parts.iter().skip(1) {
345 nested_name.push_str(&Naming::to_pascal_case(part));
346 }
347 nested_name
348 }
349 } else {
350 format!(
351 "{path_identifier}Unknown",
352 path_identifier = Naming::to_rust_identifier(&element.path)
353 )
354 };
355
356 if is_array {
357 RustType::Vec(Box::new(RustType::Custom(nested_struct_name)))
358 } else {
359 RustType::Custom(nested_struct_name)
360 }
361 }
362
363 pub fn extract_field_name_from_path(path: &str) -> &str {
365 path.split('.').next_back().unwrap_or("unknown")
366 }
367
368 pub fn requires_serde_rename(original_name: &str, rust_field_name: &str) -> bool {
370 original_name != rust_field_name
371 }
372
373 pub fn determine_field_cardinality(element: &ElementDefinition) -> (bool, bool) {
375 let is_optional = element.min.unwrap_or(0) == 0;
377
378 let is_array = element
380 .max
381 .as_ref()
382 .is_some_and(|max| max == "*" || max.parse::<u32>().unwrap_or(1) > 1);
383
384 (is_optional, is_array)
385 }
386
387 pub fn extract_choice_types_from_structure(
390 structure_def: &crate::fhir_types::StructureDefinition,
391 ) -> Vec<(String, Vec<String>)> {
392 let mut choice_types = Vec::new();
393
394 let elements = if let Some(differential) = &structure_def.differential {
396 &differential.element
397 } else if let Some(snapshot) = &structure_def.snapshot {
398 &snapshot.element
399 } else {
400 return choice_types; };
402
403 for element in elements {
404 let field_name = element.path.split('.').next_back().unwrap_or("unknown");
406 if field_name.ends_with("[x]") {
407 let base_name = field_name.strip_suffix("[x]").unwrap_or(field_name);
408
409 if let Some(element_types) = &element.element_type {
411 let type_codes: Vec<String> = element_types
412 .iter()
413 .filter_map(|et| et.code.clone())
414 .collect();
415
416 if !type_codes.is_empty() {
417 choice_types.push((base_name.to_string(), type_codes));
418 }
419 }
420 }
421 }
422
423 choice_types
424 }
425
426 fn create_companion_field_if_primitive(
428 &mut self,
429 element: &ElementDefinition,
430 ) -> CodegenResult<Option<RustField>> {
431 if let Some(element_types) = &element.element_type {
433 if let Some(first_type) = element_types.first() {
434 if let Some(type_code) = &first_type.code {
435 if self.is_primitive_type(type_code) {
437 let field_name = element.path.split('.').next_back().unwrap_or("unknown");
438 let companion_field_name = format!("_{field_name}");
439 let rust_companion_field_name =
440 crate::naming::Naming::field_name(&companion_field_name);
441
442 let companion_element_type = self.get_companion_element_type(type_code);
444
445 let is_optional = true;
448
449 let mut companion_field = RustField::new(
451 rust_companion_field_name.clone(),
452 RustType::Custom(companion_element_type),
453 );
454 companion_field.is_optional = is_optional;
455
456 companion_field.doc_comment = Some(format!(
458 "Extension element for the '{field_name}' primitive field. Contains metadata and extensions."
459 ));
460
461 if rust_companion_field_name != companion_field_name {
463 companion_field =
464 companion_field.with_serde_rename(companion_field_name);
465 }
466
467 return Ok(Some(companion_field));
468 }
469 }
470 }
471 }
472 Ok(None)
473 }
474
475 fn is_primitive_type(&self, type_code: &str) -> bool {
477 matches!(
478 type_code,
479 "boolean"
480 | "integer"
481 | "positiveInt"
482 | "unsignedInt"
483 | "decimal"
484 | "string"
485 | "code"
486 | "id"
487 | "markdown"
488 | "uri"
489 | "url"
490 | "canonical"
491 | "oid"
492 | "uuid"
493 | "base64Binary"
494 | "xhtml"
495 | "date"
496 | "dateTime"
497 | "time"
498 | "instant"
499 )
500 }
501
502 fn get_companion_element_type(&self, _primitive_type: &str) -> String {
505 "Element".to_string()
506 }
507
508 pub fn create_primitive_field_macro_call(
511 &mut self,
512 element: &ElementDefinition,
513 ) -> CodegenResult<Option<RustField>> {
514 let field_name = element.path.split('.').next_back().unwrap_or("unknown");
516
517 if field_name.ends_with("[x]") {
519 return Ok(None);
520 }
521
522 if let Some(element_types) = &element.element_type {
524 if let Some(first_type) = element_types.first() {
525 if let Some(type_code) = &first_type.code {
526 if self.is_primitive_type(type_code) {
528 let is_optional = element.min.unwrap_or(0) == 0;
530
531 let rust_type_name = match type_code.as_str() {
533 "string" => "StringType",
534 "boolean" => "BooleanType",
535 "integer" => "IntegerType",
536 "decimal" => "DecimalType",
537 "dateTime" => "DateTimeType",
538 "date" => "DateType",
539 "time" => "TimeType",
540 "uri" => "UriType",
541 "canonical" => "CanonicalType",
542 "base64Binary" => "Base64BinaryType",
543 "instant" => "InstantType",
544 "positiveInt" => "PositiveIntType",
545 "unsignedInt" => "UnsignedIntType",
546 "id" => "IdType",
547 "oid" => "OidType",
548 "uuid" => "UuidType",
549 "code" => "CodeType",
550 "markdown" => "MarkdownType",
551 "url" => "UrlType",
552 _ => return Ok(None), };
554
555 let rust_type =
557 RustType::Custom(format!("crate::primitives::{rust_type_name}"));
558 let mut field = RustField::new(field_name.to_string(), rust_type);
559
560 if is_optional {
561 field = field.optional();
562 }
563
564 field = field.with_doc(format!("Field: {field_name}"));
565
566 return Ok(Some(field));
570 }
571 }
572 }
573 }
574
575 Ok(None)
576 }
577
578 pub fn create_companion_extension_field(&self, field_name: &str) -> CodegenResult<RustField> {
581 let companion_name = format!("_{field_name}");
582 let element_type = RustType::Custom("crate::datatypes::element::Element".to_string());
583
584 let mut companion_field = RustField::new(companion_name.clone(), element_type);
585 companion_field = companion_field.optional(); companion_field = companion_field.with_doc(format!(
587 "Extension element for the '{field_name}' primitive field. Contains metadata and extensions."
588 ));
589 companion_field = companion_field.with_serde_rename(companion_name);
590
591 Ok(companion_field)
592 }
593}
594
595#[cfg(test)]
596mod tests {
597 use super::*;
598 use crate::fhir_types::ElementType;
599
600 #[test]
601 fn test_to_rust_field_name() {
602 assert_eq!(crate::naming::Naming::field_name("active"), "active");
604 assert_eq!(crate::naming::Naming::field_name("name"), "name");
605
606 assert_eq!(crate::naming::Naming::field_name("birthDate"), "birth_date");
608 assert_eq!(
609 crate::naming::Naming::field_name("multipleBirthBoolean"),
610 "multiple_birth_boolean"
611 );
612
613 assert_eq!(crate::naming::Naming::field_name("value[x]"), "value");
615 assert_eq!(crate::naming::Naming::field_name("deceased[x]"), "deceased");
616
617 assert_eq!(crate::naming::Naming::field_name("type"), "type_");
619 assert_eq!(crate::naming::Naming::field_name("use"), "use_");
620 assert_eq!(crate::naming::Naming::field_name("ref"), "ref_");
621 assert_eq!(crate::naming::Naming::field_name("for"), "for_");
622 assert_eq!(crate::naming::Naming::field_name("match"), "match_");
623 }
624
625 #[test]
626 fn test_handle_rust_keywords() {
627 assert_eq!(FieldGenerator::handle_rust_keywords("type"), "type_");
628 assert_eq!(FieldGenerator::handle_rust_keywords("struct"), "struct_");
629 assert_eq!(FieldGenerator::handle_rust_keywords("impl"), "impl_");
630 assert_eq!(FieldGenerator::handle_rust_keywords("normal"), "normal");
631 }
632
633 #[test]
634 fn test_extract_field_name_from_path() {
635 assert_eq!(
636 FieldGenerator::extract_field_name_from_path("Patient.active"),
637 "active"
638 );
639 assert_eq!(
640 FieldGenerator::extract_field_name_from_path("Bundle.entry.resource"),
641 "resource"
642 );
643 assert_eq!(
644 FieldGenerator::extract_field_name_from_path("unknown"),
645 "unknown"
646 );
647 }
648
649 #[test]
650 fn test_requires_serde_rename() {
651 assert!(!FieldGenerator::requires_serde_rename("active", "active"));
652 assert!(FieldGenerator::requires_serde_rename(
653 "birthDate",
654 "birth_date"
655 ));
656 assert!(FieldGenerator::requires_serde_rename("type", "type_"));
657 }
658
659 #[test]
660 fn test_determine_field_cardinality() {
661 use crate::fhir_types::ElementDefinition;
662
663 let optional_element = ElementDefinition {
665 id: Some("Patient.active".to_string()),
666 path: "Patient.active".to_string(),
667 short: Some("Whether this patient record is in active use".to_string()),
668 definition: None,
669 min: Some(0),
670 max: Some("1".to_string()),
671 element_type: Some(vec![ElementType {
672 code: Some("boolean".to_string()),
673 target_profile: None,
674 }]),
675 fixed: None,
676 pattern: None,
677 binding: None,
678 constraint: None,
679 };
680
681 let (is_optional, is_array) =
682 FieldGenerator::determine_field_cardinality(&optional_element);
683 assert!(is_optional);
684 assert!(!is_array);
685
686 let array_element = ElementDefinition {
688 id: Some("Patient.name".to_string()),
689 path: "Patient.name".to_string(),
690 short: Some("A name associated with the patient".to_string()),
691 definition: None,
692 min: Some(1),
693 max: Some("*".to_string()),
694 element_type: Some(vec![ElementType {
695 code: Some("HumanName".to_string()),
696 target_profile: None,
697 }]),
698 fixed: None,
699 pattern: None,
700 binding: None,
701 constraint: None,
702 };
703
704 let (is_optional, is_array) = FieldGenerator::determine_field_cardinality(&array_element);
705 assert!(!is_optional);
706 assert!(is_array);
707
708 let required_element = ElementDefinition {
710 id: Some("Patient.id".to_string()),
711 path: "Patient.id".to_string(),
712 short: Some("Logical id of this artifact".to_string()),
713 definition: None,
714 min: Some(1),
715 max: Some("1".to_string()),
716 element_type: Some(vec![ElementType {
717 code: Some("id".to_string()),
718 target_profile: None,
719 }]),
720 fixed: None,
721 pattern: None,
722 binding: None,
723 constraint: None,
724 };
725
726 let (is_optional, is_array) =
727 FieldGenerator::determine_field_cardinality(&required_element);
728 assert!(!is_optional);
729 assert!(!is_array);
730 }
731
732 #[test]
733 fn test_create_field_from_element() {
734 use crate::config::CodegenConfig;
735 use std::collections::HashMap;
736
737 let config = CodegenConfig::default();
738 let type_cache = HashMap::new();
739 let mut value_set_manager = ValueSetManager::new();
740 let mut field_generator = FieldGenerator::new(&config, &type_cache, &mut value_set_manager);
741
742 let boolean_element = ElementDefinition {
744 id: Some("Patient.active".to_string()),
745 path: "Patient.active".to_string(),
746 short: Some("Whether this patient record is in active use".to_string()),
747 definition: None,
748 min: Some(0),
749 max: Some("1".to_string()),
750 element_type: Some(vec![ElementType {
751 code: Some("boolean".to_string()),
752 target_profile: None,
753 }]),
754 fixed: None,
755 pattern: None,
756 binding: None,
757 constraint: None,
758 };
759
760 let result = field_generator.create_field_from_element(&boolean_element);
761 assert!(result.is_ok());
762
763 let field = result.unwrap();
764 assert!(field.is_some());
765
766 let field = field.unwrap();
767 assert_eq!(field.name, "active");
768 assert!(field.is_optional);
769 assert!(
771 matches!(field.field_type, RustType::Custom(ref type_name) if type_name == "BooleanType")
772 );
773 }
774
775 #[test]
776 fn test_macro_call_generation() {
777 use crate::config::CodegenConfig;
778 use std::collections::HashMap;
779
780 let config = CodegenConfig {
781 use_macro_calls: true, ..CodegenConfig::default()
783 };
784
785 let type_cache = HashMap::new();
786 let mut value_set_manager = ValueSetManager::new();
787 let mut field_generator = FieldGenerator::new(&config, &type_cache, &mut value_set_manager);
788
789 let boolean_element = ElementDefinition {
791 id: Some("Patient.active".to_string()),
792 path: "Patient.active".to_string(),
793 short: Some("Whether this patient record is in active use".to_string()),
794 definition: None,
795 min: Some(0),
796 max: Some("1".to_string()),
797 element_type: Some(vec![ElementType {
798 code: Some("boolean".to_string()),
799 target_profile: None,
800 }]),
801 fixed: None,
802 pattern: None,
803 binding: None,
804 constraint: None,
805 };
806
807 let result = field_generator.create_fields_from_element(&boolean_element);
808 assert!(result.is_ok());
809
810 let fields = result.unwrap();
811 assert_eq!(fields.len(), 2); let primitive_field = &fields[0];
815 assert_eq!(primitive_field.name, "active");
816 assert!(primitive_field.is_optional);
817 assert!(primitive_field
818 .field_type
819 .to_string()
820 .contains("BooleanType"));
821 assert_eq!(
822 primitive_field.doc_comment,
823 Some("Field: active".to_string())
824 );
825
826 let companion_field = &fields[1];
828 assert_eq!(companion_field.name, "_active");
829 assert!(companion_field.is_optional); assert!(companion_field.field_type.to_string().contains("Element"));
831 }
832
833 #[test]
834 fn test_companion_fields_always_optional() {
835 use crate::config::CodegenConfig;
836 use std::collections::HashMap;
837
838 let config = CodegenConfig::default();
839 let type_cache = HashMap::new();
840 let mut value_set_manager = ValueSetManager::new();
841 let mut field_generator = FieldGenerator::new(&config, &type_cache, &mut value_set_manager);
842
843 let required_boolean_element = ElementDefinition {
845 id: Some("Patient.active".to_string()),
846 path: "Patient.active".to_string(),
847 short: Some("Whether this patient record is in active use".to_string()),
848 definition: None,
849 min: Some(1), max: Some("1".to_string()),
851 element_type: Some(vec![ElementType {
852 code: Some("boolean".to_string()),
853 target_profile: None,
854 }]),
855 fixed: None,
856 pattern: None,
857 binding: None,
858 constraint: None,
859 };
860
861 let result = field_generator.create_fields_from_element(&required_boolean_element);
862 assert!(result.is_ok());
863
864 let fields = result.unwrap();
865 assert_eq!(fields.len(), 2); let main_field = &fields[0];
868 let companion_field = &fields[1];
869
870 assert_eq!(main_field.name, "active");
872 assert!(!main_field.is_optional); assert_eq!(companion_field.name, "_active");
876 assert!(companion_field.is_optional); let required_string_element = ElementDefinition {
880 id: Some("Patient.name".to_string()),
881 path: "Patient.name".to_string(),
882 short: Some("Patient name".to_string()),
883 definition: None,
884 min: Some(1), max: Some("1".to_string()),
886 element_type: Some(vec![ElementType {
887 code: Some("string".to_string()),
888 target_profile: None,
889 }]),
890 fixed: None,
891 pattern: None,
892 binding: None,
893 constraint: None,
894 };
895
896 let result = field_generator.create_fields_from_element(&required_string_element);
897 assert!(result.is_ok());
898
899 let fields = result.unwrap();
900 assert_eq!(fields.len(), 2); let main_field = &fields[0];
903 let companion_field = &fields[1];
904
905 assert_eq!(main_field.name, "name");
907 assert!(!main_field.is_optional); assert_eq!(companion_field.name, "_name");
911 assert!(companion_field.is_optional); }
913
914 #[test]
915 fn test_choice_type_field_generation() {
916 use crate::config::CodegenConfig;
917 use std::collections::HashMap;
918
919 let config = CodegenConfig::default();
920 let type_cache = HashMap::new();
921 let mut value_set_manager = ValueSetManager::new();
922
923 let mut field_generator = FieldGenerator::new(&config, &type_cache, &mut value_set_manager);
924
925 let element = ElementDefinition {
927 id: Some("Observation.effective[x]".to_string()),
928 path: "Observation.effective[x]".to_string(),
929 short: Some("Clinically relevant time/time-period for observation".to_string()),
930 definition: Some(
931 "The time or time-period the observed value is asserted as being true.".to_string(),
932 ),
933 min: Some(0),
934 max: Some("1".to_string()),
935 element_type: Some(vec![
936 ElementType {
937 code: Some("dateTime".to_string()),
938 target_profile: None,
939 },
940 ElementType {
941 code: Some("Period".to_string()),
942 target_profile: None,
943 },
944 ElementType {
945 code: Some("Timing".to_string()),
946 target_profile: None,
947 },
948 ElementType {
949 code: Some("instant".to_string()),
950 target_profile: None,
951 },
952 ]),
953 fixed: None,
954 pattern: None,
955 binding: None,
956 constraint: None,
957 };
958
959 let fields = field_generator
961 .create_fields_from_element(&element)
962 .unwrap();
963
964 assert_eq!(
966 fields.len(),
967 4,
968 "Should generate 4 fields for 4 choice types"
969 );
970
971 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
973 assert!(field_names.contains(&"effective_date_time"));
974 assert!(field_names.contains(&"effective_period"));
975 assert!(field_names.contains(&"effective_timing"));
976 assert!(field_names.contains(&"effective_instant"));
977
978 assert!(fields[0]
980 .serde_attributes
981 .contains(&"rename = \"effectiveDateTime\"".to_string()));
982 assert!(fields[1]
983 .serde_attributes
984 .contains(&"rename = \"effectivePeriod\"".to_string()));
985 assert!(fields[2]
986 .serde_attributes
987 .contains(&"rename = \"effectiveTiming\"".to_string()));
988 assert!(fields[3]
989 .serde_attributes
990 .contains(&"rename = \"effectiveInstant\"".to_string()));
991
992 for field in &fields {
994 assert!(field.is_optional, "Choice type fields should be optional");
995 }
996 }
997
998 #[test]
999 fn test_choice_type_documentation() {
1000 use crate::config::CodegenConfig;
1001 use std::collections::HashMap;
1002
1003 let config = CodegenConfig::default();
1004 let type_cache = HashMap::new();
1005 let mut value_set_manager = ValueSetManager::new();
1006
1007 let mut field_generator = FieldGenerator::new(&config, &type_cache, &mut value_set_manager);
1008
1009 let element = ElementDefinition {
1011 id: Some("Observation.value[x]".to_string()),
1012 path: "Observation.value[x]".to_string(),
1013 short: Some("Actual result".to_string()),
1014 definition: Some(
1015 "The information determined as a result of making the observation.".to_string(),
1016 ),
1017 min: Some(0),
1018 max: Some("1".to_string()),
1019 element_type: Some(vec![
1020 ElementType {
1021 code: Some("Quantity".to_string()),
1022 target_profile: None,
1023 },
1024 ElementType {
1025 code: Some("string".to_string()),
1026 target_profile: None,
1027 },
1028 ]),
1029 fixed: None,
1030 pattern: None,
1031 binding: None,
1032 constraint: None,
1033 };
1034
1035 let fields = field_generator
1037 .create_fields_from_element(&element)
1038 .unwrap();
1039
1040 assert_eq!(fields.len(), 2);
1042
1043 let quantity_field = &fields[0];
1044 let string_field = &fields[1];
1045
1046 assert!(quantity_field
1047 .doc_comment
1048 .as_ref()
1049 .unwrap()
1050 .contains("(Quantity)"));
1051 assert!(string_field
1052 .doc_comment
1053 .as_ref()
1054 .unwrap()
1055 .contains("(string)"));
1056 }
1057}