inferno_core 0.6.0 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,226 @@
1
+ require_relative 'primitive_type'
2
+
3
+ module Inferno
4
+ module DSL
5
+ # The FHIRResourceNavigation module is used to pick values from a FHIR resource, based on a profile.
6
+ # Originally intended for use for verifying the presence of Must Support elements on a resource.
7
+ # This module expects pre-processed metadata defining the elements of the profile
8
+ # to be present in the attribute `metadata` in the including class.
9
+ # @see Inferno::DSL::MustSupportMetadataExtractor
10
+ module FHIRResourceNavigation
11
+ DAR_EXTENSION_URL = 'http://hl7.org/fhir/StructureDefinition/data-absent-reason'.freeze
12
+ PRIMITIVE_DATA_TYPES = FHIR::PRIMITIVES.keys
13
+
14
+ # Get a value from the given FHIR element(s) by walking the given path through the element.
15
+ # @param elements [FHIR::Model, Array<FHIR::Model>]
16
+ # @param path [String]
17
+ # @return [Array<FHIR::Model>]
18
+ def resolve_path(elements, path)
19
+ elements = Array.wrap(elements)
20
+ return elements if path.blank?
21
+
22
+ paths = path.split(/(?<!hl7)\./)
23
+ segment = paths.first
24
+ remaining_path = paths.drop(1).join('.')
25
+
26
+ elements.flat_map do |element|
27
+ child = get_next_value(element, segment)
28
+ resolve_path(child, remaining_path)
29
+ end.compact
30
+ end
31
+
32
+ # Get a value from the given FHIR element(s), by navigating through the resource to the given path.
33
+ # Fields with a DataAbsentReason extension present may be selected if include_dar is true.
34
+ # To filter the resulting elements, a block may be passed in.
35
+ # @param given_element [FHIR::Model, Array<FHIR::Model>]
36
+ # @param path [String]
37
+ # @param include_dar [Boolean]
38
+ # @return [Array<FHIR::Model>]
39
+ def find_a_value_at(given_element, path, include_dar: false, &block)
40
+ return nil if given_element.nil?
41
+
42
+ elements = Array.wrap(given_element)
43
+ return find_in_elements(elements, include_dar:, &block) if path.empty?
44
+
45
+ path_segments = path.split(/(?<!hl7)\./)
46
+
47
+ segment = path_segments.shift.delete_suffix('[x]').gsub(/^class$/, 'local_class').gsub('[x]:', ':').to_sym
48
+
49
+ remaining_path = path_segments.join('.')
50
+ elements.each do |element|
51
+ child = get_next_value(element, segment)
52
+ element_found = find_a_value_at(child, remaining_path, include_dar:, &block)
53
+ return element_found if element_found.present? || element_found == false
54
+ end
55
+
56
+ nil
57
+ end
58
+
59
+ def find_in_elements(elements, include_dar: false, &)
60
+ unless include_dar
61
+ elements = elements.reject do |el|
62
+ el.respond_to?(:extension) && el.extension.any? { |ext| ext.url == DAR_EXTENSION_URL }
63
+ end
64
+ end
65
+
66
+ return elements.find(&) if block_given?
67
+
68
+ elements.first
69
+ end
70
+
71
+ def get_next_value(element, property)
72
+ extension_url = property[/(?<=where\(url=').*(?='\))/]
73
+ if extension_url.present?
74
+ element.url == extension_url ? element : nil
75
+ elsif property.to_s.include?(':') && !property.to_s.include?('url')
76
+ find_slice_via_discriminator(element, property)
77
+
78
+ else
79
+ local_name = local_field_name(property)
80
+ value = element.send(local_name)
81
+ primitive_value = get_primitive_type_value(element, property, value)
82
+ primitive_value.present? ? primitive_value : value
83
+ end
84
+ rescue NoMethodError
85
+ nil
86
+ end
87
+
88
+ def get_primitive_type_value(element, property, value)
89
+ source_value = element.source_hash["_#{property}"]
90
+
91
+ return nil unless source_value.present?
92
+
93
+ primitive_value = PrimitiveType.new(source_value)
94
+ primitive_value.value = value
95
+ primitive_value
96
+ end
97
+
98
+ def local_field_name(field_name)
99
+ # fhir_models prepends fields whose names are reserved in ruby with "local_"
100
+ # This should be used before `x.send(field_name)`
101
+ if ['method', 'class'].include?(field_name.to_s)
102
+ "local_#{field_name}"
103
+ else
104
+ field_name
105
+ end
106
+ end
107
+
108
+ def find_slice_via_discriminator(element, property)
109
+ return unless metadata.present?
110
+
111
+ element_name = local_field_name(property.to_s.split(':')[0])
112
+ slice_name = local_field_name(property.to_s.split(':')[1])
113
+
114
+ slice_by_name = metadata.must_supports[:slices].find { |slice| slice[:slice_name] == slice_name }
115
+ discriminator = slice_by_name[:discriminator]
116
+ slices = Array.wrap(element.send(element_name))
117
+ slices.find { |slice| matching_slice?(slice, discriminator) }
118
+ end
119
+
120
+ def matching_slice?(slice, discriminator)
121
+ case discriminator[:type]
122
+ when 'patternCodeableConcept'
123
+ matching_pattern_codeable_concept_slice?(slice, discriminator)
124
+ when 'patternCoding'
125
+ matching_pattern_coding_slice?(slice, discriminator)
126
+ when 'patternIdentifier'
127
+ matching_pattern_identifier_slice?(slice, discriminator)
128
+ when 'value'
129
+ matching_value_slice?(slice, discriminator)
130
+ when 'type'
131
+ matching_type_slice?(slice, discriminator)
132
+ when 'requiredBinding'
133
+ matching_required_binding_slice?(slice, discriminator)
134
+ end
135
+ end
136
+
137
+ def matching_pattern_codeable_concept_slice?(slice, discriminator)
138
+ slice_value = discriminator[:path].present? ? slice.send((discriminator[:path]).to_s)&.coding : slice.coding
139
+ slice_value&.any? do |coding|
140
+ coding.code == discriminator[:code] && coding.system == discriminator[:system]
141
+ end
142
+ end
143
+
144
+ def matching_pattern_coding_slice?(slice, discriminator)
145
+ slice_value = discriminator[:path].present? ? slice.send(discriminator[:path]) : slice
146
+ slice_value&.code == discriminator[:code] && slice_value&.system == discriminator[:system]
147
+ end
148
+
149
+ def matching_pattern_identifier_slice?(slice, discriminator)
150
+ slice.identifier.system == discriminator[:system]
151
+ end
152
+
153
+ def matching_value_slice?(slice, discriminator)
154
+ values = discriminator[:values].map { |value| value.merge(path: value[:path].split('.')) }
155
+ verify_slice_by_values(slice, values)
156
+ end
157
+
158
+ def matching_type_slice?(slice, discriminator)
159
+ case discriminator[:code]
160
+ when 'Date'
161
+ begin
162
+ Date.parse(slice)
163
+ rescue ArgumentError
164
+ false
165
+ end
166
+ when 'DateTime'
167
+ begin
168
+ DateTime.parse(slice)
169
+ rescue ArgumentError
170
+ false
171
+ end
172
+ when 'String'
173
+ slice.is_a? String
174
+ else
175
+ slice.is_a? FHIR.const_get(discriminator[:code])
176
+ end
177
+ end
178
+
179
+ def matching_required_binding_slice?(slice, discriminator)
180
+ discriminator[:path].present? ? slice.send((discriminator[:path]).to_s).coding : slice.coding
181
+ slice_value { |coding| discriminator[:values].include?(coding.code) }
182
+ end
183
+
184
+ def verify_slice_by_values(element, value_definitions)
185
+ path_prefixes = value_definitions.map { |value_definition| value_definition[:path].first }.uniq
186
+ path_prefixes.all? do |path_prefix|
187
+ value_definitions_for_path =
188
+ value_definitions
189
+ .select { |value_definition| value_definition[:path].first == path_prefix }
190
+ .each { |value_definition| value_definition[:path].shift }
191
+ find_a_value_at(element, path_prefix) do |el_found|
192
+ current_and_child_values_match?(el_found, value_definitions_for_path)
193
+ end
194
+ end
195
+ end
196
+
197
+ def current_and_child_values_match?(el_found, value_definitions_for_path)
198
+ child_element_value_definitions, current_element_value_definitions =
199
+ value_definitions_for_path.partition { |value_definition| value_definition[:path].present? }
200
+
201
+ current_element_values_match =
202
+ current_element_value_definitions
203
+ .all? { |value_definition| value_definition[:value] == el_found }
204
+
205
+ child_element_values_match =
206
+ if child_element_value_definitions.present?
207
+ verify_slice_by_values(el_found, child_element_value_definitions)
208
+ else
209
+ true
210
+ end
211
+ current_element_values_match && child_element_values_match
212
+ end
213
+
214
+ def flatten_bundles(resources)
215
+ resources.flat_map do |resource|
216
+ if resource&.resourceType == 'Bundle'
217
+ # Recursive to consider that Bundles may contain Bundles
218
+ flatten_bundles(resource.entry.map(&:resource))
219
+ else
220
+ resource
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
@@ -157,7 +157,7 @@ module Inferno
157
157
  end
