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}