1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// SPDX-FileCopyrightText: Cadasto B.V.
// SPDX-License-Identifier: BUSL-1.1
//! Extensions: the `url` resolved to its `StructureDefinition`, which the
//! extension is then walked against (<https://hl7.org/fhir/R4/extensibility.html>).
use fhir_types::codec::Object;
use fhir_types::codec::Value;
use fhir_types::definition::ElementDefinitionRead;
use crate::issue::IssueType;
use crate::issue::MessageId;
use crate::issue::Severity;
use crate::release::Release;
use crate::set::ResolveError;
use crate::validate::ValidateError;
use crate::validate::structure::Structure;
use crate::validate::walk::Cursor;
use crate::validate::walk::Walk;
/// Where an extension sits.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Placement {
/// In `extension` of an element other than an extension.
Extension,
/// In `modifierExtension`.
Modifier,
/// In `extension` of an extension: a child of a complex extension.
Child,
}
impl<'i, R: Release> Walk<'_, '_, 'i, R> {
/// Returns the cursor an extension is walked with: the definition its
/// `url` names, resolved as the definition `from` the walk holds the
/// extension's parent to, or the core `Extension` when the url does not
/// resolve; a resolved extension is held to the contexts its definition
/// declares.
pub(crate) fn extension_cursor(
&mut self,
(value, object): (&'i Value, &Object),
placement: Placement,
(location, from): (&str, &Structure<R>),
) -> Result<Cursor<R>, ValidateError> {
let fallback = |walk: &Self| -> Result<Cursor<R>, ValidateError> {
Ok(Cursor::new(
walk.validator().core("Extension")?,
"Extension",
))
};
// A missing or non-string url is reported by the walk over the element
// table (Extension.url is 1..1 uri), so it falls back without an issue here.
let Some(url) = object.get("url").and_then(|url| url.as_str()) else {
return fallback(self);
};
if !url.contains(':') {
if placement != Placement::Child {
self.out().issue(
Severity::Error,
IssueType::Extension,
MessageId::RelativeExtensionUrl,
location,
format!("the extension url {url} is relative outside a complex extension"),
);
}
// NOTE: extensibility.html §2.5.0.1: a relative url names a child the complex extension
// defines, so the slice it is matched to by url holds it, never a definition of its own.
return fallback(self);
}
let structure = match self.validator().structure(url, Some(from)) {
Ok(structure) => structure,
// A url of the instance that names no definition the set resolves (absent, a `#`
// fragment, two definitions under one canonical) is the instance's unknown extension;
// a definition the set carries and cannot read stays an error of the package set.
Err(ValidateError::Resolve(
error @ (ResolveError::NotFound { .. }
| ResolveError::Fragment { .. }
| ResolveError::Ambiguous { .. }),
)) => {
// NOTE: extensibility.html §2.5.0.2 forbids processing an unknown modifier, while
// §2.5.0.3 says to ignore other unknown extensions; the warning is our own design.
let (severity, id) = if placement == Placement::Modifier {
(Severity::Error, MessageId::UnknownModifierExtension)
} else {
(Severity::Warning, MessageId::UnknownExtension)
};
let why = match error {
ResolveError::Ambiguous { .. } => {
"the package set carries different definitions of the extension"
}
_ => "the package set defines no extension",
};
self.out().issue(
severity,
IssueType::Extension,
id,
location,
format!("{why} {url}"),
);
return fallback(self);
}
Err(error) => return Err(error),
};
if structure.type_name() != "Extension" {
self.out().issue(
Severity::Error,
IssueType::Extension,
MessageId::NotAnExtension,
location,
format!(
"{url} defines a {}, not an extension",
structure.type_name()
),
);
return fallback(self);
}
let modifier = structure
.element("Extension")
.and_then(ElementDefinitionRead::is_modifier)
.unwrap_or(false);
if modifier != (placement == Placement::Modifier) {
// NOTE: extensibility.html §2.5.0.2: a modifier extension is carried in
// modifierExtension, and an extension defined as none is not.
let wanted = if modifier {
"modifierExtension"
} else {
"extension"
};
self.out().issue(
Severity::Error,
IssueType::Extension,
MessageId::ModifierMismatch,
location,
format!("the extension {url} belongs in {wanted}"),
);
}
self.extension_context(&structure, value, location)?;
Ok(Cursor::new(structure, "Extension"))
}
}