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 field.is_repeating = is_array;
112
113 field.doc_comment =
115 DocumentationGenerator::generate_choice_field_documentation_with_binding(
116 element,
117 type_code,
118 self.value_set_manager,
119 );
120
121 let serde_name = format!(
123 "{base_name}{type_code_capitalized}",
124 type_code_capitalized = Self::capitalize_first_char(type_code)
125 );
126 field = field.with_serde_rename(serde_name);
127
128 fields.push(field);
129 }
130 }
131 }
132
133 Ok(fields)
134 }
135
136 fn create_single_field_from_element(
138 &mut self,
139 element: &ElementDefinition,
140 ) -> CodegenResult<Option<RustField>> {
141 let field_name = element.path.split('.').next_back().unwrap_or("unknown");
143 let rust_field_name = crate::naming::Naming::field_name(field_name);
144
145 let is_optional = element.min.unwrap_or(0) == 0;
147
148 let is_array = element
150 .max
151 .as_ref()
152 .is_some_and(|max| max == "*" || max.parse::<u32>().unwrap_or(1) > 1);
153
154 let field_type = if let Some(element_types) = &element.element_type {
156 if self.should_use_nested_struct_type(element, element_types) {
158 self.build_nested_struct_type(element, is_array)
159 } else {
160 let is_language_field =
164 field_name == "language" && element.path.ends_with(".language");
165 if is_language_field {
166 if is_array {
167 RustType::Vec(Box::new(RustType::Custom("StringType".to_string())))
168 } else {
169 RustType::Custom("StringType".to_string())
170 }
171 } else {
172 let mut type_mapper = TypeMapper::new(self.config, self.value_set_manager);
173 let resolved = type_mapper.map_fhir_type_with_binding(
174 element_types,
175 element.binding.as_ref(),
176 is_array,
177 );
178 let resource_name = element.path.split('.').next().unwrap_or("");
182 let collides = match &resolved {
183 RustType::Custom(name) => name == resource_name,
184 _ => false,
185 };
186 if collides {
187 if is_array {
188 RustType::Vec(Box::new(RustType::Custom("StringType".to_string())))
189 } else {
190 RustType::Custom("StringType".to_string())
191 }
192 } else {
193 resolved
194 }
195 }
196 }
197 } else {
198 if is_array {
200 RustType::Vec(Box::new(RustType::Custom("StringType".to_string())))
201 } else {
202 RustType::Custom("StringType".to_string())
203 }
204 };
205
206 let mut field = RustField::new(rust_field_name.clone(), field_type);
208 field.is_optional = is_optional;
209 field.is_repeating = is_array;
210
211 field.doc_comment = DocumentationGenerator::generate_field_documentation_with_binding(
213 element,
214 self.value_set_manager,
215 );
216
217 if rust_field_name != field_name {
219 field = field.with_serde_rename(field_name.to_string());
220 }
221
222 Ok(Some(field))
223 }
224
225 pub fn create_field_from_element(
227 &mut self,
228 element: &ElementDefinition,
229 ) -> CodegenResult<Option<RustField>> {
230 let fields = self.create_fields_from_element(element)?;
231 Ok(fields.into_iter().next())
232 }
233
234 pub fn to_rust_field_name(name: &str) -> String {
236 let clean_name = if name.ends_with("[x]") {
238 name.strip_suffix("[x]").unwrap_or(name)
239 } else {
240 name
241 };
242
243 let conflict_resolved_name = if clean_name == "base" {
245 "base_definition"
247 } else {
248 clean_name
249 };
250
251 let snake_case = conflict_resolved_name
253 .chars()
254 .enumerate()
255 .map(|(i, c)| {
256 if c.is_uppercase() && i > 0 {
257 format!("_{c}", c = c.to_lowercase())
258 } else {
259 c.to_lowercase().to_string()
260 }
261 })
262 .collect::<String>();
263
264 Self::handle_rust_keywords(&snake_case)
266 }
267
268 fn handle_rust_keywords(name: &str) -> String {
270 match name {
271 "type" => "type_".to_string(),
272 "use" => "use_".to_string(),
273 "ref" => "ref_".to_string(),
274 "mod" => "mod_".to_string(),
275 "fn" => "fn_".to_string(),
276 "let" => "let_".to_string(),
277 "const" => "const_".to_string(),
278 "static" => "static_".to_string(),
279 "struct" => "struct_".to_string(),
280 "enum" => "enum_".to_string(),
281 "impl" => "impl_".to_string(),
282 "trait" => "trait_".to_string(),
283 "for" => "for_".to_string(),
284 "if" => "if_".to_string(),
285 "else" => "else_".to_string(),
286 "while" => "while_".to_string(),
287 "loop" => "loop_".to_string(),
288 "match" => "match_".to_string(),
289 "return" => "return_".to_string(),
290 "where" => "where_".to_string(),
291 "abstract" => "abstract_".to_string(),
292 _ => name.to_string(),
293 }
294 }
295
296 pub fn type_code_to_snake_case(type_code: &str) -> String {
298 type_code
299 .chars()
300 .enumerate()
301 .map(|(i, c)| {
302 if c.is_uppercase() && i > 0 {
303 format!("_{c}", c = c.to_lowercase())
304 } else {
305 c.to_lowercase().to_string()
306 }
307 })
308 .collect()
309 }
310
311 fn capitalize_first_char(s: &str) -> String {
313 let mut chars = s.chars();
314 match chars.next() {
315 None => String::new(),
316 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
317 }
318 }
319
320 fn should_use_nested_struct_type(
322 &self,
323 element: &ElementDefinition,
324 element_types: &[ElementType],
325 ) -> bool {
326 if let Some(first_type) = element_types.first() {
328 if let Some(code) = &first_type.code {
329 if code == "BackboneElement" {
330 let path_parts: Vec<&str> = element.path.split('.').collect();
332 if path_parts.len() >= 2 {
333 let parent_struct_name = Naming::to_rust_identifier(path_parts[0]);
334 let field_name = path_parts[path_parts.len() - 1];
335
336 let nested_struct_name = if path_parts.len() == 2 {
339 format!(
341 "{parent_struct_name}{field_pascal}",
342 field_pascal = Naming::to_pascal_case(field_name)
343 )
344 } else {
345 let mut nested_name = parent_struct_name;
347 for part in path_parts.iter().skip(1) {
348 nested_name.push_str(&Naming::to_pascal_case(part));
349 }
350 nested_name
351 };
352
353 return self.type_cache.contains_key(&nested_struct_name);
355 }
356 }
357 }
358 }
359 false
360 }
361
362 fn build_nested_struct_type(&self, element: &ElementDefinition, is_array: bool) -> RustType {
364 let path_parts: Vec<&str> = element.path.split('.').collect();
365 let nested_struct_name = if path_parts.len() >= 2 {
366 let parent_struct_name = Naming::to_rust_identifier(path_parts[0]);
367 if path_parts.len() == 2 {
368 format!(
370 "{parent_struct_name}{part_pascal}",
371 part_pascal = Naming::to_pascal_case(path_parts[1])
372 )
373 } else {
374 let mut nested_name = parent_struct_name;
376 for part in path_parts.iter().skip(1) {
377 nested_name.push_str(&Naming::to_pascal_case(part));
378 }
379 nested_name
380 }
381 } else {
382 format!(
383 "{path_identifier}Unknown",
384 path_identifier = Naming::to_rust_identifier(&element.path)
385 )
386 };
387
388 if is_array {
389 RustType::Vec(Box::new(RustType::Custom(nested_struct_name)))
390 } else {
391 RustType::Custom(nested_struct_name)
392 }
393 }
394
395 pub fn extract_field_name_from_path(path: &str) -> &str {
397 path.split('.').next_back().unwrap_or("unknown")
398 }
399
400 pub fn requires_serde_rename(original_name: &str, rust_field_name: &str) -> bool {
402 original_name != rust_field_name
403 }
404
405 pub fn determine_field_cardinality(element: &ElementDefinition) -> (bool, bool) {
407 let is_optional = element.min.unwrap_or(0) == 0;
409
410 let is_array = element
412 .max
413 .as_ref()
414 .is_some_and(|max| max == "*" || max.parse::<u32>().unwrap_or(1) > 1);
415
416 (is_optional, is_array)
417 }
418
419 pub fn extract_choice_types_from_structure(
422 structure_def: &crate::fhir_types::StructureDefinition,
423 ) -> Vec<(String, Vec<String>)> {
424 let mut choice_types = Vec::new();
425
426 let elements = if let Some(differential) = &structure_def.differential {
428 &differential.element
429 } else if let Some(snapshot) = &structure_def.snapshot {
430 &snapshot.element
431 } else {
432 return choice_types; };
434
435 for element in elements {
436 let field_name = element.path.split('.').next_back().unwrap_or("unknown");
438 if field_name.ends_with("[x]") {
439 let base_name = field_name.strip_suffix("[x]").unwrap_or(field_name);
440
441 if let Some(element_types) = &element.element_type {
443 let type_codes: Vec<String> = element_types
444 .iter()
445 .filter_map(|et| et.code.clone())
446 .collect();
447
448 if !type_codes.is_empty() {
449 choice_types.push((base_name.to_string(), type_codes));
450 }
451 }
452 }
453 }
454
455 choice_types
456 }
457
458 fn create_companion_field_if_primitive(
460 &mut self,
461 element: &ElementDefinition,
462 ) -> CodegenResult<Option<RustField>> {
463 if let Some(element_types) = &element.element_type {
465 if let Some(first_type) = element_types.first() {
466 if let Some(type_code) = &first_type.code {
467 if self.is_primitive_type(type_code) {
469 let field_name = element.path.split('.').next_back().unwrap_or("unknown");
470 let companion_field_name = format!("_{field_name}");
471 let rust_companion_field_name =
472 crate::naming::Naming::field_name(&companion_field_name);
473
474 let companion_element_type = self.get_companion_element_type(type_code);
476
477 let is_optional = true;
480 let is_companion_repeating = element
481 .max
482 .as_ref()
483 .is_some_and(|max| max == "*" || max.parse::<u32>().unwrap_or(1) > 1);
484
485 let mut companion_field = RustField::new(
487 rust_companion_field_name.clone(),
488 RustType::Custom(companion_element_type),
489 );
490 companion_field.is_optional = is_optional;
491 companion_field.is_repeating = is_companion_repeating;
492
493 companion_field.doc_comment = Some(format!(
495 "Extension element for the '{field_name}' primitive field. Contains metadata and extensions."
496 ));
497
498 if rust_companion_field_name != companion_field_name {
500 companion_field =
501 companion_field.with_serde_rename(companion_field_name);
502 }
503
504 return Ok(Some(companion_field));
505 }
506 }
507 }
508 }
509 Ok(None)
510 }
511
512 fn is_primitive_type(&self, type_code: &str) -> bool {
514 matches!(
515 type_code,
516 "boolean"
517 | "integer"
518 | "positiveInt"
519 | "unsignedInt"
520 | "decimal"
521 | "string"
522 | "code"
523 | "id"
524 | "markdown"
525 | "uri"
526 | "url"
527 | "canonical"
528 | "oid"
529 | "uuid"
530 | "base64Binary"
531 | "xhtml"
532 | "date"
533 | "dateTime"
534 | "time"
535 | "instant"
536 )
537 }
538
539 fn get_companion_element_type(&self, _primitive_type: &str) -> String {
542 "Element".to_string()
543 }
544
545 pub fn create_primitive_field_macro_call(
548 &mut self,
549 element: &ElementDefinition,
550 ) -> CodegenResult<Option<RustField>> {
551 let field_name = element.path.split('.').next_back().unwrap_or("unknown");
553
554 if field_name.ends_with("[x]") {
556 return Ok(None);
557 }
558
559 if let Some(element_types) = &element.element_type {
561 if let Some(first_type) = element_types.first() {
562 if let Some(type_code) = &first_type.code {
563 if self.is_primitive_type(type_code) {
565 let is_optional = element.min.unwrap_or(0) == 0;
567
568 let rust_type_name = match type_code.as_str() {
570 "string" => "StringType",
571 "boolean" => "BooleanType",
572 "integer" => "IntegerType",
573 "decimal" => "DecimalType",
574 "dateTime" => "DateTimeType",
575 "date" => "DateType",
576 "time" => "TimeType",
577 "uri" => "UriType",
578 "canonical" => "CanonicalType",
579 "base64Binary" => "Base64BinaryType",
580 "instant" => "InstantType",
581 "positiveInt" => "PositiveIntType",
582 "unsignedInt" => "UnsignedIntType",
583 "id" => "IdType",
584 "oid" => "OidType",
585 "uuid" => "UuidType",
586 "code" => "CodeType",
587 "markdown" => "MarkdownType",
588 "url" => "UrlType",
589 _ => return Ok(None), };
591
592 let rust_type =
594 RustType::Custom(format!("crate::primitives::{rust_type_name}"));
595 let mut field = RustField::new(field_name.to_string(), rust_type);
596
597 if is_optional {
598 field = field.optional();
599 }
600
601 field = field.with_doc(format!("Field: {field_name}"));
602
603 return Ok(Some(field));
607 }
608 }
609 }
610 }
611
612 Ok(None)
613 }
614
615 pub fn create_companion_extension_field(&self, field_name: &str) -> CodegenResult<RustField> {
618 let companion_name = format!("_{field_name}");
619 let element_type = RustType::Custom("crate::datatypes::element::Element".to_string());
620
621 let mut companion_field = RustField::new(companion_name.clone(), element_type);
622 companion_field = companion_field.optional(); companion_field = companion_field.with_doc(format!(
624 "Extension element for the '{field_name}' primitive field. Contains metadata and extensions."
625 ));
626 companion_field = companion_field.with_serde_rename(companion_name);
627
628 Ok(companion_field)
629 }
630}
631
632#[cfg(test)]
633mod tests {
634 use super::*;
635 use crate::fhir_types::ElementType;
636
637 #[test]
638 fn test_to_rust_field_name() {
639 assert_eq!(crate::naming::Naming::field_name("active"), "active");
641 assert_eq!(crate::naming::Naming::field_name("name"), "name");
642
643 assert_eq!(crate::naming::Naming::field_name("birthDate"), "birth_date");
645 assert_eq!(
646 crate::naming::Naming::field_name("multipleBirthBoolean"),
647 "multiple_birth_boolean"
648 );
649
650 assert_eq!(crate::naming::Naming::field_name("value[x]"), "value");
652 assert_eq!(crate::naming::Naming::field_name("deceased[x]"), "deceased");
653
654 assert_eq!(crate::naming::Naming::field_name("type"), "type_");
656 assert_eq!(crate::naming::Naming::field_name("use"), "use_");
657 assert_eq!(crate::naming::Naming::field_name("ref"), "ref_");
658 assert_eq!(crate::naming::Naming::field_name("for"), "for_");
659 assert_eq!(crate::naming::Naming::field_name("match"), "match_");
660 }
661
662 #[test]
663 fn test_handle_rust_keywords() {
664 assert_eq!(FieldGenerator::handle_rust_keywords("type"), "type_");
665 assert_eq!(FieldGenerator::handle_rust_keywords("struct"), "struct_");
666 assert_eq!(FieldGenerator::handle_rust_keywords("impl"), "impl_");
667 assert_eq!(FieldGenerator::handle_rust_keywords("normal"), "normal");
668 }
669
670 #[test]
671 fn test_extract_field_name_from_path() {
672 assert_eq!(
673 FieldGenerator::extract_field_name_from_path("Patient.active"),
674 "active"
675 );
676 assert_eq!(
677 FieldGenerator::extract_field_name_from_path("Bundle.entry.resource"),
678 "resource"
679 );
680 assert_eq!(
681 FieldGenerator::extract_field_name_from_path("unknown"),
682 "unknown"
683 );
684 }
685
686 #[test]
687 fn test_requires_serde_rename() {
688 assert!(!FieldGenerator::requires_serde_rename("active", "active"));
689 assert!(FieldGenerator::requires_serde_rename(
690 "birthDate",
691 "birth_date"
692 ));
693 assert!(FieldGenerator::requires_serde_rename("type", "type_"));
694 }
695
696 #[test]
697 fn test_determine_field_cardinality() {
698 use crate::fhir_types::ElementDefinition;
699
700 let optional_element = ElementDefinition {
702 id: Some("Patient.active".to_string()),
703 path: "Patient.active".to_string(),
704 short: Some("Whether this patient record is in active use".to_string()),
705 definition: None,
706 min: Some(0),
707 max: Some("1".to_string()),
708 element_type: Some(vec![ElementType {
709 code: Some("boolean".to_string()),
710 target_profile: None,
711 }]),
712 fixed: None,
713 pattern: None,
714 binding: None,
715 constraint: None,
716 };
717
718 let (is_optional, is_array) =
719 FieldGenerator::determine_field_cardinality(&optional_element);
720 assert!(is_optional);
721 assert!(!is_array);
722
723 let array_element = ElementDefinition {
725 id: Some("Patient.name".to_string()),
726 path: "Patient.name".to_string(),
727 short: Some("A name associated with the patient".to_string()),
728 definition: None,
729 min: Some(1),
730 max: Some("*".to_string()),
731 element_type: Some(vec![ElementType {
732 code: Some("HumanName".to_string()),
733 target_profile: None,
734 }]),
735 fixed: None,
736 pattern: None,
737 binding: None,
738 constraint: None,
739 };
740
741 let (is_optional, is_array) = FieldGenerator::determine_field_cardinality(&array_element);
742 assert!(!is_optional);
743 assert!(is_array);
744
745 let required_element = ElementDefinition {
747 id: Some("Patient.id".to_string()),
748 path: "Patient.id".to_string(),
749 short: Some("Logical id of this artifact".to_string()),
750 definition: None,
751 min: Some(1),
752 max: Some("1".to_string()),
753 element_type: Some(vec![ElementType {
754 code: Some("id".to_string()),
755 target_profile: None,
756 }]),
757 fixed: None,
758 pattern: None,
759 binding: None,
760 constraint: None,
761 };
762
763 let (is_optional, is_array) =
764 FieldGenerator::determine_field_cardinality(&required_element);
765 assert!(!is_optional);
766 assert!(!is_array);
767 }
768
769 #[test]
770 fn test_create_field_from_element() {
771 use crate::config::CodegenConfig;
772 use std::collections::HashMap;
773
774 let config = CodegenConfig::default();
775 let type_cache = HashMap::new();
776 let mut value_set_manager = ValueSetManager::new();
777 let mut field_generator = FieldGenerator::new(&config, &type_cache, &mut value_set_manager);
778
779 let boolean_element = ElementDefinition {
781 id: Some("Patient.active".to_string()),
782 path: "Patient.active".to_string(),
783 short: Some("Whether this patient record is in active use".to_string()),
784 definition: None,
785 min: Some(0),
786 max: Some("1".to_string()),
787 element_type: Some(vec![ElementType {
788 code: Some("boolean".to_string()),
789 target_profile: None,
790 }]),
791 fixed: None,
792 pattern: None,
793 binding: None,
794 constraint: None,
795 };
796
797 let result = field_generator.create_field_from_element(&boolean_element);
798 assert!(result.is_ok());
799
800 let field = result.unwrap();
801 assert!(field.is_some());
802
803 let field = field.unwrap();
804 assert_eq!(field.name, "active");
805 assert!(field.is_optional);
806 assert!(
808 matches!(field.field_type, RustType::Custom(ref type_name) if type_name == "BooleanType")
809 );
810 }
811
812 #[test]
813 fn test_macro_call_generation() {
814 use crate::config::CodegenConfig;
815 use std::collections::HashMap;
816
817 let config = CodegenConfig {
818 use_macro_calls: true, ..CodegenConfig::default()
820 };
821
822 let type_cache = HashMap::new();
823 let mut value_set_manager = ValueSetManager::new();
824 let mut field_generator = FieldGenerator::new(&config, &type_cache, &mut value_set_manager);
825
826 let boolean_element = ElementDefinition {
828 id: Some("Patient.active".to_string()),
829 path: "Patient.active".to_string(),
830 short: Some("Whether this patient record is in active use".to_string()),
831 definition: None,
832 min: Some(0),
833 max: Some("1".to_string()),
834 element_type: Some(vec![ElementType {
835 code: Some("boolean".to_string()),
836 target_profile: None,
837 }]),
838 fixed: None,
839 pattern: None,
840 binding: None,
841 constraint: None,
842 };
843
844 let result = field_generator.create_fields_from_element(&boolean_element);
845 assert!(result.is_ok());
846
847 let fields = result.unwrap();
848 assert_eq!(fields.len(), 2); let primitive_field = &fields[0];
852 assert_eq!(primitive_field.name, "active");
853 assert!(primitive_field.is_optional);
854 assert!(primitive_field
855 .field_type
856 .to_string()
857 .contains("BooleanType"));
858 assert_eq!(
859 primitive_field.doc_comment,
860 Some("Field: active".to_string())
861 );
862
863 let companion_field = &fields[1];
865 assert_eq!(companion_field.name, "_active");
866 assert!(companion_field.is_optional); assert!(companion_field.field_type.to_string().contains("Element"));
868 }
869
870 #[test]
871 fn test_companion_fields_always_optional() {
872 use crate::config::CodegenConfig;
873 use std::collections::HashMap;
874
875 let config = CodegenConfig::default();
876 let type_cache = HashMap::new();
877 let mut value_set_manager = ValueSetManager::new();
878 let mut field_generator = FieldGenerator::new(&config, &type_cache, &mut value_set_manager);
879
880 let required_boolean_element = ElementDefinition {
882 id: Some("Patient.active".to_string()),
883 path: "Patient.active".to_string(),
884 short: Some("Whether this patient record is in active use".to_string()),
885 definition: None,
886 min: Some(1), max: Some("1".to_string()),
888 element_type: Some(vec![ElementType {
889 code: Some("boolean".to_string()),
890 target_profile: None,
891 }]),
892 fixed: None,
893 pattern: None,
894 binding: None,
895 constraint: None,
896 };
897
898 let result = field_generator.create_fields_from_element(&required_boolean_element);
899 assert!(result.is_ok());
900
901 let fields = result.unwrap();
902 assert_eq!(fields.len(), 2); let main_field = &fields[0];
905 let companion_field = &fields[1];
906
907 assert_eq!(main_field.name, "active");
909 assert!(!main_field.is_optional); assert_eq!(companion_field.name, "_active");
913 assert!(companion_field.is_optional); let required_string_element = ElementDefinition {
917 id: Some("Patient.name".to_string()),
918 path: "Patient.name".to_string(),
919 short: Some("Patient name".to_string()),
920 definition: None,
921 min: Some(1), max: Some("1".to_string()),
923 element_type: Some(vec![ElementType {
924 code: Some("string".to_string()),
925 target_profile: None,
926 }]),
927 fixed: None,
928 pattern: None,
929 binding: None,
930 constraint: None,
931 };
932
933 let result = field_generator.create_fields_from_element(&required_string_element);
934 assert!(result.is_ok());
935
936 let fields = result.unwrap();
937 assert_eq!(fields.len(), 2); let main_field = &fields[0];
940 let companion_field = &fields[1];
941
942 assert_eq!(main_field.name, "name");
944 assert!(!main_field.is_optional); assert_eq!(companion_field.name, "_name");
948 assert!(companion_field.is_optional); }
950
951 #[test]
952 fn test_choice_type_field_generation() {
953 use crate::config::CodegenConfig;
954 use std::collections::HashMap;
955
956 let config = CodegenConfig::default();
957 let type_cache = HashMap::new();
958 let mut value_set_manager = ValueSetManager::new();
959
960 let mut field_generator = FieldGenerator::new(&config, &type_cache, &mut value_set_manager);
961
962 let element = ElementDefinition {
964 id: Some("Observation.effective[x]".to_string()),
965 path: "Observation.effective[x]".to_string(),
966 short: Some("Clinically relevant time/time-period for observation".to_string()),
967 definition: Some(
968 "The time or time-period the observed value is asserted as being true.".to_string(),
969 ),
970 min: Some(0),
971 max: Some("1".to_string()),
972 element_type: Some(vec![
973 ElementType {
974 code: Some("dateTime".to_string()),
975 target_profile: None,
976 },
977 ElementType {
978 code: Some("Period".to_string()),
979 target_profile: None,
980 },
981 ElementType {
982 code: Some("Timing".to_string()),
983 target_profile: None,
984 },
985 ElementType {
986 code: Some("instant".to_string()),
987 target_profile: None,
988 },
989 ]),
990 fixed: None,
991 pattern: None,
992 binding: None,
993 constraint: None,
994 };
995
996 let fields = field_generator
998 .create_fields_from_element(&element)
999 .unwrap();
1000
1001 assert_eq!(
1003 fields.len(),
1004 4,
1005 "Should generate 4 fields for 4 choice types"
1006 );
1007
1008 let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
1010 assert!(field_names.contains(&"effective_date_time"));
1011 assert!(field_names.contains(&"effective_period"));
1012 assert!(field_names.contains(&"effective_timing"));
1013 assert!(field_names.contains(&"effective_instant"));
1014
1015 assert!(fields[0]
1017 .serde_attributes
1018 .contains(&"rename = \"effectiveDateTime\"".to_string()));
1019 assert!(fields[1]
1020 .serde_attributes
1021 .contains(&"rename = \"effectivePeriod\"".to_string()));
1022 assert!(fields[2]
1023 .serde_attributes
1024 .contains(&"rename = \"effectiveTiming\"".to_string()));
1025 assert!(fields[3]
1026 .serde_attributes
1027 .contains(&"rename = \"effectiveInstant\"".to_string()));
1028
1029 for field in &fields {
1031 assert!(field.is_optional, "Choice type fields should be optional");
1032 }
1033 }
1034
1035 #[test]
1036 fn test_choice_type_documentation() {
1037 use crate::config::CodegenConfig;
1038 use std::collections::HashMap;
1039
1040 let config = CodegenConfig::default();
1041 let type_cache = HashMap::new();
1042 let mut value_set_manager = ValueSetManager::new();
1043
1044 let mut field_generator = FieldGenerator::new(&config, &type_cache, &mut value_set_manager);
1045
1046 let element = ElementDefinition {
1048 id: Some("Observation.value[x]".to_string()),
1049 path: "Observation.value[x]".to_string(),
1050 short: Some("Actual result".to_string()),
1051 definition: Some(
1052 "The information determined as a result of making the observation.".to_string(),
1053 ),
1054 min: Some(0),
1055 max: Some("1".to_string()),
1056 element_type: Some(vec![
1057 ElementType {
1058 code: Some("Quantity".to_string()),
1059 target_profile: None,
1060 },
1061 ElementType {
1062 code: Some("string".to_string()),
1063 target_profile: None,
1064 },
1065 ]),
1066 fixed: None,
1067 pattern: None,
1068 binding: None,
1069 constraint: None,
1070 };
1071
1072 let fields = field_generator
1074 .create_fields_from_element(&element)
1075 .unwrap();
1076
1077 assert_eq!(fields.len(), 2);
1079
1080 let quantity_field = &fields[0];
1081 let string_field = &fields[1];
1082
1083 assert!(quantity_field
1084 .doc_comment
1085 .as_ref()
1086 .unwrap()
1087 .contains("(Quantity)"));
1088 assert!(string_field
1089 .doc_comment
1090 .as_ref()
1091 .unwrap()
1092 .contains("(string)"));
1093 }
1094}