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                    field.is_repeating = is_array;
112
113                    // Add documentation
114                    field.doc_comment =
115                        DocumentationGenerator::generate_choice_field_documentation_with_binding(
116                            element,
117                            type_code,
118                            self.value_set_manager,
119                        );
120
121                    // Add serde rename for the original FHIR field name with type suffix
122                    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    /// Create a single RustField from an ElementDefinition
137    fn create_single_field_from_element(
138        &mut self,
139        element: &ElementDefinition,
140    ) -> CodegenResult<Option<RustField>> {
141        // Get the field name from the path (last segment)
142        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        // Determine if this field is optional (min = 0)
146        let is_optional = element.min.unwrap_or(0) == 0;
147
148        // Determine if this field is an array (max = "*" or > 1)
149        let is_array = element
150            .max
151            .as_ref()
152            .is_some_and(|max| max == "*" || max.parse::<u32>().unwrap_or(1) > 1);
153
154        // Get the field type
155        let field_type = if let Some(element_types) = &element.element_type {
156            // Check if this should use a nested struct type
157            if self.should_use_nested_struct_type(element, element_types) {
158                self.build_nested_struct_type(element, is_array)
159            } else {
160                // The `language` field is always a plain code string across all FHIR versions.
161                // R5 adds a `required` binding to all-languages, but we keep it as StringType
162                // so the hardcoded trait-impl generator stays consistent with R4.
163                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                    // Guard against naming collision: if the resolved binding enum has the
179                    // same name as the containing resource/type, the field would create a
180                    // self-referential struct. Fall back to StringType in that case.
181                    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            // No type specified, default to StringType
199            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        // Create the field
207        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        // Add documentation if available
212        field.doc_comment = DocumentationGenerator::generate_field_documentation_with_binding(
213            element,
214            self.value_set_manager,
215        );
216
217        // Add serde rename if the field name was changed
218        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    /// Legacy method for backward compatibility
226    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    /// Convert a FHIR field name to a valid Rust field name
235    pub fn to_rust_field_name(name: &str) -> String {
236        // Handle FHIR choice types (fields ending with [x])
237        let clean_name = if name.ends_with("[x]") {
238            name.strip_suffix("[x]").unwrap_or(name)
239        } else {
240            name
241        };
242
243        // Handle field name conflicts with inherited base field
244        let conflict_resolved_name = if clean_name == "base" {
245            // Rename FHIR 'base' elements to avoid conflict with the inherited base field
246            "base_definition"
247        } else {
248            clean_name
249        };
250
251        // Convert to snake_case and handle Rust keywords
252        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        // Handle Rust keywords by appending underscore
265        Self::handle_rust_keywords(&snake_case)
266    }
267
268    /// Handle Rust keywords by appending underscore
269    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    /// Convert FHIR type code to snake_case for field suffix
297    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    /// Capitalize the first character of a string
312    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    /// Check if a field should use a nested struct type instead of BackboneElement
321    fn should_use_nested_struct_type(
322        &self,
323        element: &ElementDefinition,
324        element_types: &[ElementType],
325    ) -> bool {
326        // Check if this element is a BackboneElement and we have nested elements for it
327        if let Some(first_type) = element_types.first() {
328            if let Some(code) = &first_type.code {
329                if code == "BackboneElement" {
330                    // Extract parent struct name and field name from the path
331                    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                        // For nested structures, we need to build the correct nested struct name
337                        // For example: Bundle.entry.search -> BundleEntrySearch
338                        let nested_struct_name = if path_parts.len() == 2 {
339                            // Direct nested struct (e.g., Bundle.entry -> BundleEntry)
340                            format!(
341                                "{parent_struct_name}{field_pascal}",
342                                field_pascal = Naming::to_pascal_case(field_name)
343                            )
344                        } else {
345                            // Sub-nested struct (e.g., Bundle.entry.search -> BundleEntrySearch)
346                            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                        // Check if we have generated this nested struct
354                        return self.type_cache.contains_key(&nested_struct_name);
355                    }
356                }
357            }
358        }
359        false
360    }
361
362    /// Build the correct nested struct type based on the full path
363    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                // Direct nested struct (e.g., Bundle.entry -> BundleEntry)
369                format!(
370                    "{parent_struct_name}{part_pascal}",
371                    part_pascal = Naming::to_pascal_case(path_parts[1])
372                )
373            } else {
374                // Sub-nested struct (e.g., Bundle.entry.search -> BundleEntrySearch)
375                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    /// Extract field name from element path (utility method)
396    pub fn extract_field_name_from_path(path: &str) -> &str {
397        path.split('.').next_back().unwrap_or("unknown")
398    }
399
400    /// Check if a field name requires serde rename
401    pub fn requires_serde_rename(original_name: &str, rust_field_name: &str) -> bool {
402        original_name != rust_field_name
403    }
404
405    /// Determine field cardinality from ElementDefinition
406    pub fn determine_field_cardinality(element: &ElementDefinition) -> (bool, bool) {
407        // Determine if this field is optional (min = 0)
408        let is_optional = element.min.unwrap_or(0) == 0;
409
410        // Determine if this field is an array (max = "*" or > 1)
411        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    /// Extract choice type information from a StructureDefinition
420    /// Returns a vector of (base_name, type_codes) tuples for each choice type found
421    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        // Get elements from differential or snapshot
427        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; // No elements to process
433        };
434
435        for element in elements {
436            // Check if this is a choice type field (ends with [x])
437            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                // Extract type codes from element types
442                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    /// Create a companion field for primitive types that handles extensions
459    fn create_companion_field_if_primitive(
460        &mut self,
461        element: &ElementDefinition,
462    ) -> CodegenResult<Option<RustField>> {
463        // Check if this element has a primitive type
464        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                    // Check if this is a primitive type
468                    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                        // Map primitive type to companion element type
475                        let companion_element_type = self.get_companion_element_type(type_code);
476
477                        // Companion elements are always optional in FHIR specification
478                        // They contain extensions and metadata for primitive fields
479                        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                        // Create the companion field
486                        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                        // Add documentation
494                        companion_field.doc_comment = Some(format!(
495                            "Extension element for the '{field_name}' primitive field. Contains metadata and extensions."
496                        ));
497
498                        // Add serde rename if needed
499                        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    /// Check if a type code represents a FHIR primitive type
513    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    /// Get the companion element type for a primitive type
540    /// All companion elements now use the base Element type
541    fn get_companion_element_type(&self, _primitive_type: &str) -> String {
542        "Element".to_string()
543    }
544
545    /// Generate explicit primitive fields with companion extension fields
546    /// This creates two RustField instances: the primitive field and its companion Element field
547    pub fn create_primitive_field_macro_call(
548        &mut self,
549        element: &ElementDefinition,
550    ) -> CodegenResult<Option<RustField>> {
551        // Get the field name from the path (last segment)
552        let field_name = element.path.split('.').next_back().unwrap_or("unknown");
553
554        // Skip choice types for now as they need special handling
555        if field_name.ends_with("[x]") {
556            return Ok(None);
557        }
558
559        // Check if this element has a primitive type
560        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                    // Check if this is a primitive type
564                    if self.is_primitive_type(type_code) {
565                        // Determine if this field is optional (min = 0)
566                        let is_optional = element.min.unwrap_or(0) == 0;
567
568                        // Map FHIR primitive type to Rust type
569                        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), // Unknown primitive type
590                        };
591
592                        // Create the main primitive field with proper type
593                        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                        // Note: The companion extension field will be handled separately by the caller
604                        // to maintain clean separation of concerns
605
606                        return Ok(Some(field));
607                    }
608                }
609            }
610        }
611
612        Ok(None)
613    }
614
615    /// Generate the companion extension field for a primitive field
616    /// Returns a RustField representing the _fieldname extension companion
617    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 fields are always optional
623        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        // Test basic field names
640        assert_eq!(crate::naming::Naming::field_name("active"), "active");
641        assert_eq!(crate::naming::Naming::field_name("name"), "name");
642
643        // Test PascalCase to snake_case conversion
644        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        // Test choice types with [x] suffix
651        assert_eq!(crate::naming::Naming::field_name("value[x]"), "value");
652        assert_eq!(crate::naming::Naming::field_name("deceased[x]"), "deceased");
653
654        // Test Rust keywords
655        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        // Test optional field
701        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        // Test required array field
724        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        // Test required single field
746        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        // Test simple boolean field
780        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        // The field type should be BooleanType (custom type from the type mapping)
807        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, // Enable macro call generation
819            ..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        // Test boolean field with macro calls enabled
827        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); // Should return primitive field + companion extension field
849
850        // Check the main primitive field
851        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        // Check the companion extension field
864        let companion_field = &fields[1];
865        assert_eq!(companion_field.name, "_active");
866        assert!(companion_field.is_optional); // Companion fields are always optional
867        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        // Test required boolean field (min = 1) - companion should still be optional
881        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), // Required field
887            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); // Main field + companion field
903
904        let main_field = &fields[0];
905        let companion_field = &fields[1];
906
907        // Main field should be required (not optional) since min = 1
908        assert_eq!(main_field.name, "active");
909        assert!(!main_field.is_optional); // Required field
910
911        // Companion field should ALWAYS be optional regardless of main field
912        assert_eq!(companion_field.name, "_active");
913        assert!(companion_field.is_optional); // Should always be optional
914
915        // Test required string field (min = 1) - companion should still be optional
916        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), // Required field
922            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); // Main field + companion field
938
939        let main_field = &fields[0];
940        let companion_field = &fields[1];
941
942        // Main field should be required (not optional) since min = 1
943        assert_eq!(main_field.name, "name");
944        assert!(!main_field.is_optional); // Required field
945
946        // Companion field should ALWAYS be optional regardless of main field
947        assert_eq!(companion_field.name, "_name");
948        assert!(companion_field.is_optional); // Should always be optional
949    }
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        // Create a test element definition for a choice type
963        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        // Generate fields
997        let fields = field_generator
998            .create_fields_from_element(&element)
999            .unwrap();
1000
1001        // Verify that multiple fields were generated
1002        assert_eq!(
1003            fields.len(),
1004            4,
1005            "Should generate 4 fields for 4 choice types"
1006        );
1007
1008        // Verify field names
1009        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        // Verify serde rename attributes
1016        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        // Verify all fields are optional
1030        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        // Create a test element definition for a choice type
1047        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        // Generate fields
1073        let fields = field_generator
1074            .create_fields_from_element(&element)
1075            .unwrap();
1076
1077        // Verify that documentation includes type information
1078        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}