Skip to main content

fml_check/
types.rs

1//! Navigating types: the type of a variable, its elements, and the types of those.
2//!
3//! Shared by the checker and the engine, so both see the same elements: inherited ones from base
4//! structures, backbone elements declared inline, content references (`Questionnaire.item.item`),
5//! a primitive's `value`, and the type a choice element takes.
6
7use std::sync::Arc;
8
9use crate::definitions::{Definitions, Element, Kind, Structure, same_primitive, type_url};
10
11/// The type of a value: a structure, or a backbone element inside one.
12#[derive(Debug, Clone)]
13pub enum Type {
14    /// Not known: no definition, a choice without a chosen type, an expression result.
15    Unknown,
16    Node {
17        structure: Arc<Structure>,
18        /// The element path to a backbone element; empty for the structure itself.
19        path: Vec<String>,
20    },
21}
22
23impl Type {
24    /// A key that identifies the type, for caching and comparisons.
25    pub fn key(&self) -> String {
26        match self {
27            Type::Unknown => "?".to_owned(),
28            Type::Node { structure, path } => {
29                let mut key = structure.url.clone();
30                for segment in path {
31                    key.push('.');
32                    key.push_str(segment);
33                }
34                key
35            }
36        }
37    }
38
39    /// `Patient` or `Patient.contact`, for messages.
40    pub fn display(&self) -> String {
41        match self {
42            Type::Unknown => "?".to_owned(),
43            Type::Node { structure, path } => {
44                let mut name = structure.type_name.clone();
45                for segment in path {
46                    name.push('.');
47                    name.push_str(segment);
48                }
49                name
50            }
51        }
52    }
53
54    /// The FHIR type name, as `fhirType()` gives it: `Patient`, `string`, or `BackboneElement` for
55    /// an element declared inline.
56    pub fn fhir_type(&self) -> Option<String> {
57        match self {
58            Type::Unknown => None,
59            Type::Node { structure, path } if path.is_empty() => Some(structure.type_name.clone()),
60            Type::Node { .. } => Some("BackboneElement".to_owned()),
61        }
62    }
63
64    pub fn structure(&self) -> Option<&Arc<Structure>> {
65        match self {
66            Type::Unknown => None,
67            Type::Node { structure, .. } => Some(structure),
68        }
69    }
70
71    /// Whether this is a resource (a resource structure itself, not an element of one).
72    pub fn is_resource(&self) -> bool {
73        matches!(self, Type::Node { structure, path } if path.is_empty() && structure.kind == Kind::Resource)
74    }
75
76    pub fn is_primitive(&self) -> bool {
77        matches!(self, Type::Node { structure, path } if path.is_empty() && structure.kind == Kind::PrimitiveType)
78    }
79}
80
81/// An element found on a type.
82#[derive(Debug, Clone)]
83pub struct ElementRef {
84    /// The element's name, as looked up.
85    pub name: String,
86    pub element: Element,
87    /// The structure that defines the element.
88    pub owner: Arc<Structure>,
89    /// The path to the element inside `owner`.
90    pub path: Vec<String>,
91}
92
93/// The result of looking up an element.
94#[derive(Debug, Clone)]
95pub enum Lookup {
96    Found(Box<ElementRef>),
97    /// The type is known and has no such element.
98    Missing,
99    /// The type, or part of the chain needed to answer, is not known.
100    Unknown,
101}
102
103/// Navigates types through a set of definitions.
104#[derive(Clone, Copy)]
105pub struct Types<'a> {
106    pub definitions: &'a dyn Definitions,
107}
108
109impl<'a> Types<'a> {
110    pub fn new(definitions: &'a dyn Definitions) -> Self {
111        Self { definitions }
112    }
113
114    /// The type a canonical URL names.
115    pub fn structure(&self, url: &str) -> Type {
116        match self.definitions.structure(url) {
117            Some(structure) => Type::Node {
118                structure,
119                path: Vec::new(),
120            },
121            None => Type::Unknown,
122        }
123    }
124
125    /// The element `name` of `ty`, declared or inherited.
126    pub fn lookup(&self, ty: &Type, name: &str) -> Lookup {
127        let Type::Node { structure, path } = ty else {
128            return Lookup::Unknown;
129        };
130        if path.is_empty() {
131            return self.lookup_in_structure(structure.clone(), name, 0);
132        }
133        let Some(node) = self.element_at(structure, path) else {
134            return Lookup::Unknown;
135        };
136        if let Some(element) = node.children.get(name) {
137            let mut element_path = path.clone();
138            element_path.push(name.to_owned());
139            return Lookup::Found(Box::new(ElementRef {
140                name: name.to_owned(),
141                element: element.clone(),
142                owner: structure.clone(),
143                path: element_path,
144            }));
145        }
146        // Not declared inline: inherited from the backbone's own type (BackboneElement, Element).
147        match node.types.first() {
148            Some(code) => match self.definitions.structure(&type_url(code, &structure.url)) {
149                Some(base) => self.lookup_in_structure(base, name, 0),
150                None => Lookup::Unknown,
151            },
152            None => Lookup::Unknown,
153        }
154    }
155
156    fn lookup_in_structure(&self, structure: Arc<Structure>, name: &str, depth: usize) -> Lookup {
157        // A primitive's value: `string.value` in the StructureDefinition, not a separate element
158        // in schemas that store primitives as plain JSON values.
159        if structure.kind == Kind::PrimitiveType && name == "value" {
160            return Lookup::Found(Box::new(ElementRef {
161                name: name.to_owned(),
162                element: Element::default(),
163                owner: structure.clone(),
164                path: vec![name.to_owned()],
165            }));
166        }
167        if let Some(element) = structure.elements.get(name) {
168            return Lookup::Found(Box::new(ElementRef {
169                name: name.to_owned(),
170                element: element.clone(),
171                owner: structure.clone(),
172                path: vec![name.to_owned()],
173            }));
174        }
175        let Some(base) = &structure.base else {
176            return Lookup::Missing;
177        };
178        if depth > 16 {
179            return Lookup::Unknown;
180        }
181        match self.definitions.structure(base) {
182            Some(base) => self.lookup_in_structure(base, name, depth + 1),
183            None => Lookup::Unknown,
184        }
185    }
186
187    /// The element at `path` inside `structure`, following content references.
188    fn element_at(&self, structure: &Structure, path: &[String]) -> Option<Element> {
189        let (first, rest) = path.split_first()?;
190        let mut current = self.resolve_reference(structure.elements.get(first)?)?;
191        for segment in rest {
192            current = self.resolve_reference(current.children.get(segment)?)?;
193        }
194        Some(current)
195    }
196
197    /// The element itself, or the element its content reference points to.
198    fn resolve_reference(&self, element: &Element) -> Option<Element> {
199        match &element.content_reference {
200            Some((url, target)) => {
201                let referenced = self.definitions.structure(url)?;
202                self.element_at(&referenced, target)
203            }
204            None => Some(element.clone()),
205        }
206    }
207
208    /// The type of an element: its backbone, the content it references, or its type. `chosen`
209    /// picks the type of a choice element.
210    pub fn element_type(&self, element: &ElementRef, chosen: Option<&str>) -> Type {
211        if let Some((url, target)) = &element.element.content_reference {
212            return match self.definitions.structure(url) {
213                Some(structure) => Type::Node {
214                    structure,
215                    path: target.clone(),
216                },
217                None => Type::Unknown,
218            };
219        }
220        if !element.element.children.is_empty() {
221            // Children a snapshot lists for an element of a structure type (`typeId.root` under
222            // an `II`) are that type's own: the element has the type. Children the type does not
223            // have make it a backbone element.
224            if let [code] = element.element.types.as_slice() {
225                let typed = self.structure(&type_url(code, &element.owner.url));
226                if matches!(&typed, Type::Node { structure, .. } if !structure.is_abstract)
227                    && element
228                        .element
229                        .children
230                        .keys()
231                        .all(|name| matches!(self.lookup(&typed, name), Lookup::Found(_)))
232                {
233                    return typed;
234                }
235            }
236            return Type::Node {
237                structure: element.owner.clone(),
238                path: element.path.clone(),
239            };
240        }
241        let code = match (chosen, element.element.types.as_slice()) {
242            (Some(chosen), _) => chosen.to_owned(),
243            (None, [single]) => single.clone(),
244            _ => return Type::Unknown,
245        };
246        self.structure(&type_url(&code, &element.owner.url))
247    }
248
249    /// The type `name` names for a value of `element`: one of the element's types, by structure
250    /// name (`IVL_TS`), type name or URL, or a type next to them that derives from one (a `CE` in
251    /// a `CD` element). This is how a logical model's value says its type (`xsi:type`).
252    pub fn named_type(&self, element: &ElementRef, name: &str) -> Type {
253        let candidates: Vec<String> = element
254            .element
255            .types
256            .iter()
257            .map(|code| type_url(code, &element.owner.url))
258            .collect();
259        for url in &candidates {
260            if let Some(structure) = self.definitions.structure(url)
261                && (structure.name == name
262                    || structure.type_name == name
263                    || url == name
264                    || url.ends_with(&format!("/{name}")))
265            {
266                return Type::Node {
267                    structure,
268                    path: Vec::new(),
269                };
270            }
271        }
272        if name.contains("://") {
273            return self.structure(name);
274        }
275        // Model URLs name types by id, where `IVL_TS` is `IVL-TS`.
276        for url in &candidates {
277            let Some((prefix, _)) = url.rsplit_once('/') else {
278                continue;
279            };
280            for id in [name.to_owned(), name.replace('_', "-")] {
281                if let Some(structure) = self.definitions.structure(&format!("{prefix}/{id}"))
282                    && (structure.name == name || structure.type_name.ends_with(name))
283                {
284                    return Type::Node {
285                        structure,
286                        path: Vec::new(),
287                    };
288                }
289            }
290        }
291        Type::Unknown
292    }
293
294    /// Whether `chosen` is one of `types`, or derives from one of them (`OperationOutcome` for an
295    /// element of type `Resource`).
296    pub fn is_one_of(&self, chosen: &str, types: &[String], context: &str) -> bool {
297        let matches = |name: &str| {
298            types
299                .iter()
300                .any(|t| same_primitive(t, name) || t == name || t.ends_with(&format!("/{name}")))
301        };
302        if matches(chosen) {
303            return true;
304        }
305        let mut url = type_url(chosen, context);
306        for _ in 0..16 {
307            let Some(structure) = self.definitions.structure(&url) else {
308                // Not a known structure: a type of another model, nothing to compare with.
309                return !self.definitions.covers(&url);
310            };
311            if matches(&structure.type_name) {
312                return true;
313            }
314            match &structure.base {
315                Some(base) => url = base.clone(),
316                None => return false,
317            }
318        }
319        true
320    }
321}