Class: Inferno::DSL::FHIRValidation::Validator

Inherits:
Object
  • Object
show all
Defined in:
lib/inferno/dsl/fhir_validation.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(requirements = nil) ⇒ Validator

Returns a new instance of Validator.



53
54
55
56
# File 'lib/inferno/dsl/fhir_validation.rb', line 53

def initialize(requirements = nil, &)
  instance_eval(&)
  @requirements = requirements
end

Instance Attribute Details

#requirementsObject (readonly)

Returns the value of attribute requirements.



50
51
52
# File 'lib/inferno/dsl/fhir_validation.rb', line 50

def requirements
  @requirements
end

Instance Method Details

#additional_validation_messages(resource, profile_url) ⇒ Object



101
102
103
104
105
# File 'lib/inferno/dsl/fhir_validation.rb', line 101

def additional_validation_messages(resource, profile_url)
  additional_validations
    .flat_map { |step| step.call(resource, profile_url) }
    .select { |message| message.is_a? Hash }
end

#additional_validationsObject



73
74
75
# File 'lib/inferno/dsl/fhir_validation.rb', line 73

def additional_validations
  @additional_validations ||= []
end

#call_validator(resource, profile_url) ⇒ Object



221
222
223
224
225
226
# File 'lib/inferno/dsl/fhir_validation.rb', line 221

def call_validator(resource, profile_url)
  Faraday.new(
    url,
    params: { profile: profile_url }
  ).post('validate', resource.source_contents)
end

#default_validator_urlObject



59
60
61
# File 'lib/inferno/dsl/fhir_validation.rb', line 59

def default_validator_url
  ENV.fetch('VALIDATOR_URL')
end

#exclude_message {|message| ... } ⇒ Object

Filter out unwanted validation messages. Any messages for which the block evalutates to a truthy value will be excluded.

Examples:

validator do
  exclude_message { |message| message.type == 'info' }
end

Yield Parameters:



115
116
117
118
# File 'lib/inferno/dsl/fhir_validation.rb', line 115

def exclude_message(&block)
  @exclude_message = block if block_given?
  @exclude_message
end

#exclude_unresolved_url_messageObject



157
158
159
# File 'lib/inferno/dsl/fhir_validation.rb', line 157

def exclude_unresolved_url_message
  proc { |message| message.message.match?(/\A\S+: [^:]+: URL value '.*' does not resolve/) }
end

#filter_messages(message_hashes) ⇒ Object



162
163
164
165
# File 'lib/inferno/dsl/fhir_validation.rb', line 162

def filter_messages(message_hashes)
  message_hashes.reject! { |message| exclude_unresolved_url_message.call(Entities::Message.new(message)) }
  message_hashes.reject! { |message| exclude_message.call(Entities::Message.new(message)) } if exclude_message
end

#issue_message(issue, resource) ⇒ Object



199
200
201
202
203
204
205
206
207
208
209
# File 'lib/inferno/dsl/fhir_validation.rb', line 199

def issue_message(issue, resource)
  location = if issue.respond_to?(:expression)
               issue.expression&.join(', ')
             else
               issue.location&.join(', ')
             end

  location_prefix = resource.id ? "#{resource.resourceType}/#{resource.id}" : resource.resourceType

  "#{location_prefix}: #{location}: #{issue&.details&.text}"
end

#issue_severity(issue) ⇒ Object



187
188
189
190
191
192
193
194
195
196
# File 'lib/inferno/dsl/fhir_validation.rb', line 187

def issue_severity(issue)
  case issue.severity
  when 'warning'
    'warning'
  when 'information'
    'info'
  else
    'error'
  end
end

#message_hash_from_issue(issue, resource) ⇒ Object



179
180
181
182
183
184
# File 'lib/inferno/dsl/fhir_validation.rb', line 179

def message_hash_from_issue(issue, resource)
  {
    type: issue_severity(issue),
    message: issue_message(issue, resource)
  }
end

#message_hashes_from_outcome(outcome, resource, profile_url) ⇒ Object



168
169
170
171
172
173
174
175
176
# File 'lib/inferno/dsl/fhir_validation.rb', line 168

def message_hashes_from_outcome(outcome, resource, profile_url)
  message_hashes = outcome.issue&.map { |issue| message_hash_from_issue(issue, resource) } || []

  message_hashes.concat(additional_validation_messages(resource, profile_url))

  filter_messages(message_hashes)

  message_hashes
