Skip to main content

rh_codegen/generators/
existence_trait_generator.rs

1use crate::fhir_types::StructureDefinition;
2use crate::rust_types::{RustTrait, RustTraitMethod, RustType};
3use crate::CodegenResult;
4
5/// Generator for existence check traits that provide has_xxx() methods
6pub 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    /// Generate an existence trait with has_xxx() methods for checking field presence
20    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        // Only add super trait if it's not empty (Resource has no super trait)
32        if !base_trait.is_empty() {
33            rust_trait.super_traits.push(base_trait.clone());
34        }
35
36        // Add existence methods for each element
37        self.add_existence_methods(structure_def, &mut rust_trait)?;
38
39        Ok(rust_trait)
40    }
41
42    /// Add existence check methods for all elements
43    fn add_existence_methods(
44        &self,
45        structure_def: &StructureDefinition,
46        rust_trait: &mut RustTrait,
47    ) -> CodegenResult<()> {
48        // Prefer differential elements (what the struct actually has) over snapshot.
49        // Snapshot includes inherited fields from parent types that won't be on the
50        // generated struct, causing missing-method errors in the trait impl.
51        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            // Skip the root element
67            if element.path == structure_def.id {
68                continue;
69            }
70
71            // Extract field name from the element path
72            if let Some(field_name) = element.path.strip_prefix(&format!("{}.", structure_def.id)) {
73                // Skip nested elements (those with dots in the remaining path)
74                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    /// Add a single existence check method
86    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        // Determine if this is an array field
96        let is_array = element
97            .max
98            .as_ref()
99            .map(|max| max != "1" && max != "0")
100            .unwrap_or(false);
101
102        // Generate appropriate documentation
103        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    /// Get the appropriate base trait name based on the structure definition
118    fn get_base_trait_name(&self, structure_def: &StructureDefinition) -> String {
119        // Special case: Resource itself should not have a super trait to avoid circular dependency
120        if structure_def.id == "Resource" {
121            return String::new(); // No super trait
122        }
123
124        // Check if this inherits from DomainResource
125        if let Some(base_definition) = &structure_def.base_definition {
126            if base_definition.contains("DomainResource") {
127                return "DomainResourceExistence".to_string();
128            }
129        }
130
131        // Default to Resource for other cases
132        "ResourceExistence".to_string()
133    }
134
135    /// Generate comprehensive documentation for the existence trait
136    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        // This would need a proper StructureDefinition for a real test
180        // For now, just verify the generator can be created
181    }
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}