Skip to main content

ferrobridge_validator/validate/
mod.rs

1// SPDX-FileCopyrightText: Cadasto B.V.
2// SPDX-License-Identifier: BUSL-1.1
3
4//! The validator: an instance walked over the R4 element table, held to the
5//! snapshot of the profile it is validated against and of every datatype and
6//! extension definition it reaches.
7//!
8//! The element table `fhir_types::r4::schema::SCHEMAS` is the base structure:
9//! which members an object may carry, their base cardinality, their types,
10//! and the hop a `contentReference` makes. The snapshot elements of the
11//! `StructureDefinitions` in play add the profile's constraints: cardinality,
12//! the types a choice may take, `fixed[x]`, `pattern[x]`, `maxLength`,
13//! slicing and the profiles an element's types name. Invariants, bindings,
14//! target profiles and claimed profiles are listed in the report as
15//! unevaluated.
16
17mod codes;
18mod extension;
19mod listing;
20mod matching;
21mod primitive;
22mod profiles;
23mod resolver;
24mod slicing;
25mod structure;
26mod walk;
27
28use std::collections::BTreeMap;
29use std::sync::Arc;
30use std::sync::Mutex;
31use std::sync::PoisonError;
32
33use fhir_types::codec::EncodeError;
34use fhir_types::codec::Value;
35
36use crate::report::Report;
37use crate::set::PackageId;
38use crate::set::PackageSet;
39use crate::set::ResolveError;
40use codes::Enumeration;
41use primitive::PrimitiveRule;
42use resolver::Contained;
43use resolver::Resolver;
44use structure::Structure;
45
46/// The canonical base of the definitions FHIR R4 core publishes.
47const CORE: &str = "http://hl7.org/fhir/StructureDefinition/";
48
49/// A canonical as one package references it: the cache key of a resolution.
50type Scoped = (String, Option<PackageId>);
51
52/// Validates R4 resources against the profiles of a package set.
53#[derive(Debug)]
54pub struct Validator<'set> {
55    set: &'set PackageSet,
56    structures: Mutex<BTreeMap<Scoped, Arc<Structure>>>,
57    primitives: Mutex<BTreeMap<String, Arc<PrimitiveRule>>>,
58    codes: Mutex<BTreeMap<Scoped, Arc<Enumeration>>>,
59    resolver: Box<dyn Resolver>,
60}
61
62/// Why a validation could not run to the end.
63///
64/// An instance that does not conform is never an error: it is a
65/// [`Report`] with issues. An error says the package set cannot answer.
66#[derive(Debug, thiserror::Error)]
67pub enum ValidateError {
68    /// A definition the validation needs did not resolve.
69    #[error(transparent)]
70    Resolve(#[from] ResolveError),
71    /// A definition carries no snapshot, which the validator reads.
72    #[error("the StructureDefinition {url} carries no snapshot")]
73    NoSnapshot {
74        /// The definition's canonical URL.
75        url: String,
76    },
77    /// The `regex` extension of a primitive's definition does not compile.
78    #[error("the lexical form of {type_code} does not compile")]
79    Regex {
80        /// The primitive type.
81        type_code: String,
82        /// The compile error.
83        #[source]
84        source: regex::Error,
85    },
86    /// An element definition's `fixed[x]` or `pattern[x]` has no JSON form.
87    #[error("the value of {element} in {structure} has no JSON form")]
88    Encode {
89        /// The definition's canonical URL.
90        structure: String,
91        /// The element id.
92        element: String,
93        /// The encode error.
94        #[source]
95        source: EncodeError,
96    },
97    /// The element table names a type it does not carry.
98    #[error("the R4 element table carries no type {name}")]
99    UnknownType {
100        /// The type name.
101        name: String,
102    },
103}
104
105impl<'set> Validator<'set> {
106    /// Creates a validator over `set`.
107    #[must_use]
108    pub fn new(set: &'set PackageSet) -> Self {
109        Self {
110            set,
111            structures: Mutex::new(BTreeMap::new()),
112            primitives: Mutex::new(BTreeMap::new()),
113            codes: Mutex::new(BTreeMap::new()),
114            resolver: Box::new(Contained),
115        }
116    }
117
118    /// Validates `resource` against the core definition of its type.
119    ///
120    /// Each profile the resource claims in `meta.profile` is listed as
121    /// unevaluated.
122    ///
123    /// # Errors
124    ///
125    /// Returns [`ValidateError`] when a definition the validation reaches does
126    /// not resolve, carries no snapshot, or cannot be read.
127    pub fn validate(&self, resource: &Value) -> Result<Report, ValidateError> {
128        walk::Walk::new(self).run(resource, None)
129    }
130
131    /// Validates `resource` against the profile `profile` names, a canonical
132    /// with an optional `|version`.
133    ///
134    /// # Errors
135    ///
136    /// Returns [`ValidateError`] when the profile or a definition the
137    /// validation reaches does not resolve, carries no snapshot, or cannot be
138    /// read.
139    pub fn validate_against(
140        &self,
141        resource: &Value,
142        profile: &str,
143    ) -> Result<Report, ValidateError> {
144        walk::Walk::new(self).run(resource, Some(profile))
145    }
146
147    /// Returns the indexed snapshot of the definition `canonical` names, as
148    /// the definition `from` references it: within the dependency closure of
149    /// `from`'s package first.
150    fn structure(
151        &self,
152        canonical: &str,
153        from: Option<&Structure>,
154    ) -> Result<Arc<Structure>, ValidateError> {
155        let scope = from.map(Structure::origin);
156        let key = (canonical.to_owned(), scope);
157        if let Some(found) = self
158            .structures
159            .lock()
160            .unwrap_or_else(PoisonError::into_inner)
161            .get(&key)
162        {
163            return Ok(Arc::clone(found));
164        }
165        let (definition, origin) = self.set.structure_definition_in(canonical, scope)?;
166        let structure = Arc::new(Structure::new(definition, origin)?);
167        self.structures
168            .lock()
169            .unwrap_or_else(PoisonError::into_inner)
170            .insert(key, Arc::clone(&structure));
171        Ok(structure)
172    }
173
174    /// Returns the core definition of the type `name`.
175    fn core(&self, name: &str) -> Result<Arc<Structure>, ValidateError> {
176        self.structure(&format!("{CORE}{name}"), None)
177    }
178
179    /// Returns the resolver a `resolve()` in a discriminator path uses.
180    fn resolver(&self) -> &dyn Resolver {
181        self.resolver.as_ref()
182    }
183
184    /// Returns the codes of the value set `canonical` names, when the
185    /// package set enumerates them.
186    fn codes(&self, canonical: &str, from: &Structure) -> Result<Arc<Enumeration>, ValidateError> {
187        let key = (canonical.to_owned(), Some(from.origin()));
188        if let Some(found) = self
189            .codes
190            .lock()
191            .unwrap_or_else(PoisonError::into_inner)
192            .get(&key)
193        {
194            return Ok(Arc::clone(found));
195        }
196        let enumeration = Arc::new(codes::enumerate(self.set, canonical, from.origin())?);
197        self.codes
198            .lock()
199            .unwrap_or_else(PoisonError::into_inner)
200            .insert(key, Arc::clone(&enumeration));
201        Ok(enumeration)
202    }
203
204    /// Returns the lexical rule of the primitive type `code`.
205    fn primitive(&self, code: &str) -> Result<Arc<PrimitiveRule>, ValidateError> {
206        if let Some(found) = self
207            .primitives
208            .lock()
209            .unwrap_or_else(PoisonError::into_inner)
210            .get(code)
211        {
212            return Ok(Arc::clone(found));
213        }
214        let rule = Arc::new(PrimitiveRule::read(&*self.core(code)?, code)?);
215        self.primitives
216            .lock()
217            .unwrap_or_else(PoisonError::into_inner)
218            .insert(code.to_owned(), Arc::clone(&rule));
219        Ok(rule)
220    }
221}