end

#operation_outcome_from_validator_response(response, runnable) ⇒ Object



234
235
236
237
238
239
240
241
242
243
# File 'lib/inferno/dsl/fhir_validation.rb', line 234

def operation_outcome_from_validator_response(response, runnable)
  sanitized_body = remove_invalid_characters(response.body)

  FHIR::OperationOutcome.new(JSON.parse(sanitized_body))
rescue JSON::ParserError
  runnable.add_message('error', "Validator Response: HTTP #{response.status}\n#{sanitized_body}")
  raise Inferno::Exceptions::ErrorInValidatorException,
        'Validator response was an unexpected format. ' \
        'Review Messages tab or validator service logs for more information.'
end

#perform_additional_validation {|resource, profile_url| ... } ⇒ Object

Perform validation steps in addition to FHIR validation.

Examples:

perform_additional_validation do |resource, profile_url|
  if something_is_wrong
    { type: 'error', message: 'something is wrong' }
  else
    { type: 'info', message: 'everything is ok' }
  end
end

Yield Parameters:

  • resource (FHIR::Model)

    the resource being validated

  • profile_url (String)

    the profile the resource is being validated against

Yield Returns:

  • (Array<Hash<Symbol, String>>, Hash<Symbol, String>)

    The block should return a Hash or an Array of Hashes if any validation messages should be added. The Hash must contain two keys: ‘:type` and `:message`. `:type` can have a value of `’info’‘, `’warning’‘, or `’error’‘. A type of `’error’‘ means the resource is invalid. `:message` contains the message string itself.



96
97
98
# File 'lib/inferno/dsl/fhir_validation.rb', line 96

def perform_additional_validation(&block)
  additional_validations << block
end

#remove_invalid_characters(string) ⇒ Object



229
230
231
# File 'lib/inferno/dsl/fhir_validation.rb', line 229

def remove_invalid_characters(string)
  string.gsub(/[^[:print:]\r\n]+/, '')
end

#resource_is_valid?(resource, profile_url, runnable, add_messages_to_runnable: true) ⇒ Boolean



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/inferno/dsl/fhir_validation.rb', line 121

def resource_is_valid?(resource, profile_url, runnable, add_messages_to_runnable: true) # rubocop:disable Metrics/CyclomaticComplexity
  profile_url ||= FHIR::Definitions.resource_definition(resource.resourceType).url

  begin
    response = call_validator(resource, profile_url)
  rescue StandardError => e
    # This could be a complete failure to connect (validator isn't running)
    # or a timeout (validator took too long to respond).
    runnable.add_message('error', e.message)
    raise Inferno::Exceptions::ErrorInValidatorException, "Unable to connect to validator at #{url}."
  end
  outcome = operation_outcome_from_validator_response(response, runnable)

  message_hashes = message_hashes_from_outcome(outcome, resource, profile_url)

  if add_messages_to_runnable
    message_hashes
      .each { |message_hash| runnable.add_message(message_hash[:type], message_hash[:message]) }
  end

  unless response.status == 200
    raise Inferno::Exceptions::ErrorInValidatorException,
          'Error occurred in the validator. Review Messages tab or validator service logs for more information.'
  end

  message_hashes
    .none? { |message_hash| message_hash[:type] == 'error' }
rescue Inferno::Exceptions::ErrorInValidatorException
  raise
rescue StandardError => e
  runnable.add_message('error', e.message)
  raise Inferno::Exceptions::ErrorInValidatorException,
        'Error occurred in the validator. Review Messages tab or validator service logs for more information.'
end

#url(validator_url = nil) ⇒ Object

Set the url of the validator service

Parameters:

  • validator_url (String) (defaults to: nil)


66
67
68
69
70
# File 'lib/inferno/dsl/fhir_validation.rb', line 66

def url(validator_url = nil)
  @url = validator_url if validator_url
  @url ||= default_validator_url
  @url
end

#validate(resource, profile_url) ⇒ String

Post a resource to the validation service for validating.

Parameters:

Returns:

  • (String)

    the body of the validation response



216
217
218
# File 'lib/inferno/dsl/fhir_validation.rb', line 216

def validate(resource, profile_url)
  call_validator(resource, profile_url).body
end