158
158
 
159
159
  # @see Inferno::DSL::FHIRResourceValidation#resource_is_valid?
160
- def resource_is_valid?(resource, profile_url, runnable)
160
+ def resource_is_valid?(resource, profile_url, runnable, add_messages_to_runnable: true) # rubocop:disable Metrics/CyclomaticComplexity
161
161
  profile_url ||= FHIR::Definitions.resource_definition(resource.resourceType).url
162
162
 
163
163
  begin
@@ -173,8 +173,10 @@ module Inferno
173
173
 
174
174
  message_hashes = message_hashes_from_outcome(outcome, resource, profile_url)
175
175
 
176
- message_hashes
177
- .each { |message_hash| runnable.add_message(message_hash[:type], message_hash[:message]) }
176
+ if add_messages_to_runnable
177
+ message_hashes
178
+ .each { |message_hash| runnable.add_message(message_hash[:type], message_hash[:message]) }
179
+ end
178
180
 
179
181
  unless response.status == 200
180
182
  raise Inferno::Exceptions::ErrorInValidatorException,
@@ -30,9 +30,13 @@ module Inferno
30
30
  # @param resource [FHIR::Model]
31
31
  # @param profile_url [String]
32
32
  # @param validator [Symbol] the name of the validator to use
33
+ # @param add_messages_to_runnable [Boolean] whether to add validation messages to runnable or not
33
34
  # @return [Boolean] whether the resource is valid
