Module: DaVinciCRDTestKit::LogicalModelsOverrideHelper

Included in:
RequestsLogicalModelValidation, ResponseLogicalModelValidation
Defined in:
lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb

Instance Method Summary collapse

Instance Method Details

#allowed_resource_type?(resource_type, allowed_resource_types) ⇒ Boolean

Returns:

  • (Boolean)


209
210
211
212
213
214
215
# File 'lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb', line 209

def allowed_resource_type?(resource_type, allowed_resource_types)
  if allowed_resource_types.nil?
    FHIR::RESOURCES.include?(resource_type)
  else
    allowed_resource_types.include?(resource_type)
  end
end

#check_appointment_conformance(appointment, request_body, error_prefix, ig_semver, validator: :default) ⇒ Object


Appointment conformance (requires extra help to decide profile and check profile-based slicing)



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb', line 102

def check_appointment_conformance(appointment, request_body, error_prefix, ig_semver, validator: :default)
  target_appointment_profile =
    if appointment.basedOn.present?
      'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-appointment-with-order'
    else
      'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-appointment-no-order'
    end

  validation_issues = []
  resource_is_valid?(resource: appointment, profile_url: "#{target_appointment_profile}|#{ig_semver}",
                     add_messages_to_runnable: false, validator_response_details: validation_issues, validator:)

  manually_check_appointment_validation_errors(validation_issues, appointment, request_body)
    .each do |issue|
      add_message(issue.severity, "#{error_prefix}#{issue.message}")
    end
end

#check_order_like_resource_conformance(resource_hash, request_body, error_prefix, ig_semver, allowed_types:, disallowed_message:, validator: :default) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb', line 83

def check_order_like_resource_conformance(resource_hash, request_body, error_prefix, ig_semver,
                                          allowed_types:, disallowed_message:, validator: :default)
  parse_action_resource(resource_hash, error_prefix) do |resource|
    case resource
    when FHIR::Appointment
      check_appointment_conformance(resource, request_body, error_prefix, ig_semver, validator:)
    when *allowed_types
      resource_is_valid?(resource:, profile_url: structure_definition_map(ig_semver)[resource.resourceType],
                         message_prefix: error_prefix, validator:)
    else
      add_message('error', "#{error_prefix}resource type '#{resource.resourceType}' #{disallowed_message}.")
    end
  end
end

#check_resource_conformance_to_coverage_profile(resource_hash, error_prefix, ig_semver, validator: :default) ⇒ Object


Check resource conformance outside the logical models



32
33
34
# File 'lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb', line 32

def check_resource_conformance_to_coverage_profile(resource_hash, error_prefix, ig_semver, validator: :default)
  check_resource_type_and_validate(resource_hash, error_prefix, ig_semver, FHIR::Coverage, validator:)
end

#check_resource_conformance_to_order_or_encounter_profile(resource_hash, request_body, error_prefix, ig_semver, validator: :default) ⇒ Object



41
42
43
44
45
46
47
48
# File 'lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb', line 41

def check_resource_conformance_to_order_or_encounter_profile(resource_hash, request_body, error_prefix, ig_semver,
                                                             validator: :default)
  check_order_like_resource_conformance(resource_hash, request_body, error_prefix, ig_semver,
                                        validator:,
                                        allowed_types: ProfilesAndResourceTypes::ORDER_OR_ENCOUNTER_RESOURCE_CLASSES,
                                        disallowed_message: 'is not allowed as a target ' \
                                                            'for a coverage-information action')
end

#check_resource_conformance_to_order_profile(resource_hash, request_body, error_prefix, ig_semver, validator: :default) ⇒ Object



50
51
52
53
54
55
56
# File 'lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb', line 50

def check_resource_conformance_to_order_profile(resource_hash, request_body, error_prefix, ig_semver,
                                                validator: :default)
  check_order_like_resource_conformance(resource_hash, request_body, error_prefix, ig_semver,
                                        validator:,
                                        allowed_types: ProfilesAndResourceTypes::ORDER_RESOURCE_CLASSES,
                                        disallowed_message: 'is not allowed for CRD orders')
