inferno_core 0.4.20 → 0.4.21

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ccd619a77a04e3fa092d9b2090a1bc1f53b8d0e34027b8cfd3dd0c03a8212d04
4
- data.tar.gz: 20191611704b5e4dffac41bf0fa528a38ba79b1d86052b43c20853646af2d9fe
3
+ metadata.gz: 13f75c775423384dd5bf4a2b13081bc05a155155fca9a6bcea1986d2baf09b15
4
+ data.tar.gz: 7b6c586b121fad54caa57fb5ae8d8c7300467b543ece25c4222c21cc65ac83c7
5
5
  SHA512:
6
- metadata.gz: 6fa722f33598020a4ec742729e66148aaa61b652b2f08d07cf79be1f17f6a8120e467432818fb39383d4fbe50a8f902e5384dc93c30cddb654fd53abbadab40e
7
- data.tar.gz: 6a2740906607fda7a0dba543b9255a8fb4c0e296288bad0d5044d40dd20347d7e797177b708715328e778c05766600f2dc728244a3863f69521d340cae011b26
6
+ metadata.gz: 7f0ab91414e4554a430972dd25543339cd39245c0d154dcc42d0a623c83b4aeaccf32709d08074abd4dcae07cf3deb75144a2db3cd3af548a37ddc552b0df909
7
+ data.tar.gz: 2f328c92cb3d66853eccde4aee267be6e8e668b778f75e1d278a3b5d4ba86840e4f0e260fbc5bf941017ee8bde1cd20699a53153cd9e26ee8e9bee8fa730ab7c
@@ -60,26 +60,64 @@ module Inferno
60
60
  @fhir_clients ||= {}
61
61
  end
62
62
 
63
+ # Wrapper for checking if parameter contents are primitive
64
+ #
65
+ # @param param [FHIR::Parameters::Parameter] Parameter to be checked
66
+ # @private
67
+ def primitive_parameter?(param)
68
+ param_val = param.to_hash.except('name')
69
+ param_val.any? { |datatype, param_value| FHIR.primitive?(datatype: datatype[5..], value: param_value) }
70
+ end
71
+
72
+ # Converts a list of FHIR Parameters into a query string for GET requests
73
+ #
74
+ # @param body [FHIR::Parameters] Must all be primitive if making GET request
75
+ # @private
76
+ def body_to_path(body)
77
+ query_hashes = body.parameter.map do |param|
78
+ if primitive_parameter?(param)
79
+ { param.name => param.to_hash.except('name').values[0] }
80
+ else
81
+ Inferno::Application[:logger].error "Cannot use GET request with non-primitive datatype #{param.name}"
82
+ raise ArgumentError, "Cannot use GET request with non-primitive datatype #{param.name}"
83
+ end
84
+ end
85
+ query_hashes.map(&:to_query).join('&')
86
+ end
87
+
63
88
  # Perform a FHIR operation
64
89
  #
65
90
  # @note This is a placeholder method until the FHIR::Client supports
66
- # general operations
91
+ # general operations. Note that while both POST and GET methods are allowed,
92
+ # GET is only allowed when the operation does not affect the server's state.
93
+ # See https://build.fhir.org/operationdefinition-definitions.html#OperationDefinition.affectsState
94
+ #
95
+ # @note Currently does not allow for repeated parameters if using GET
67
96
  #
68
97
  # @param path [String]
69
- # @param body [FHIR::Parameters]
98
+ # @param body [FHIR::Parameters] Must all be primitive if making GET request
70
99
  # @param client [Symbol]
71
100
  # @param name [Symbol] Name for this request to allow it to be used by
72
101
  # other tests
73
102
  # @param headers [Hash] custom headers for this operation
103
+ # @param operation_method [Symbol] indicates which request type to use for the operation
74
104
  # @return [Inferno::Entities::Request]
75
- def fhir_operation(path, body: nil, client: :default, name: nil, headers: {})
105
+ def fhir_operation(path, body: nil, client: :default, name: nil, headers: {}, operation_method: :post)
76
106
  store_request_and_refresh_token(fhir_client(client), name) do
77
107
  tcp_exception_handler do
78
108
  operation_headers = fhir_client(client).fhir_headers
79
109
  operation_headers.merge!('Content-Type' => 'application/fhir+json') if body.present?
80
110
  operation_headers.merge!(headers) if headers.present?
81
-
82
- fhir_client(client).send(:post, path, body, operation_headers)
111
+ case operation_method
112
+ when :post
113
+ fhir_client(client).send(:post, path, body, operation_headers)
114
+ when :get
115
+ path = "#{path}?#{body_to_path(body)}" if body.present?
116
+ fhir_client(client).send(:get, path, operation_headers)
117
+ else
118
+ Inferno::Application[:logger].error "Cannot perform #{operation_method} requests, use GET or POST"
119
+ raise ArgumentError, "Cannot perform #{operation_method} requests, use GET or POST"
120
+ end
83
121
  end
