Skip to main content

rh_codegen/generators/
accessor_trait_generator.rs

1//! Generator for accessor traits.
2use crate::fhir_types::{ElementDefinition, ElementType, StructureDefinition};
3use crate::rust_types::{RustTrait, RustTraitMethod, RustType};
4use crate::type_mapper::TypeMapper;
5use crate::value_sets::ValueSetManager;
6use crate::CodegenResult;
7
8#[derive(Default)]
9pub struct AccessorTraitGenerator {}
10
11impl AccessorTraitGenerator {
12    pub fn new() -> Self {
13        Self::default()
14    }
15
16    pub fn add_accessor_methods(
17        &self,
18        rust_trait: &mut RustTrait,
19        structure_def: &StructureDefinition,
20    ) -> CodegenResult<()> {
21        // Create TypeMapper for proper type resolution
22        let config = crate::config::CodegenConfig::default();
23        let mut value_set_manager = ValueSetManager::new();
24        let mut type_mapper = TypeMapper::new(&config, &mut value_set_manager);
25
26        let elements = structure_def
27            .differential
28            .as_ref()
29            .map_or(Vec::new(), |d| d.element.clone());
30
31        if elements.is_empty() {
32            if let Some(snapshot) = &structure_def.snapshot {
33                let snapshot_elements = snapshot.element.clone();
34                for element in &snapshot_elements {
35                    if self.should_generate_accessor(element, structure_def) {
36                        if let Some(method) =
37                            self.create_accessor_method(element, &mut type_mapper)?
38                        {
39                            rust_trait.add_method(method);
40                        }
41                    }
42                }
43            }
44        } else {
45            for element in &elements {
46                if self.should_generate_accessor(element, structure_def) {
47                    if let Some(method) = self.create_accessor_method(element, &mut type_mapper)? {
48                        rust_trait.add_method(method);
49                    }
50                }
51            }
52        }
53
54        self.add_choice_type_accessor_methods(rust_trait, structure_def)?;
55
56        Ok(())
57    }
58
59    fn should_generate_accessor(
60        &self,
61        element: &ElementDefinition,
62        structure_def: &StructureDefinition,
63    ) -> bool {
64        let field_path = &element.path;
65        let base_name = &structure_def.name;
66
67        // The path must start with the base name of the structure.
68        if !field_path.starts_with(base_name) {
69            return false;
70        }
71
72        // We are interested in direct fields of the resource, which have paths like "Patient.active".
73        // Splitting by '.' should result in exactly two parts.
74        let path_parts: Vec<&str> = field_path.split('.').collect();
75        if path_parts.len() != 2 {
76            return false;
77        }
78
79        // The first part must match the base name.
80        if path_parts[0] != base_name {
81            return false;
82        }
83
84        // We don't generate accessors for choice types here, they are handled separately.
85        let field_name = path_parts[1];
86        !field_name.ends_with("[x]")
87    }
88
89    fn create_accessor_method(
90        &self,
91        element: &ElementDefinition,
92        type_mapper: &mut TypeMapper,
93    ) -> CodegenResult<Option<RustTraitMethod>> {
94        let path_parts: Vec<&str> = element.path.split('.').collect();
95        let field_name = path_parts.last().unwrap().to_string();
96        let rust_field_name = crate::naming::Naming::field_name(&field_name);
97
98        let is_optional = element.min.unwrap_or(0) == 0;
99        let is_array = element.max.as_deref() == Some("*")
100            || element
101                .max
102                .as_deref()
103                .unwrap_or("1")
104                .parse::<i32>()
105                .unwrap_or(1)
106                > 1;
107
108        let Some(element_types) = element.element_type.as_ref() else {
109            return Ok(None);
110        };
111
112        // Check if this is a BackboneElement that should use a specific nested type
113        let rust_type = if self.is_backbone_element(element_types) {
114            self.get_nested_type_for_backbone_element(element, is_array)
115        } else {
116            // Use TypeMapper to get the correct type including enum bindings
117            type_mapper.map_fhir_type_with_binding(
118                element_types,
119                element.binding.as_ref(),
120                is_array,
121            )
122        };
123
124        // Convert the type for trait return types
125        let return_type = match rust_type {
126            RustType::Custom(_) => {
127                // For enum types or custom types, keep as-is
128                // Don't convert StringType to &str to maintain compatibility with implementations
129                rust_type.clone()
130            }
131            RustType::Vec(inner) => RustType::Slice(inner),
132            other => other,
133        };
134
135        let method = RustTraitMethod::new(rust_field_name)
136            .with_doc(format!("Returns a reference to the {field_name} field."))
137            .with_return_type(if is_optional && !is_array {
138                return_type.clone().wrap_in_option()
139            } else {
140                return_type.clone()
141            })
142            .with_body(format!("self.{field_name}"));
143
144        Ok(Some(method))
145    }
146
147    /// Check if the element types contain BackboneElement
148    fn is_backbone_element(&self, element_types: &[ElementType]) -> bool {
149        element_types
150            .iter()
151            .any(|et| et.code.as_deref() == Some("BackboneElement"))
152    }
153
154    /// Get the specific nested type for a BackboneElement field
155    fn get_nested_type_for_backbone_element(
156        &self,
157        element: &ElementDefinition,
158        is_array: bool,
159    ) -> RustType {
160        let path_parts: Vec<&str> = element.path.split('.').collect();
161
162        if path_parts.len() == 2 {
163            let resource_name = path_parts[0];
164            let field_name = path_parts[1];
165
166            // Generate the expected nested type name: ResourceFieldName (e.g., AccountCoverage)
167            let field_name_pascal = crate::naming::Naming::to_pascal_case(field_name);
168            let nested_type_name = format!("{resource_name}{field_name_pascal}");
169
170            let rust_type = RustType::Custom(nested_type_name);
171
172            if is_array {
173                RustType::Vec(Box::new(rust_type))
174            } else {
175                rust_type
176            }
177        } else {
178            // Fallback to BackboneElement if we can't determine the specific type
179            let rust_type = RustType::Custom("BackboneElement".to_string());
180            if is_array {
181                RustType::Vec(Box::new(rust_type))
182            } else {
183                rust_type
184            }
185        }
186    }
187
188    fn add_choice_type_accessor_methods(
189        &self,
190        _rust_trait: &mut RustTrait,
191        _structure_def: &StructureDefinition,
192    ) -> CodegenResult<()> {
193        // Implementation for choice type accessors can be added here.
194        Ok(())
195    }
196}