end

#check_resource_conformance_to_questionnaire_task_profile(resource_hash, error_prefix, ig_semver, validator: :default) ⇒ Object



36
37
38
39
# File 'lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb', line 36

def check_resource_conformance_to_questionnaire_task_profile(resource_hash, error_prefix, ig_semver,
                                                             validator: :default)
  check_resource_type_and_validate(resource_hash, error_prefix, ig_semver, FHIR::Task, validator:)
end

#check_resource_type_and_validate(resource_hash, error_prefix, ig_semver, expected_class, validator: :default) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
# File 'lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb', line 71

def check_resource_type_and_validate(resource_hash, error_prefix, ig_semver, expected_class, validator: :default)
  parse_action_resource(resource_hash, error_prefix) do |resource|
    if resource.is_a?(expected_class)
      resource_is_valid?(resource:, profile_url: structure_definition_map(ig_semver)[resource.resourceType],
                         message_prefix: error_prefix, validator:)
    else
      add_message('error', "#{error_prefix}found resource type '#{resource.resourceType}' " \
                           "expected '#{expected_class.name.split('::').last}'.")
    end
  end
end

#filter_logical_model_extension_issues(issues, validator) ⇒ Object



16
17
18
19
20
# File 'lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb', line 16

def filter_logical_model_extension_issues(issues, validator)
  return if validator == :no_custom_extensions

  issues.each { |issue| issue.filtered = issue.filtered || logical_model_extension_issue?(issue) }
end

#local_reference?(value, error_prefix, allowed_resource_types: nil) ⇒ Boolean


Local/Relative reference helpers

Returns:

  • (Boolean)


175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb', line 175

def local_reference?(value, error_prefix, allowed_resource_types: nil)
  is_local_reference = true
  local_reference_match = value.match(%r{^([A-Za-z]+)/(.+)$})
  if local_reference_match.present?
    resource_type = local_reference_match[1]
    id = local_reference_match[2]

    unless allowed_resource_type?(resource_type, allowed_resource_types)
      allowed_types_error_suffix =
        if allowed_resource_types.nil?
          'a valid FHIR resource type.'
        else
          "one of the allowed resource types (#{allowed_resource_types.join(', ')})"
        end

      add_message('error',
                  "#{error_prefix} local reference resourceType '#{resource_type}' " \
                  "is not #{allowed_types_error_suffix}.")
      is_local_reference = false
    end
    unless id.match(/\A[A-Za-z0-9\-.]{1,64}\z/)
      add_message('error',
                  "#{error_prefix} local reference id '#{id}' does not meet " \
                  '[FHIR id data type](https://hl7.org/fhir/R4/datatypes.html#id) requirements.')
      is_local_reference = false
    end
  else
    add_message('error', "#{error_prefix} expected a local reference, got '#{value}'.")
    is_local_reference = false
  end

  is_local_reference
end

#logical_model_extension_issue?(issue) ⇒ Boolean

Logical models validation doesn't currently respect the "extensions": ["any"] validator flag

Returns:

  • (Boolean)


24
25
26
# File 'lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb', line 24

def logical_model_extension_issue?(issue)
  issue.message.match(/\.extension: Unrecognized property/).present?
end

#manually_check_appointment_validation_errors(validation_issues, appointment, request_body) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb', line 120

