Skip to main content

rh_codegen/generators/
field_generator.rs

1//! Field generation functionality for FHIR types
2//!
3//! This module handles the creation of Rust fields from FHIR ElementDefinitions,
4//! including field naming, type mapping, and metadata assignment.
5
6use 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
17/// Generator for creating Rust fields from FHIR ElementDefinitions
18pub 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    /// Create a new field generator
26    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    /// Create RustField(s) from an ElementDefinition
39    /// Returns a vector because choice types generate multiple fields
40    pub fn create_fields_from_element(
41        &mut self,
42        element: &ElementDefinition,
43    ) -> CodegenResult<Vec<RustField>> {
44        // Get the field name from the path (last segment)
45        let field_name = element.path.split('.').next_back().unwrap_or("unknown");
46
47        // Check if this is a choice type (ends with [x])
48        if field_name.ends_with("[x]") {
49            return self.create_choice_type_fields(element);
50        }
51
52        // Check if this is a primitive type that should use explicit field generation
53        if self.config.use_macro_calls {
54            if let Some(primitive_field) = self.create_primitive_field_macro_call(element)? {
55                // Also create the companion extension field
56                let companion_field = self.create_companion_extension_field(field_name)?;
57                return Ok(vec![primitive_field, companion_field]);
58            }
59        }
60
61        // Regular field - create single field and possibly companion field
62        let mut fields = Vec::new();
63        if let Some(field) = self.create_single_field_from_element(element)? {
64            fields.push(field);
65
66            // Check if we need to create a companion field for primitive types
67            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    /// Create multiple fields for FHIR choice types (e.g., value[x], effective[x])
75    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        // Determine if this field is optional (min = 0)
84        let is_optional = element.min.unwrap_or(0) == 0;
85
86        // Determine if this field is an array (max = "*" or > 1)
87        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                    // Create field name: base_name + type_code in snake_case
96                    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                    // Map the type
101                    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                    // Create the field
109                    let mut field = RustField::new(rust_field_name.clone(), field_type);
110                    field.is_optional = is_optional;
111
112                    // Add documentation
113                    field.doc_comment =
114                        DocumentationGenerator::generate_choice_field_documentation_with_binding(
115                            element,
116                            type_code,
117                            self.value_set_manager,
118                        );
119
120                    // Add serde rename for the original FHIR field name with type suffix
121                    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    /// Create a single RustField from an ElementDefinition
136    fn create_single_field_from_element(
137        &mut self,
138        element: &ElementDefinition,
139    ) -> CodegenResult<Option<RustField>> {
140        // Get the field name from the path (last segment)
141        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        // Determine if this field is optional (min = 0)
145        let is_optional = element.min.unwrap_or(0) == 0;
146
147        // Determine if this field is an array (max = "*" or > 1)
148        let is_array = element
149            .max
150            .as_ref()
151            .is_some_and(|max| max == "*" || max.parse::<u32>().unwrap_or(1) > 1);
152
153        // Get the field type
154        let field_type = if let Some(element_types) = &element.element_type {
155            // Check if this should use a nested struct type
156            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            // No type specified, default to StringType
168            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        // Create the field
176        let mut field = RustField::new(rust_field_name.clone(), field_type);
177        field.is_optional = is_optional;
178
179        // Add documentation if available
180        field.doc_comment = DocumentationGenerator::generate_field_documentation_with_binding(
181            element,
182            self.value_set_manager,
183        );
184
185        // Add serde rename if the field name was changed
186        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    /// Legacy method for backward compatibility
194    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    /// Convert a FHIR field name to a valid Rust field name
203    pub fn to_rust_field_name(name: &str) -> String {
204        // Handle FHIR choice types (fields ending with [x])
205        let clean_name = if name.ends_with("[x]") {
206            name.strip_suffix("[x]").unwrap_or(name)
207        } else {
208            name
209        };
210
211        // Handle field name conflicts with inherited base field
212        let conflict_resolved_name = if clean_name == "base" {
213            // Rename FHIR 'base' elements to avoid conflict with the inherited base field
214            "base_definition"
215        } else {
216            clean_name
217        };
218
219        // Convert to snake_case and handle Rust keywords
220        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        // Handle Rust keywords by appending underscore
233        Self::handle_rust_keywords(&snake_case)
234    }
235
236    /// Handle Rust keywords by appending underscore
237    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    /// Convert FHIR type code to snake_case for field suffix
265    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    /// Capitalize the first character of a string
280    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    /// Check if a field should use a nested struct type instead of BackboneElement
289    fn should_use_nested_struct_type(
290        &self,
291        element: &ElementDefinition,
292        element_types: &[ElementType],
293    ) -> bool {
294        // Check if this element is a BackboneElement and we have nested elements for it
295        if let Some(first_type) = element_types.first() {
296            if let Some(code) = &first_type.code {
297                if code == "BackboneElement" {
298                    // Extract parent struct name and field name from the path
299                    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                        // For nested structures, we need to build the correct nested struct name
305                        // For example: Bundle.entry.search -> BundleEntrySearch
306                        let nested_struct_name = if path_parts.len() == 2 {
307                            // Direct nested struct (e.g., Bundle.entry -> BundleEntry)
308                            format!(
309                                "{parent_struct_name}{field_pascal}",
310                                field_pascal = Naming::to_pascal_case(field_name)
311                            )
312                        } else {
313                            // Sub-nested struct (e.g., Bundle.entry.search -> BundleEntrySearch)
314                            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                        // Check if we have generated this nested struct
322                        return self.type_cache.contains_key(&nested_struct_name);
323                    }
324                }
325            }
326        }
327        false
328    }
329
330    /// Build the correct nested struct type based on the full path
331    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                // Direct nested struct (e.g., Bundle.entry -> BundleEntry)
337                format!(
338                    "{parent_struct_name}{part_pascal}",
339                    part_pascal = Naming::to_pascal_case(path_parts[1])
340                )
341            } else {
342                // Sub-nested struct (e.g., Bundle.entry.search -> BundleEntrySearch)
343                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    /// Extract field name from element path (utility method)
364    pub fn extract_field_name_from_path(path: &str) -> &str {
365        path.split('.').next_back().unwrap_or("unknown")
366    }
367
368    /// Check if a field name requires serde rename
369    pub fn requires_serde_rename(original_name: &str, rust_field_name: &str) -> bool {
370        original_name != rust_field_name
371    }
372
373    /// Determine field cardinality from ElementDefinition
374    pub fn determine_field_cardinality(element: &ElementDefinition) -> (bool, bool) {
375        // Determine if this field is optional (min = 0)
376        let is_optional = element.min.unwrap_or(0) == 0;
377
378        // Determine if this field is an array (max = "*" or > 1)
379        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    /// Extract choice type information from a StructureDefinition
388    /// Returns a vector of (base_name, type_codes) tuples for each choice type found
389    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        // Get elements from differential or snapshot
395        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; // No elements to process
401        };
402
403        for element in elements {
404            // Check if this is a choice type field (ends with [x])
405            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                // Extract type codes from element types
410                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    /// Create a companion field for primitive types that handles extensions
427    fn create_companion_field_if_primitive(
428        &mut self,
429        element: &ElementDefinition,
430    ) -> CodegenResult<Option<RustField>> {
431        // Check if this element has a primitive type
432        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                    // Check if this is a primitive type
436                    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                        // Map primitive type to companion element type
443                        let companion_element_type = self.get_companion_element_type(type_code);
444
445                        // Companion elements are always optional in FHIR specification
446                        // They contain extensions and metadata for primitive fields
447                        let is_optional = true;
448
449                        // Create the companion field
450                        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                        // Add documentation
457                        companion_field.doc_comment = Some(format!(
458                            "Extension element for the '{field_name}' primitive field. Contains metadata and extensions."
459                        ));
460
461                        // Add serde rename if needed
462                        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    /// Check if a type code represents a FHIR primitive type
476    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    /// Get the companion element type for a primitive type
503    /// All companion elements now use the base Element type
504    fn get_companion_element_type(&self, _primitive_type: &str) -> String {
505        "Element".to_string()
506    }
507
508    /// Generate explicit primitive fields with companion extension fields
509    /// This creates two RustField instances: the primitive field and its companion Element field
510    pub fn create_primitive_field_macro_call(
511        &mut self,
512        element: &ElementDefinition,
513    ) -> CodegenResult<Option<RustField>> {
514        // Get the field name from the path (last segment)
515        let field_name = element.path.split('.').next_back().unwrap_or("unknown");
516
517        // Skip choice types for now as they need special handling
518        if field_name.ends_with("[x]") {
519            return Ok(None);
520        }
521
522        // Check if this element has a primitive type
523        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                    // Check if this is a primitive type
527                    if self.is_primitive_type(type_code) {
528                        // Determine if this field is optional (min = 0)
529                        let is_optional = element.min.unwrap_or(0) == 0;
530
531                        // Map FHIR primitive type to Rust type
532                        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), // Unknown primitive type
553                        };
554
555                        // Create the main primitive field with proper type
556                        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                        // Note: The companion extension field will be handled separately by the caller
567                        // to maintain clean separation of concerns
568
569                        return Ok(Some(field));
570                    }
571                }
572            }
573        }
574
575        Ok(None)
576    }
577
578    /// Generate the companion extension field for a primitive field
579    /// Returns a RustField representing the _fieldname extension companion
580    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 fields are always optional
586        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        // Test basic field names
603        assert_eq!(crate::naming::Naming::field_name("active"), "active");
604        assert_eq!(crate::naming::Naming::field_name("name"), "name");
605
606        // Test PascalCase to snake_case conversion
607        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        // Test choice types with [x] suffix
614        assert_eq!(crate::naming::Naming::field_name("value[x]"), "value");
615        assert_eq!(crate::naming::Naming::field_name("deceased[x]"), "deceased");
616
617        // Test Rust keywords
618        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        // Test optional field
664        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        // Test required array field
687        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        // Test required single field
709        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        // Test simple boolean field
743        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        // The field type should be BooleanType (custom type from the type mapping)
770        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, // Enable macro call generation
782            ..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        // Test boolean field with macro calls enabled
790        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); // Should return primitive field + companion extension field
812
813        // Check the main primitive field
814        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        // Check the companion extension field
827        let companion_field = &fields[1];
828        assert_eq!(companion_field.name, "_active");
829        assert!(companion_field.is_optional); // Companion fields are always optional
830        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        // Test required boolean field (min = 1) - companion should still be optional
844        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), // Required field
850            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); // Main field + companion field
866
867        let main_field = &fields[0];
868        let companion_field = &fields[1];
869
870        // Main field should be required (not optional) since min = 1
871        assert_eq!(main_field.name, "active");
872        assert!(!main_field.is_optional); // Required field
873
874        // Companion field should ALWAYS be optional regardless of main field
875        assert_eq!(companion_field.name, "_active");
876        assert!(companion_field.is_optional); // Should always be optional
877
878        // Test required string field (min = 1) - companion should still be optional
879        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), // Required field
885            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); // Main field + companion field
901
902        let main_field = &fields[0];
903        let companion_field = &fields[1];
904
905        // Main field should be required (not optional) since min = 1
906        assert_eq!(main_field.name, "name");
907        assert!(!main_field.is_optional); // Required field
908
909        // Companion field should ALWAYS be optional regardless of main field
910        assert_eq!(companion_field.name, "_name");
911        assert!(companion_field.is_optional); // Should always be optional
912    }
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        // Create a test element definition for a choice type
926        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        // Generate fields
960        let fields = field_generator
961            .create_fields_from_element(&element)
962            .unwrap();
963
964        // Verify that multiple fields were generated
965        assert_eq!(
966            fields.len(),
967            4,
968            "Should generate 4 fields for 4 choice types"
969        );
970
971        // Verify field names
972        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        // Verify serde rename attributes
979        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        // Verify all fields are optional
993        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        // Create a test element definition for a choice type
1010        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        // Generate fields
1036        let fields = field_generator
1037            .create_fields_from_element(&element)
1038            .unwrap();
1039
1040        // Verify that documentation includes type information
1041        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}