34
- def resource_is_valid?(resource: self.resource, profile_url: nil, validator: :default)
35
- find_validator(validator).resource_is_valid?(resource, profile_url, self)
35
+ def resource_is_valid?(
36
+ resource: self.resource, profile_url: nil,
37
+ validator: :default, add_messages_to_runnable: true
38
+ )
39
+ find_validator(validator).resource_is_valid?(resource, profile_url, self, add_messages_to_runnable:)
36
40
  end
37
41
 
38
42
  # Find a particular validator. Looks through a runnable's parents up to
@@ -113,7 +117,7 @@ module Inferno
113
117
  end
114
118
 
115
119
  # @see Inferno::DSL::FHIRValidation#resource_is_valid?
116
- def resource_is_valid?(resource, profile_url, runnable)
120
+ def resource_is_valid?(resource, profile_url, runnable, add_messages_to_runnable: true) # rubocop:disable Metrics/CyclomaticComplexity
117
121
  profile_url ||= FHIR::Definitions.resource_definition(resource.resourceType).url
118
122
 
119
123
  begin
@@ -128,8 +132,10 @@ module Inferno
128
132
 
129
133
  message_hashes = message_hashes_from_outcome(outcome, resource, profile_url)
130
134
 
131
- message_hashes
132
- .each { |message_hash| runnable.add_message(message_hash[:type], message_hash[:message]) }
135
+ if add_messages_to_runnable
136
+ message_hashes
137
+ .each { |message_hash| runnable.add_message(message_hash[:type], message_hash[:message]) }
138
+ end
133
139
 
