1use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "lowercase")]
10pub enum Severity {
11 Error,
13 Warning,
15 Information,
17}
18
19impl Severity {
20 fn rank(&self) -> u8 {
21 match self {
22 Severity::Error => 2,
23 Severity::Warning => 1,
24 Severity::Information => 0,
25 }
26 }
27}
28
29impl PartialOrd for Severity {
30 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
31 Some(self.cmp(other))
32 }
33}
34
35impl Ord for Severity {
36 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
37 self.rank().cmp(&other.rank())
38 }
39}
40
41impl std::fmt::Display for Severity {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 match self {
44 Severity::Error => write!(f, "error"),
45 Severity::Warning => write!(f, "warning"),
46 Severity::Information => write!(f, "information"),
47 }
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
53#[serde(rename_all = "lowercase")]
54pub enum BindingStrength {
55 Required,
57 Extensible,
59 Preferred,
61 Example,
63}
64
65impl BindingStrength {
66 pub fn from_fhir_str(s: &str) -> Option<Self> {
68 match s {
69 "required" => Some(BindingStrength::Required),
70 "extensible" => Some(BindingStrength::Extensible),
71 "preferred" => Some(BindingStrength::Preferred),
72 "example" => Some(BindingStrength::Example),
73 _ => None,
74 }
75 }
76}
77
78impl std::fmt::Display for BindingStrength {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 match self {
81 BindingStrength::Required => write!(f, "required"),
82 BindingStrength::Extensible => write!(f, "extensible"),
83 BindingStrength::Preferred => write!(f, "preferred"),
84 BindingStrength::Example => write!(f, "example"),
85 }
86 }
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
94pub struct ElementBinding {
95 pub path: String,
97 pub strength: BindingStrength,
99 pub value_set_url: String,
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub description: Option<String>,
104}
105
106impl ElementBinding {
107 pub fn new(
109 path: impl Into<String>,
110 strength: BindingStrength,
111 value_set_url: impl Into<String>,
112 ) -> Self {
113 Self {
114 path: path.into(),
115 strength,
116 value_set_url: value_set_url.into(),
117 description: None,
118 }
119 }
120
121 pub fn with_description(mut self, description: impl Into<String>) -> Self {
123 self.description = Some(description.into());
124 self
125 }
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
133pub struct Invariant {
134 pub key: String,
136 pub severity: Severity,
138 pub human: String,
140 pub expression: String,
142 #[serde(skip_serializing_if = "Option::is_none")]
144 pub xpath: Option<String>,
145}
146
147impl Invariant {
148 pub fn new(
150 key: impl Into<String>,
151 severity: Severity,
152 human: impl Into<String>,
153 expression: impl Into<String>,
154 ) -> Self {
155 Self {
156 key: key.into(),
157 severity,
158 human: human.into(),
159 expression: expression.into(),
160 xpath: None,
161 }
162 }
163
164 pub fn with_xpath(mut self, xpath: impl Into<String>) -> Self {
166 self.xpath = Some(xpath.into());
167 self
168 }
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
176pub struct ElementCardinality {
177 pub path: String,
179 pub min: usize,
181 pub max: Option<usize>,
183}
184
185impl ElementCardinality {
186 pub fn new(path: impl Into<String>, min: usize, max: Option<usize>) -> Self {
188 Self {
189 path: path.into(),
190 min,
191 max,
192 }
193 }
194
195 pub fn is_unbounded(&self) -> bool {
197 self.max.is_none()
198 }
199
200 pub fn is_required(&self) -> bool {
202 self.min > 0
203 }
204
205 pub fn is_array(&self) -> bool {
207 self.max.is_none_or(|m| m > 1)
208 }
209
210 pub fn to_fhir_notation(&self) -> String {
212 match self.max {
213 None => format!("{}..*", self.min),
214 Some(max) => format!("{}..{}", self.min, max),
215 }
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn test_severity_ordering() {
225 assert!(Severity::Error > Severity::Warning);
226 assert!(Severity::Warning > Severity::Information);
227 }
228
229 #[test]
230 fn test_invariant_creation() {
231 let inv = Invariant::new(
232 "pat-1",
233 Severity::Error,
234 "Name is required",
235 "name.exists()",
236 );
237 assert_eq!(inv.key, "pat-1");
238 assert_eq!(inv.severity, Severity::Error);
239 assert_eq!(inv.human, "Name is required");
240 assert_eq!(inv.expression, "name.exists()");
241 assert!(inv.xpath.is_none());
242 }
243
244 #[test]
245 fn test_invariant_with_xpath() {
246 let inv = Invariant::new("obs-1", Severity::Warning, "Code required", "code.exists()")
247 .with_xpath("f:code");
248 assert!(inv.xpath.is_some());
249 }
250
251 #[test]
252 fn test_element_cardinality_creation() {
253 let card = ElementCardinality::new("Patient.identifier", 0, None);
254 assert_eq!(card.path, "Patient.identifier");
255 assert_eq!(card.min, 0);
256 assert!(card.max.is_none());
257 assert!(card.is_unbounded());
258 assert!(!card.is_required());
259 assert!(card.is_array());
260 }
261
262 #[test]
263 fn test_element_cardinality_required_single() {
264 let card = ElementCardinality::new("Observation.code", 1, Some(1));
265 assert!(card.is_required());
266 assert!(!card.is_unbounded());
267 assert!(!card.is_array());
268 assert_eq!(card.to_fhir_notation(), "1..1");
269 }
270
271 #[test]
272 fn test_element_cardinality_optional_array() {
273 let card = ElementCardinality::new("Patient.name", 0, None);
274 assert!(!card.is_required());
275 assert!(card.is_unbounded());
276 assert!(card.is_array());
277 assert_eq!(card.to_fhir_notation(), "0..*");
278 }
279
280 #[test]
281 fn test_element_cardinality_bounded_array() {
282 let card = ElementCardinality::new("Patient.photo", 0, Some(5));
283 assert!(!card.is_required());
284 assert!(!card.is_unbounded());
285 assert!(card.is_array());
286 assert_eq!(card.to_fhir_notation(), "0..5");
287 }
288
289 #[test]
290 fn test_invariant_with_xpath_value() {
291 let inv = Invariant::new("obs-1", Severity::Warning, "Code required", "code.exists()")
292 .with_xpath("f:code");
293 assert_eq!(inv.xpath.unwrap(), "f:code");
294 }
295
296 #[test]
297 fn test_invariant_serialization() {
298 let inv = Invariant::new("test-1", Severity::Error, "Test", "true");
299 let json = serde_json::to_string(&inv).unwrap();
300 assert!(json.contains("test-1"));
301 assert!(json.contains("error"));
302 }
303
304 #[test]
305 fn test_binding_strength_parsing() {
306 assert_eq!(
307 BindingStrength::from_fhir_str("required"),
308 Some(BindingStrength::Required)
309 );
310 assert_eq!(
311 BindingStrength::from_fhir_str("extensible"),
312 Some(BindingStrength::Extensible)
313 );
314 assert_eq!(
315 BindingStrength::from_fhir_str("preferred"),
316 Some(BindingStrength::Preferred)
317 );
318 assert_eq!(
319 BindingStrength::from_fhir_str("example"),
320 Some(BindingStrength::Example)
321 );
322 assert_eq!(BindingStrength::from_fhir_str("invalid"), None);
323 }
324
325 #[test]
326 fn test_element_binding_creation() {
327 let binding = ElementBinding::new(
328 "Patient.gender",
329 BindingStrength::Required,
330 "http://hl7.org/fhir/ValueSet/administrative-gender",
331 );
332 assert_eq!(binding.path, "Patient.gender");
333 assert_eq!(binding.strength, BindingStrength::Required);
334 assert_eq!(
335 binding.value_set_url,
336 "http://hl7.org/fhir/ValueSet/administrative-gender"
337 );
338 assert!(binding.description.is_none());
339 }
340
341 #[test]
342 fn test_element_binding_with_description() {
343 let binding = ElementBinding::new(
344 "Patient.gender",
345 BindingStrength::Required,
346 "http://hl7.org/fhir/ValueSet/administrative-gender",
347 )
348 .with_description("The gender of the patient");
349 assert!(binding.description.is_some());
350 assert_eq!(binding.description.unwrap(), "The gender of the patient");
351 }
352}