rh_foundation/snapshot/
path.rs1#[derive(Debug, Clone, PartialEq, Eq)]
2pub struct ElementPath {
3 parts: Vec<String>,
4 original: String,
5}
6
7impl ElementPath {
8 pub fn new(path: &str) -> Self {
9 let parts: Vec<String> = path.split('.').map(|s| s.to_string()).collect();
10 Self {
11 parts,
12 original: path.to_string(),
13 }
14 }
15
16 pub fn from_parts(parts: Vec<String>) -> Self {
32 let original = parts.join(".");
33 Self { parts, original }
34 }
35
36 pub fn parts(&self) -> &[String] {
37 &self.parts
38 }
39
40 pub fn as_str(&self) -> &str {
41 &self.original
42 }
43
44 pub fn depth(&self) -> usize {
45 self.parts.len()
46 }
47
48 pub fn is_parent_of(&self, other: &ElementPath) -> bool {
49 if self.depth() >= other.depth() {
50 return false;
51 }
52
53 for (i, part) in self.parts.iter().enumerate() {
54 if other.parts.get(i) != Some(part) {
55 return false;
56 }
57 }
58
59 true
60 }
61
62 pub fn is_child_of(&self, other: &ElementPath) -> bool {
63 other.is_parent_of(self)
64 }
65
66 pub fn is_immediate_child_of(&self, parent: &ElementPath) -> bool {
67 self.depth() == parent.depth() + 1 && self.is_child_of(parent)
68 }
69
70 pub fn parent(&self) -> Option<&[String]> {
94 if self.parts.len() <= 1 {
95 return None;
96 }
97
98 Some(&self.parts[0..self.parts.len() - 1])
99 }
100
101 pub fn matches_choice_type(&self, base_path: &ElementPath) -> bool {
102 if self.depth() != base_path.depth() {
103 return false;
104 }
105
106 for i in 0..self.parts.len() - 1 {
107 if self.parts[i] != base_path.parts[i] {
108 return false;
109 }
110 }
111
112 let base_last = base_path.parts.last().unwrap();
113 let self_last = self.parts.last().unwrap();
114
115 if base_last.ends_with("[x]") {
116 let base_prefix = &base_last[..base_last.len() - 3];
117 self_last.starts_with(base_prefix)
118 } else {
119 false
120 }
121 }
122
123 fn is_lowercase_start(s: &str) -> bool {
124 s.chars().next().is_some_and(|c| c.is_lowercase())
125 }
126
127 pub fn normalize_choice_type(&self) -> ElementPath {
128 let mut normalized_parts = self.parts.clone();
129 if let Some(last) = normalized_parts.last_mut() {
130 if last.len() > 3 && Self::is_lowercase_start(last) {
131 for (i, c) in last.char_indices() {
132 if c.is_uppercase() {
133 let prefix = &last[..i];
134 *last = format!("{prefix}[x]");
135 break;
136 }
137 }
138 }
139 }
140 Self::from_parts(normalized_parts)
141 }
142
143 pub fn is_slice(&self) -> bool {
144 self.original.contains(':')
145 }
146
147 pub fn slice_name(&self) -> Option<&str> {
148 for part in &self.parts {
149 if let Some(colon_pos) = part.rfind(':') {
150 return Some(&part[colon_pos + 1..]);
151 }
152 }
153 None
154 }
155
156 pub fn base_path(&self) -> ElementPath {
157 if !self.is_slice() {
158 return self.clone();
159 }
160
161 let base_parts: Vec<String> = self
162 .parts
163 .iter()
164 .map(|part| {
165 if let Some(colon_pos) = part.rfind(':') {
166 part[..colon_pos].to_string()
167 } else {
168 part.clone()
169 }
170 })
171 .collect();
172
173 Self::from_parts(base_parts)
174 }
175
176 pub fn is_reslice(&self) -> bool {
177 if let Some(last_part) = self.parts.last() {
178 last_part.matches(':').count() > 1
179 } else {
180 false
181 }
182 }
183
184 pub fn parent_slice(&self) -> Option<ElementPath> {
185 if !self.is_reslice() {
186 return None;
187 }
188
189 if let Some(last_part) = self.parts.last() {
190 let colon_pos = last_part.rfind(':').unwrap();
191 let mut parent_parts = self.parts.clone();
192 parent_parts.pop();
193 parent_parts.push(last_part[..colon_pos].to_string());
194 return Some(Self::from_parts(parent_parts));
195 }
196
197 None
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn test_path_parsing() {
207 let path = ElementPath::new("Patient.name.given");
208 assert_eq!(path.parts(), &["Patient", "name", "given"]);
209 assert_eq!(path.as_str(), "Patient.name.given");
210 assert_eq!(path.depth(), 3);
211 }
212
213 #[test]
214 fn test_single_part_path() {
215 let path = ElementPath::new("Patient");
216 assert_eq!(path.parts(), &["Patient"]);
217 assert_eq!(path.depth(), 1);
218 }
219
220 #[test]
221 fn test_is_parent_of() {
222 let parent = ElementPath::new("Patient.name");
223 let child = ElementPath::new("Patient.name.given");
224 let not_child = ElementPath::new("Patient.identifier");
225
226 assert!(parent.is_parent_of(&child));
227 assert!(!parent.is_parent_of(¬_child));
228 assert!(!parent.is_parent_of(&parent));
229 }
230
231 #[test]
232 fn test_is_child_of() {
233 let parent = ElementPath::new("Patient.name");
234 let child = ElementPath::new("Patient.name.given");
235
236 assert!(child.is_child_of(&parent));
237 assert!(!parent.is_child_of(&child));
238 }
239
240 #[test]
241 fn test_is_immediate_child_of() {
242 let parent = ElementPath::new("Patient.name");
243 let immediate_child = ElementPath::new("Patient.name.given");
244 let grandchild = ElementPath::new("Patient.name.given.extension");
245
246 assert!(immediate_child.is_immediate_child_of(&parent));
247 assert!(!grandchild.is_immediate_child_of(&parent));
248 assert!(!parent.is_immediate_child_of(&immediate_child));
249 }
250
251 #[test]
252 fn test_parent() {
253 let path = ElementPath::new("Patient.name.given");
254 let parent_parts = path.parent().unwrap();
255 assert_eq!(parent_parts, &["Patient", "name"]);
256
257 let root = ElementPath::new("Patient");
258 assert!(root.parent().is_none());
259 }
260
261 #[test]
262 fn test_matches_choice_type() {
263 let base = ElementPath::new("Observation.value[x]");
264 let string_variant = ElementPath::new("Observation.valueString");
265 let quantity_variant = ElementPath::new("Observation.valueQuantity");
266 let codeable_variant = ElementPath::new("Observation.valueCodeableConcept");
267 let other = ElementPath::new("Observation.status");
268
269 assert!(string_variant.matches_choice_type(&base));
270 assert!(quantity_variant.matches_choice_type(&base));
271 assert!(codeable_variant.matches_choice_type(&base));
272 assert!(!other.matches_choice_type(&base));
273 }
274
275 #[test]
276 fn test_normalize_choice_type() {
277 let string_path = ElementPath::new("Observation.valueString");
278 let normalized = string_path.normalize_choice_type();
279 assert_eq!(normalized.as_str(), "Observation.value[x]");
280
281 let quantity_path = ElementPath::new("Observation.valueQuantity");
282 let normalized = quantity_path.normalize_choice_type();
283 assert_eq!(normalized.as_str(), "Observation.value[x]");
284
285 let codeable_path = ElementPath::new("Observation.valueCodeableConcept");
286 let normalized = codeable_path.normalize_choice_type();
287 assert_eq!(normalized.as_str(), "Observation.value[x]");
288 }
289
290 #[test]
291 fn test_normalize_non_choice_type() {
292 let normal_path = ElementPath::new("Patient.name");
293 let normalized = normal_path.normalize_choice_type();
294 assert_eq!(normalized.as_str(), "Patient.name");
295 }
296
297 #[test]
298 fn test_multi_level_parent_child() {
299 let root = ElementPath::new("Patient");
300 let level1 = ElementPath::new("Patient.name");
301 let level2 = ElementPath::new("Patient.name.given");
302 let level3 = ElementPath::new("Patient.name.given.extension");
303
304 assert!(root.is_parent_of(&level1));
305 assert!(root.is_parent_of(&level2));
306 assert!(root.is_parent_of(&level3));
307
308 assert!(level1.is_parent_of(&level2));
309 assert!(level1.is_parent_of(&level3));
310
311 assert!(level2.is_parent_of(&level3));
312 }
313
314 #[test]
315 fn test_parent_chain() {
316 let path = ElementPath::new("Patient.name.given.extension");
317
318 let parent1 = path.parent().unwrap();
320 assert_eq!(parent1, &["Patient", "name", "given"]);
321
322 let parent1_path = ElementPath::from_parts(parent1.to_vec());
324 let parent2 = parent1_path.parent().unwrap();
325 assert_eq!(parent2, &["Patient", "name"]);
326
327 let parent2_path = ElementPath::from_parts(parent2.to_vec());
328 let parent3 = parent2_path.parent().unwrap();
329 assert_eq!(parent3, &["Patient"]);
330
331 let parent3_path = ElementPath::from_parts(parent3.to_vec());
332 assert!(parent3_path.parent().is_none());
333 }
334
335 #[test]
336 fn test_is_slice() {
337 let slice_path = ElementPath::new("Patient.identifier:MRN");
338 let normal_path = ElementPath::new("Patient.identifier");
339
340 assert!(slice_path.is_slice());
341 assert!(!normal_path.is_slice());
342 }
343
344 #[test]
345 fn test_slice_name() {
346 let slice_path = ElementPath::new("Patient.identifier:MRN");
347 assert_eq!(slice_path.slice_name(), Some("MRN"));
348
349 let normal_path = ElementPath::new("Patient.identifier");
350 assert_eq!(normal_path.slice_name(), None);
351
352 let nested_slice = ElementPath::new("Patient.identifier:MRN.system");
353 assert_eq!(nested_slice.slice_name(), Some("MRN"));
354 }
355
356 #[test]
357 fn test_base_path() {
358 let slice_path = ElementPath::new("Patient.identifier:MRN");
359 let base = slice_path.base_path();
360 assert_eq!(base.as_str(), "Patient.identifier");
361
362 let normal_path = ElementPath::new("Patient.identifier");
363 let base_normal = normal_path.base_path();
364 assert_eq!(base_normal.as_str(), "Patient.identifier");
365 }
366
367 #[test]
368 fn test_is_reslice() {
369 let reslice_path = ElementPath::new("Patient.identifier:MRN:subslice");
370 let slice_path = ElementPath::new("Patient.identifier:MRN");
371 let normal_path = ElementPath::new("Patient.identifier");
372
373 assert!(reslice_path.is_reslice());
374 assert!(!slice_path.is_reslice());
375 assert!(!normal_path.is_reslice());
376 }
377
378 #[test]
379 fn test_parent_slice() {
380 let reslice_path = ElementPath::new("Patient.identifier:MRN:subslice");
381 let parent = reslice_path.parent_slice().unwrap();
382 assert_eq!(parent.as_str(), "Patient.identifier:MRN");
383
384 let slice_path = ElementPath::new("Patient.identifier:MRN");
385 assert!(slice_path.parent_slice().is_none());
386 }
387
388 #[test]
389 fn test_slice_with_children() {
390 let slice_child = ElementPath::new("Patient.identifier:MRN.system");
391 assert!(slice_child.is_slice());
392 assert_eq!(slice_child.slice_name(), Some("MRN"));
393
394 let base = slice_child.base_path();
395 assert_eq!(base.as_str(), "Patient.identifier.system");
396 }
397
398 #[test]
399 fn test_is_lowercase_start() {
400 assert!(ElementPath::is_lowercase_start("abc"));
401 assert!(!ElementPath::is_lowercase_start("Abc"));
402 assert!(!ElementPath::is_lowercase_start(""));
403 assert!(!ElementPath::is_lowercase_start("123"));
404 assert!(!ElementPath::is_lowercase_start("ABC"));
405 }
406
407 #[test]
408 fn test_normalize_choice_type_minimum_length() {
409 let path = ElementPath::new("Observation.valA");
411 let normalized = path.normalize_choice_type();
412 assert_eq!(normalized.as_str(), "Observation.val[x]");
413
414 let short_path = ElementPath::new("Observation.vaA");
416 let short_normalized = short_path.normalize_choice_type();
417 assert_eq!(short_normalized.as_str(), "Observation.vaA");
418 }
419}