134
140
  unless response.status == 200
135
141
  raise Inferno::Exceptions::ErrorInValidatorException,
@@ -0,0 +1,366 @@
1
+ require_relative 'value_extractor'
2
+
3
+ module Inferno
4
+ module DSL
5
+ # The MustSupportMetadataExtractor takes a StructureDefinition and parses it into a hash-based metadata
6
+ # that simplifies checking for MustSupport elements.
7
+ # MustSupport elements may be either plain elements, extensions, or slices.
8
+ # This logic was originally developed for the US Core Test Kit and has been migrated into Inferno core.
9
+ class MustSupportMetadataExtractor
10
+ attr_accessor :profile_elements, :profile, :resource, :ig_resources, :requirement_extension_url
11
+
12
+ # Construct a new extractor
13
+ # @param profile_elements [Array<FHIR::ElementDefinition>] the elements of the profile to consider,
14
+ # ie, profile.snapshot.element
15
+ # @param profile [FHIR::StructureDefinition] the profile to parse
16
+ # @param resource [String] the resourceType that the profile applies to, ie, profile.type
17
+ # @param ig_resources [Inferno::Entities::IG]
18
+ # @param requirement_extension_url [String] the URL of an extension to flag elements as required even if not MS
19
+ def initialize(profile_elements, profile, resource, ig_resources, requirement_extension_url = nil)
20
+ self.profile_elements = profile_elements
21
+ self.profile = profile
22
+ self.resource = resource
23
+ self.ig_resources = ig_resources
24
+ self.requirement_extension_url = requirement_extension_url
25
+ end
26
+
27
+ # Retrieval method for the must support metadata
28
+ # @return [Hash]
29
+ def must_supports
30
+ @must_supports ||= {
31
+ extensions: must_support_extensions,
32
+ slices: must_support_slices,
33
+ elements: must_support_elements
34
+ }
35
+ end
36
+
37
+ def by_requirement_extension_only?(element)
38
+ requirement_extension_url && !element.mustSupport &&
39
+ element.extension.any? do |extension|
40
+ extension.url == requirement_extension_url && extension.valueBoolean
41
+ end
42
+ end
43
+
44
+ def all_must_support_elements
45
+ profile_elements.select { |element| element.mustSupport || by_requirement_extension_only?(element) }
46
+ end
47
+
48
+ def must_support_extension_elements
49
+ all_must_support_elements.select { |element| element.path.end_with? 'extension' }
50
+ end
51
+
52
+ def must_support_extensions
53
+ must_support_extension_elements.map do |element|
54
+ {
55
+ id: element.id,
56
+ path: element.path.gsub("#{resource}.", ''),
57
+ url: element.type.first.profile.first
58
+ }.tap do |metadata|
59
+ metadata[:by_requirement_extension_only] = true if by_requirement_extension_only?(element)
60
+ end
61
+ end
62
+ end
63
+
64
+ def must_support_slice_elements
65
+ all_must_support_elements.select do |element|
66
+ !element.path.end_with?('extension') && element.sliceName.present?
67
+ end
68
+ end
69
+
70
+ def sliced_element(slice)
71
+ profile_elements.find do |element|
72
+ element.id == slice.path || element.id == slice.id.sub(":#{slice.sliceName}", '')
73
+ end
74
+ end
75
+
76
+ def discriminators(slice)
77
+ slice&.slicing&.discriminator
78
+ end
79
+
80
+ def find_element_by_discriminator_path(current_element, discriminator_path)
81
+ if discriminator_path.present?
82
+ profile_elements.find { |element| element.id == "#{current_element.id}.#{discriminator_path}" } ||
83
+ profile_elements.find { |element| element.id == "#{current_element.path}.#{discriminator_path}" }
84
+ else
85
+ current_element
86
+ end
87
+ end
88
+
89
+ def save_pattern_slice(pattern_element, discriminator_path, metadata)
90
+ if pattern_element.patternCodeableConcept
91
+ {
92
+ type: 'patternCodeableConcept',
93
+ path: discriminator_path,
94
+ code: pattern_element.patternCodeableConcept.coding.first.code,
95
+ system: pattern_element.patternCodeableConcept.coding.first.system
96
+ }
97
+ elsif pattern_element.patternCoding
98
+ {
99
+ type: 'patternCoding',
100
+ path: discriminator_path,
101
+ code: pattern_element.patternCoding.code,
102
+ system: pattern_element.patternCoding.system
103
+ }
104
+ elsif pattern_element.patternIdentifier
105
+ {
106
+ type: 'patternIdentifier',
107
+ path: discriminator_path,
108
+ system: pattern_element.patternIdentifier.system
109
+ }
110
+ elsif required_binding_pattern?(pattern_element)
111
+ {
112
+ type: 'requiredBinding',
113
+ path: discriminator_path,
114
+ values: extract_required_binding_values(pattern_element, metadata)
115
+ }
116
+ else
117
+ # prevent errors in case an IG does something different
118
+ {
119
+ type: 'unsupported',
120
+ path: discriminator_path
121
+ }
122
+ end
123
+ end
124
+
125
+ def required_binding_pattern?(pattern_element)
126
+ pattern_element.binding&.strength == 'required' && pattern_element.binding&.valueSet
127
+ end
128
+
129
+ def extract_required_binding_values(pattern_element, metadata)
130
+ value_extractor = ValueExtractor.new(ig_resources, resource, profile_elements)
131
+
132
+ value_extractor.codings_from_value_set_binding(pattern_element).presence ||
133
+ value_extractor.values_from_resource_metadata([metadata[:path]]).presence || []
134
+ end
135
+
136
+ def must_support_type_slice_elements
137
+ must_support_slice_elements.select do |element|
138
+ discriminators(sliced_element(element))&.first&.type == 'type'
139
+ end
140
+ end
141
+
142
+ def discriminator_path(discriminator)
143
+ if discriminator.path == '$this'
144
+ ''
145
+ elsif discriminator.path.start_with?('$this.')
146
+ discriminator.path[6..]
147
+ else
148
+ discriminator.path
149
+ end
150
+ end
151
+
152
+ def type_slices
153
+ must_support_type_slice_elements.map do |current_element|
154
+ discriminator = discriminators(sliced_element(current_element)).first
155
+ type_path = discriminator_path(discriminator)
156
+ type_element = find_element_by_discriminator_path(current_element, type_path)
157
+
158
+ type_code = type_element.type.first.code
159
+
160
+ {
161
+ slice_id: current_element.id,
162
+ slice_name: current_element.sliceName,
163
+ path: current_element.path.gsub("#{resource}.", ''),
164
+ discriminator: {
165
+ type: 'type',
166
+ code: type_code.upcase_first
167
+ }
168
+ }.tap do |metadata|
169
+ metadata[:by_requirement_extension_only] = true if by_requirement_extension_only?(current_element)
170
+ end
171
+ end
172
+ end
173
+
174
+ def must_support_value_slice_elements
175
+ must_support_slice_elements.select do |element|
176
+ # discriminator type 'pattern' is deprecated in FHIR R5 and made equivalent to 'value'
177
+ ['value', 'pattern'].include?(discriminators(sliced_element(element))&.first&.type)
178
+ end
179
+ end
180
+
181
+ def value_slices # rubocop:disable Metrics/CyclomaticComplexity
182
+ must_support_value_slice_elements.map do |current_element|
183
+ {
184
+ slice_id: current_element.id,
185
+ slice_name: current_element.sliceName,
186
+ path: current_element.path.gsub("#{resource}.", '')
187
+ }.tap do |metadata|
188
+ fixed_values = []
189
+ pattern_value = {}
190
+
191
+ element_discriminators = discriminators(sliced_element(current_element))
192
+
193
+ element_discriminators.each do |discriminator|
194
+ discriminator_path = discriminator_path(discriminator)
195
+ pattern_element = find_element_by_discriminator_path(current_element, discriminator_path)
196
+
197
+ # This is a workaround for a known version of a profile that has a bad discriminator:
198
+ # the discriminator refers to a nested field within a CodeableConcept,
199
+ # but the profile doesn't contain an element definition for it, so there's no way to
200
+ # define a fixed value on the element to define the slice.
201
+ # In this instance the element has a second (good) discriminator on the CodeableConcept field itself,
202
+ # and in subsequent versions of the profile, the bad discriminator was removed.
203
+ next if pattern_element.nil? && element_discriminators.length > 1
204
+
205
+ if pattern_element.fixed.present?
206
+ fixed_values << {
207
+ path: discriminator_path,
208
+ value: pattern_element.fixed
209
+ }
210
+ elsif pattern_value.present?
211
+ raise StandardError, "Found more than one pattern slices for the same element #{pattern_element}."
212
+ else
213
+ pattern_value = save_pattern_slice(pattern_element, discriminator_path, metadata)
214
+ end
215
+ end
216
+
217
+ if fixed_values.present?
218
+ metadata[:discriminator] = {
219
+ type: 'value',
220
+ values: fixed_values
221
+ }
222
+ elsif pattern_value.present?
223
+ metadata[:discriminator] = pattern_value
224
+ end
225
+
226
+ metadata[:by_requirement_extension_only] = true if by_requirement_extension_only?(current_element)
227
+ end
228
+ end
229
+ end
230
+
231
+ def must_support_slices
232
+ type_slices + value_slices
233
+ end
234
+
235
+ def plain_must_support_elements
236
+ all_must_support_elements - must_support_extension_elements - must_support_slice_elements
237
+ end
238
+
239
+ def element_part_of_slice_discrimination?(element)
240
+ must_support_slice_elements.any? { |ms_slice| element.id.include?(ms_slice.id) }
241
+ end
242
+
243
+ def handle_fixed_values(metadata, element)
244
+ if element.fixed.present?
245
+ metadata[:fixed_value] = element.fixed
246
+ elsif element.patternCodeableConcept.present? && !element_part_of_slice_discrimination?(element)
247
+ metadata[:fixed_value] = element.patternCodeableConcept.coding.first.code
248
+ metadata[:path] += '.coding.code'
249
+ elsif element.fixedCode.present?
250
+ metadata[:fixed_value] = element.fixedCode
251
+ elsif element.patternIdentifier.present? && !element_part_of_slice_discrimination?(element)
252
+ metadata[:fixed_value] = element.patternIdentifier.system
253
+ metadata[:path] += '.system'
254
+ end
255
+ end
256
+
257
+ def type_must_support_extension?(extensions)
258
+ extensions&.any? do |extension|
259
+ extension.url == 'http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support' &&
260
+ extension.valueBoolean
261
+ end
262
+ end
263
+
264
+ def save_type_code?(type)
265
+ type.code == 'Reference'
266
+ end
267
+
268
+ def get_type_must_support_metadata(current_metadata, current_element)
269
+ current_element.type.map do |type|
270
+ next unless type_must_support_extension?(type.extension)
271
+
272
+ metadata =
273
+ {
274
+ path: "#{current_metadata[:path].delete_suffix('[x]')}#{type.code.upcase_first}",
275
+ original_path: current_metadata[:path]
276
+ }
277
+ metadata[:types] = [type.code] if save_type_code?(type)
278
+ handle_type_must_support_target_profiles(type, metadata) if type.code == 'Reference'
279
+
280
+ metadata
281
+ end.compact
282
+ end
283
+
284
+ def handle_type_must_support_target_profiles(type, metadata)
285
+ target_profiles = extract_target_profiles(type)
286
+
287
+ # remove target_profile for FHIR Base resource type.
288
+ target_profiles.delete_if { |reference| reference.start_with?('http://hl7.org/fhir/StructureDefinition') }
289
+ metadata[:target_profiles] = target_profiles if target_profiles.present?
290
+ end
291
+
292
+ def extract_target_profiles(type)
293
+ target_profiles = []
294
+
295
+ if type.targetProfile&.length == 1
296
+ target_profiles << type.targetProfile.first
297
+ else
298
+ type.source_hash['_targetProfile']&.each_with_index do |hash, index|
299
+ if hash.present?
300
+ element = FHIR::Element.new(hash)
301
+ target_profiles << type.targetProfile[index] if type_must_support_extension?(element.extension)
302
+ end
303
+ end
304
+ end
305
+
306
+ target_profiles
307
+ end
308
+
309
+ def handle_choice_type_in_sliced_element(current_metadata, must_support_elements_metadata)
310
+ choice_element_metadata = must_support_elements_metadata.find do |metadata|
311
+ metadata[:original_path].present? &&
312
+ current_metadata[:path].include?(metadata[:original_path])
313
+ end
314
+
315
+ return unless choice_element_metadata.present?
316
+
317
+ current_metadata[:original_path] = current_metadata[:path]
318
+ current_metadata[:path] =
319
+ current_metadata[:path].sub(choice_element_metadata[:original_path], choice_element_metadata[:path])
320
+ end
321
+
322
+ def must_support_elements
323
+ must_support_elements_metadata = []
324
+ plain_must_support_elements.each do |current_element|
325
+ current_metadata = {
326
+ path: current_element.id.gsub("#{resource}.", '')
327
+ }
328
+ current_metadata[:by_requirement_extension_only] = true if by_requirement_extension_only?(current_element)
329
+
330
+ type_must_support_metadata = get_type_must_support_metadata(current_metadata, current_element)
331
+
332
+ if type_must_support_metadata.any?
333
+ must_support_elements_metadata.concat(type_must_support_metadata)
334
+ else
335
+ handle_choice_type_in_sliced_element(current_metadata, must_support_elements_metadata)
336
+
337
+ supported_types = extract_supported_types(current_element)
338
+ current_metadata[:types] = supported_types if supported_types.present?
339
+
340
+ if current_element.type.first&.code == 'Reference'
341
+ handle_type_must_support_target_profiles(current_element.type.first,
342
+ current_metadata)
343
+ end
344
+
345
+ handle_fixed_values(current_metadata, current_element)
346
+
347
+ remove_conflicting_metadata_without_fixed_value(must_support_elements_metadata, current_metadata)
348
+
349
+ must_support_elements_metadata << current_metadata
350
+ end
351
+ end
352
+ must_support_elements_metadata.uniq
353
+ end
354
+
355
+ def extract_supported_types(current_element)
356
+ current_element.type.select { |type| save_type_code?(type) }.map(&:code)
357
+ end
358
+
359
+ def remove_conflicting_metadata_without_fixed_value(must_support_elements_metadata, current_metadata)
360
+ must_support_elements_metadata.delete_if do |metadata|
361
+ metadata[:path] == current_metadata[:path] && metadata[:fixed_value].blank?
362
+ end
363
+ end
364
+ end
365
+ end
366
+ end
@@ -0,0 +1,9 @@
1
+ require 'fhir_models'
2
+
3
+ module Inferno
4
+ module DSL
5
+ class PrimitiveType < FHIR::Element
6
+ attr_accessor :value
7
+ end
8
+ end
9
+ end