fhir_client 1.6.3 → 1.6.7

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +34 -0
  3. data/.csslintrc +2 -0
  4. data/.eslintignore +1 -0
  5. data/.eslintrc +213 -0
  6. data/.rubocop.yml +1159 -0
  7. data/.travis.yml +8 -0
  8. data/Gemfile +5 -5
  9. data/Rakefile +14 -13
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/fhir_client.gemspec +34 -21
  13. data/lib/fhir_client.rb +13 -18
  14. data/lib/fhir_client/client.rb +533 -0
  15. data/lib/{client_exception.rb → fhir_client/client_exception.rb} +1 -3
  16. data/lib/{ext → fhir_client/ext}/bundle.rb +5 -7
  17. data/lib/{ext → fhir_client/ext}/model.rb +10 -2
  18. data/lib/fhir_client/ext/reference.rb +28 -0
  19. data/lib/{fhir_api_validation.json → fhir_client/fhir_api_validation.json} +0 -0
  20. data/lib/{model → fhir_client/model}/client_reply.rb +47 -51
  21. data/lib/{model → fhir_client/model}/tag.rb +6 -7
  22. data/lib/fhir_client/patch_format.rb +8 -0
  23. data/lib/{resource_address.rb → fhir_client/resource_address.rb} +135 -139
  24. data/lib/fhir_client/resource_format.rb +11 -0
  25. data/lib/{sections → fhir_client/sections}/crud.rb +28 -30
  26. data/lib/{sections → fhir_client/sections}/feed.rb +1 -4
  27. data/lib/{sections → fhir_client/sections}/history.rb +14 -15
  28. data/lib/{sections → fhir_client/sections}/operations.rb +22 -28
  29. data/lib/fhir_client/sections/search.rb +52 -0
  30. data/lib/{sections → fhir_client/sections}/tags.rb +12 -12
  31. data/lib/{sections → fhir_client/sections}/transactions.rb +17 -20
  32. data/lib/{sections → fhir_client/sections}/validate.rb +12 -15
  33. data/lib/{tasks → fhir_client/tasks}/tasks.rake +19 -18
  34. data/lib/fhir_client/version.rb +5 -0
  35. metadata +127 -83
  36. data/lib/client_interface.rb +0 -533
  37. data/lib/ext/reference.rb +0 -11
  38. data/lib/patch_format.rb +0 -10
  39. data/lib/resource_format.rb +0 -13
  40. data/lib/sections/search.rb +0 -53
  41. data/test/fixtures/bundle-example.xml +0 -79
  42. data/test/fixtures/parameters-example.json +0 -18
  43. data/test/fixtures/parameters-example.xml +0 -17
  44. data/test/test_helper.rb +0 -8
  45. data/test/unit/basic_test.rb +0 -17
  46. data/test/unit/bundle_test.rb +0 -21
  47. data/test/unit/parameters_test.rb +0 -44
