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    /// Every element of `ty`, declared or inherited, own elements first and each in definition
157    /// order. Empty when the type is not known.
158    pub fn elements(&self, ty: &Type) -> Vec<ElementRef> {
159        let Type::Node { structure, path } = ty else {
160            return Vec::new();
161        };
162        let mut found = Vec::new();
163        if path.is_empty() {
164            self.collect_elements(structure.clone(), &mut found);
165            return found;
166        }
167        let Some(node) = self.element_at(structure, path) else {
168            return found;
169        };
170        let mut children: Vec<(&String, &Element)> = node.children.iter().collect();
171        children.sort_by_key(|(_, element)| element.index);
172        for (name, element) in children {
173            let mut element_path = path.clone();
174            element_path.push(name.clone());
175            found.push(ElementRef {
176                name: name.clone(),
177                element: element.clone(),
178                owner: structure.clone(),
179                path: element_path,
180            });
181        }
182        if let Some(base) = node
183            .types
184            .first()
185            .and_then(|code| self.definitions.structure(&type_url(code, &structure.url)))
186        {
187            self.collect_elements(base, &mut found);
188        }
189        found
190    }
191
192    fn collect_elements(&self, structure: Arc<Structure>, found: &mut Vec<ElementRef>) {
193        let mut current = Some(structure);
194        let mut depth = 0;
195        while let Some(structure) = current.take() {
196            if structure.kind == Kind::PrimitiveType && !found.iter().any(|e| e.name == "value") {
197                found.push(ElementRef {
198                    name: "value".to_owned(),
199                    element: Element::default(),
200                    owner: structure.clone(),
201                    path: vec!["value".to_owned()],
202                });
203            }
204            let mut elements: Vec<(&String, &Element)> = structure.elements.iter().collect();
205            elements.sort_by_key(|(_, element)| element.index);
206            for (name, element) in elements {
207                if found.iter().any(|e| &e.name == name) {
208                    continue;
209                }
210                found.push(ElementRef {
211                    name: name.clone(),
212                    element: element.clone(),
213                    owner: structure.clone(),
214                    path: vec![name.clone()],
215                });
216            }
217            depth += 1;
218            if depth > 16 {
219                break;
220            }
221            current = structure
222                .base
223                .as_deref()
224                .and_then(|base| self.definitions.structure(base));
225        }
226    }
227
228    fn lookup_in_structure(&self, structure: Arc<Structure>, name: &str, depth: usize) -> Lookup {
229        // A primitive's value: `string.value` in the StructureDefinition, not a separate element
230        // in schemas that store primitives as plain JSON values.
231        if structure.kind == Kind::PrimitiveType && name == "value" {
232            return Lookup::Found(Box::new(ElementRef {
233                name: name.to_owned(),
234                element: Element::default(),
235                owner: structure.clone(),
236                path: vec![name.to_owned()],
237            }));
238        }
239        if let Some(element) = structure.elements.get(name) {
240            return Lookup::Found(Box::new(ElementRef {
241                name: name.to_owned(),
242                element: element.clone(),
243                owner: structure.clone(),
244                path: vec![name.to_owned()],
245            }));
246        }
247        let Some(base) = &structure.base else {
248            return Lookup::Missing;
249        };
250        if depth > 16 {
251            return Lookup::Unknown;
252        }
253        match self.definitions.structure(base) {
254            Some(base) => self.lookup_in_structure(base, name, depth + 1),
255            None => Lookup::Unknown,
256        }
257    }
258
259    /// The element at `path` inside `structure`, following content references.
260    fn element_at(&self, structure: &Structure, path: &[String]) -> Option<Element> {
261        let (first, rest) = path.split_first()?;
262        let mut current = self.resolve_reference(structure.elements.get(first)?)?;
263        for segment in rest {
264            current = self.resolve_reference(current.children.get(segment)?)?;
265        }
266        Some(current)
267    }
268
269    /// The element itself, or the element its content reference points to.
270    fn resolve_reference(&self, element: &Element) -> Option<Element> {
271        match &element.content_reference {
272            Some((url, target)) => {
273                let referenced = self.definitions.structure(url)?;
274                self.element_at(&referenced, target)
275            }
276            None => Some(element.clone()),
277        }
278    }
279
280    /// The type of an element: its backbone, the content it references, or its type. `chosen`
281    /// picks the type of a choice element.
282    pub fn element_type(&self, element: &ElementRef, chosen: Option<&str>) -> Type {
283        if let Some((url, target)) = &element.element.content_reference {
284            return match self.definitions.structure(url) {
285                Some(structure) => Type::Node {
286                    structure,
287                    path: target.clone(),
288                },
289                None => Type::Unknown,
290            };
291        }
292        if !element.element.children.is_empty() {
293            // Children a snapshot lists for an element of a structure type (`typeId.root` under
294            // an `II`) are that type's own: the element has the type. Children the type does not
295            // have make it a backbone element.
296            if let [code] = element.element.types.as_slice() {
297                let typed = self.structure(&type_url(code, &element.owner.url));
298                if matches!(&typed, Type::Node { structure, .. } if !structure.is_abstract)
299                    && element
300                        .element
301                        .children
302                        .keys()
303                        .all(|name| matches!(self.lookup(&typed, name), Lookup::Found(_)))
304                {
305                    return typed;
306                }
307            }
308            return Type::Node {
309                structure: element.owner.clone(),
310                path: element.path.clone(),
311            };
312        }
313        let code = match (chosen, element.element.types.as_slice()) {
314            (Some(chosen), _) => chosen.to_owned(),
315            (None, [single]) => single.clone(),
316            _ => return Type::Unknown,
317        };
318        self.structure(&type_url(&code, &element.owner.url))
319    }
320
321    /// The type `name` names for a value of `element`: one of the element's types, by structure
322    /// name (`IVL_TS`), type name or URL, or a type next to them that derives from one (a `CE` in
323    /// a `CD` element). This is how a logical model's value says its type (`xsi:type`).
324    pub fn named_type(&self, element: &ElementRef, name: &str) -> Type {
325        let candidates: Vec<String> = element
326            .element
327            .types
328            .iter()
329            .map(|code| type_url(code, &element.owner.url))
330            .collect();
331        for url in &candidates {
332            if let Some(structure) = self.definitions.structure(url)
333                && (structure.name == name
334                    || structure.type_name == name
335                    || url == name
336                    || url.ends_with(&format!("/{name}")))
337            {
338                return Type::Node {
339                    structure,
340                    path: Vec::new(),
341                };
342            }
343        }
344        if name.contains("://") {
345            return self.structure(name);
346        }
347        // Model URLs name types by id, where `IVL_TS` is `IVL-TS`.
348        for url in &candidates {
349            let Some((prefix, _)) = url.rsplit_once('/') else {
350                continue;
351            };
352            for id in [name.to_owned(), name.replace('_', "-")] {
353                if let Some(structure) = self.definitions.structure(&format!("{prefix}/{id}"))
354                    && (structure.name == name || structure.type_name.ends_with(name))
355                {
356                    return Type::Node {
357                        structure,
358                        path: Vec::new(),
359                    };
360                }
361            }
362        }
363        Type::Unknown
364    }
365
366    /// Whether `chosen` is one of `types`, or derives from one of them (`OperationOutcome` for an
367    /// element of type `Resource`).
368    pub fn is_one_of(&self, chosen: &str, types: &[String], context: &str) -> bool {
369        let matches = |name: &str| {
370            types
371                .iter()
372                .any(|t| same_primitive(t, name) || t == name || t.ends_with(&format!("/{name}")))
373        };
374        if matches(chosen) {
375            return true;
376        }
377        let mut url = type_url(chosen, context);
378        for _ in 0..16 {
379            let Some(structure) = self.definitions.structure(&url) else {
380                // Not a known structure: a type of another model, nothing to compare with.
381                return !self.definitions.covers(&url);
382            };
383            if matches(&structure.type_name) {
384                return true;
385            }
386            match &structure.base {
387                Some(base) => url = base.clone(),
388                None => return false,
389            }
390        }
391        true
392    }
393}