fhir_client 1.6.3 → 1.6.7

Sign up to get free protection for your applications and to get access to all the features.
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