1use std::mem;
4
5use crate::{
6 element_map::{self, ElementMap, ElementSet},
7 type_hints::{self, TypeHints},
8 FhirRelease, FhirReleases,
9};
10
11const RESOURCE_COMMON_PRIMITIVE_FIELDS: &[&str] = &["implicitRules", "language"];
12const COMMON_SEQUENCE_FIELDS: &[&str] = &["extension", "modifierExtension"];
13
14fn type_hints(fhir_release: FhirRelease) -> &'static TypeHints {
15 match fhir_release {
16 FhirReleases::R4 => &type_hints::r4::TYPE_HINTS,
17 FhirReleases::R4B => &type_hints::r4b::TYPE_HINTS,
18 FhirReleases::R5 => &type_hints::r5::TYPE_HINTS,
19 _ => panic!("invalid FHIR release"),
20 }
21}
22
23fn element_map(fhir_release: FhirRelease) -> &'static ElementMap {
24 match fhir_release {
25 FhirReleases::R4 => &element_map::r4::ELEMENT_MAP,
26 FhirReleases::R4B => &element_map::r4b::ELEMENT_MAP,
27 FhirReleases::R5 => &element_map::r5::ELEMENT_MAP,
28 _ => panic!("invalid FHIR release"),
29 }
30}
31
32trait FirstLetterUppercase {
33 fn is_first_letter_uppercase(&self) -> bool;
34}
35
36impl FirstLetterUppercase for &str {
37 fn is_first_letter_uppercase(&self) -> bool {
38 self.chars()
39 .next()
40 .map(|c| c.is_uppercase())
41 .unwrap_or(false)
42 }
43}
44
45#[derive(Debug, Clone)]
49pub struct ElementPath {
50 fhir_release: FhirRelease,
51 type_stack: TypeStack,
52}
53
54impl ElementPath {
55 #[inline]
56 pub fn new(fhir_release: FhirRelease) -> ElementPath {
57 ElementPath {
58 fhir_release,
59 type_stack: TypeStack::new(fhir_release),
60 }
61 }
62
63 #[inline]
64 pub fn is_empty(&self) -> bool {
65 self.type_stack.is_empty()
66 }
67
68 #[inline]
69 pub fn current_element(&self) -> Option<&str> {
70 self.current_type_path().last_split()
71 }
72
73 #[inline]
74 pub fn current_element_is_resource(&self) -> bool {
75 self.resolve_current_type() == Some("Resource")
76 }
77
78 #[inline]
79 pub fn current_element_is_extension(&self) -> bool {
80 matches!(
81 self.current_type_path().last_split(),
82 Some("extension") | Some("modifierExtension")
83 )
84 }
85
86 #[inline]
87 pub fn current_element_is_primitive(&self) -> bool {
88 if self.in_resource() {
89 return false;
90 }
91
92 let current_type_path = self.current_type_path();
93
94 let is_id = current_type_path.last_split() == Some("id");
95 if is_id {
96 return true;
97 }
98
99 if self.parent_element_is_resource()
100 && current_type_path
101 .last_split()
102 .map(|s| RESOURCE_COMMON_PRIMITIVE_FIELDS.contains(&s))
103 .unwrap_or(false)
104 {
105 return true;
106 }
107
108 type_hints(self.fhir_release)
109 .all_primitives_paths
110 .contains(&self.current_type_path().path)
111 }
112
113 #[inline]
114 pub fn current_element_is_sequence(&self) -> bool {
115 let current_type_path = self.current_type_path();
116
117 let is_common_sequence_field = current_type_path
118 .last_split()
119 .map(|p| COMMON_SEQUENCE_FIELDS.contains(&p))
120 .unwrap_or(false);
121
122 if is_common_sequence_field {
123 return true;
124 }
125
126 let is_contained =
127 current_type_path.len() == 2 && current_type_path.last_split() == Some("contained");
128
129 if is_contained {
130 return true;
131 }
132
133 type_hints(self.fhir_release)
134 .array_paths
135 .contains(¤t_type_path.path)
136 }
137
138 #[inline]
139 pub fn current_element_is_boolean(&self) -> bool {
140 type_hints(self.fhir_release)
141 .boolean_paths
142 .contains(&self.current_type_path().path)
143 }
144
145 #[inline]
146 pub fn current_element_is_integer(&self) -> bool {
147 type_hints(self.fhir_release)
148 .integer_paths
149 .contains(&self.current_type_path().path)
150 }
151
152 #[inline]
153 pub fn current_element_is_integer64(&self) -> bool {
154 type_hints(self.fhir_release)
155 .integer64_paths
156 .contains(&self.current_type_path().path)
157 }
158
159 #[inline]
160 pub fn current_element_is_unsigned_integer(&self) -> bool {
161 type_hints(self.fhir_release)
162 .unsigned_integer_paths
163 .contains(&self.current_type_path().path)
164 }
165
166 #[inline]
167 pub fn current_element_is_positive_integer(&self) -> bool {
168 type_hints(self.fhir_release)
169 .positive_integer_paths
170 .contains(&self.current_type_path().path)
171 }
172
173 #[inline]
174 pub fn current_element_is_decimal(&self) -> bool {
175 type_hints(self.fhir_release)
176 .decimal_paths
177 .contains(&self.current_type_path().path)
178 }
179
180 #[inline]
181 pub fn parent_element_is_resource(&self) -> bool {
182 if let Some(path) = self.current_type_path().parent() {
183 path.is_first_letter_uppercase() && !path.contains('.')
184 } else {
185 false
186 }
187 }
188
189 #[inline]
190 pub fn parent_element_is_boolean(&self) -> bool {
191 if let Some(path) = self.current_type_path().parent() {
192 type_hints(self.fhir_release).boolean_paths.contains(path)
193 } else {
194 false
195 }
196 }
197
198 #[inline]
199 pub fn parent_element_is_integer(&self) -> bool {
200 if let Some(path) = self.current_type_path().parent() {
201 type_hints(self.fhir_release).integer_paths.contains(path)
202 } else {
203 false
204 }
205 }
206
207 #[inline]
208 pub fn parent_element_is_integer64(&self) -> bool {
209 if let Some(path) = self.current_type_path().parent() {
210 type_hints(self.fhir_release).integer64_paths.contains(path)
211 } else {
212 false
213 }
214 }
215
216 #[inline]
217 pub fn parent_element_is_unsigned_integer(&self) -> bool {
218 if let Some(path) = self.current_type_path().parent() {
219 type_hints(self.fhir_release)
220 .unsigned_integer_paths
221 .contains(path)
222 } else {
223 false
224 }
225 }
226
227 #[inline]
228 pub fn parent_element_is_positive_integer(&self) -> bool {
229 if let Some(path) = self.current_type_path().parent() {
230 type_hints(self.fhir_release)
231 .positive_integer_paths
232 .contains(path)
233 } else {
234 false
235 }
236 }
237
238 #[inline]
239 pub fn parent_element_is_decimal(&self) -> bool {
240 if let Some(path) = self.current_type_path().parent() {
241 type_hints(self.fhir_release).decimal_paths.contains(path)
242 } else {
243 false
244 }
245 }
246
247 #[inline]
248 pub fn push(&mut self, element: &str) {
249 match self.resolve_current_type() {
250 Some("Resource") => self
251 .type_stack
252 .push(TypePath::new(element, self.fhir_release)),
253 Some(ty) => {
254 let mut type_path = TypePath::new(ty, self.fhir_release);
255 type_path.push(element);
256 self.type_stack.push(type_path);
257 }
258 None => self.type_stack.last_mut().push(element),
259 }
260 }
261
262 #[inline]
263 pub fn pop(&mut self) {
264 self.type_stack.last_mut().pop();
265
266 if self.type_stack.len() > 1
267 && self.type_stack.last().len() <= 1
268 && !self.in_contained_resource()
269 {
270 self.type_stack.pop();
271 }
272 }
273
274 #[inline]
275 pub fn children(&self) -> Option<&'static ElementSet> {
276 let mut type_path = self.current_type_path().path.as_str();
277
278 if let Some(current_type) = self.resolve_current_type() {
279 if current_type != "Resource" {
280 type_path = current_type;
281 }
282 } else if let Some(content_reference) = type_hints(self.fhir_release)
283 .content_reference_paths
284 .get(type_path)
285 {
286 type_path = content_reference;
287 }
288
289 element_map(self.fhir_release).get(type_path).copied()
290 }
291
292 #[inline]
293 pub fn position_of_child(&self, child: &str) -> usize {
294 if child == "resourceType"
295 && !self.current_type_path().path.starts_with("ExampleScenario.instance")
297 && !self.current_type_path().path.starts_with("Consent.provision")
299 && !self.current_type_path().path.starts_with("Subscription.filterBy")
301 {
302 0
303 } else {
304 self.children()
305 .and_then(|set| set.get_index(child))
306 .map(|i| i + 1)
307 .unwrap_or(usize::MAX)
309 }
310 }
311
312 fn current_type_path(&self) -> &TypePath {
313 self.type_stack.last()
314 }
315
316 fn resolve_current_type(&self) -> Option<&str> {
317 if self.current_element_is_extension() {
318 return Some("Extension");
319 }
320
321 let current_type_path = self.current_type_path();
322
323 if current_type_path.len() == 2 {
324 match current_type_path.last_split() {
325 Some("meta") => return Some("Meta"),
326 Some("text") => return Some("Narrative"),
327 Some("contained") => return Some("Resource"),
328 _ => (),
329 }
330 }
331
332 type_hints(self.fhir_release)
333 .type_paths
334 .get(¤t_type_path.path)
335 .copied()
336 }
337
338 fn in_resource(&self) -> bool {
339 let current_type_path = self.current_type_path();
340
341 current_type_path.len() == 1 && current_type_path.path.as_str().is_first_letter_uppercase()
342 }
343
344 fn in_contained_resource(&self) -> bool {
345 if !self.in_resource() {
346 return false;
347 }
348
349 let previous_type_path = if let Some(previous) = self.type_stack.second_last() {
350 previous
351 } else {
352 return false;
353 };
354
355 let in_contained_field = previous_type_path.last_split() == Some("contained");
356
357 if in_contained_field {
358 return true;
359 }
360
361 type_hints(self.fhir_release)
362 .type_paths
363 .get(&previous_type_path.path)
364 == Some(&"Resource")
365 }
366}
367
368#[derive(Debug, Clone)]
369struct TypeStack {
370 root: TypePath,
371 stack: Vec<TypePath>,
372}
373
374impl TypeStack {
375 fn new(fhir_release: FhirRelease) -> TypeStack {
376 TypeStack {
377 root: TypePath::empty(fhir_release),
378 stack: vec![],
379 }
380 }
381
382 fn push(&mut self, value: TypePath) {
383 self.stack.push(value)
384 }
385
386 fn pop(&mut self) {
387 self.stack.pop();
388 }
389
390 fn is_empty(&self) -> bool {
391 self.stack.is_empty() && self.root.is_empty()
392 }
393
394 fn len(&self) -> usize {
395 self.stack.len() + 1
396 }
397
398 fn last(&self) -> &TypePath {
399 if let Some(last) = self.stack.last() {
400 last
401 } else {
402 &self.root
403 }
404 }
405
406 fn last_mut(&mut self) -> &mut TypePath {
407 if let Some(last) = self.stack.last_mut() {
408 last
409 } else {
410 &mut self.root
411 }
412 }
413
414 fn second_last(&self) -> Option<&TypePath> {
415 match self.stack.as_slice() {
416 [_] => Some(&self.root),
417 [.., second_last, _] => Some(second_last),
418 _ => None,
419 }
420 }
421}
422
423#[derive(Debug, Clone)]
424struct TypePath {
425 fhir_release: FhirRelease,
426 path: String,
427 content_reference_replacement_stack: Vec<ContentReferenceReplacement>,
428}
429
430#[derive(Debug, Clone)]
431struct ContentReferenceReplacement {
432 content_reference: &'static str,
433 replaced: String,
434}
435
436impl TypePath {
437 fn new(typ_name: &str, fhir_release: FhirRelease) -> TypePath {
438 TypePath {
439 fhir_release,
440 path: typ_name.to_string(),
441 content_reference_replacement_stack: vec![],
442 }
443 }
444
445 fn empty(fhir_release: FhirRelease) -> TypePath {
446 TypePath {
447 fhir_release,
448 path: String::new(),
449 content_reference_replacement_stack: vec![],
450 }
451 }
452
453 fn parent(&self) -> Option<&str> {
454 self.path.rsplit_once('.').map(|s| s.0)
455 }
456
457 fn last_split(&self) -> Option<&str> {
458 self.path.rsplit_once('.').map(|s| s.1)
459 }
460
461 fn is_empty(&self) -> bool {
462 self.len() == 0
463 }
464
465 fn len(&self) -> usize {
466 if self.path.is_empty() {
467 0
468 } else {
469 1 + self.path.chars().filter(|c| *c == '.').count()
470 }
471 }
472
473 fn push(&mut self, element: &str) {
474 if let Some(content_reference) = type_hints(self.fhir_release)
475 .content_reference_paths
476 .get(&self.path)
477 {
478 self.content_reference_replacement_stack
479 .push(ContentReferenceReplacement {
480 content_reference,
481 replaced: mem::replace(&mut self.path, content_reference.to_string()),
482 })
483 }
484
485 if !self.path.is_empty() {
486 self.path.push('.');
487 }
488 self.path.push_str(element);
489 }
490
491 fn pop(&mut self) {
492 self.path.truncate(self.path.rfind('.').unwrap_or(0));
493
494 let last_replacement = match self.content_reference_replacement_stack.last_mut() {
495 Some(last_replacement) => last_replacement,
496 None => return,
497 };
498
499 if last_replacement.content_reference == self.path {
500 self.path = mem::take(&mut last_replacement.replaced);
501
502 self.content_reference_replacement_stack.pop();
503 };
504 }
505}