84
122
  end
85
123
  end
@@ -116,17 +116,34 @@ module Inferno
116
116
  def resource_is_valid?(resource, profile_url, runnable)
117
117
  profile_url ||= FHIR::Definitions.resource_definition(resource.resourceType).url
118
118
 
119
- outcome = FHIR::OperationOutcome.new(JSON.parse(validate(resource, profile_url)))
119
+ begin
120
+ response = call_validator(resource, profile_url)
121
+ rescue StandardError => e
122
+ # This could be a complete failure to connect (validator isn't running)
123
+ # or a timeout (validator took too long to respond).
124
+ runnable.add_message('error', e.message)
125
+ raise Inferno::Exceptions::ErrorInValidatorException, "Unable to connect to validator at #{url}."
126
+ end
127
+ outcome = operation_outcome_from_validator_response(response.body, runnable)
120
128
 
121
- message_hashes = outcome.issue&.map { |issue| message_hash_from_issue(issue, resource) } || []
129
+ message_hashes = message_hashes_from_outcome(outcome, resource, profile_url)
122
130
 
123
- message_hashes.concat(additional_validation_messages(resource, profile_url))
131
+ message_hashes
132
+ .each { |message_hash| runnable.add_message(message_hash[:type], message_hash[:message]) }
124
133
 
125
- filter_messages(message_hashes)
134
+ unless response.status == 200
135
+ raise Inferno::Exceptions::ErrorInValidatorException,
136
+ 'Error occurred in the validator. Review Messages tab or validator service logs for more information.'
137
+ end
126
138
 
127
139
  message_hashes
128
- .each { |message_hash| runnable.add_message(message_hash[:type], message_hash[:message]) }
129
140
  .none? { |message_hash| message_hash[:type] == 'error' }
141
+ rescue Inferno::Exceptions::ErrorInValidatorException
142
+ raise
143
+ rescue StandardError => e
144
+ runnable.add_message('error', e.message)
145
+ raise Inferno::Exceptions::ErrorInValidatorException,
146
+ 'Error occurred in the validator. Review Messages tab or validator service logs for more information.'
130
147
  end
131
148
 
132
149
  # @private
@@ -134,6 +151,17 @@ module Inferno
134
151
  message_hashes.reject! { |message| exclude_message.call(Entities::Message.new(message)) } if exclude_message
135
152
  end
136
153
 
154
+ # @private
155
+ def message_hashes_from_outcome(outcome, resource, profile_url)
156
+ message_hashes = outcome.issue&.map { |issue| message_hash_from_issue(issue, resource) } || []
157
+
158
+ message_hashes.concat(additional_validation_messages(resource, profile_url))
159
+
160
+ filter_messages(message_hashes)
161
+
162
+ message_hashes
163
+ end
164
+
137
165
  # @private
138
166
  def message_hash_from_issue(issue, resource)
139
167
  {
@@ -173,10 +201,27 @@ module Inferno
173
201
  # @param profile_url [String]
174
202
  # @return [String] the body of the validation response
175
203
  def validate(resource, profile_url)
204
+ call_validator(resource, profile_url).body
205
+ end
206
+
207
+ # @private
208
+ def call_validator(resource, profile_url)
176
209
  Faraday.new(
177
210
  url,
178
211
  params: { profile: profile_url }
179
- ).post('validate', resource.source_contents).body
212
+ ).post('validate', resource.source_contents)
213
+ end
214
+
215
+ # @private
216
+ def operation_outcome_from_validator_response(response, runnable)
217
+ if response.start_with? '{'
218
+ FHIR::OperationOutcome.new(JSON.parse(response))
219
+ else
220
+ runnable.add_message('error', "Validator Response:\n#{response}")
221
+ raise Inferno::Exceptions::ErrorInValidatorException,
222
+ 'Validator response was an unexpected format. '\
223
+ 'Review Messages tab or validator service logs for more information.'
224
+ end
180
225
  end
181
226
  end
182
227
 
@@ -39,6 +39,19 @@ module Inferno
39
39
  end
40
40
  end
41
41
 
42
+ # ErrorInValidatorException is used when an exception occurred in
43
+ # calling the validator service, for example a connection timeout
44
+ # or an unexpected response format.
45
+ # Note: This class extends TestResultException instead of RuntimeError
46
+ # to bypass printing the stack trace in the UI, since
47
+ # the stack trace of this exception is not likely be useful.
48
+ # Instead the message should point to where in the validator an error occurred.
49
+ class ErrorInValidatorException < TestResultException
50
+ def result
51
+ 'error'
52
+ end
53
+ end
54
+
42
55
  class ParentNotLoadedException < RuntimeError
43
56
  def initialize(klass, id)
44
57
  super("No #{klass.name.demodulize} found with id '#{id}'")