Skip to main content

ferrobridge_validator/
issue.rs

1// SPDX-FileCopyrightText: Cadasto B.V.
2// SPDX-License-Identifier: BUSL-1.1
3
4//! The issue model: one finding about an instance, in the shape of an R4
5//! `OperationOutcome.issue` (<https://hl7.org/fhir/R4/operationoutcome.html>).
6//!
7//! R4 defines no validator messages (validation.html is informative), so the
8//! message ids of [`MessageId`] are our own vocabulary; no specification
9//! governs it.
10
11use std::fmt;
12
13/// How severe an issue is: the R4 `issue-severity` value set.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub enum Severity {
16    /// The issue stopped the validation.
17    Fatal,
18    /// The instance does not conform.
19    Error,
20    /// The instance conforms, and something about it deserves attention.
21    Warning,
22    /// A finding with no bearing on conformance.
23    Information,
24}
25
26impl Severity {
27    /// Returns the R4 code.
28    #[must_use]
29    pub const fn code(self) -> &'static str {
30        match self {
31            Self::Fatal => "fatal",
32            Self::Error => "error",
33            Self::Warning => "warning",
34            Self::Information => "information",
35        }
36    }
37}
38
39/// The kind of an issue: the R4 `issue-type` code system, every code of it.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
41pub enum IssueType {
42    /// `invalid`: content invalid against the specification or a profile.
43    Invalid,
44    /// `structure`: a structural issue in the content.
45    Structure,
46    /// `required`: a required element is missing.
47    Required,
48    /// `value`: an element or header value is invalid.
49    Value,
50    /// `invariant`: a content validation rule failed.
51    Invariant,
52    /// `security`: an authentication or authorization problem.
53    Security,
54    /// `login`: the client needs to log in.
55    Login,
56    /// `unknown`: the user or system was not able to be authenticated.
57    Unknown,
58    /// `expired`: the user session expired.
59    Expired,
60    /// `forbidden`: the user does not have the rights.
61    Forbidden,
62    /// `suppressed`: some information was not or might not be returned.
63    Suppressed,
64    /// `processing`: processing issues.
65    Processing,
66    /// `not-supported`: the content or operation is not supported.
67    NotSupported,
68    /// `duplicate`: an attempt was made to create a duplicate record.
69    Duplicate,
70    /// `multiple-matches`: multiple matching records were found.
71    MultipleMatches,
72    /// `not-found`: the reference provided was not found.
73    NotFound,
74    /// `deleted`: the reference pointed at deleted content.
75    Deleted,
76    /// `too-long`: provided content is too long.
77    TooLong,
78    /// `code-invalid`: the code or system could not be understood.
79    CodeInvalid,
80    /// `extension`: an extension was not acceptable or not recognized.
81    Extension,
82    /// `too-costly`: the operation was stopped to protect server resources.
83    TooCostly,
84    /// `business-rule`: a business rule was violated.
85    BusinessRule,
86    /// `conflict`: a content or version conflict.
87    Conflict,
88    /// `transient`: a transient processing issue.
89    Transient,
90    /// `lock-error`: a resource or record lock failed.
91    LockError,
92    /// `no-store`: the persistent store is unavailable.
93    NoStore,
94    /// `exception`: an unexpected internal error.
95    Exception,
96    /// `timeout`: an internal timeout occurred.
97    Timeout,
98    /// `incomplete`: not all data sources returned results.
99    Incomplete,
100    /// `throttled`: the system is not prepared to handle this request.
101    Throttled,
102    /// `informational`: a message unrelated to the processing success.
103    Informational,
104}
105
106impl IssueType {
107    /// Returns the R4 code.
108    #[must_use]
109    pub const fn code(self) -> &'static str {
110        match self {
111            Self::Invalid => "invalid",
112            Self::Structure => "structure",
113            Self::Required => "required",
114            Self::Value => "value",
115            Self::Invariant => "invariant",
116            Self::Security => "security",
117            Self::Login => "login",
118            Self::Unknown => "unknown",
119            Self::Expired => "expired",
120            Self::Forbidden => "forbidden",
121            Self::Suppressed => "suppressed",
122            Self::Processing => "processing",
123            Self::NotSupported => "not-supported",
124            Self::Duplicate => "duplicate",
125            Self::MultipleMatches => "multiple-matches",
126            Self::NotFound => "not-found",
127            Self::Deleted => "deleted",
128            Self::TooLong => "too-long",
129            Self::CodeInvalid => "code-invalid",
130            Self::Extension => "extension",
131            Self::TooCostly => "too-costly",
132            Self::BusinessRule => "business-rule",
133            Self::Conflict => "conflict",
134            Self::Transient => "transient",
135            Self::LockError => "lock-error",
136            Self::NoStore => "no-store",
137            Self::Exception => "exception",
138            Self::Timeout => "timeout",
139            Self::Incomplete => "incomplete",
140            Self::Throttled => "throttled",
141            Self::Informational => "informational",
142        }
143    }
144}
145
146/// The message ids of the validator, our own vocabulary.
147#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
148pub enum MessageId {
149    /// The instance is no JSON object.
150    NotAnObject,
151    /// `resourceType` is absent or names no R4 resource.
152    UnknownResourceType,
153    /// The profile constrains another resource type than the instance's.
154    ProfileTypeMismatch,
155    /// A property the type does not define (json.html §2.6.2).
156    UnknownElement,
157    /// A value of the wrong JSON type for its element.
158    WrongJsonType,
159    /// A repeating element written as a single value, or the reverse
160    /// (json.html §2.6.2).
161    WrongShape,
162    /// An empty object (json.html §2.6.2).
163    EmptyObject,
164    /// An empty array (json.html §2.6.2.1).
165    EmptyArray,
166    /// An empty string (json.html §2.6.2).
167    EmptyString,
168    /// A `null` where no aligned primitive array needs it (json.html §2.6.2.3).
169    NullValue,
170    /// The value array and the `_` array of a repeating primitive differ in
171    /// length (json.html §2.6.2.3).
172    MisalignedArrays,
173    /// Two forms of one choice element.
174    DuplicateChoice,
175    /// A choice form whose type the definition does not allow.
176    ChoiceTypeNotAllowed,
177    /// Fewer occurrences than the minimum cardinality.
178    MinCardinality,
179    /// More occurrences than the maximum cardinality.
180    MaxCardinality,
181    /// A primitive value outside the lexical form of its type.
182    LexicalForm,
183    /// An integer outside the range of its type (datatypes.html §2.24.0.1).
184    IntegerRange,
185    /// A value longer than the length the primitive's definition allows.
186    TooLong,
187    /// A value longer than the profile's `maxLength`.
188    MaxLength,
189    /// A value that differs from the profile's `fixed[x]`.
190    FixedMismatch,
191    /// A value that does not contain the profile's `pattern[x]`.
192    PatternMismatch,
193    /// An extension whose `url` the package set does not define.
194    UnknownExtension,
195    /// A modifier extension whose `url` the package set does not define
196    /// (extensibility.html §2.5.0.2).
197    UnknownModifierExtension,
198    /// An extension `url` that resolves to a `StructureDefinition` of another
199    /// type.
200    NotAnExtension,
201    /// A relative `url` outside a complex extension (extensibility.html
202    /// §2.5.0.1).
203    RelativeExtensionUrl,
204    /// An extension in `extension` defined as a modifier, or one in
205    /// `modifierExtension` defined as none.
206    ModifierMismatch,
207    /// An item of a closed slicing that matches no slice (profiling.html
208    /// §5.1.0.10).
209    UnmatchedSliceItem,
210    /// An item of an ordered slicing that comes before an item of a slice
211    /// declared earlier.
212    SliceOrder,
213    /// An item of a slicing with the rules `openAtEnd` that matches a slice
214    /// after an item that matched none.
215    OpenAtEnd,
216    /// An item that matches more than one slice (profiling.html §5.1.0.11).
217    OverlappingSlices,
218    /// A reference a discriminator resolves names no resource where it
219    /// points.
220    UnresolvedReference,
221    /// Content that conforms to none of the profiles its type names.
222    TypeProfileMismatch,
223    /// A resource of a type none of its element's types names.
224    ResourceTypeNotAllowed,
225    /// A constraint the validator does not evaluate.
226    ConstraintUnevaluated,
227    /// The instance raised no issue and every constraint was evaluated.
228    AllOk,
229}
230
231impl MessageId {
232    /// Returns the message id as written in an `OperationOutcome`.
233    #[must_use]
234    pub const fn code(self) -> &'static str {
235        match self {
236            Self::NotAnObject => "not-an-object",
237            Self::UnknownResourceType => "unknown-resource-type",
238            Self::ProfileTypeMismatch => "profile-type-mismatch",
239            Self::UnknownElement => "unknown-element",
240            Self::WrongJsonType => "wrong-json-type",
241            Self::WrongShape => "wrong-shape",
242            Self::EmptyObject => "empty-object",
243            Self::EmptyArray => "empty-array",
244            Self::EmptyString => "empty-string",
245            Self::NullValue => "null-value",
246            Self::MisalignedArrays => "misaligned-arrays",
247            Self::DuplicateChoice => "duplicate-choice",
248            Self::ChoiceTypeNotAllowed => "choice-type-not-allowed",
249            Self::MinCardinality => "min-cardinality",
250            Self::MaxCardinality => "max-cardinality",
251            Self::LexicalForm => "lexical-form",
252            Self::IntegerRange => "integer-range",
253            Self::TooLong => "too-long",
254            Self::MaxLength => "max-length",
255            Self::FixedMismatch => "fixed-mismatch",
256            Self::PatternMismatch => "pattern-mismatch",
257            Self::UnknownExtension => "unknown-extension",
258            Self::UnknownModifierExtension => "unknown-modifier-extension",
259            Self::NotAnExtension => "not-an-extension",
260            Self::RelativeExtensionUrl => "relative-extension-url",
261            Self::ModifierMismatch => "modifier-mismatch",
262            Self::UnmatchedSliceItem => "unmatched-slice-item",
263            Self::SliceOrder => "slice-order",
264            Self::OpenAtEnd => "open-at-end",
265            Self::OverlappingSlices => "overlapping-slices",
266            Self::UnresolvedReference => "unresolved-reference",
267            Self::TypeProfileMismatch => "type-profile-mismatch",
268            Self::ResourceTypeNotAllowed => "resource-type-not-allowed",
269            Self::ConstraintUnevaluated => "constraint-unevaluated",
270            Self::AllOk => "all-ok",
271        }
272    }
273}
274
275impl fmt::Display for MessageId {
276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277        f.write_str(self.code())
278    }
279}
280
281/// What an issue reports against: one of our message ids, or the key of a
282/// constraint a `StructureDefinition` declares.
283#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
284pub enum Rule {
285    /// A message id of the validator.
286    Message(MessageId),
287    /// A constraint of an element definition
288    /// (<https://hl7.org/fhir/R4/elementdefinition.html>).
289    Constraint {
290        /// The constraint key, for example `ele-1`.
291        key: String,
292        /// The canonical of the `StructureDefinition` that declares it.
293        source: String,
294    },
295}
296
297/// One finding about an instance.
298#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
299pub struct Issue {
300    /// How severe it is.
301    pub severity: Severity,
302    /// Its R4 issue type.
303    pub code: IssueType,
304    /// The FHIRPath location of the element, with indexes on repeating
305    /// elements (`Patient.name[0].given[1]`).
306    pub expression: String,
307    /// The message id or constraint it reports.
308    pub rule: Rule,
309    /// The human-readable message, naming paths and the values the profile
310    /// expects and never an instance value.
311    pub message: String,
312}