inferno_core 0.4.20 → 0.4.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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}'")