1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// SPDX-FileCopyrightText: Cadasto B.V.
// SPDX-License-Identifier: BUSL-1.1
//! Path navigation (N1 §3) and type specifiers (N1 §6.3, §10.1).
use std::collections::BTreeSet;
use super::Engine;
use super::error::EvalError;
use crate::ast::TypeSpecifier;
use crate::model::{FhirType, Model, is_a, named_type, specializes};
use crate::value::{Collection, Item, SYSTEM_TYPES};
/// A resolved type specifier.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TypeRef {
/// A type of the `System` namespace.
System(&'static str),
/// A type of the FHIR model.
Fhir(FhirType),
}
impl<'a> Engine<'_, 'a> {
/// The member `name` of every item of `items`.
///
/// An identifier at the start of a path that names the type of the item
/// (or a supertype) is the item itself (N1 §3, §8.6). A member that no
/// typed item's type defines is an error, and so is a type name that is
/// not the item's type, while system values, untyped nodes and an empty
/// input answer an empty collection.
pub(crate) fn navigate(
&self,
items: &Collection<'a>,
name: &str,
at_root: bool,
) -> Result<Collection<'a>, EvalError> {
let model = self.model();
let mut found = Vec::new();
let mut defined = false;
let mut lacking = BTreeSet::new();
for item in items {
match item {
Item::Node(node) => {
if at_root
&& let Some(ty) = node.fhir_type()
&& !ty.is_primitive()
&& is_a(model, ty, name)
{
found.push(item.clone());
defined = true;
continue;
}
match node.member(model, name)? {
Some(members) => {
defined = true;
found.extend(members);
}
None => {
lacking.insert(node.fhir_type().map_or("(untyped)", |ty| ty.name));
}
}
}
Item::Type(info) => {
defined = true;
match name {
"namespace" => found.push(Item::String(String::from(info.namespace))),
"name" => found.extend(info.name.clone().map(Item::String)),
_ => {}
}
}
// NOTE: N1 §11 lists children(), descendants() and polymorphic members as
// untypable, and they yield system values; a member of one is empty, not an error.
_ => defined = true,
}
}
if !defined && !lacking.is_empty() {
let types = lacking.into_iter().collect::<Vec<_>>().join(" | ");
if at_root && named_type(model, name).is_some() {
return Err(EvalError::ContextType {
named: String::from(name),
context: types,
});
}
return Err(EvalError::UnknownElement {
type_name: types,
element: String::from(name),
});
}
Ok(found)
}
/// Resolves a type specifier: the FHIR model first, then `System` (N1
/// §10.1).
pub(crate) fn resolve_type(&self, specifier: &TypeSpecifier) -> Result<TypeRef, EvalError> {
let model = self.model();
let system = || {
SYSTEM_TYPES
.iter()
.find(|name| **name == specifier.name)
.map(|name| TypeRef::System(name))
};
let fhir = || named_type(model, &specifier.name).map(TypeRef::Fhir);
let resolved = match specifier.namespace.as_deref() {
None => fhir().or_else(system),
Some("FHIR") => fhir(),
Some("System") => system(),
Some(_) => None,
};
resolved.ok_or_else(|| EvalError::UnknownType {
name: specifier.to_string(),
})
}
/// Whether `item` is of the type `ty` or a subtype for `as` and
/// `ofType()`, every FHIR primitive an independent type (FHIR R4
/// §2.9.1.5): a FHIR primitive is not of a system type (FHIR R4 §2.9.1.2).
pub(crate) fn is_type(&self, item: &Item<'a>, ty: &TypeRef) -> bool {
self.type_test(item, ty, is_a)
}
/// Whether `item` is of the type `ty` or a subclass for the `is` operator
/// and function (N1 §6.3.1), a FHIR primitive specializing the type its
/// definition names (`code` is a `string`).
pub(crate) fn is_instance(&self, item: &Item<'a>, ty: &TypeRef) -> bool {
self.type_test(item, ty, specializes)
}
/// The type test both forms share, with the walk up the FHIR hierarchy.
fn type_test(
&self,
item: &Item<'a>,
ty: &TypeRef,
walk: fn(&dyn Model, &FhirType, &str) -> bool,
) -> bool {
match (item, ty) {
(_, TypeRef::System("Any")) => true,
(Item::Node(_) | Item::Type(_), TypeRef::System(_)) => false,
(other, TypeRef::System(name)) => other.system_type() == Some(*name),
(Item::Node(node), TypeRef::Fhir(fhir)) => node
.fhir_type()
.is_some_and(|own| walk(self.model(), own, fhir.name)),
(_, TypeRef::Fhir(_)) => false,
}
}
}