use super::utils::GeneratorUtils;
use crate::fhir_types::StructureDefinition;
use crate::naming::Naming;
use crate::rust_types::{RustTraitImpl, RustTraitImplMethod};
use crate::CodegenResult;
pub struct TraitImplGenerator;
#[allow(dead_code)]
impl TraitImplGenerator {
pub fn new() -> Self {
Self
}
#[allow(dead_code)]
fn extract_base_resource_type(base_definition: &str) -> Option<String> {
if base_definition.starts_with("http://hl7.org/fhir/StructureDefinition/") {
if let Some(last_segment) = base_definition.split('/').next_back() {
return Some(last_segment.to_string());
}
}
None
}
#[allow(dead_code)]
fn is_core_resource(base_definition: &str) -> bool {
matches!(
base_definition,
"http://hl7.org/fhir/StructureDefinition/Resource"
| "http://hl7.org/fhir/StructureDefinition/DomainResource"
)
}
fn resolve_to_core_resource_type(
base_resource_type: &str,
_base_definition_url: &str,
) -> String {
match base_resource_type.to_lowercase().as_str() {
"vitalsigns" => "Observation".to_string(),
"bodyweight" | "bodyheight" | "bmi" | "bodytemp" | "heartrate" | "resprate"
| "oxygensat" => "Observation".to_string(),
_ => {
if GeneratorUtils::is_fhir_resource_type(base_resource_type) {
base_resource_type.to_string()
} else {
base_resource_type.to_string()
}
}
}
}
fn get_resource_type_for_struct(
struct_name: &str,
structure_def: &StructureDefinition,
) -> String {
if let Some(base_def) = &structure_def.base_definition {
if Self::is_core_resource(base_def) {
return struct_name.to_string();
}
if let Some(base_resource_type) = Self::extract_base_resource_type(base_def) {
let core_resource_type =
Self::resolve_to_core_resource_type(&base_resource_type, base_def);
if GeneratorUtils::is_fhir_resource_type(&core_resource_type) {
return core_resource_type;
}
}
}
struct_name.to_string()
}
pub fn generate_trait_impls(
&self,
structure_def: &StructureDefinition,
) -> CodegenResult<Vec<RustTraitImpl>> {
let mut trait_impls = Vec::new();
if structure_def.kind != "resource" {
return Ok(trait_impls);
}
let struct_name = Naming::struct_name(structure_def);
trait_impls.push(self.generate_resource_trait_impl(&struct_name, structure_def));
trait_impls.push(self.generate_resource_mutators_trait_impl(&struct_name, structure_def));
trait_impls.push(self.generate_resource_existence_trait_impl(&struct_name, structure_def));
if let Some(base_def) = &structure_def.base_definition {
if base_def.contains("DomainResource") {
trait_impls.push(self.generate_domain_resource_trait_impl(&struct_name));
trait_impls.push(self.generate_domain_resource_mutators_trait_impl(&struct_name));
trait_impls.push(self.generate_domain_resource_existence_trait_impl(&struct_name));
}
}
if struct_name != "Resource" {
let specific_trait_impl =
self.generate_specific_resource_trait_impl(&struct_name, structure_def);
if !specific_trait_impl.is_empty() {
trait_impls.push(specific_trait_impl);
}
let specific_mutators_trait_impl =
self.generate_specific_resource_mutators_trait_impl(&struct_name, structure_def);
if !specific_mutators_trait_impl.is_empty() {
trait_impls.push(specific_mutators_trait_impl);
}
let specific_existence_trait_impl =
self.generate_specific_resource_existence_trait_impl(&struct_name, structure_def);
if !specific_existence_trait_impl.is_empty() {
trait_impls.push(specific_existence_trait_impl);
}
}
Ok(trait_impls)
}
fn generate_resource_trait_impl(
&self,
struct_name: &str,
structure_def: &StructureDefinition,
) -> RustTraitImpl {
let mut trait_impl = RustTraitImpl::new(
"crate::traits::resource::ResourceAccessors".to_string(),
struct_name.to_string(),
);
let (base_access, use_trait_methods) =
self.get_resource_base_access(struct_name, structure_def);
let id_method = RustTraitImplMethod::new("id".to_string())
.with_return_type("Option<String>".to_string())
.with_body(if use_trait_methods {
format!("{base_access}.id()")
} else {
format!("{base_access}.id.clone()")
});
trait_impl.add_method(id_method);
let meta_method = RustTraitImplMethod::new("meta".to_string())
.with_return_type("Option<crate::datatypes::meta::Meta>".to_string())
.with_body(if use_trait_methods {
format!("{base_access}.meta()")
} else {
format!("{base_access}.meta.clone()")
});
trait_impl.add_method(meta_method);
let implicit_rules_method = RustTraitImplMethod::new("implicit_rules".to_string())
.with_return_type("Option<String>".to_string())
.with_body(if use_trait_methods {
format!("{base_access}.implicit_rules()")
} else {
format!("{base_access}.implicit_rules.clone()")
});
trait_impl.add_method(implicit_rules_method);
let language_method = RustTraitImplMethod::new("language".to_string())
.with_return_type("Option<String>".to_string())
.with_body(if use_trait_methods {
format!("{base_access}.language()")
} else {
format!("{base_access}.language.clone()")
});
trait_impl.add_method(language_method);
trait_impl
}
fn get_resource_base_access(
&self,
struct_name: &str,
structure_def: &StructureDefinition,
) -> (String, bool) {
if struct_name == "Resource" {
("self".to_string(), false)
} else if struct_name == "DomainResource" {
("self.base".to_string(), false)
} else if let Some(base_def) = &structure_def.base_definition {
if base_def.contains("DomainResource") {
("self.base.base".to_string(), false)
} else if base_def.contains("Resource") && struct_name != "DomainResource" {
("self.base".to_string(), false)
} else if base_def.starts_with("http://hl7.org/fhir/StructureDefinition/") {
("self.base".to_string(), true)
} else {
("self.base.base".to_string(), false)
}
} else {
("self.base.base".to_string(), false)
}
}
fn generate_domain_resource_trait_impl(&self, struct_name: &str) -> RustTraitImpl {
let mut trait_impl = RustTraitImpl::new(
"crate::traits::domain_resource::DomainResourceAccessors".to_string(),
struct_name.to_string(),
);
let text_method = RustTraitImplMethod::new("text".to_string())
.with_return_type("Option<crate::datatypes::narrative::Narrative>".to_string())
.with_body("self.base.text.clone()".to_string());
trait_impl.add_method(text_method);
let contained_method = RustTraitImplMethod::new("contained".to_string())
.with_return_type("&[crate::resources::resource::Resource]".to_string())
.with_body("self.base.contained.as_deref().unwrap_or(&[])".to_string());
trait_impl.add_method(contained_method);
let extension_method = RustTraitImplMethod::new("extension".to_string())
.with_return_type("&[crate::datatypes::extension::Extension]".to_string())
.with_body("self.base.extension.as_deref().unwrap_or(&[])".to_string());
trait_impl.add_method(extension_method);
let modifier_extension_method = RustTraitImplMethod::new("modifier_extension".to_string())
.with_return_type("&[crate::datatypes::extension::Extension]".to_string())
.with_body("self.base.modifier_extension.as_deref().unwrap_or(&[])".to_string());
trait_impl.add_method(modifier_extension_method);
trait_impl
}
fn generate_resource_mutators_trait_impl(
&self,
struct_name: &str,
structure_def: &StructureDefinition,
) -> RustTraitImpl {
let mut trait_impl = RustTraitImpl::new(
"crate::traits::resource::ResourceMutators".to_string(),
struct_name.to_string(),
);
let (base_access, use_trait_methods) =
self.get_resource_base_access(struct_name, structure_def);
let resource_access = if base_access == "self" {
String::new()
} else {
base_access
.strip_prefix("self.")
.unwrap_or(&base_access)
.to_string()
};
let new_method = RustTraitImplMethod::new("new".to_string())
.with_return_type("Self".to_string())
.with_body("Self::default()".to_string())
.with_self_param(None); trait_impl.add_method(new_method);
if use_trait_methods {
let set_id_method = RustTraitImplMethod::new("set_id".to_string())
.with_param(crate::rust_types::RustMethodParam::new(
"value".to_string(),
crate::rust_types::RustType::String,
))
.with_return_type("Self".to_string())
.with_body("let mut resource = self.clone();\n resource.base = resource.base.set_id(value);\n resource".to_string())
.with_self_param(Some("self".to_string()));
trait_impl.add_method(set_id_method);
let set_meta_method = RustTraitImplMethod::new("set_meta".to_string())
.with_param(crate::rust_types::RustMethodParam::new(
"value".to_string(),
crate::rust_types::RustType::Custom("crate::datatypes::meta::Meta".to_string()),
))
.with_return_type("Self".to_string())
.with_body("let mut resource = self.clone();\n resource.base = resource.base.set_meta(value);\n resource".to_string())
.with_self_param(Some("self".to_string()));
trait_impl.add_method(set_meta_method);
let set_implicit_rules_method = RustTraitImplMethod::new("set_implicit_rules".to_string())
.with_param(crate::rust_types::RustMethodParam::new(
"value".to_string(),
crate::rust_types::RustType::String,
))
.with_return_type("Self".to_string())
.with_body("let mut resource = self.clone();\n resource.base = resource.base.set_implicit_rules(value);\n resource".to_string())
.with_self_param(Some("self".to_string()));
trait_impl.add_method(set_implicit_rules_method);
let set_language_method = RustTraitImplMethod::new("set_language".to_string())
.with_param(crate::rust_types::RustMethodParam::new(
"value".to_string(),
crate::rust_types::RustType::String,
))
.with_return_type("Self".to_string())
.with_body("let mut resource = self.clone();\n resource.base = resource.base.set_language(value);\n resource".to_string())
.with_self_param(Some("self".to_string()));
trait_impl.add_method(set_language_method);
} else {
let set_id_method = RustTraitImplMethod::new("set_id".to_string())
.with_param(crate::rust_types::RustMethodParam::new(
"value".to_string(),
crate::rust_types::RustType::String,
))
.with_return_type("Self".to_string())
.with_body(if resource_access.is_empty() {
"let mut resource = self.clone();\n resource.id = Some(value);\n resource".to_string()
} else {
format!(
"let mut resource = self.clone();\n resource.{resource_access}.id = Some(value);\n resource"
)
})
.with_self_param(Some("self".to_string())); trait_impl.add_method(set_id_method);
let set_meta_method = RustTraitImplMethod::new("set_meta".to_string())
.with_param(crate::rust_types::RustMethodParam::new(
"value".to_string(),
crate::rust_types::RustType::Custom("crate::datatypes::meta::Meta".to_string()),
))
.with_return_type("Self".to_string())
.with_body(if resource_access.is_empty() {
"let mut resource = self.clone();\n resource.meta = Some(value);\n resource".to_string()
} else {
format!(
"let mut resource = self.clone();\n resource.{resource_access}.meta = Some(value);\n resource"
)
})
.with_self_param(Some("self".to_string()));
trait_impl.add_method(set_meta_method);
let set_implicit_rules_method = RustTraitImplMethod::new("set_implicit_rules".to_string())
.with_param(crate::rust_types::RustMethodParam::new(
"value".to_string(),
crate::rust_types::RustType::String,
))
.with_return_type("Self".to_string())
.with_body(if resource_access.is_empty() {
"let mut resource = self.clone();\n resource.implicit_rules = Some(value);\n resource".to_string()
} else {
format!(
"let mut resource = self.clone();\n resource.{resource_access}.implicit_rules = Some(value);\n resource"
)
})
.with_self_param(Some("self".to_string()));
trait_impl.add_method(set_implicit_rules_method);
let set_language_method = RustTraitImplMethod::new("set_language".to_string())
.with_param(crate::rust_types::RustMethodParam::new(
"value".to_string(),
crate::rust_types::RustType::String,
))
.with_return_type("Self".to_string())
.with_body(if resource_access.is_empty() {
"let mut resource = self.clone();\n resource.language = Some(value);\n resource".to_string()
} else {
format!(
"let mut resource = self.clone();\n resource.{resource_access}.language = Some(value);\n resource"
)
})
.with_self_param(Some("self".to_string()));
trait_impl.add_method(set_language_method);
}
trait_impl
}
fn generate_resource_existence_trait_impl(
&self,
struct_name: &str,
structure_def: &StructureDefinition,
) -> RustTraitImpl {
let mut trait_impl = RustTraitImpl::new(
"crate::traits::resource::ResourceExistence".to_string(),
struct_name.to_string(),
);
let (base_access, use_trait_methods) =
self.get_resource_base_access(struct_name, structure_def);
let has_id_method = RustTraitImplMethod::new("has_id".to_string())
.with_return_type("bool".to_string())
.with_body(if use_trait_methods {
format!("{base_access}.has_id()")
} else {
format!("{base_access}.id.is_some()")
});
trait_impl.add_method(has_id_method);
let has_meta_method = RustTraitImplMethod::new("has_meta".to_string())
.with_return_type("bool".to_string())
.with_body(if use_trait_methods {
format!("{base_access}.has_meta()")
} else {
format!("{base_access}.meta.is_some()")
});
trait_impl.add_method(has_meta_method);
let has_implicit_rules_method = RustTraitImplMethod::new("has_implicit_rules".to_string())
.with_return_type("bool".to_string())
.with_body(if use_trait_methods {
format!("{base_access}.has_implicit_rules()")
} else {
format!("{base_access}.implicit_rules.is_some()")
});
trait_impl.add_method(has_implicit_rules_method);
let has_language_method = RustTraitImplMethod::new("has_language".to_string())
.with_return_type("bool".to_string())
.with_body(if use_trait_methods {
format!("{base_access}.has_language()")
} else {
format!("{base_access}.language.is_some()")
});
trait_impl.add_method(has_language_method);
trait_impl
}
fn generate_domain_resource_mutators_trait_impl(&self, struct_name: &str) -> RustTraitImpl {
let mut trait_impl = RustTraitImpl::new(
"crate::traits::domain_resource::DomainResourceMutators".to_string(),
struct_name.to_string(),
);
let new_method = RustTraitImplMethod::new("new".to_string())
.with_return_type("Self".to_string())
.with_body("Self::default()".to_string())
.with_self_param(None); trait_impl.add_method(new_method);
let set_text_method = RustTraitImplMethod::new("set_text".to_string())
.with_param(crate::rust_types::RustMethodParam::new(
"value".to_string(),
crate::rust_types::RustType::Custom("crate::datatypes::narrative::Narrative".to_string()),
))
.with_return_type("Self".to_string())
.with_body("let mut resource = self.clone();\n resource.base.text = Some(value);\n resource".to_string())
.with_self_param(Some("self".to_string()));
trait_impl.add_method(set_text_method);
let set_contained_method = RustTraitImplMethod::new("set_contained".to_string())
.with_param(crate::rust_types::RustMethodParam::new(
"value".to_string(),
crate::rust_types::RustType::Vec(Box::new(crate::rust_types::RustType::Custom("crate::resources::resource::Resource".to_string()))),
))
.with_return_type("Self".to_string())
.with_body("let mut resource = self.clone();\n resource.base.contained = Some(value);\n resource".to_string())
.with_self_param(Some("self".to_string()));
trait_impl.add_method(set_contained_method);
let add_contained_method = RustTraitImplMethod::new("add_contained".to_string())
.with_param(crate::rust_types::RustMethodParam::new(
"item".to_string(),
crate::rust_types::RustType::Custom("crate::resources::resource::Resource".to_string()),
))
.with_return_type("Self".to_string())
.with_body("let mut resource = self.clone();\n resource.base.contained.get_or_insert_with(Vec::new).push(item);\n resource".to_string())
.with_self_param(Some("self".to_string()));
trait_impl.add_method(add_contained_method);
let set_extension_method = RustTraitImplMethod::new("set_extension".to_string())
.with_param(crate::rust_types::RustMethodParam::new(
"value".to_string(),
crate::rust_types::RustType::Vec(Box::new(crate::rust_types::RustType::Custom("crate::datatypes::extension::Extension".to_string()))),
))
.with_return_type("Self".to_string())
.with_body("let mut resource = self.clone();\n resource.base.extension = Some(value);\n resource".to_string())
.with_self_param(Some("self".to_string()));
trait_impl.add_method(set_extension_method);
let add_extension_method = RustTraitImplMethod::new("add_extension".to_string())
.with_param(crate::rust_types::RustMethodParam::new(
"item".to_string(),
crate::rust_types::RustType::Custom("crate::datatypes::extension::Extension".to_string()),
))
.with_return_type("Self".to_string())
.with_body("let mut resource = self.clone();\n resource.base.extension.get_or_insert_with(Vec::new).push(item);\n resource".to_string())
.with_self_param(Some("self".to_string()));
trait_impl.add_method(add_extension_method);
let set_modifier_extension_method = RustTraitImplMethod::new("set_modifier_extension".to_string())
.with_param(crate::rust_types::RustMethodParam::new(
"value".to_string(),
crate::rust_types::RustType::Vec(Box::new(crate::rust_types::RustType::Custom("crate::datatypes::extension::Extension".to_string()))),
))
.with_return_type("Self".to_string())
.with_body("let mut resource = self.clone();\n resource.base.modifier_extension = Some(value);\n resource".to_string())
.with_self_param(Some("self".to_string()));
trait_impl.add_method(set_modifier_extension_method);
let add_modifier_extension_method =
RustTraitImplMethod::new("add_modifier_extension".to_string())
.with_param(crate::rust_types::RustMethodParam::new(
"item".to_string(),
crate::rust_types::RustType::Custom("crate::datatypes::extension::Extension".to_string()),
))
.with_return_type("Self".to_string())
.with_body("let mut resource = self.clone();\n resource.base.modifier_extension.get_or_insert_with(Vec::new).push(item);\n resource".to_string())
.with_self_param(Some("self".to_string()));
trait_impl.add_method(add_modifier_extension_method);
trait_impl
}
fn generate_domain_resource_existence_trait_impl(&self, struct_name: &str) -> RustTraitImpl {
let mut trait_impl = RustTraitImpl::new(
"crate::traits::domain_resource::DomainResourceExistence".to_string(),
struct_name.to_string(),
);
let has_id_method = RustTraitImplMethod::new("has_id".to_string())
.with_return_type("bool".to_string())
.with_body("self.base.base.id.is_some()".to_string());
trait_impl.add_method(has_id_method);
let has_meta_method = RustTraitImplMethod::new("has_meta".to_string())
.with_return_type("bool".to_string())
.with_body("self.base.base.meta.is_some()".to_string());
trait_impl.add_method(has_meta_method);
let has_implicit_rules_method = RustTraitImplMethod::new("has_implicit_rules".to_string())
.with_return_type("bool".to_string())
.with_body("self.base.base.implicit_rules.is_some()".to_string());
trait_impl.add_method(has_implicit_rules_method);
let has_language_method = RustTraitImplMethod::new("has_language".to_string())
.with_return_type("bool".to_string())
.with_body("self.base.base.language.is_some()".to_string());
trait_impl.add_method(has_language_method);
let has_text_method = RustTraitImplMethod::new("has_text".to_string())
.with_return_type("bool".to_string())
.with_body("self.base.text.is_some()".to_string());
trait_impl.add_method(has_text_method);
let has_contained_method = RustTraitImplMethod::new("has_contained".to_string())
.with_return_type("bool".to_string())
.with_body("self.base.contained.as_ref().is_some_and(|c| !c.is_empty())".to_string());
trait_impl.add_method(has_contained_method);
let has_extension_method = RustTraitImplMethod::new("has_extension".to_string())
.with_return_type("bool".to_string())
.with_body("self.base.extension.as_ref().is_some_and(|e| !e.is_empty())".to_string());
trait_impl.add_method(has_extension_method);
let has_modifier_extension_method =
RustTraitImplMethod::new("has_modifier_extension".to_string())
.with_return_type("bool".to_string())
.with_body(
"self.base.modifier_extension.as_ref().is_some_and(|m| !m.is_empty())"
.to_string(),
);
trait_impl.add_method(has_modifier_extension_method);
trait_impl
}
fn generate_specific_resource_trait_impl(
&self,
struct_name: &str,
structure_def: &StructureDefinition,
) -> RustTraitImpl {
let trait_name = format!(
"crate::traits::{}::{}Accessors",
crate::naming::Naming::to_snake_case(struct_name),
struct_name
);
let mut trait_impl = RustTraitImpl::new(trait_name, struct_name.to_string());
let elements = if let Some(differential) = &structure_def.differential {
&differential.element
} else if let Some(snapshot) = &structure_def.snapshot {
&snapshot.element
} else {
return trait_impl; };
for element in elements {
if self.should_generate_accessor_impl(element, structure_def) {
if let Some(method) = self.generate_field_accessor_method(element) {
trait_impl.add_method(method);
}
}
}
trait_impl
}
fn generate_specific_resource_mutators_trait_impl(
&self,
struct_name: &str,
structure_def: &StructureDefinition,
) -> RustTraitImpl {
let trait_name = format!(
"crate::traits::{}::{}Mutators",
crate::naming::Naming::to_snake_case(struct_name),
struct_name
);
let mut trait_impl = RustTraitImpl::new(trait_name, struct_name.to_string());
let new_method = RustTraitImplMethod::new("new".to_string())
.with_return_type("Self".to_string())
.with_body("Self::default()".to_string())
.with_self_param(None); trait_impl.add_method(new_method);
let elements = if let Some(differential) = &structure_def.differential {
&differential.element
} else if let Some(snapshot) = &structure_def.snapshot {
&snapshot.element
} else {
return trait_impl; };
for element in elements {
if self.should_generate_accessor_impl(element, structure_def) {
if let Some(methods) = self.generate_field_mutator_methods(element) {
for method in methods {
trait_impl.add_method(method);
}
}
}
}
trait_impl
}
fn generate_specific_resource_existence_trait_impl(
&self,
struct_name: &str,
structure_def: &StructureDefinition,
) -> RustTraitImpl {
let trait_name = format!(
"crate::traits::{}::{}Existence",
crate::naming::Naming::to_snake_case(struct_name),
struct_name
);
let mut trait_impl = RustTraitImpl::new(trait_name, struct_name.to_string());
let is_profile = crate::generators::type_registry::TypeRegistry::is_profile(structure_def);
let extends_domain_resource = structure_def
.base_definition
.as_ref()
.map(|base| base.ends_with("/DomainResource"))
.unwrap_or(false);
let extends_resource_directly = structure_def
.base_definition
.as_ref()
.map(|base| base.ends_with("/Resource") && !base.ends_with("/DomainResource"))
.unwrap_or(false);
if !is_profile {
let (id_access, meta_access, implicit_rules_access, language_access) =
if extends_resource_directly {
(
"self.base.id.is_some()".to_string(),
"self.base.meta.is_some()".to_string(),
"self.base.implicit_rules.is_some()".to_string(),
"self.base.language.is_some()".to_string(),
)
} else {
(
"self.base.base.id.is_some()".to_string(),
"self.base.base.meta.is_some()".to_string(),
"self.base.base.implicit_rules.is_some()".to_string(),
"self.base.base.language.is_some()".to_string(),
)
};
let has_id_method = RustTraitImplMethod::new("has_id".to_string())
.with_return_type("bool".to_string())
.with_body(id_access);
trait_impl.add_method(has_id_method);
let has_meta_method = RustTraitImplMethod::new("has_meta".to_string())
.with_return_type("bool".to_string())
.with_body(meta_access);
trait_impl.add_method(has_meta_method);
let has_implicit_rules_method =
RustTraitImplMethod::new("has_implicit_rules".to_string())
.with_return_type("bool".to_string())
.with_body(implicit_rules_access);
trait_impl.add_method(has_implicit_rules_method);
let has_language_method = RustTraitImplMethod::new("has_language".to_string())
.with_return_type("bool".to_string())
.with_body(language_access);
trait_impl.add_method(has_language_method);
if extends_domain_resource {
let has_text_method = RustTraitImplMethod::new("has_text".to_string())
.with_return_type("bool".to_string())
.with_body("self.base.text.is_some()".to_string());
trait_impl.add_method(has_text_method);
let has_contained_method = RustTraitImplMethod::new("has_contained".to_string())
.with_return_type("bool".to_string())
.with_body(
"self.base.contained.as_ref().is_some_and(|c| !c.is_empty())".to_string(),
);
trait_impl.add_method(has_contained_method);
let has_extension_method = RustTraitImplMethod::new("has_extension".to_string())
.with_return_type("bool".to_string())
.with_body(
"self.base.extension.as_ref().is_some_and(|e| !e.is_empty())".to_string(),
);
trait_impl.add_method(has_extension_method);
let has_modifier_extension_method =
RustTraitImplMethod::new("has_modifier_extension".to_string())
.with_return_type("bool".to_string())
.with_body(
"self.base.modifier_extension.as_ref().is_some_and(|m| !m.is_empty())"
.to_string(),
);
trait_impl.add_method(has_modifier_extension_method);
}
}
let elements = if let Some(differential) = &structure_def.differential {
&differential.element
} else if let Some(snapshot) = &structure_def.snapshot {
&snapshot.element
} else {
return trait_impl; };
let mut choice_fields = std::collections::HashSet::new();
for element in elements {
let path_parts: Vec<&str> = element.path.split('.').collect();
if path_parts.len() == 2 && path_parts[0] == structure_def.name {
let field_name = path_parts[1];
if field_name.ends_with("[x]") {
choice_fields.insert(field_name.trim_end_matches("[x]").to_string());
}
}
}
for choice_field in &choice_fields {
let choice_path = format!("{}.{}[x]", structure_def.name, choice_field);
if let Some(choice_element) = elements.iter().find(|e| e.path == choice_path) {
if let Some(method) =
self.generate_choice_type_existence_method(choice_field, choice_element)
{
trait_impl.add_method(method);
}
}
}
for element in elements {
let path_parts: Vec<&str> = element.path.split('.').collect();
if path_parts.len() == 2 && path_parts[0] == structure_def.name {
let field_name = path_parts[1];
if !field_name.ends_with("[x]")
&& self.should_generate_accessor_impl(element, structure_def)
{
if let Some(method) = self.generate_field_existence_method(element) {
trait_impl.add_method(method);
}
}
}
}
trait_impl
}
fn should_generate_accessor_impl(
&self,
element: &crate::fhir_types::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 generate_field_accessor_method(
&self,
element: &crate::fhir_types::ElementDefinition,
) -> Option<RustTraitImplMethod> {
use crate::config::CodegenConfig;
use crate::type_mapper::TypeMapper;
use crate::value_sets::ValueSetManager;
let path_parts: Vec<&str> = element.path.split('.').collect();
let field_name = path_parts.last()?.to_string();
let rust_field_name = crate::naming::Naming::field_name(&field_name);
let is_array = element.max.as_deref() == Some("*")
|| element
.max
.as_deref()
.unwrap_or("1")
.parse::<i32>()
.unwrap_or(1)
> 1;
let is_optional = element.min.unwrap_or(0) == 0;
let config = CodegenConfig::default();
let mut value_set_manager = ValueSetManager::new();
let mut type_mapper = TypeMapper::new(&config, &mut value_set_manager);
let fhir_types = element.element_type.as_ref()?;
let rust_type = if self.is_backbone_element(fhir_types) {
self.get_nested_type_for_backbone_element(element, is_array)
} else {
type_mapper.map_fhir_type_with_binding(fhir_types, element.binding.as_ref(), is_array)
};
let (return_type, body) = if is_array {
let inner_type = match &rust_type {
crate::rust_types::RustType::Vec(inner) => inner.to_string(),
crate::rust_types::RustType::Option(inner) => {
if let crate::rust_types::RustType::Vec(vec_inner) = inner.as_ref() {
vec_inner.to_string()
} else {
inner.to_string()
}
}
_ => rust_type.to_string(),
};
let return_type = format!("&[{inner_type}]");
let body = if is_optional {
format!("self.{rust_field_name}.as_deref().unwrap_or(&[])")
} else {
format!("&self.{rust_field_name}")
};
(return_type, body)
} else {
if is_optional {
let inner_type = match &rust_type {
crate::rust_types::RustType::Option(inner) => inner.to_string(),
_ => rust_type.to_string(),
};
let return_type = format!("Option<{inner_type}>");
let body = if self.is_copy_type(&rust_type) {
format!("self.{rust_field_name}")
} else {
format!("self.{rust_field_name}.clone()")
};
(return_type, body)
} else {
let return_type = match &rust_type {
crate::rust_types::RustType::Option(inner) => inner.to_string(),
_ => rust_type.to_string(),
};
let body = if self.is_copy_type(&rust_type) {
format!("self.{rust_field_name}")
} else {
format!("self.{rust_field_name}.clone()")
};
(return_type, body)
}
};
Some(
RustTraitImplMethod::new(rust_field_name)
.with_return_type(return_type)
.with_body(body),
)
}
fn generate_field_mutator_methods(
&self,
element: &crate::fhir_types::ElementDefinition,
) -> Option<Vec<RustTraitImplMethod>> {
let path_parts: Vec<&str> = element.path.split('.').collect();
let field_name = path_parts.last()?.to_string();
let rust_field_name = crate::naming::Naming::field_name(&field_name);
let is_array = element.max.as_deref() == Some("*")
|| element
.max
.as_deref()
.unwrap_or("1")
.parse::<i32>()
.unwrap_or(1)
> 1;
let is_optional = element.min.unwrap_or(0) == 0;
let rust_type = self.get_field_rust_type(element, &field_name).ok()?;
let mut methods = Vec::new();
if is_array {
let inner_type = match &rust_type {
crate::rust_types::RustType::Vec(inner) => inner.to_string(),
crate::rust_types::RustType::Option(inner) => {
if let crate::rust_types::RustType::Vec(vec_inner) = inner.as_ref() {
vec_inner.to_string()
} else {
inner.to_string()
}
}
_ => rust_type.to_string(),
};
let set_method_name = format!("set_{rust_field_name}");
let set_body = if is_optional {
format!(
"let mut resource = self.clone();\n resource.{rust_field_name} = Some(value);\n resource"
)
} else {
format!(
"let mut resource = self.clone();\n resource.{rust_field_name} = value;\n resource"
)
};
methods.push(
RustTraitImplMethod::new(set_method_name)
.with_param(crate::rust_types::RustMethodParam::new(
"value".to_string(),
crate::rust_types::RustType::Vec(Box::new(
crate::rust_types::RustType::Custom(inner_type.clone()),
)),
))
.with_return_type("Self".to_string())
.with_body(set_body)
.with_self_param(Some("self".to_string())),
);
let add_method_name = format!("add_{rust_field_name}");
let add_body = if is_optional {
format!(
"let mut resource = self.clone();\n resource.{rust_field_name}.get_or_insert_with(Vec::new).push(item);\n resource"
)
} else {
format!(
"let mut resource = self.clone();\n resource.{rust_field_name}.push(item);\n resource"
)
};
methods.push(
RustTraitImplMethod::new(add_method_name)
.with_param(crate::rust_types::RustMethodParam::new(
"item".to_string(),
crate::rust_types::RustType::Custom(inner_type),
))
.with_return_type("Self".to_string())
.with_body(add_body)
.with_self_param(Some("self".to_string())),
);
} else {
let method_name = format!("set_{rust_field_name}");
let inner_type = match &rust_type {
crate::rust_types::RustType::Option(inner) => inner.to_string(),
_ => rust_type.to_string(),
};
let body = if is_optional {
format!(
"let mut resource = self.clone();\n resource.{rust_field_name} = Some(value);\n resource"
)
} else {
format!(
"let mut resource = self.clone();\n resource.{rust_field_name} = value;\n resource"
)
};
methods.push(
RustTraitImplMethod::new(method_name)
.with_param(crate::rust_types::RustMethodParam::new(
"value".to_string(),
crate::rust_types::RustType::Custom(inner_type),
))
.with_return_type("Self".to_string())
.with_body(body)
.with_self_param(Some("self".to_string())),
);
}
Some(methods)
}
fn generate_field_existence_method(
&self,
element: &crate::fhir_types::ElementDefinition,
) -> Option<RustTraitImplMethod> {
let path_parts: Vec<&str> = element.path.split('.').collect();
let field_name = path_parts.last()?.to_string();
let rust_field_name = crate::naming::Naming::field_name(&field_name);
let is_array = element.max.as_deref() == Some("*")
|| element
.max
.as_deref()
.unwrap_or("1")
.parse::<i32>()
.unwrap_or(1)
> 1;
let is_optional = element.min.unwrap_or(0) == 0;
let method_name = format!("has_{rust_field_name}");
let body = if is_array {
if is_optional {
format!("self.{rust_field_name}.as_ref().is_some_and(|v| !v.is_empty())")
} else {
format!("!self.{rust_field_name}.is_empty()")
}
} else if is_optional {
format!("self.{rust_field_name}.is_some()")
} else {
"true".to_string()
};
Some(
RustTraitImplMethod::new(method_name)
.with_return_type("bool".to_string())
.with_body(body),
)
}
fn generate_choice_type_existence_method(
&self,
choice_field: &str,
choice_element: &crate::fhir_types::ElementDefinition,
) -> Option<RustTraitImplMethod> {
let types = choice_element.element_type.as_ref()?;
if types.is_empty() {
return None;
}
let is_optional = choice_element.min.unwrap_or(0) == 0;
let mut variants = Vec::new();
for type_def in types {
if let Some(type_code) = &type_def.code {
let type_suffix = Naming::type_suffix(type_code);
let field_name = format!("{choice_field}_{type_suffix}");
let rust_field_name = Naming::field_name(&field_name);
variants.push(rust_field_name);
}
}
if variants.is_empty() {
return None;
}
let method_name = format!("has_{}", Naming::to_snake_case(choice_field));
let body = if is_optional {
variants
.iter()
.map(|v| format!("self.{v}.is_some()"))
.collect::<Vec<_>>()
.join(" || ")
} else {
"true".to_string()
};
Some(
RustTraitImplMethod::new(method_name)
.with_return_type("bool".to_string())
.with_body(body),
)
}
fn get_inner_type_for_slice(&self, rust_type: &crate::rust_types::RustType) -> String {
match rust_type {
crate::rust_types::RustType::Vec(inner) => inner.to_string(),
crate::rust_types::RustType::Option(inner) => {
if let crate::rust_types::RustType::Vec(vec_inner) = inner.as_ref() {
vec_inner.to_string()
} else {
inner.to_string()
}
}
_ => rust_type.to_string(),
}
}
fn get_type_for_option(&self, rust_type: &crate::rust_types::RustType) -> String {
match rust_type {
crate::rust_types::RustType::Option(inner) => inner.to_string(),
_ => rust_type.to_string(),
}
}
fn is_copy_type(&self, rust_type: &crate::rust_types::RustType) -> bool {
match rust_type {
crate::rust_types::RustType::Boolean
| crate::rust_types::RustType::Integer
| crate::rust_types::RustType::Float => true,
crate::rust_types::RustType::Option(inner) => self.is_copy_type(inner),
crate::rust_types::RustType::Custom(type_name) => {
self.is_copy_primitive_type(type_name)
}
_ => false,
}
}
fn is_copy_primitive_type(&self, type_name: &str) -> bool {
matches!(
type_name,
"BooleanType"
| "IntegerType"
| "UnsignedIntType"
| "PositiveIntType"
| "DecimalType"
| "Integer64Type"
)
}
fn is_enum_type(&self, rust_type: &crate::rust_types::RustType) -> bool {
match rust_type {
crate::rust_types::RustType::Custom(type_name) => self.is_enum_type_name(type_name),
_ => false,
}
}
fn is_enum_type_name(&self, type_name: &str) -> bool {
type_name.ends_with("Status")
|| type_name.ends_with("Kind")
|| type_name.ends_with("Code")
|| type_name.ends_with("Codes")
|| type_name.ends_with("Priority")
|| type_name.ends_with("Intent")
|| matches!(
type_name,
"PublicationStatus"
| "CapabilityStatementKind"
| "CodeSearchSupport"
| "FmStatus"
| "ReportStatusCodes"
| "ReportResultCodes"
| "VerificationresultStatus"
| "TaskStatus"
| "TaskIntent"
| "RequestPriority"
| "SupplydeliveryStatus"
| "SupplyrequestStatus"
)
}
fn determine_method_return_type(
&self,
element: &crate::fhir_types::ElementDefinition,
) -> String {
let is_optional = element.min.unwrap_or(0) == 0;
let is_array = element
.max
.as_ref()
.is_some_and(|max| max == "*" || max.parse::<u32>().unwrap_or(1) > 1);
let base_type = if let Some(element_types) = &element.element_type {
if let Some(first_type) = element_types.first() {
if let Some(code) = &first_type.code {
match code.as_str() {
"string" | "code" | "id" | "markdown" | "uri" | "url" | "canonical"
| "dateTime" | "date" | "time" | "instant" | "base64Binary" | "oid"
| "uuid" => "String".to_string(),
"boolean" => "bool".to_string(),
"integer" | "positiveInt" | "unsignedInt" => "i32".to_string(),
"decimal" => "f64".to_string(),
"Reference" => "crate::datatypes::reference::Reference".to_string(),
"Identifier" => "crate::datatypes::identifier::Identifier".to_string(),
"CodeableConcept" => {
"crate::datatypes::codeable_concept::CodeableConcept".to_string()
}
"Coding" => "crate::datatypes::coding::Coding".to_string(),
"Address" => "crate::datatypes::address::Address".to_string(),
"HumanName" => "crate::datatypes::human_name::HumanName".to_string(),
"ContactPoint" => {
"crate::datatypes::contact_point::ContactPoint".to_string()
}
"Attachment" => "crate::datatypes::attachment::Attachment".to_string(),
"Annotation" => "crate::datatypes::annotation::Annotation".to_string(),
"BackboneElement" => {
"crate::datatypes::backbone_element::BackboneElement".to_string()
}
_ => "String".to_string(), }
} else {
"String".to_string()
}
} else {
"String".to_string()
}
} else {
"String".to_string()
};
if is_array {
if is_optional {
format!("Option<Vec<{base_type}>>")
} else {
format!("Vec<{base_type}>")
}
} else if is_optional {
format!("Option<{base_type}>")
} else {
base_type
}
}
fn generate_method_body(
&self,
field_name: &str,
element: &crate::fhir_types::ElementDefinition,
) -> String {
let rust_field_name = if field_name == "type" {
"type_".to_string()
} else {
crate::naming::Naming::field_name(field_name)
};
let field_access = format!("self.{rust_field_name}");
let is_optional = element.min.unwrap_or(0) == 0;
let is_array = element
.max
.as_ref()
.is_some_and(|max| max == "*" || max.parse::<u32>().unwrap_or(1) > 1);
if is_array {
format!("{field_access}.clone()")
} else if let Some(type_def) = element
.element_type
.as_ref()
.and_then(|types| types.first())
{
if let Some(code) = &type_def.code {
match code.as_str() {
"string" | "code" | "id" | "markdown" | "uri" | "url" | "canonical"
| "dateTime" | "date" | "time" | "instant" | "base64Binary" | "oid"
| "uuid" => {
if is_optional {
format!("{field_access}.as_ref().map(|s| s.to_string())")
} else {
format!("{field_access}.to_string()")
}
}
"boolean" => {
if is_optional {
format!("{field_access}.map(|b| b.into())")
} else {
format!("{field_access}.into()")
}
}
"integer" | "positiveInt" | "unsignedInt" => {
if is_optional {
format!("{field_access}.map(|i| i.into())")
} else {
format!("{field_access}.into()")
}
}
"decimal" => {
if is_optional {
format!("{field_access}.map(|d| d.into())")
} else {
format!("{field_access}.into()")
}
}
"CodeableConcept" | "Reference" | "Identifier" | "Coding" | "Address"
| "HumanName" | "ContactPoint" | "Attachment" | "Annotation"
| "BackboneElement" => {
format!("{field_access}.clone()")
}
_ => {
if is_optional {
format!("{field_access}.as_ref().map(|v| format!(\"{{:?}}\", v))")
} else {
format!("format!(\"{{:?}}\", {field_access})")
}
}
}
} else {
format!("{field_access}.clone()")
}
} else {
format!("{field_access}.clone()")
}
}
fn is_backbone_element(&self, element_types: &[crate::fhir_types::ElementType]) -> bool {
element_types
.iter()
.any(|et| et.code.as_deref() == Some("BackboneElement"))
}
fn get_nested_type_for_backbone_element(
&self,
element: &crate::fhir_types::ElementDefinition,
is_array: bool,
) -> crate::rust_types::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 = crate::rust_types::RustType::Custom(nested_type_name);
if is_array {
crate::rust_types::RustType::Vec(Box::new(rust_type))
} else {
rust_type
}
} else {
let rust_type = crate::rust_types::RustType::Custom("BackboneElement".to_string());
if is_array {
crate::rust_types::RustType::Vec(Box::new(rust_type))
} else {
rust_type
}
}
}
fn get_field_rust_type(
&self,
element: &crate::fhir_types::ElementDefinition,
field_name: &str,
) -> CodegenResult<crate::rust_types::RustType> {
use crate::rust_types::RustType;
let Some(element_type) = element.element_type.as_ref().and_then(|t| t.first()) else {
return Ok(RustType::String);
};
let Some(code) = &element_type.code else {
return Ok(RustType::String);
};
if code == "code" {
if let Some(binding) = &element.binding {
if binding.strength == "required" {
if let Some(value_set_url) = &binding.value_set {
if let Some(enum_name) =
self.extract_enum_name_from_value_set(value_set_url)
{
return Ok(RustType::Custom(enum_name));
}
}
}
}
}
use crate::generators::TypeUtilities;
TypeUtilities::map_fhir_type_to_rust(element_type, field_name, &element.path)
}
fn extract_enum_name_from_value_set(&self, url: &str) -> Option<String> {
let url_without_version = url.split('|').next().unwrap_or(url);
let value_set_name = url_without_version.split('/').next_back()?;
let name = value_set_name
.split(&['-', '.'][..])
.filter(|part| !part.is_empty())
.map(|part| {
let mut chars = part.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
}
})
.collect::<String>();
if name.chars().next().unwrap_or('0').is_ascii_digit() {
Some(format!("ValueSet{name}"))
} else {
Some(name)
}
}
}
impl Default for TraitImplGenerator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fhir_types::StructureDefinitionDifferential;
fn create_test_structure_definition(
name: &str,
base_definition: Option<&str>,
) -> StructureDefinition {
StructureDefinition {
resource_type: "StructureDefinition".to_string(),
id: name.to_lowercase(),
url: format!("http://test.com/{name}"),
version: Some("1.0.0".to_string()),
name: name.to_string(),
title: Some(name.to_string()),
status: "active".to_string(),
description: None,
purpose: None,
kind: "resource".to_string(),
is_abstract: false,
base_type: "Resource".to_string(),
base_definition: base_definition.map(|s| s.to_string()),
differential: None,
snapshot: None,
}
}
#[test]
fn test_resource_type_for_core_resource() {
let patient = create_test_structure_definition(
"Patient",
Some("http://hl7.org/fhir/StructureDefinition/DomainResource"),
);
let result = TraitImplGenerator::get_resource_type_for_struct("Patient", &patient);
assert_eq!(
result, "Patient",
"Core resource should return its own name"
);
}
#[test]
fn test_resource_type_for_group_profile() {
let group_definition = create_test_structure_definition(
"GroupDefinition",
Some("http://hl7.org/fhir/StructureDefinition/Group"),
);
let result =
TraitImplGenerator::get_resource_type_for_struct("GroupDefinition", &group_definition);
assert_eq!(result, "Group", "Group profile should return 'Group'");
}
#[test]
fn test_resource_type_for_observation_profile() {
let vital_signs = create_test_structure_definition(
"VitalSigns",
Some("http://hl7.org/fhir/StructureDefinition/Observation"),
);
let result = TraitImplGenerator::get_resource_type_for_struct("VitalSigns", &vital_signs);
assert_eq!(
result, "Observation",
"Observation profile should return 'Observation'"
);
}
#[test]
fn test_resource_type_for_profile_on_profile() {
let bmi = create_test_structure_definition(
"BMI",
Some("http://hl7.org/fhir/StructureDefinition/vitalsigns"),
);
let result = TraitImplGenerator::get_resource_type_for_struct("BMI", &bmi);
assert_eq!(
result, "Observation",
"BMI profile should resolve to 'Observation' via vitalsigns"
);
}
#[test]
fn test_resource_type_without_base_definition() {
let custom_resource = create_test_structure_definition("CustomResource", None);
let result =
TraitImplGenerator::get_resource_type_for_struct("CustomResource", &custom_resource);
assert_eq!(
result, "CustomResource",
"Resource without baseDefinition should return struct name"
);
}
#[test]
fn test_is_core_resource() {
assert!(TraitImplGenerator::is_core_resource(
"http://hl7.org/fhir/StructureDefinition/Resource"
));
assert!(TraitImplGenerator::is_core_resource(
"http://hl7.org/fhir/StructureDefinition/DomainResource"
));
assert!(!TraitImplGenerator::is_core_resource(
"http://hl7.org/fhir/StructureDefinition/Patient"
));
assert!(!TraitImplGenerator::is_core_resource(
"http://hl7.org/fhir/StructureDefinition/Group"
));
}
#[test]
fn test_extract_base_resource_type() {
assert_eq!(
TraitImplGenerator::extract_base_resource_type(
"http://hl7.org/fhir/StructureDefinition/Group"
),
Some("Group".to_string())
);
assert_eq!(
TraitImplGenerator::extract_base_resource_type(
"http://hl7.org/fhir/StructureDefinition/Observation"
),
Some("Observation".to_string())
);
assert_eq!(
TraitImplGenerator::extract_base_resource_type(
"http://hl7.org/fhir/StructureDefinition/vitalsigns"
),
Some("vitalsigns".to_string())
);
assert_eq!(
TraitImplGenerator::extract_base_resource_type("invalid-url"),
None
);
}
#[test]
fn test_resolve_to_core_resource_type() {
assert_eq!(
TraitImplGenerator::resolve_to_core_resource_type(
"vitalsigns",
"http://hl7.org/fhir/StructureDefinition/vitalsigns"
),
"Observation"
);
assert_eq!(
TraitImplGenerator::resolve_to_core_resource_type(
"Patient",
"http://hl7.org/fhir/StructureDefinition/Patient"
),
"Patient"
);
assert_eq!(
TraitImplGenerator::resolve_to_core_resource_type(
"Group",
"http://hl7.org/fhir/StructureDefinition/Group"
),
"Group"
);
assert_eq!(
TraitImplGenerator::resolve_to_core_resource_type(
"bmi",
"http://hl7.org/fhir/StructureDefinition/bmi"
),
"Observation"
);
assert_eq!(
TraitImplGenerator::resolve_to_core_resource_type(
"UnknownProfile",
"http://hl7.org/fhir/StructureDefinition/UnknownProfile"
),
"UnknownProfile"
);
}
#[test]
fn test_empty_trait_implementations_are_filtered() {
let generator = TraitImplGenerator::new();
let mut structure_def = create_test_structure_definition("EmptyProfile", None);
structure_def.differential = Some(StructureDefinitionDifferential { element: vec![] });
let trait_impls = generator.generate_trait_impls(&structure_def).unwrap();
assert!(
!trait_impls.is_empty(),
"Should have at least Resource trait impl"
);
let specific_trait_name = format!(
"crate::traits::{}::{}Accessors",
crate::naming::Naming::to_snake_case("EmptyProfile"),
"EmptyProfile"
);
let has_empty_specific_impl = trait_impls
.iter()
.any(|impl_| impl_.trait_name == specific_trait_name && impl_.is_empty());
assert!(
!has_empty_specific_impl,
"Should not include empty specific trait implementations"
);
}
}