Skip to main content

rh_codegen/generators/
cardinality_generator.rs

1//! Cardinality constant generation
2//!
3//! This module generates constant declarations for FHIR element cardinalities that can be
4//! embedded in generated resource and datatype structs.
5
6use crate::fhir_types::StructureDefinition;
7
8/// Generator for cardinality constants
9pub struct CardinalityGenerator;
10
11impl CardinalityGenerator {
12    /// Generate a CARDINALITIES constant for a StructureDefinition
13    ///
14    /// Returns Rust code as a string containing the static declaration.
15    ///
16    /// Uses `once_cell::sync::Lazy` for runtime initialization.
17    ///
18    /// Example output:
19    /// ```rust,ignore
20    /// pub static CARDINALITIES: once_cell::sync::Lazy<Vec<rh_foundation::ElementCardinality>> =
21    ///     once_cell::sync::Lazy::new(|| vec![
22    ///         rh_foundation::ElementCardinality::new("Patient.identifier", 0, None),
23    ///         rh_foundation::ElementCardinality::new("Patient.active", 0, Some(1)),
24    ///     ]);
25    /// ```
26    pub fn generate_cardinalities_constant(structure_def: &StructureDefinition) -> String {
27        let cardinalities = Self::extract_cardinalities(structure_def);
28
29        if cardinalities.is_empty() {
30            return String::new();
31        }
32
33        let mut code = String::new();
34        code.push_str("/// FHIR cardinality constraints for this resource/datatype\n");
35        code.push_str("///\n");
36        code.push_str(
37            "/// These define the minimum and maximum occurrences allowed for each element.\n",
38        );
39        code.push_str("pub static CARDINALITIES: once_cell::sync::Lazy<Vec<rh_foundation::ElementCardinality>> = once_cell::sync::Lazy::new(|| vec![\n");
40
41        for card in &cardinalities {
42            let path = escape_rust_string(&card.path);
43            let max_str = match card.max {
44                None => "None".to_string(),
45                Some(n) => format!("Some({n})"),
46            };
47
48            code.push_str(&format!(
49                "    rh_foundation::ElementCardinality::new(\"{path}\", {}, {max_str}),\n",
50                card.min
51            ));
52        }
53
54        code.push_str("]);\n");
55        code
56    }
57
58    /// Extract cardinality constraints from a StructureDefinition
59    ///
60    /// Returns a vector of ElementCardinality structs for all elements in the resource.
61    fn extract_cardinalities(
62        structure_def: &StructureDefinition,
63    ) -> Vec<rh_foundation::validation::ElementCardinality> {
64        let mut cardinalities = Vec::new();
65
66        // Get the base type name (e.g., "Patient" from "Patient" or "http://hl7.org/fhir/StructureDefinition/Patient")
67        let base_type = structure_def.name.as_str();
68
69        // Process snapshot elements if available
70        if let Some(snapshot) = &structure_def.snapshot {
71            for element in &snapshot.element {
72                // Skip the root element (e.g., just "Patient")
73                if element.path == base_type {
74                    continue;
75                }
76
77                // Only process direct child elements (not nested BackboneElement sub-elements for now)
78                // We can enhance this later to handle nested elements
79                if element.path.matches('.').count() > 1 {
80                    // This is a nested element like "Patient.contact.name"
81                    // For Phase 9, we'll handle these but need to be careful about the path
82                    // For now, include them all
83                }
84
85                let min = element.min.unwrap_or(0) as usize;
86                let max = if let Some(max_str) = &element.max {
87                    if max_str == "*" {
88                        None // Unbounded
89                    } else {
90                        max_str.parse::<usize>().ok()
91                    }
92                } else {
93                    Some(1) // Default to 1 if not specified
94                };
95
96                cardinalities.push(rh_foundation::validation::ElementCardinality::new(
97                    element.path.clone(),
98                    min,
99                    max,
100                ));
101            }
102        }
103
104        cardinalities
105    }
106}
107
108/// Escape a string for use in a Rust string literal
109fn escape_rust_string(s: &str) -> String {
110    s.replace('\\', "\\\\")
111        .replace('"', "\\\"")
112        .replace('\n', "\\n")
113        .replace('\r', "\\r")
114        .replace('\t', "\\t")
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_escape_rust_string() {
123        assert_eq!(escape_rust_string("hello"), "hello");
124        assert_eq!(escape_rust_string("hello \"world\""), "hello \\\"world\\\"");
125        assert_eq!(escape_rust_string("path\\to\\file"), "path\\\\to\\\\file");
126    }
127}