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        if let Some(snapshot) = &structure_def.snapshot {
49            for element in &snapshot.element {
50                // Skip the root element
51                if element.path == structure_def.id {
52                    continue;
53                }
54
55                // Extract field name from the element path
56                if let Some(field_name) =
57                    element.path.strip_prefix(&format!("{}.", structure_def.id))
58                {
59                    // Skip nested elements (those with dots in the remaining path)
60                    if field_name.contains('.') {
61                        continue;
62                    }
63
64                    self.add_existence_method(element, field_name, rust_trait)?;
65                }
66            }
67        }
68
69        Ok(())
70    }
71
72    /// Add a single existence check method
73    fn add_existence_method(
74        &self,
75        element: &crate::fhir_types::ElementDefinition,
76        field_name: &str,
77        rust_trait: &mut RustTrait,
78    ) -> CodegenResult<()> {
79        let rust_field_name = crate::naming::Naming::field_name(field_name);
80        let method_name = format!("has_{rust_field_name}");
81
82        // Determine if this is an array field
83        let is_array = element
84            .max
85            .as_ref()
86            .map(|max| max != "1" && max != "0")
87            .unwrap_or(false);
88
89        // Generate appropriate documentation
90        let doc_comment = if is_array {
91            format!("Returns true if the {rust_field_name} field is not empty.")
92        } else {
93            format!("Returns true if the {rust_field_name} field is present (Some).")
94        };
95
96        let method = RustTraitMethod::new(method_name)
97            .with_return_type(RustType::Boolean)
98            .with_doc(doc_comment);
99
100        rust_trait.add_method(method);
101        Ok(())
102    }
103
104    /// Get the appropriate base trait name based on the structure definition
105    fn get_base_trait_name(&self, structure_def: &StructureDefinition) -> String {
106        // Special case: Resource itself should not have a super trait to avoid circular dependency
107        if structure_def.id == "Resource" {
108            return String::new(); // No super trait
109        }
110
111        // Check if this inherits from DomainResource
112        if let Some(base_definition) = &structure_def.base_definition {
113            if base_definition.contains("DomainResource") {
114                return "DomainResourceExistence".to_string();
115            }
116        }
117
118        // Default to Resource for other cases
119        "ResourceExistence".to_string()
120    }
121
122    /// Generate comprehensive documentation for the existence trait
123    fn generate_trait_documentation(&self, structure_def: &StructureDefinition) -> String {
124        let mut docs = vec![
125            format!("{} Existence Checks", structure_def.id),
126            "".to_string(),
127            "This trait provides existence check methods for this FHIR resource type.".to_string(),
128            "".to_string(),
129        ];
130
131        if let Some(description) = &structure_def.description {
132            docs.push(description.clone());
133            docs.push("".to_string());
134        }
135
136        docs.extend(vec![
137            "**Source:**".to_string(),
138            format!("- URL: {}", &structure_def.url),
139            format!(
140                "- Version: {}",
141                structure_def
142                    .version
143                    .as_ref()
144                    .unwrap_or(&"Unknown".to_string())
145            ),
146            format!("- Kind: {}", &structure_def.kind),
147            format!("- Type: {}", &structure_def.base_type),
148        ]);
149
150        if let Some(base_definition) = &structure_def.base_definition {
151            docs.push(format!("- Base Definition: {base_definition}"));
152        }
153
154        docs.join("\n")
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn test_existence_trait_generation() {
164        let _generator = ExistenceTraitGenerator::new();
165
166        // This would need a proper StructureDefinition for a real test
167        // For now, just verify the generator can be created
168    }
169
170    #[test]
171    fn test_base_trait_selection() {
172        let generator = ExistenceTraitGenerator::new();
173
174        let structure_def = StructureDefinition {
175            resource_type: "StructureDefinition".to_string(),
176            id: "TestResource".to_string(),
177            url: "http://example.com/TestResource".to_string(),
178            version: None,
179            name: "TestResource".to_string(),
180            title: None,
181            status: "draft".to_string(),
182            description: None,
183            purpose: None,
184            kind: "resource".to_string(),
185            is_abstract: false,
186            base_type: "TestResource".to_string(),
187            base_definition: Some(
188                "http://hl7.org/fhir/StructureDefinition/DomainResource".to_string(),
189            ),
190            differential: None,
191            snapshot: None,
192        };
193
194        let base_trait = generator.get_base_trait_name(&structure_def);
195        assert_eq!(base_trait, "DomainResourceExistence");
196    }
197}