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            return Type::Node {
222                structure: element.owner.clone(),
223                path: element.path.clone(),
224            };
225        }
226        let code = match (chosen, element.element.types.as_slice()) {
227            (Some(chosen), _) => chosen.to_owned(),
228            (None, [single]) => single.clone(),
229            _ => return Type::Unknown,
230        };
231        self.structure(&type_url(&code, &element.owner.url))
232    }
233
234    /// Whether `chosen` is one of `types`, or derives from one of them (`OperationOutcome` for an
235    /// element of type `Resource`).
236    pub fn is_one_of(&self, chosen: &str, types: &[String], context: &str) -> bool {
237        let matches = |name: &str| {
238            types
239                .iter()
240                .any(|t| same_primitive(t, name) || t == name || t.ends_with(&format!("/{name}")))
241        };
242        if matches(chosen) {
243            return true;
244        }
245        let mut url = type_url(chosen, context);
246        for _ in 0..16 {
247            let Some(structure) = self.definitions.structure(&url) else {
248                // Not a known structure: a type of another model, nothing to compare with.
249                return !self.definitions.covers(&url);
250            };
251            if matches(&structure.type_name) {
252                return true;
253            }
254            match &structure.base {
255                Some(base) => url = base.clone(),
256                None => return false,
257            }
258        }
259        true
260    }
261}