def manually_check_appointment_validation_errors(validation_issues, appointment, request_body)
  @matched_participant_slice_indexes = []
  validation_issues.reverse.reject do |issue|
    issue.filtered ||
      resolved_participant_primary_performer_slice_issue?(issue, appointment) ||
      resolved_participant_patient_slice_issue?(issue, appointment, request_body) ||
      (
        # list reversed to hit these issues after the slice matching
        @matched_participant_slice_indexes.present? &&
        issue.message.match(/Appointment\.participant\[#{Regexp.union(@matched_participant_slice_indexes.map(&:to_s))}\]: This element does not match any known slice defined in the profile/) # rubocop:disable Layout/LineLength
      )
  end.reverse
end

#parse_action_resource(resource_hash, error_prefix) {|resource| ... } ⇒ Object


Resource conformance helpers

Yields:

  • (resource)


62
63
64
65
66
67
68
69
# File 'lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb', line 62

def parse_action_resource(resource_hash, error_prefix)
  resource = FHIR.from_contents(resource_hash.to_json)
  unless resource.present?
    add_message('error', "#{error_prefix}resource is not FHIR.")
    return
  end
  yield resource
end

#primary_performer_type?(types) ⇒ Boolean

Returns:

  • (Boolean)


147
148
149
150
151
152
153
154
# File 'lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb', line 147

def primary_performer_type?(types)
  types&.any? do |type|
    type.coding&.any? do |coding|
      coding.code == 'PPRF' &&
        coding.system == 'http://terminology.hl7.org/CodeSystem/v3-ParticipationType'
    end
  end
end

#referenced_resource_present_in_bundle?(bundle, local_reference, error_prefix, bundle_location) ⇒ Boolean

Returns:

  • (Boolean)


217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb', line 217

def referenced_resource_present_in_bundle?(bundle, local_reference, error_prefix, bundle_location)
  unless bundle.present? && bundle['entry'].present?
    add_message('error',
                "#{error_prefix} referenced resource '#{local_reference}' " \
                "not found in the #{bundle_location} Bundle.")
    return false
  end

  target_resource_type, target_id = local_reference.split('/')
  found = bundle['entry'].any? do |entry|
    entry.dig('resource', 'resourceType') == target_resource_type && entry.dig('resource', 'id') == target_id
  end
  return true if found

  add_message('error',
              "#{error_prefix} referenced resource '#{local_reference}' " \
              "not found in the #{bundle_location} Bundle.")
  false
end

#reject_resource_issues(issues) ⇒ Object


Clean up messages returned from logical model validation



9
10
11
12
13
14
# File 'lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb', line 9

def reject_resource_issues(issues)
  issues.reject do |issue|
    issue.location&.match?(%r{/\*[A-Za-z]+/}) || # looking for /*<resourceType>/
      issue.message&.match(/.resource: Unable to find a match for the specified profile among choices/)
  end
end

#resolved_participant_patient_slice_issue?(issue, appointment, request_body) ⇒ Boolean

NOTE: for simplicity and to avoid duplication of checks, this looks for a particular patient reference from the context.patientId, the profile of which will be verified during prefetch profile checking

Returns:

  • (Boolean)


159
160
161
162
163
164
165
166
167
168
169
# File 'lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb', line 159

def resolved_participant_patient_slice_issue?(issue, appointment, request_body)
  return false unless issue.message.match(/Slice 'Appointment.participant:Patient': a matching slice is required, but not found/) # rubocop:disable Layout/LineLength

  local_patient_ref = "Patient/#{request_body.dig('context', 'patientId')}"
  absolute_patient_ref = "#{request_body['fhirServer'].chomp('/')}/#{local_patient_ref}"
  appointment.participant.each_with_index.any? do |participant, index|
    match = [local_patient_ref, absolute_patient_ref].include?(participant.actor&.reference)
    @matched_participant_slice_indexes << index if match
    match
  end
end

#resolved_participant_primary_performer_slice_issue?(issue, appointment) ⇒ Boolean

Returns:

  • (Boolean)


134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb', line 134

def resolved_participant_primary_performer_slice_issue?(issue, appointment)
  return false unless issue.message.match(/Slice 'Appointment.participant:PrimaryPerformer': a matching slice is required, but not found/) # rubocop:disable Layout/LineLength

  appointment.participant.each_with_index.any? do |participant, index|
    # type + profile of the reference checked during prefetch checking
    match = participant.actor.present? &&
            participant.actor.reference.present? &&
            primary_performer_type?(participant.type)
    @matched_participant_slice_indexes << index if match
    match
  end
end