rh_foundation/snapshot/
path.rs1use crate::snapshot::path_helpers::{
2 find_slice_name, has_reslice, matches_choice_type_parts, normalized_choice_segment,
3 parent_slice_parts, parts_are_parent_child, split_path, starts_with_lowercase,
4 strip_slice_segments,
5};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct ElementPath {
9 parts: Vec<String>,
10 original: String,
11}
12
13impl ElementPath {
14 pub fn new(path: &str) -> Self {
15 let parts = split_path(path);
16 Self {
17 parts,
18 original: path.to_string(),
19 }
20 }
21
22 pub fn from_parts(parts: Vec<String>) -> Self {
38 let original = parts.join(".");
39 Self { parts, original }
40 }
41
42 pub fn parts(&self) -> &[String] {
43 &self.parts
44 }
45
46 pub fn as_str(&self) -> &str {
47 &self.original
48 }
49
50 pub fn depth(&self) -> usize {
51 self.parts.len()
52 }
53
54 pub fn is_parent_of(&self, other: &ElementPath) -> bool {
55 parts_are_parent_child(&self.parts, &other.parts)
56 }
57
58 pub fn is_child_of(&self, other: &ElementPath) -> bool {
59 other.is_parent_of(self)
60 }
61
62 pub fn is_immediate_child_of(&self, parent: &ElementPath) -> bool {
63 self.depth() == parent.depth() + 1 && self.is_child_of(parent)
64 }
65
66 pub fn parent(&self) -> Option<&[String]> {
90 if self.parts.len() <= 1 {
91 return None;
92 }
93
94 Some(&self.parts[0..self.parts.len() - 1])
95 }
96
97 pub fn matches_choice_type(&self, base_path: &ElementPath) -> bool {
98 matches_choice_type_parts(&self.parts, &base_path.parts)
99 }
100
101 fn is_lowercase_start(s: &str) -> bool {
102 starts_with_lowercase(s)
103 }
104
105 pub fn normalize_choice_type(&self) -> ElementPath {
106 let mut normalized_parts = self.parts.clone();
107 if let Some(last) = normalized_parts.last_mut() {
108 if Self::is_lowercase_start(last) {
109 if let Some(normalized) = normalized_choice_segment(last) {
110 *last = normalized;
111 }
112 }
113 }
114 Self::from_parts(normalized_parts)
115 }
116
117 pub fn is_slice(&self) -> bool {
118 self.original.contains(':')
119 }
120
121 pub fn slice_name(&self) -> Option<&str> {
122 find_slice_name(&self.parts)
123 }
124
125 pub fn base_path(&self) -> ElementPath {
126 if !self.is_slice() {
127 return self.clone();
128 }
129
130 Self::from_parts(strip_slice_segments(&self.parts))
131 }
132
133 pub fn is_reslice(&self) -> bool {
134 self.parts
135 .last()
136 .is_some_and(|last_part| has_reslice(last_part))
137 }
138
139 pub fn parent_slice(&self) -> Option<ElementPath> {
140 parent_slice_parts(&self.parts).map(Self::from_parts)
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_path_parsing() {
150 let path = ElementPath::new("Patient.name.given");
151 assert_eq!(path.parts(), &["Patient", "name", "given"]);
152 assert_eq!(path.as_str(), "Patient.name.given");
153 assert_eq!(path.depth(), 3);
154 }
155
156 #[test]
157 fn test_single_part_path() {
158 let path = ElementPath::new("Patient");
159 assert_eq!(path.parts(), &["Patient"]);
160 assert_eq!(path.depth(), 1);
161 }
162
163 #[test]
164 fn test_is_parent_of() {
165 let parent = ElementPath::new("Patient.name");
166 let child = ElementPath::new("Patient.name.given");
167 let not_child = ElementPath::new("Patient.identifier");
168
169 assert!(parent.is_parent_of(&child));
170 assert!(!parent.is_parent_of(¬_child));
171 assert!(!parent.is_parent_of(&parent));
172 }
173
174 #[test]
175 fn test_is_child_of() {
176 let parent = ElementPath::new("Patient.name");
177 let child = ElementPath::new("Patient.name.given");
178
179 assert!(child.is_child_of(&parent));
180 assert!(!parent.is_child_of(&child));
181 }
182
183 #[test]
184 fn test_is_immediate_child_of() {
185 let parent = ElementPath::new("Patient.name");
186 let immediate_child = ElementPath::new("Patient.name.given");
187 let grandchild = ElementPath::new("Patient.name.given.extension");
188
189 assert!(immediate_child.is_immediate_child_of(&parent));
190 assert!(!grandchild.is_immediate_child_of(&parent));
191 assert!(!parent.is_immediate_child_of(&immediate_child));
192 }
193
194 #[test]
195 fn test_parent() {
196 let path = ElementPath::new("Patient.name.given");
197 let parent_parts = path.parent().unwrap();
198 assert_eq!(parent_parts, &["Patient", "name"]);
199
200 let root = ElementPath::new("Patient");
201 assert!(root.parent().is_none());
202 }
203
204 #[test]
205 fn test_matches_choice_type() {
206 let base = ElementPath::new("Observation.value[x]");
207 let string_variant = ElementPath::new("Observation.valueString");
208 let quantity_variant = ElementPath::new("Observation.valueQuantity");
209 let codeable_variant = ElementPath::new("Observation.valueCodeableConcept");
210 let other = ElementPath::new("Observation.status");
211
212 assert!(string_variant.matches_choice_type(&base));
213 assert!(quantity_variant.matches_choice_type(&base));
214 assert!(codeable_variant.matches_choice_type(&base));
215 assert!(!other.matches_choice_type(&base));
216 }
217
218 #[test]
219 fn test_normalize_choice_type() {
220 let string_path = ElementPath::new("Observation.valueString");
221 let normalized = string_path.normalize_choice_type();
222 assert_eq!(normalized.as_str(), "Observation.value[x]");
223
224 let quantity_path = ElementPath::new("Observation.valueQuantity");
225 let normalized = quantity_path.normalize_choice_type();
226 assert_eq!(normalized.as_str(), "Observation.value[x]");
227
228 let codeable_path = ElementPath::new("Observation.valueCodeableConcept");
229 let normalized = codeable_path.normalize_choice_type();
230 assert_eq!(normalized.as_str(), "Observation.value[x]");
231 }
232
233 #[test]
234 fn test_normalize_non_choice_type() {
235 let normal_path = ElementPath::new("Patient.name");
236 let normalized = normal_path.normalize_choice_type();
237 assert_eq!(normalized.as_str(), "Patient.name");
238 }
239
240 #[test]
241 fn test_multi_level_parent_child() {
242 let root = ElementPath::new("Patient");
243 let level1 = ElementPath::new("Patient.name");
244 let level2 = ElementPath::new("Patient.name.given");
245 let level3 = ElementPath::new("Patient.name.given.extension");
246
247 assert!(root.is_parent_of(&level1));
248 assert!(root.is_parent_of(&level2));
249 assert!(root.is_parent_of(&level3));
250
251 assert!(level1.is_parent_of(&level2));
252 assert!(level1.is_parent_of(&level3));
253
254 assert!(level2.is_parent_of(&level3));
255 }
256
257 #[test]
258 fn test_parent_chain() {
259 let path = ElementPath::new("Patient.name.given.extension");
260
261 let parent1 = path.parent().unwrap();
263 assert_eq!(parent1, &["Patient", "name", "given"]);
264
265 let parent1_path = ElementPath::from_parts(parent1.to_vec());
267 let parent2 = parent1_path.parent().unwrap();
268 assert_eq!(parent2, &["Patient", "name"]);
269
270 let parent2_path = ElementPath::from_parts(parent2.to_vec());
271 let parent3 = parent2_path.parent().unwrap();
272 assert_eq!(parent3, &["Patient"]);
273
274 let parent3_path = ElementPath::from_parts(parent3.to_vec());
275 assert!(parent3_path.parent().is_none());
276 }
277
278 #[test]
279 fn test_is_slice() {
280 let slice_path = ElementPath::new("Patient.identifier:MRN");
281 let normal_path = ElementPath::new("Patient.identifier");
282
283 assert!(slice_path.is_slice());
284 assert!(!normal_path.is_slice());
285 }
286
287 #[test]
288 fn test_slice_name() {
289 let slice_path = ElementPath::new("Patient.identifier:MRN");
290 assert_eq!(slice_path.slice_name(), Some("MRN"));
291
292 let normal_path = ElementPath::new("Patient.identifier");
293 assert_eq!(normal_path.slice_name(), None);
294
295 let nested_slice = ElementPath::new("Patient.identifier:MRN.system");
296 assert_eq!(nested_slice.slice_name(), Some("MRN"));
297 }
298
299 #[test]
300 fn test_base_path() {
301 let slice_path = ElementPath::new("Patient.identifier:MRN");
302 let base = slice_path.base_path();
303 assert_eq!(base.as_str(), "Patient.identifier");
304
305 let normal_path = ElementPath::new("Patient.identifier");
306 let base_normal = normal_path.base_path();
307 assert_eq!(base_normal.as_str(), "Patient.identifier");
308 }
309
310 #[test]
311 fn test_is_reslice() {
312 let reslice_path = ElementPath::new("Patient.identifier:MRN:subslice");
313 let slice_path = ElementPath::new("Patient.identifier:MRN");
314 let normal_path = ElementPath::new("Patient.identifier");
315
316 assert!(reslice_path.is_reslice());
317 assert!(!slice_path.is_reslice());
318 assert!(!normal_path.is_reslice());
319 }
320
321 #[test]
322 fn test_parent_slice() {
323 let reslice_path = ElementPath::new("Patient.identifier:MRN:subslice");
324 let parent = reslice_path.parent_slice().unwrap();
325 assert_eq!(parent.as_str(), "Patient.identifier:MRN");
326
327 let slice_path = ElementPath::new("Patient.identifier:MRN");
328 assert!(slice_path.parent_slice().is_none());
329 }
330
331 #[test]
332 fn test_slice_with_children() {
333 let slice_child = ElementPath::new("Patient.identifier:MRN.system");
334 assert!(slice_child.is_slice());
335 assert_eq!(slice_child.slice_name(), Some("MRN"));
336
337 let base = slice_child.base_path();
338 assert_eq!(base.as_str(), "Patient.identifier.system");
339 }
340
341 #[test]
342 fn test_is_lowercase_start() {
343 assert!(ElementPath::is_lowercase_start("abc"));
344 assert!(!ElementPath::is_lowercase_start("Abc"));
345 assert!(!ElementPath::is_lowercase_start(""));
346 assert!(!ElementPath::is_lowercase_start("123"));
347 assert!(!ElementPath::is_lowercase_start("ABC"));
348 }
349
350 #[test]
351 fn test_normalize_choice_type_minimum_length() {
352 let path = ElementPath::new("Observation.valA");
354 let normalized = path.normalize_choice_type();
355 assert_eq!(normalized.as_str(), "Observation.val[x]");
356
357 let short_path = ElementPath::new("Observation.vaA");
359 let short_normalized = short_path.normalize_choice_type();
360 assert_eq!(short_normalized.as_str(), "Observation.vaA");
361 }
362}