use crate::fhir_types::{ElementDefinition, ElementType, StructureDefinition};
use crate::rust_types::{RustTrait, RustTraitMethod, RustType};
use crate::type_mapper::TypeMapper;
use crate::value_sets::ValueSetManager;
use crate::CodegenResult;
#[derive(Default)]
pub struct AccessorTraitGenerator {}
impl AccessorTraitGenerator {
pub fn new() -> Self {
Self::default()
}
pub fn add_accessor_methods(
&self,
rust_trait: &mut RustTrait,
structure_def: &StructureDefinition,
) -> CodegenResult<()> {
let config = crate::config::CodegenConfig::default();
let mut value_set_manager = ValueSetManager::new();
let mut type_mapper = TypeMapper::new(&config, &mut value_set_manager);
let elements = structure_def
.differential
.as_ref()
.map_or(Vec::new(), |d| d.element.clone());
if elements.is_empty() {
if let Some(snapshot) = &structure_def.snapshot {
let snapshot_elements = snapshot.element.clone();
for element in &snapshot_elements {
if self.should_generate_accessor(element, structure_def) {
if let Some(method) =
self.create_accessor_method(element, &mut type_mapper)?
{
rust_trait.add_method(method);
}
}
}
}
} else {
for element in &elements {
if self.should_generate_accessor(element, structure_def) {
if let Some(method) = self.create_accessor_method(element, &mut type_mapper)? {
rust_trait.add_method(method);
}
}
}
}
self.add_choice_type_accessor_methods(rust_trait, structure_def)?;
Ok(())
}
fn should_generate_accessor(
&self,
element: &ElementDefinition,
structure_def: &StructureDefinition,
) -> bool {
let field_path = &element.path;
let base_name = &structure_def.name;
if !field_path.starts_with(base_name) {
return false;
}
let path_parts: Vec<&str> = field_path.split('.').collect();
if path_parts.len() != 2 {
return false;
}
if path_parts[0] != base_name {
return false;
}
let field_name = path_parts[1];
!field_name.ends_with("[x]")
}
fn create_accessor_method(
&self,
element: &ElementDefinition,
type_mapper: &mut TypeMapper,
) -> CodegenResult<Option<RustTraitMethod>> {
let path_parts: Vec<&str> = element.path.split('.').collect();
let field_name = path_parts.last().unwrap().to_string();
let rust_field_name = crate::naming::Naming::field_name(&field_name);
let is_optional = element.min.unwrap_or(0) == 0;
let is_array = element.max.as_deref() == Some("*")
|| element
.max
.as_deref()
.unwrap_or("1")
.parse::<i32>()
.unwrap_or(1)
> 1;
let Some(element_types) = element.element_type.as_ref() else {
return Ok(None);
};
let rust_type = if self.is_backbone_element(element_types) {
self.get_nested_type_for_backbone_element(element, is_array)
} else {
type_mapper.map_fhir_type_with_binding(
element_types,
element.binding.as_ref(),
is_array,
)
};
let return_type = match rust_type {
RustType::Custom(_) => {
rust_type.clone()
}
RustType::Vec(inner) => RustType::Slice(inner),
other => other,
};
let method = RustTraitMethod::new(rust_field_name)
.with_doc(format!("Returns a reference to the {field_name} field."))
.with_return_type(if is_optional && !is_array {
return_type.clone().wrap_in_option()
} else {
return_type.clone()
})
.with_body(format!("self.{field_name}"));
Ok(Some(method))
}
fn is_backbone_element(&self, element_types: &[ElementType]) -> bool {
element_types
.iter()
.any(|et| et.code.as_deref() == Some("BackboneElement"))
}
fn get_nested_type_for_backbone_element(
&self,
element: &ElementDefinition,
is_array: bool,
) -> RustType {
let path_parts: Vec<&str> = element.path.split('.').collect();
if path_parts.len() == 2 {
let resource_name = path_parts[0];
let field_name = path_parts[1];
let field_name_pascal = crate::naming::Naming::to_pascal_case(field_name);
let nested_type_name = format!("{resource_name}{field_name_pascal}");
let rust_type = RustType::Custom(nested_type_name);
if is_array {
RustType::Vec(Box::new(rust_type))
} else {
rust_type
}
} else {
let rust_type = RustType::Custom("BackboneElement".to_string());
if is_array {
RustType::Vec(Box::new(rust_type))
} else {
rust_type
}
}
}
fn add_choice_type_accessor_methods(
&self,
_rust_trait: &mut RustTrait,
_structure_def: &StructureDefinition,
) -> CodegenResult<()> {
Ok(())
}
}