@@ -1,533 +0,0 @@
1
- module FHIR
2
-
3
- class Client
4
-
5
- include FHIR::Sections::History
6
- include FHIR::Sections::Crud
7
- include FHIR::Sections::Validate
8
- include FHIR::Sections::Tags
9
- include FHIR::Sections::Feed
10
- include FHIR::Sections::Search
11
- include FHIR::Sections::Operations
12
- include FHIR::Sections::Transactions
13
-
14
- attr_accessor :reply
15
- attr_accessor :use_format_param
16
- attr_accessor :use_basic_auth
17
- attr_accessor :use_oauth2_auth
18
- attr_accessor :security_headers
19
- attr_accessor :client
20
-
21
- attr_accessor :default_format
22
-
23
- attr_accessor :cached_conformance
24
-
25
- # Call method to initialize FHIR client. This method must be invoked
26
- # with a valid base server URL prior to using the client.
27
- #
28
- # @param baseServiceUrl Base service URL for FHIR Service.
29
- # @return
30
- #
31
- def initialize(baseServiceUrl)
32
- FHIR.logger.info "Initializing client with #{@baseServiceUrl}"
33
- @baseServiceUrl = baseServiceUrl
34
- @use_format_param = false
35
- @default_format = FHIR::Formats::ResourceFormat::RESOURCE_XML
36
- set_no_auth
37
- end
38
-
39
- def default_json
40
- @default_format = FHIR::Formats::ResourceFormat::RESOURCE_JSON
41
- end
42
-
43
- def default_xml
44
- @default_format = FHIR::Formats::ResourceFormat::RESOURCE_XML
45
- end
46
-
47
- # Set the client to use no authentication mechanisms
48
- def set_no_auth
49
- FHIR.logger.info "Configuring the client to use no authentication."
50
- @use_oauth2_auth = false
51
- @use_basic_auth = false
52
- @security_headers = {}
53
- @client = RestClient
54
- end
55
-
56
- # Set the client to use HTTP Basic Authentication
57
- def set_basic_auth(client,secret)
58
- FHIR.logger.info "Configuring the client to use HTTP Basic authentication."
59
- token = Base64.encode64("#{client}:#{secret}")
60
- value = "Basic #{token}"
61
- @security_headers = { 'Authorization' => value }
62
- @use_oauth2_auth = false
63
- @use_basic_auth = true
64
- @client = RestClient
65
- end
66
-
67
- # Set the client to use Bearer Token Authentication
68
- def set_bearer_token(token)
69
- FHIR.logger.info "Configuring the client to use Bearer Token authentication."
70
- value = "Bearer #{token}"
71
- @security_headers = { 'Authorization' => value }
72
- @use_oauth2_auth = false
73
- @use_basic_auth = true
74
- @client = RestClient
75
- end
76
-
77
- # Set the client to use OpenID Connect OAuth2 Authentication
78
- # client -- client id
79
- # secret -- client secret
80
- # authorizePath -- absolute path of authorization endpoint
81
- # tokenPath -- absolute path of token endpoint
82
- def set_oauth2_auth(client,secret,authorizePath,tokenPath)
83
- FHIR.logger.info "Configuring the client to use OpenID Connect OAuth2 authentication."
84
- @use_oauth2_auth = true
85
- @use_basic_auth = false
86
- @security_headers = {}
87
- options = {
88
- :site => @baseServiceUrl,
89
- :authorize_url => authorizePath,
90
- :token_url => tokenPath,
91
- :raise_errors => true
92
- }
93
- client = OAuth2::Client.new(client,secret,options)
94
- @client = client.client_credentials.get_token
95
- end
96
-
97
- # Get the OAuth2 server and endpoints from the conformance statement
98
- # (the server should not require OAuth2 or other special security to access
99
- # the conformance statement).
100
- # <rest>
101
- # <mode value="server"/>
102
- # <documentation value="All the functionality defined in FHIR"/>
103
- # <security>
104
- # <extension url="http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris">
105
- # <extension url="register">
106
- # <valueUri value="https://authorize-dstu2.smarthealthit.org/register"/>
107
- # </extension>
108
- # <extension url="authorize">
109
- # <valueUri value="https://authorize-dstu2.smarthealthit.org/authorize"/>
110
- # </extension>
111
- # <extension url="token">
112
- # <valueUri value="https://authorize-dstu2.smarthealthit.org/token"/>
113
- # </extension>
114
- # </extension>
115
- # <service>
116
- # <coding>
117
- # <system value="http://hl7.org/fhir/vs/restful-security-service"/>
118
- # <code value="OAuth2"/>
119
- # </coding>
120
- # <text value="OAuth version 2 (see oauth.net)."/>
121
- # </service>
122
- # <description value="SMART on FHIR uses OAuth2 for authorization"/>
123
- # </security>
124
- def get_oauth2_metadata_from_conformance
125
- options = {
126
- :authorize_url => nil,
127
- :token_url => nil
128
- }
129
- oauth_extension = 'http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris'
130
- authorize_extension = 'authorize'
131
- token_extension = 'token'
132
- begin
133
- conformance = conformanceStatement
134
- conformance.rest.each do |rest|
135
- rest.security.service.each do |service|
136
- service.coding.each do |coding|
137
- if coding.code == 'SMART-on-FHIR'
138
- rest.security.extension.where({url: oauth_extension}).first.extension.each do |ext|
139
- case ext.url
140
- when authorize_extension
141
- options[:authorize_url] = ext.value.value
142
- when "#{oauth_extension}\##{authorize_extension}"
143
- options[:authorize_url] = ext.value.value
144
- when token_extension
145
- options[:token_url] = ext.value.value
146
- when "#{oauth_extension}\##{token_extension}"
147
- options[:token_url] = ext.value.value
148
- end
149
- end
150
- end
151
- end
152
- end
153
- end
154
- rescue Exception => e
155
- FHIR.logger.error 'Failed to locate SMART-on-FHIR OAuth2 Security Extensions.'
156
- end
157
- options.delete_if{|k,v|v.nil?}
158
- options.clear if options.keys.size!=2
159
- options
160
- end
161
-
162
- # Method returns a conformance statement for the system queried.
163
- # @return
164
- def conformanceStatement(format=FHIR::Formats::ResourceFormat::RESOURCE_XML)
165
- if (@cached_conformance.nil? || format!=@default_format)
166
- format = try_conformance_formats(format)
167
- end
168
- @cached_conformance
169
- end
170
-
171
- def try_conformance_formats(default_format)
172
- formats = [FHIR::Formats::ResourceFormat::RESOURCE_XML,
173
- FHIR::Formats::ResourceFormat::RESOURCE_JSON,
174
- FHIR::Formats::ResourceFormat::RESOURCE_XML_DSTU2,
175
- FHIR::Formats::ResourceFormat::RESOURCE_JSON_DSTU2,
176
- 'application/xml',
177
- 'application/json']
178
- formats.insert(0, default_format)
179
-
180
- @cached_conformance = nil
181
- @default_format = nil
182
-
183
- formats.each do |frmt|
184
- reply = get 'metadata', fhir_headers({format: frmt})
185
- if reply.code == 200
186
- @cached_conformance = parse_reply(FHIR::Conformance, frmt, reply)
187
- @default_format = frmt
188
- break
189
- end
190
- end
191
- @default_format = default_format if @default_format.nil?
192
- @default_format
193
- end
194
-
195
- def resource_url(options)
196
- FHIR::ResourceAddress.new.resource_url(options, @use_format_param)
197
- end
198
-
199
- def full_resource_url(options)
200
- @baseServiceUrl + resource_url(options)
201
- end
202
-
203
- def fhir_headers(options={})
204
- FHIR::ResourceAddress.new.fhir_headers(options, @use_format_param)
205
- end
206
-
207
- def parse_reply(klass, format, response)
208
- FHIR.logger.info "Parsing response with {klass: #{klass}, format: #{format}, code: #{response.code}}."
209
- return nil if ![200,201].include? response.code
210
- res = nil
211
- begin
212
- res = FHIR.from_contents(response.body)
213
- res.client = self if !res.nil?
214
- FHIR.logger.warn "Expected #{klass} but got #{res.class}" if res.class!=klass
215
- rescue Exception => e
216
- FHIR.logger.error "Failed to parse #{format} as resource #{klass}: #{e.message} %n #{e.backtrace.join("\n")} #{response}"
217
- nil
218
- end
219
- res
220
- end
221
-
222
- def strip_base(path)
223
- path.gsub(@baseServiceUrl, '')
224
- end
225
-
226
- def reissue_request(request)
227
- if [:get, :delete, :head].include?(request['method'])
228
- method(request['method']).call(request['url'], request['headers'])
229
- elsif [:post, :put].include?(request['method'])
230
- resource = FHIR.from_contents(request['payload']) unless request['payload'].nil?
231
- method(request['method']).call(request['url'], resource, request['headers'])
232
- end
233
- end
234
-
235
- private
236
-
237
- def base_path(path)
238
- if path.start_with?('/')
239
- if @baseServiceUrl.end_with?('/')
240
- @baseServiceUrl.chop
241
- else
242
- @baseServiceUrl
243
- end
244
- else
245
- @baseServiceUrl + '/'
246
- end
247
- end
248
-
249
- # Extract the request payload in the specified format, defaults to XML
250
- def request_payload(resource, headers)
251
- if headers
252
- format_specified = headers[:format] || headers["format"]
253
- if format_specified.downcase.include?('xml')
254
- resource.to_xml
255
- elsif format_specified.downcase.include?('json')
256
- resource.to_json
257
- else
258
- resource.to_xml
259
- end
260
- else
261
- resource.to_xml
262
- end
263
- end
264
-
265
- def request_patch_payload(patchset, format)
266
- if (format == FHIR::Formats::PatchFormat::PATCH_JSON)
267
- patchset.each do |patch|
268
- # remove the resource name from the patch path, since the JSON representation doesn't have that
269
- patch[:path] = patch[:path].slice(patch[:path].index('/')..-1)
270
- end
271
- patchset.to_json
272
- elsif (format == FHIR::Formats::PatchFormat::PATCH_XML)
273
- builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
274
- patchset.each do |patch|
275
- xml.diff {
276
- # TODO: support other kinds besides just replace
277
- xml.replace(patch[:value], sel: patch[:path] + '/@value') if patch[:op] == 'replace'
278
- }
279
- end
280
- end
281
- builder.to_xml
282
- end
283
- end
284
-
285
- def clean_headers(headers)
286
- headers.delete_if{|k,v|(k.nil? || v.nil?)}
287
- headers.inject({}){|h,(k,v)| h[k.to_s]=v.to_s; h}
288
- end
289
-
290
- def scrubbed_response_headers(result)
291
- result.each_key do |k|
292
- v = result[k]
293
- result[k] = v[0] if (v.is_a? Array)
294
- end
295
- end
296
-
297
- def get(path, headers)
298
- url = URI(URI.escape(build_url(path))).to_s
299
- FHIR.logger.info "GETTING: #{url}"
300
- headers = clean_headers(headers)
301
- if @use_oauth2_auth
302
- # @client.refresh!
303
- begin
304
- response = @client.get(url, {:headers=>headers})
305
- rescue Exception => e
306
- response = e.response if e.response
307
- end
308
- req = {
309
- :method => :get,
310
- :url => url,
311
- :path => url.gsub(@baseServiceUrl,''),
312
- :headers => headers,
313
- :payload => nil
314
- }
315
- res = {
316
- :code => response.status.to_s,
317
- :headers => response.headers,
318
- :body => response.body
319
- }
320
- FHIR.logger.info "GET - Request: #{req.to_s}, Response: #{response.body.force_encoding("UTF-8")}"
321
- @reply = FHIR::ClientReply.new(req, res)
322
- else
323
- headers.merge!(@security_headers) if @use_basic_auth
324
- begin
325
- response = @client.get(url, headers)
326
- rescue Exception => e
327
- response = e.response if e.response
328
- end
329
-
330
- FHIR.logger.info "GET - Request: #{response.request.to_json}, Response: #{response.body.force_encoding("UTF-8")}"
331
- response.request.args[:path] = response.request.args[:url].gsub(@baseServiceUrl,'')
332
- headers = response.headers.inject({}){ |h,(k,v)| h[k.to_s.gsub('_','-')] = v.to_s; h}
333
- res = {
334
- :code => response.code,
335
- :headers => scrubbed_response_headers(headers),
336
- :body => response.body
337
- }
338
-
339
- @reply = FHIR::ClientReply.new(response.request.args, res)
340
- end
341
- end
342
-
343
- def post(path, resource, headers)
344
- url = URI(build_url(path)).to_s
345
- FHIR.logger.info "POSTING: #{url}"
346
- headers = clean_headers(headers)
347
- payload = request_payload(resource, headers) if resource
348
- if @use_oauth2_auth
349
- # @client.refresh!
350
- begin
351
- response = @client.post(url, {:headers=>headers,:body=>payload})
352
- rescue Exception => e
353
- response = e.response if e.response
354
- end
355
- req = {
356
- :method => :post,
357
- :url => url,
358
- :path => url.gsub(@baseServiceUrl,''),
359
- :headers => headers,
360
- :payload => payload
361
- }
362
- res = {
363
- :code => response.status.to_s,
364
- :headers => response.headers,
365
- :body => response.body
366
- }
367
- FHIR.logger.info "POST - Request: #{req.to_s}, Response: #{response.body.force_encoding("UTF-8")}"
368
- @reply = FHIR::ClientReply.new(req, res)
369
- else
370
- headers.merge!(@security_headers) if @use_basic_auth
371
- @client.post(url, payload, headers){ |response, request, result|
372
- FHIR.logger.info "POST - Request: #{request.to_json}, Response: #{response.force_encoding("UTF-8")}"
373
- request.args[:path] = url.gsub(@baseServiceUrl,'')
374
- res = {
375
- :code => result.code,
376
- :headers => scrubbed_response_headers(result.each_key{}),
377
- :body => response
378
- }
379
- @reply = FHIR::ClientReply.new(request.args, res)
380
- }
381
- end
382
- end
383
-
384
- def put(path, resource, headers)
385
- url = URI(build_url(path)).to_s
386
- FHIR.logger.info "PUTTING: #{url}"
387
- headers = clean_headers(headers)
388
- payload = request_payload(resource, headers) if resource
389
- if @use_oauth2_auth
390
- # @client.refresh!
391
- begin
392
- response = @client.put(url, {:headers=>headers,:body=>payload})
393
- rescue Exception => e
394
- response = e.response if e.response
395
- end
396
- req = {
397
- :method => :put,
398
- :url => url,
399
- :path => url.gsub(@baseServiceUrl,''),
400
- :headers => headers,
401
- :payload => payload
402
- }
403
- res = {
404
- :code => response.status.to_s,
405
- :headers => response.headers,
406
- :body => response.body
407
- }
408
- FHIR.logger.info "PUT - Request: #{req.to_s}, Response: #{response.body.force_encoding("UTF-8")}"
409
- @reply = FHIR::ClientReply.new(req, res)
410
- else
411
- headers.merge!(@security_headers) if @use_basic_auth
412
- @client.put(url, payload, headers){ |response, request, result|
413
- FHIR.logger.info "PUT - Request: #{request.to_json}, Response: #{response.force_encoding("UTF-8")}"
414
- request.args[:path] = url.gsub(@baseServiceUrl,'')
415
- res = {
416
- :code => result.code,
417
- :headers => scrubbed_response_headers(result.each_key{}),
418
- :body => response
419
- }
420
- @reply = FHIR::ClientReply.new(request.args, res)
421
- }
422
- end
423
- end
424
-
425
- def patch(path, patchset, headers)
426
- url = URI(build_url(path)).to_s
427
- FHIR.logger.info "PATCHING: #{url}"
428
- headers = clean_headers(headers)
429
- payload = request_patch_payload(patchset, headers['format'])
430
- if @use_oauth2_auth
431
- # @client.refresh!
432
- begin
433
- response = @client.patch(url, {:headers=>headers,:body=>payload})
434
- rescue Exception => e
435
- response = e.response if e.response
436
- end
437
- req = {
438
- :method => :patch,
439
- :url => url,
440
- :path => url.gsub(@baseServiceUrl,''),
441
- :headers => headers,
442
- :payload => payload
443
- }
444
- res = {
445
- :code => response.status.to_s,
446
- :headers => response.headers,
447
- :body => response.body
448
- }
449
- FHIR.logger.info "PATCH - Request: #{req.to_s}, Response: #{response.body.force_encoding("UTF-8")}"
450
- @reply = FHIR::ClientReply.new(req, res)
451
- else
452
- headers.merge!(@security_headers) if @use_basic_auth
453
- # url = 'http://requestb.in/o8juy3o8'
454
- @client.patch(url, payload, headers){ |response, request, result|
455
- FHIR.logger.info "PATCH - Request: #{request.to_json}, Response: #{response.force_encoding("UTF-8")}"
456
- request.args[:path] = url.gsub(@baseServiceUrl,'')
457
- res = {
458
- :code => result.code,
459
- :headers => scrubbed_response_headers(result.each_key{}),
460
- :body => response
461
- }
462
- @reply = FHIR::ClientReply.new(request.args, res)
463
- }
464
- end
465
- end
466
-
467
- def delete(path, headers)
468
- url = URI(build_url(path)).to_s
469
- FHIR.logger.info "DELETING: #{url}"
470
- headers = clean_headers(headers)
471
- if @use_oauth2_auth
472
- # @client.refresh!
473
- begin
474
- response = @client.delete(url, {:headers=>headers})
475
- rescue Exception => e
476
- response = e.response if e.response
477
- end
478
- req = {
479
- :method => :delete,
480
- :url => url,
481
- :path => url.gsub(@baseServiceUrl,''),
482
- :headers => headers,
483
- :payload => nil
484
- }
485
- res = {
486
- :code => response.status.to_s,
487
- :headers => response.headers,
488
- :body => response.body
489
- }
490
- FHIR.logger.info "DELETE - Request: #{req.to_s}, Response: #{response.body.force_encoding("UTF-8")}"
491
- @reply = FHIR::ClientReply.new(req, res)
492
- else
493
- headers.merge!(@security_headers) if @use_basic_auth
494
- @client.delete(url, headers){ |response, request, result|
495
- FHIR.logger.info "DELETE - Request: #{request.to_json}, Response: #{response.force_encoding("UTF-8")}"
496
- request.args[:path] = url.gsub(@baseServiceUrl,'')
497
- res = {
498
- :code => result.code,
499
- :headers => scrubbed_response_headers(result.each_key{}),
500
- :body => response
501
- }
502
- @reply = FHIR::ClientReply.new(request.args, res)
503
- }
504
- end
505
- end
506
-
507
- def head(path, headers)
508
- headers.merge!(@security_headers) unless @security_headers.blank?
509
- url = URI(build_url(path)).to_s
510
- FHIR.logger.info "HEADING: #{url}"
511
- RestClient.head(url, headers){ |response, request, result|
512
- FHIR.logger.info "HEAD - Request: #{request.to_json}, Response: #{response.force_encoding("UTF-8")}"
513
- request.args[:path] = url.gsub(@baseServiceUrl,'')
514
- res = {
515
- :code => result.code,
516
- :headers => scrubbed_response_headers(result.each_key{}),
517
- :body => response
518
- }
519
- @reply = FHIR::ClientReply.new(request.args, res)
520
- }
521
- end
522
-
523
- def build_url(path)
524
- if path =~ /^\w+:\/\//
525
- path
526
- else
527
- "#{base_path(path)}#{path}"
528
- end
529
- end
530
-
531
- end
532
-
533
- end