rh_codegen/generators/
existence_trait_generator.rs1use crate::fhir_types::StructureDefinition;
2use crate::rust_types::{RustTrait, RustTraitMethod, RustType};
3use crate::CodegenResult;
4
5pub struct ExistenceTraitGenerator;
7
8impl Default for ExistenceTraitGenerator {
9 fn default() -> Self {
10 Self::new()
11 }
12}
13
14impl ExistenceTraitGenerator {
15 pub fn new() -> Self {
16 Self
17 }
18
19 pub fn generate_existence_trait(
21 &mut self,
22 structure_def: &StructureDefinition,
23 ) -> CodegenResult<RustTrait> {
24 let struct_name = crate::naming::Naming::struct_name(structure_def);
25 let trait_name = format!("{struct_name}Existence");
26 let base_trait = self.get_base_trait_name(structure_def);
27
28 let mut rust_trait = RustTrait::new(trait_name.clone());
29 rust_trait.doc_comment = Some(self.generate_trait_documentation(structure_def));
30
31 if !base_trait.is_empty() {
33 rust_trait.super_traits.push(base_trait.clone());
34 }
35
36 self.add_existence_methods(structure_def, &mut rust_trait)?;
38
39 Ok(rust_trait)
40 }
41
42 fn add_existence_methods(
44 &self,
45 structure_def: &StructureDefinition,
46 rust_trait: &mut RustTrait,
47 ) -> CodegenResult<()> {
48 let elements: Vec<_> = if let Some(diff) = &structure_def.differential {
52 if !diff.element.is_empty() {
53 diff.element.iter().collect()
54 } else if let Some(snapshot) = &structure_def.snapshot {
55 snapshot.element.iter().collect()
56 } else {
57 return Ok(());
58 }
59 } else if let Some(snapshot) = &structure_def.snapshot {
60 snapshot.element.iter().collect()
61 } else {
62 return Ok(());
63 };
64
65 for element in elements {
66 if element.path == structure_def.id {
68 continue;
69 }
70
71 if let Some(field_name) = element.path.strip_prefix(&format!("{}.", structure_def.id)) {
73 if field_name.contains('.') {
75 continue;
76 }
77
78 self.add_existence_method(element, field_name, rust_trait)?;
79 }
80 }
81
82 Ok(())
83 }
84
85 fn add_existence_method(
87 &self,
88 element: &crate::fhir_types::ElementDefinition,
89 field_name: &str,
90 rust_trait: &mut RustTrait,
91 ) -> CodegenResult<()> {
92 let rust_field_name = crate::naming::Naming::field_name(field_name);
93 let method_name = format!("has_{rust_field_name}");
94
95 let is_array = element
97 .max
98 .as_ref()
99 .map(|max| max != "1" && max != "0")
100 .unwrap_or(false);
101
102 let doc_comment = if is_array {
104 format!("Returns true if the {rust_field_name} field is not empty.")
105 } else {
106 format!("Returns true if the {rust_field_name} field is present (Some).")
107 };
108
109 let method = RustTraitMethod::new(method_name)
110 .with_return_type(RustType::Boolean)
111 .with_doc(doc_comment);
112
113 rust_trait.add_method(method);
114 Ok(())
115 }
116
117 fn get_base_trait_name(&self, structure_def: &StructureDefinition) -> String {
119 if structure_def.id == "Resource" {
121 return String::new(); }
123
124 if let Some(base_definition) = &structure_def.base_definition {
126 if base_definition.contains("DomainResource") {
127 return "DomainResourceExistence".to_string();
128 }
129 }
130
131 "ResourceExistence".to_string()
133 }
134
135 fn generate_trait_documentation(&self, structure_def: &StructureDefinition) -> String {
137 let mut docs = vec![
138 format!("{} Existence Checks", structure_def.id),
139 "".to_string(),
140 "This trait provides existence check methods for this FHIR resource type.".to_string(),
141 "".to_string(),
142 ];
143
144 if let Some(description) = &structure_def.description {
145 docs.push(description.clone());
146 docs.push("".to_string());
147 }
148
149 docs.extend(vec![
150 "**Source:**".to_string(),
151 format!("- URL: {}", &structure_def.url),
152 format!(
153 "- Version: {}",
154 structure_def
155 .version
156 .as_ref()
157 .unwrap_or(&"Unknown".to_string())
158 ),
159 format!("- Kind: {}", &structure_def.kind),
160 format!("- Type: {}", &structure_def.base_type),
161 ]);
162
163 if let Some(base_definition) = &structure_def.base_definition {
164 docs.push(format!("- Base Definition: {base_definition}"));
165 }
166
167 docs.join("\n")
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn test_existence_trait_generation() {
177 let _generator = ExistenceTraitGenerator::new();
178
179 }
182
183 #[test]
184 fn test_base_trait_selection() {
185 let generator = ExistenceTraitGenerator::new();
186
187 let structure_def = StructureDefinition {
188 resource_type: "StructureDefinition".to_string(),
189 id: "TestResource".to_string(),
190 url: "http://example.com/TestResource".to_string(),
191 version: None,
192 name: "TestResource".to_string(),
193 title: None,
194 status: "draft".to_string(),
195 description: None,
196 purpose: None,
197 kind: "resource".to_string(),
198 is_abstract: false,
199 base_type: "TestResource".to_string(),
200 base_definition: Some(
201 "http://hl7.org/fhir/StructureDefinition/DomainResource".to_string(),
202 ),
203 differential: None,
204 snapshot: None,
205 };
206
207 let base_trait = generator.get_base_trait_name(&structure_def);
208 assert_eq!(base_trait, "DomainResourceExistence");
209 }
210}