1use crate::fhir_types::{ElementConstraint, StructureDefinition};
7use rh_foundation::{Invariant, Severity};
8
9pub fn extract_invariants(structure_def: &StructureDefinition) -> Vec<Invariant> {
15 let mut invariants = Vec::new();
16
17 if let Some(ref snapshot) = structure_def.snapshot {
18 for element in &snapshot.element {
19 if let Some(ref constraints) = element.constraint {
20 for constraint in constraints {
21 if let Some(invariant) = convert_constraint(constraint) {
22 if !invariants
23 .iter()
24 .any(|i: &Invariant| i.key == invariant.key)
25 {
26 invariants.push(invariant);
27 }
28 }
29 }
30 }
31 }
32 }
33
34 if let Some(ref differential) = structure_def.differential {
35 for element in &differential.element {
36 if let Some(ref constraints) = element.constraint {
37 for constraint in constraints {
38 if let Some(invariant) = convert_constraint(constraint) {
39 if !invariants
40 .iter()
41 .any(|i: &Invariant| i.key == invariant.key)
42 {
43 invariants.push(invariant);
44 }
45 }
46 }
47 }
48 }
49 }
50 invariants.sort_by(|a, b| a.key.cmp(&b.key));
51 invariants
52}
53
54fn convert_constraint(constraint: &ElementConstraint) -> Option<Invariant> {
59 let expression = constraint.expression.as_ref()?;
60
61 let severity = match constraint.severity.as_str() {
62 "error" => Severity::Error,
63 "warning" => Severity::Warning,
64 _ => Severity::Information,
65 };
66
67 let mut invariant = Invariant::new(&constraint.key, severity, &constraint.human, expression);
68
69 if let Some(xpath) = &constraint.xpath {
70 invariant = invariant.with_xpath(xpath);
71 }
72
73 Some(invariant)
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use crate::fhir_types::{
80 ElementDefinition, StructureDefinitionDifferential, StructureDefinitionSnapshot,
81 };
82
83 fn create_test_constraint(
84 key: &str,
85 severity: &str,
86 human: &str,
87 expression: Option<&str>,
88 ) -> ElementConstraint {
89 ElementConstraint {
90 key: key.to_string(),
91 severity: severity.to_string(),
92 human: human.to_string(),
93 expression: expression.map(|s| s.to_string()),
94 xpath: None,
95 source: None,
96 }
97 }
98
99 fn create_test_element(constraints: Vec<ElementConstraint>) -> ElementDefinition {
100 ElementDefinition {
101 id: Some("Patient".to_string()),
102 path: "Patient".to_string(),
103 short: None,
104 definition: None,
105 min: None,
106 max: None,
107 element_type: None,
108 fixed: None,
109 pattern: None,
110 binding: None,
111 constraint: Some(constraints),
112 }
113 }
114
115 #[test]
116 fn test_convert_constraint_error() {
117 let constraint =
118 create_test_constraint("pat-1", "error", "Name is required", Some("name.exists()"));
119
120 let invariant = convert_constraint(&constraint).unwrap();
121 assert_eq!(invariant.key, "pat-1");
122 assert_eq!(invariant.severity, Severity::Error);
123 assert_eq!(invariant.human, "Name is required");
124 assert_eq!(invariant.expression, "name.exists()");
125 }
126
127 #[test]
128 fn test_convert_constraint_warning() {
129 let constraint = create_test_constraint(
130 "pat-2",
131 "warning",
132 "Telecom recommended",
133 Some("telecom.exists()"),
134 );
135
136 let invariant = convert_constraint(&constraint).unwrap();
137 assert_eq!(invariant.severity, Severity::Warning);
138 }
139
140 #[test]
141 fn test_convert_constraint_no_expression() {
142 let constraint = ElementConstraint {
143 key: "pat-1".to_string(),
144 severity: "error".to_string(),
145 human: "Name is required".to_string(),
146 expression: None,
147 xpath: Some("f:name".to_string()),
148 source: None,
149 };
150
151 let invariant = convert_constraint(&constraint);
152 assert!(invariant.is_none());
153 }
154
155 #[test]
156 fn test_extract_invariants_from_snapshot() {
157 let constraints = vec![
158 create_test_constraint("pat-1", "error", "Name required", Some("name.exists()")),
159 create_test_constraint(
160 "pat-2",
161 "warning",
162 "Telecom recommended",
163 Some("telecom.exists()"),
164 ),
165 ];
166
167 let element = create_test_element(constraints);
168 let structure_def = StructureDefinition {
169 resource_type: "StructureDefinition".to_string(),
170 id: "Patient".to_string(),
171 url: "http://hl7.org/fhir/StructureDefinition/Patient".to_string(),
172 version: Some("4.0.1".to_string()),
173 name: "Patient".to_string(),
174 title: Some("Patient".to_string()),
175 status: "active".to_string(),
176 description: None,
177 purpose: None,
178 kind: "resource".to_string(),
179 is_abstract: false,
180 base_type: "Patient".to_string(),
181 base_definition: None,
182 differential: None,
183 snapshot: Some(StructureDefinitionSnapshot {
184 element: vec![element],
185 }),
186 };
187
188 let invariants = extract_invariants(&structure_def);
189 assert_eq!(invariants.len(), 2);
190 assert_eq!(invariants[0].key, "pat-1");
191 assert_eq!(invariants[1].key, "pat-2");
192 }
193
194 #[test]
195 fn test_extract_invariants_deduplication() {
196 let constraints = vec![create_test_constraint(
197 "pat-1",
198 "error",
199 "Name required",
200 Some("name.exists()"),
201 )];
202
203 let element1 = create_test_element(constraints.clone());
204 let element2 = create_test_element(constraints);
205
206 let structure_def = StructureDefinition {
207 resource_type: "StructureDefinition".to_string(),
208 id: "Patient".to_string(),
209 url: "http://hl7.org/fhir/StructureDefinition/Patient".to_string(),
210 version: Some("4.0.1".to_string()),
211 name: "Patient".to_string(),
212 title: Some("Patient".to_string()),
213 status: "active".to_string(),
214 description: None,
215 purpose: None,
216 kind: "resource".to_string(),
217 is_abstract: false,
218 base_type: "Patient".to_string(),
219 base_definition: None,
220 differential: Some(StructureDefinitionDifferential {
221 element: vec![element1],
222 }),
223 snapshot: Some(StructureDefinitionSnapshot {
224 element: vec![element2],
225 }),
226 };
227
228 let invariants = extract_invariants(&structure_def);
229 assert_eq!(invariants.len(), 1);
230 assert_eq!(invariants[0].key, "pat-1");
231 }
232
233 #[test]
234 fn test_extract_invariants_sorted() {
235 let constraints = vec![
236 create_test_constraint("pat-3", "error", "Test 3", Some("true")),
237 create_test_constraint("pat-1", "error", "Test 1", Some("true")),
238 create_test_constraint("pat-2", "error", "Test 2", Some("true")),
239 ];
240
241 let element = create_test_element(constraints);
242 let structure_def = StructureDefinition {
243 resource_type: "StructureDefinition".to_string(),
244 id: "Test".to_string(),
245 url: "http://example.com/Test".to_string(),
246 version: None,
247 name: "Test".to_string(),
248 title: None,
249 status: "active".to_string(),
250 description: None,
251 purpose: None,
252 kind: "resource".to_string(),
253 is_abstract: false,
254 base_type: "Test".to_string(),
255 base_definition: None,
256 differential: None,
257 snapshot: Some(StructureDefinitionSnapshot {
258 element: vec![element],
259 }),
260 };
261
262 let invariants = extract_invariants(&structure_def);
263 assert_eq!(invariants.len(), 3);
264 assert_eq!(invariants[0].key, "pat-1");
265 assert_eq!(invariants[1].key, "pat-2");
266 assert_eq!(invariants[2].key, "pat-3");
267 }
268
269 #[test]
270 fn test_extract_invariants_empty() {
271 let structure_def = StructureDefinition {
272 resource_type: "StructureDefinition".to_string(),
273 id: "Test".to_string(),
274 url: "http://example.com/Test".to_string(),
275 version: None,
276 name: "Test".to_string(),
277 title: None,
278 status: "active".to_string(),
279 description: None,
280 purpose: None,
281 kind: "resource".to_string(),
282 is_abstract: false,
283 base_type: "Test".to_string(),
284 base_definition: None,
285 differential: None,
286 snapshot: None,
287 };
288
289 let invariants = extract_invariants(&structure_def);
290 assert_eq!(invariants.len(), 0);
291 }
292}