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
27/// No definitions: only checks that need none (variables, groups, transforms) run.
28#[derive(Debug, Default, Clone, Copy)]
29pub struct NoDefinitions;
30
31impl Definitions for NoDefinitions {
32    fn structure(&self, _url: &str) -> Option<Arc<Structure>> {
33        None
34    }
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub enum Kind {
39    PrimitiveType,
40    ComplexType,
41    Resource,
42    Logical,
43}
44
45/// A resource, data type or logical model.
46#[derive(Debug, Clone, PartialEq)]
47pub struct Structure {
48    pub url: String,
49    pub name: String,
50    /// The type it defines, such as `Patient`.
51    pub type_name: String,
52    pub kind: Kind,
53    pub is_abstract: bool,
54    /// Canonical URL of the base structure.
55    pub base: Option<String>,
56    /// Top-level elements defined here, not inherited ones.
57    pub elements: BTreeMap<String, Element>,
58}
59
60/// An element of a structure.
61#[derive(Debug, Clone, Default, PartialEq)]
62pub struct Element {
63    /// Type codes or URLs. Several for a choice element (`value[x]`).
64    pub types: Vec<String>,
65    /// For a choice element, the concrete element names (`valueString`, …).
66    pub choices: Vec<String>,
67    /// Repeats (`max` > 1).
68    pub array: bool,
69    /// Inline child elements of a backbone element, shared so an element is cheap to copy.
70    pub children: Arc<BTreeMap<String, Element>>,
71    /// A content reference to another element: structure URL and element path.
72    pub content_reference: Option<(String, Vec<String>)>,
73    /// For a concrete choice element (`valueString`), the choice it belongs to (`value`).
74    pub choice_of: Option<String>,
75    /// Position in the structure's element order, for writing elements in definition order.
76    pub index: Option<usize>,
77}
78
79impl Element {
80    pub fn is_choice(&self) -> bool {
81        !self.choices.is_empty() || self.types.len() > 1
82    }
83}
84
85/// Canonical URL of type `code` as used inside the structure at `context_url`: type codes resolve
86/// in the same FHIR release as the structure that uses them, so inside a version-specific core
87/// structure (`http://hl7.org/fhir/4.0/StructureDefinition/Patient`) `HumanName` is the 4.0 one.
88/// Anywhere else, profiles included, type codes are the core types.
89pub fn type_url(code: &str, context_url: &str) -> String {
90    if code.contains("://") || code.starts_with("urn:") {
91        return code.to_owned();
92    }
93    match versioned_core(context_url) {
94        Some(prefix) => format!("{prefix}/StructureDefinition/{code}"),
95        None => format!("http://hl7.org/fhir/StructureDefinition/{code}"),
96    }
97}
98
99/// `http://hl7.org/fhir/4.0` for a structure URL of a version-specific core release.
100pub fn versioned_core(url: &str) -> Option<&str> {
101    let url = url.split('|').next().unwrap_or(url);
102    let version = url
103        .strip_prefix("http://hl7.org/fhir/")?
104        .split_once("/StructureDefinition/")?
105        .0;
106    let is_version = !version.is_empty() && version.chars().all(|c| c.is_ascii_digit() || c == '.');
107    is_version.then(|| &url[.."http://hl7.org/fhir/".len() + version.len()])
108}
109
110/// FHIR primitive type codes that are also FHIRPath System types under another name.
111pub fn same_primitive(a: &str, b: &str) -> bool {
112    let system = |code: &str| -> String {
113        match code {
114            "String" | "string" | "code" | "id" | "markdown" | "uri" | "url" | "canonical"
115            | "oid" | "uuid" | "base64Binary" | "xhtml" => "String".into(),
116            "Integer" | "integer" | "positiveInt" | "unsignedInt" => "Integer".into(),
117            "Integer64" | "integer64" => "Long".into(),
118            "Decimal" | "decimal" => "Decimal".into(),
119            "Boolean" | "boolean" => "Boolean".into(),
120            "Date" | "date" => "Date".into(),
121            "DateTime" | "dateTime" | "instant" => "DateTime".into(),
122            "Time" | "time" => "Time".into(),
123            other => other.into(),
124        }
125    };
126    system(a) == system(b)
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn type_codes_resolve_in_the_release_of_the_structure() {
135        let core = "http://hl7.org/fhir/StructureDefinition/HumanName";
136        assert_eq!(
137            type_url(
138                "HumanName",
139                "http://hl7.org/fhir/StructureDefinition/Patient"
140            ),
141            core
142        );
143        assert_eq!(
144            type_url(
145                "HumanName",
146                "http://hl7.org/fhir/4.0/StructureDefinition/Patient|4.0.1"
147            ),
148            "http://hl7.org/fhir/4.0/StructureDefinition/HumanName"
149        );
150        // Profiles in hl7.org/fhir packages use the core types.
151        assert_eq!(
152            type_url(
153                "HumanName",
154                "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"
155            ),
156            core
157        );
158        assert_eq!(
159            type_url("HumanName", "http://example.org/StructureDefinition/P"),
160            core
161        );
162        assert_eq!(
163            type_url("http://example.org/T", core),
164            "http://example.org/T"
165        );
166    }
167}