Skip to main content

fml_check/
definitions.rs

1//! The structure definitions a map is checked against.
2//!
3//! A deliberately small model: what the checker needs to follow element paths and compare types.
4//! [`Definitions`] supplies structures by canonical URL; the `fhirschema` feature provides the core
5//! FHIR releases, and anything else (logical models, profiles from packages) can be added by
6//! implementing the trait.
7
8use std::collections::BTreeMap;
9use std::sync::Arc;
10
11/// Supplies structure definitions by canonical URL.
12pub trait Definitions {
13    /// The structure at `url`, such as `http://hl7.org/fhir/StructureDefinition/Patient` or a
14    /// version-specific `http://hl7.org/fhir/4.0/StructureDefinition/Patient`. A `|version`
15    /// suffix may be present.
16    fn structure(&self, url: &str) -> Option<Arc<Structure>>;
17
18    /// Whether `url` belongs to a set of definitions this provider covers. When it does and
19    /// [`structure`](Self::structure) finds nothing, the structure does not exist; otherwise the
20    /// checker cannot tell and stays silent.
21    fn covers(&self, url: &str) -> bool {
22        let _ = url;
23        false
24    }
25
26    /// The templates through which the profile at `url` applies, for models that apply profiles
27    /// by template id (`type-profile-style` = `cda`): a value conforms when one of its
28    /// `templateId`s matches one of these. Empty for any other structure.
29    fn templates(&self, url: &str) -> Vec<TemplateId> {
30        let _ = url;
31        Vec::new()
32    }
33}
34
35/// A template identifier: the `root` and `extension` of an `II`, as a profile's
36/// `urn:hl7ii:root:extension` identifier gives them.
37#[derive(Debug, Clone, PartialEq, Eq, Hash)]
38pub struct TemplateId {
39    pub root: String,
40    pub extension: Option<String>,
41}
42
43impl TemplateId {
44    /// Reads `urn:hl7ii:root:extension`, `urn:hl7ii:root` or `urn:oid:root`.
45    pub fn parse(identifier: &str) -> Option<TemplateId> {
46        if let Some(rest) = identifier.strip_prefix("urn:hl7ii:") {
47            let (root, extension) = match rest.split_once(':') {
48                Some((root, extension)) => (root, Some(extension.to_owned())),
49                None => (rest, None),
50            };
51            return (!root.is_empty()).then(|| TemplateId {
52                root: root.to_owned(),
53                extension,
54            });
55        }
56        let root = identifier.strip_prefix("urn:oid:")?;
57        (!root.is_empty()).then(|| TemplateId {
58            root: root.to_owned(),
59            extension: None,
60        })
61    }
62
63    /// Whether an instance's `templateId` with this `root` and `extension` is this template. A
64    /// template without an extension matches its root whatever the extension.
65    pub fn matches(&self, root: &str, extension: Option<&str>) -> bool {
66        self.root == root
67            && match &self.extension {
68                Some(wanted) => extension == Some(wanted.as_str()),
69                None => true,
70            }
71    }
72}
73
74/// No definitions: only checks that need none (variables, groups, transforms) run.
75#[derive(Debug, Default, Clone, Copy)]
76pub struct NoDefinitions;
77
78impl Definitions for NoDefinitions {
79    fn structure(&self, _url: &str) -> Option<Arc<Structure>> {
80        None
81    }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
85pub enum Kind {
86    PrimitiveType,
87    ComplexType,
88    Resource,
89    Logical,
90}
91
92/// A resource, data type or logical model.
93#[derive(Debug, Clone, PartialEq)]
94pub struct Structure {
95    pub url: String,
96    pub name: String,
97    /// The type it defines, such as `Patient`.
98    pub type_name: String,
99    pub kind: Kind,
100    pub is_abstract: bool,
101    /// Canonical URL of the base structure.
102    pub base: Option<String>,
103    /// Top-level elements defined here, not inherited ones.
104    pub elements: BTreeMap<String, Element>,
105}
106
107/// An element of a structure.
108#[derive(Debug, Clone, Default, PartialEq)]
109pub struct Element {
110    /// Type codes or URLs. Several for a choice element (`value[x]`).
111    pub types: Vec<String>,
112    /// For a choice element, the concrete element names (`valueString`, …).
113    pub choices: Vec<String>,
114    /// Repeats (`max` > 1).
115    pub array: bool,
116    /// Inline child elements of a backbone element, shared so an element is cheap to copy.
117    pub children: Arc<BTreeMap<String, Element>>,
118    /// A content reference to another element: structure URL and element path.
119    pub content_reference: Option<(String, Vec<String>)>,
120    /// For a concrete choice element (`valueString`), the choice it belongs to (`value`).
121    pub choice_of: Option<String>,
122    /// Position in the structure's element order, for writing elements in definition order.
123    pub index: Option<usize>,
124}
125
126impl Element {
127    pub fn is_choice(&self) -> bool {
128        !self.choices.is_empty() || self.types.len() > 1
129    }
130
131    /// An element of a logical model with several types and no `[x]`: it keeps its name, and
132    /// each value says its type (in XML with `xsi:type`).
133    pub fn is_polymorphic(&self) -> bool {
134        self.choices.is_empty() && self.types.len() > 1
135    }
136}
137
138/// Canonical URL of type `code` as used inside the structure at `context_url`: type codes resolve
139/// in the same FHIR release as the structure that uses them, so inside a version-specific core
140/// structure (`http://hl7.org/fhir/4.0/StructureDefinition/Patient`) `HumanName` is the 4.0 one.
141/// Anywhere else, profiles included, type codes are the core types.
142pub fn type_url(code: &str, context_url: &str) -> String {
143    if code.contains("://") || code.starts_with("urn:") {
144        return code.to_owned();
145    }
146    match versioned_core(context_url) {
147        Some(prefix) => format!("{prefix}/StructureDefinition/{code}"),
148        None => format!("http://hl7.org/fhir/StructureDefinition/{code}"),
149    }
150}
151
152/// `http://hl7.org/fhir/4.0` for a structure URL of a version-specific core release.
153pub fn versioned_core(url: &str) -> Option<&str> {
154    let url = url.split('|').next().unwrap_or(url);
155    let version = url
156        .strip_prefix("http://hl7.org/fhir/")?
157        .split_once("/StructureDefinition/")?
158        .0;
159    let is_version = !version.is_empty() && version.chars().all(|c| c.is_ascii_digit() || c == '.');
160    is_version.then(|| &url[.."http://hl7.org/fhir/".len() + version.len()])
161}
162
163/// FHIR primitive type codes that are also FHIRPath System types under another name.
164pub fn same_primitive(a: &str, b: &str) -> bool {
165    let system = |code: &str| -> String {
166        match code {
167            "String" | "string" | "code" | "id" | "markdown" | "uri" | "url" | "canonical"
168            | "oid" | "uuid" | "base64Binary" | "xhtml" => "String".into(),
169            "Integer" | "integer" | "positiveInt" | "unsignedInt" => "Integer".into(),
170            "Integer64" | "integer64" => "Long".into(),
171            "Decimal" | "decimal" => "Decimal".into(),
172            "Boolean" | "boolean" => "Boolean".into(),
173            "Date" | "date" => "Date".into(),
174            "DateTime" | "dateTime" | "instant" => "DateTime".into(),
175            "Time" | "time" => "Time".into(),
176            other => other.into(),
177        }
178    };
179    system(a) == system(b)
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn type_codes_resolve_in_the_release_of_the_structure() {
188        let core = "http://hl7.org/fhir/StructureDefinition/HumanName";
189        assert_eq!(
190            type_url(
191                "HumanName",
192                "http://hl7.org/fhir/StructureDefinition/Patient"
193            ),
194            core
195        );
196        assert_eq!(
197            type_url(
198                "HumanName",
199                "http://hl7.org/fhir/4.0/StructureDefinition/Patient|4.0.1"
200            ),
201            "http://hl7.org/fhir/4.0/StructureDefinition/HumanName"
202        );
203        // Profiles in hl7.org/fhir packages use the core types.
204        assert_eq!(
205            type_url(
206                "HumanName",
207                "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"
208            ),
209            core
210        );
211        assert_eq!(
212            type_url("HumanName", "http://example.org/StructureDefinition/P"),
213            core
214        );
215        assert_eq!(
216            type_url("http://example.org/T", core),
217            "http://example.org/T"
218        );
219    }
220}