fhir_client 3.0.7 → 3.1.0

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: e672a83cc2c6d38d9b7b907e593fcee05ecead0a75320f79a8a65627deff0df7
4
- data.tar.gz: 79219638d996eaa08b9ec7b38957da73eb1e6e5c5d26b428480178ee2e939fc4
3
+ metadata.gz: 0652e0b08ba87a20e874e73ea62fca47611c6d3631681f6d9b6316e1a3e08528
4
+ data.tar.gz: 3980e902188d8fa627f8c9a8c72fb010ecffbc3c8fb056bd5a3a78cc50aebe05
5
5
  SHA512:
6
- metadata.gz: 904a33c45ee559acfbc0372435c9ee31e76412ef840d3616ae6f8e584aa9381d8a43cb81d937a3818891a82fe26ca5acbcaa74778602bbbe42c01c78d2e7ea35
7
- data.tar.gz: ad3ec868c7c139105890e4091e1780efffb30649df4b7e63a5fbc8ed599dcfa249cfac2d7b84f12236712a32ec4d69b4a58c5d8fb1891330a6fc30907dfddae2
6
+ metadata.gz: b2f783cbbd33c2a437d6c45ea01ce3ba7aa60ca277d18b2dcb7dd0e09fcd037e854f1b6fd1be3d6487c296ef129280d3bbca2cbe2aa85fa5e0ac6d7e86bdfbaf
7
+ data.tar.gz: eb13cd8355a0116d1b30043231881a7748fa164c39a5b2075f2d5517f99f353934d4b14121e056b0e0117077e3c070a28b5edf3f256792993e5614499c824956
data/.travis.yml CHANGED
@@ -1,9 +1,9 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.2
4
3
  - 2.3
5
4
  - 2.4
6
5
  - 2.5
6
+ - 2.6
7
7
  before_install:
8
8
  - gem update --system
9
9
  - gem install bundler
data/README.md CHANGED
@@ -84,6 +84,15 @@ patient = FHIR::DSTU2::Patient.read('example')
84
84
  patient = client.read(FHIR::DSTU2::Patient, 'example').resource
85
85
  ```
86
86
 
87
+ ### Configuration
88
+
89
+ You can specify additional properties for the `client`:
90
+
91
+ ```ruby
92
+ client.additional_headers = {Prefer: 'return=representation'}
93
+ client.proxy = 'https://your-proxy.com/'
94
+ ```
95
+
87
96
  ### CRUD Examples
88
97
  ```ruby
89
98
  # read an existing patient with id "example"
data/fhir_client.gemspec CHANGED
@@ -30,7 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.add_dependency 'rest-client', '~> 2.0'
31
31
  spec.add_dependency 'tilt', '>= 1.1'
32
32
 
33
- spec.add_development_dependency 'bundler', '~> 1.13'
33
+ spec.add_development_dependency 'bundler', '~> 2.0'
34
34
  spec.add_development_dependency 'rake', '~> 10.0'
35
35
  spec.add_development_dependency 'pry'
36
36
  spec.add_development_dependency 'webmock'
data/lib/fhir_client.rb CHANGED
@@ -13,6 +13,7 @@ end
13
13
  require_relative 'fhir_client/client'
14
14
  require_relative 'fhir_client/resource_address'
15
15
  require_relative 'fhir_client/resource_format'
16
+ require_relative 'fhir_client/return_preferences'
16
17
  require_relative 'fhir_client/patch_format'
17
18
  require_relative 'fhir_client/client_exception'
18
19
  require_relative 'fhir_client/version'
@@ -23,6 +23,12 @@ module FHIR
23
23
  attr_accessor :fhir_version
24
24
  attr_accessor :cached_capability_statement
25
25
  attr_accessor :additional_headers
26
+ attr_accessor :proxy
27
+ attr_accessor :exception_class
28
+
29
+ attr_accessor :use_accept_header
30
+ attr_accessor :use_accept_charset
31
+ attr_accessor :use_return_preference
26
32
 
27
33
  # Call method to initialize FHIR client. This method must be invoked
28
34
  # with a valid base server URL prior to using the client.
@@ -31,12 +37,19 @@ module FHIR
31
37
  # @param default_format Default Format Mime type
32
38
  # @return
33
39
  #
34
- def initialize(base_service_url, default_format: FHIR::Formats::ResourceFormat::RESOURCE_XML)
40
+ def initialize(base_service_url, default_format: FHIR::Formats::ResourceFormat::RESOURCE_XML, proxy: nil)
35
41
  @base_service_url = base_service_url
36
42
  FHIR.logger.info "Initializing client with #{@base_service_url}"
37
43
  @use_format_param = false
44
+ @use_accept_header = true
45
+ @use_accept_charset = true
38
46
  @default_format = default_format
39
47
  @fhir_version = :stu3
48
+ @use_return_preference = false
49
+ @return_preference = FHIR::Formats::ReturnPreferences::REPRESENTATION
50
+ @exception_class = ClientException
51
+ @proxy = proxy
52
+
40
53
  set_no_auth
41
54
  end
42
55
 
@@ -74,6 +87,20 @@ module FHIR
74
87
  end
75
88
  end
76
89
 
90
+ #
91
+ # Instructs the client to specify the minimal Prefer Header where applicable
92
+ def use_minimal_preference
93
+ @use_return_preference = true
94
+ @return_preference = FHIR::Formats::ReturnPreferences::MINIMAL
95
+ end
96
+
97
+ #
98
+ # Instructs the client to specify the representation Prefer Header where applicable
99
+ def use_representation_preference
100
+ @use_return_preference = true
101
+ @return_preference = FHIR::Formats::ReturnPreferences::REPRESENTATION
102
+ end
103
+
77
104
  def versioned_resource_class(klass)
78
105
  if @fhir_version == :stu3
79
106
  FHIR.const_get(klass)
@@ -102,6 +129,8 @@ module FHIR
102
129
  @use_basic_auth = false
103
130
  @security_headers = {}
104
131
  @client = RestClient
132
+ @client.proxy = proxy unless proxy.nil?
133
+ @client
105
134
  end
106
135
 
107
136
  # Set the client to use HTTP Basic Authentication
@@ -113,6 +142,8 @@ module FHIR
113
142
  @use_oauth2_auth = false
114
143
  @use_basic_auth = true
115
144
  @client = RestClient
145
+ @client.proxy = proxy unless proxy.nil?
146
+ @client
116
147
  end
117
148
 
118
149
  # Set the client to use Bearer Token Authentication
@@ -123,6 +154,8 @@ module FHIR
123
154
  @use_oauth2_auth = false
124
155
  @use_basic_auth = true
125
156
  @client = RestClient
157
+ @client.proxy = proxy unless proxy.nil?
158
+ @client
126
159
  end
127
160
 
128
161
  # Set the client to use OpenID Connect OAuth2 Authentication
@@ -142,6 +175,7 @@ module FHIR
142
175
  raise_errors: true
143
176
  }
144
177
  client = OAuth2::Client.new(client, secret, options)
178
+ client.connection.proxy(proxy) unless proxy.nil?
145
179
  @client = client.client_credentials.get_token
146
180
  end
147
181
 
@@ -248,7 +282,7 @@ module FHIR
248
282
  @default_format = nil
249
283
 
250
284
  formats.each do |frmt|
251
- reply = get 'metadata', fhir_headers(format: frmt)
285
+ reply = get 'metadata', fhir_headers({accept: "#{frmt}"})
252
286
  next unless reply.code == 200
253
287
  begin
254
288
  @cached_capability_statement = parse_reply(FHIR::CapabilityStatement, frmt, reply)
@@ -272,7 +306,7 @@ module FHIR
272
306
  end
273
307
 
274
308
  def resource_url(options)
275
- FHIR::ResourceAddress.new.resource_url(options, @use_format_param)
309
+ FHIR::ResourceAddress.resource_url(options, @use_format_param)
276
310
  end
277
311
 
278
312
  def full_resource_url(options)
@@ -280,9 +314,7 @@ module FHIR
280
314
  end
281
315
 
282
316
  def fhir_headers(options = {})
283
- options.merge!(additional_headers) unless additional_headers.nil?
284
-
285
- FHIR::ResourceAddress.new.fhir_headers(options, @use_format_param)
317
+ FHIR::ResourceAddress.fhir_headers(options, additional_headers, @default_format, @use_accept_header, @use_accept_charset)
286
318
  end
287
319
 
288
320
  def parse_reply(klass, format, response)
@@ -348,8 +380,10 @@ module FHIR
348
380
  # Extract the request payload in the specified format, defaults to XML
349
381
  def request_payload(resource, headers)
350
382
  if headers
351
- format_specified = headers[:format] || headers['format']
352
- if format_specified.downcase.include?('xml')
383
+ format_specified = headers['Content-Type']
384
+ if format_specified.nil?
385
+ resource.to_xml
386
+ elsif format_specified.downcase.include?('xml')
353
387
  resource.to_xml
354
388
  elsif format_specified.downcase.include?('json')
355
389
  resource.to_json
@@ -383,7 +417,7 @@ module FHIR
383
417
 
384
418
  def clean_headers(headers)
385
419
  headers.delete_if { |k, v| (k.nil? || v.nil?) }
386
- headers.each_with_object({}) { |(k, v), h| h[k.to_s] = v.to_s; h }
420
+ FHIR::ResourceAddress.convert_symbol_headers(headers)
387
421
  end
388
422
 
389
423
  def scrubbed_response_headers(result)
@@ -393,10 +427,10 @@ module FHIR
393
427
  end
394
428
  end
395
429
 
396
- def get(path, headers)
430
+ def get(path, headers = {})
397
431
  url = Addressable::URI.parse(build_url(path)).to_s
398
432
  FHIR.logger.info "GETTING: #{url}"
399
- headers = clean_headers(headers)
433
+ headers = clean_headers(headers) unless headers.empty?
400
434
  if @use_oauth2_auth
401
435
  # @client.refresh!
402
436
  begin
@@ -572,7 +606,7 @@ module FHIR
572
606
  url = URI(build_url(path)).to_s
573
607
  FHIR.logger.info "PATCHING: #{url}"
574
608
  headers = clean_headers(headers)
575
- payload = request_patch_payload(patchset, headers['format'])
609
+ payload = request_patch_payload(patchset, headers['Content-Type'])
576
610
  if @use_oauth2_auth
577
611
  # @client.refresh!
578
612
  begin
@@ -50,7 +50,7 @@ module FHIR
50
50
 
51
51
  private
52
52
  def handle_response(response)
53
- raise ClientException.new "Server returned #{response.code}.", response if response.code.between?(400, 599)
53
+ raise client.exception_class.new "Server returned #{response.code}.", response if response.code.between?(400, 599)
54
54
  response.resource
55
55
  end
56
56
  end
@@ -66,59 +66,59 @@ module FHIR
66
66
  end
67
67
 
68
68
  def read(id, client = self.client)
69
- handle_response client.read(self, id)
69
+ handle_response client.exception_class, client.read(self, id)
70
70
  end
71
71
 
72
72
  def read_with_summary(id, summary, client = self.client)
73
- handle_response client.read(self, id, client.default_format, summary)
73
+ handle_response client.exception_class, client.read(self, id, client.default_format, summary)
74
74
  end
75
75
 
76
76
  def vread(id, version_id, client = self.client)
77
- handle_response client.vread(self, id, version_id)
77
+ handle_response client.exception_class, client.vread(self, id, version_id)
78
78
  end
79
79
 
80
80
  def resource_history(client = self.client)
81
- handle_response client.resource_history(self)
81
+ handle_response client.exception_class, client.resource_history(self)
82
82
  end
83
83
 
84
84
  def resource_history_as_of(last_update, client = self.client)
85
- handle_response client.resource_history_as_of(self, last_update)
85
+ handle_response client.exception_class, client.resource_history_as_of(self, last_update)
86
86
  end
87
87
 
88
88
  def resource_instance_history(id, client = self.client)
89
- handle_response client.resource_instance_history(self, id)
89
+ handle_response client.exception_class, client.resource_instance_history(self, id)
90
90
  end
91
91
 
92
92
  def resource_instance_history_as_of(id, last_update, client = self.client)
93
- handle_response client.resource_instance_history_as_of(self, id, last_update)
93
+ handle_response client.exception_class, client.resource_instance_history_as_of(self, id, last_update)
94
94
  end
95
95
 
96
96
  def search(params = {}, client = self.client)
97
- handle_response client.search(self, search: { parameters: params })
97
+ handle_response client.exception_class, client.search(self, search: { parameters: params })
98
98
  end
99
99
 
100
100
  def create(model, client = self.client)
101
101
  model = new(model) unless model.is_a?(self)
102
- handle_response client.create(model)
102
+ handle_response client.exception_class, client.create(model)
103
103
  end
104
104
 
105
105
  def conditional_create(model, params, client = self.client)
106
106
  model = new(model) unless model.is_a?(self)
107
- handle_response client.conditional_create(model, params)
107
+ handle_response client.exception_class, client.conditional_create(model, params)
108
108
  end
109
109
 
110
110
  def partial_update(id, patchset, options = {})
111
- handle_response client.partial_update(self, id, patchset, options)
111
+ handle_response client.exception_class, client.partial_update(self, id, patchset, options)
112
112
  end
113
113
 
114
114
  def all(client = self.client)
115
- handle_response client.read_feed(self)
115
+ handle_response client.exception_class, client.read_feed(self)
116
116
  end
117
117
 
118
118
  private
119
119
 
120
- def handle_response(response)
121
- raise ClientException.new "Server returned #{response.code}.", response if response.code.between?(400, 599)
120
+ def handle_response(exception_class, response)
121
+ raise exception_class.new "Server returned #{response.code}.", response if response.code.between?(400, 599)
122
122
  response.resource
123
123
  end
124
124
  end
@@ -59,7 +59,7 @@ module FHIR
59
59
  if relative? || reference == client.full_resource_url(resource: resource_class, id: reference_id)
60
60
  read_client = client
61
61
  else
62
- read_client = FHIR::Client.new base_uri, default_format: client.default_format
62
+ read_client = FHIR::Client.new base_uri, default_format: client.default_format, proxy: client.proxy
63
63
  end
64
64
  resource_class.read(reference_id, read_client)
65
65
  end
@@ -69,7 +69,7 @@ module FHIR
69
69
  if relative? || reference == client.full_resource_url(resource: resource_class, id: reference_id)
70
70
  read_client = client
71
71
  else
72
- read_client = FHIR::Client.new base_uri, default_format: client.default_format
72
+ read_client = FHIR::Client.new base_uri, default_format: client.default_format, proxy: client.proxy
73
73
  end
74
74
  resource_class.vread(reference_id, version_id, read_client)
75
75
  end
@@ -6,46 +6,83 @@ module FHIR
6
6
  format: 'application/fhir+xml'
7
7
  }.freeze
8
8
 
9
- DEFAULT_CHARSET = 'UTF-8'.freeze
10
-
11
- def fhir_headers(options, use_format_param = false)
12
- options = DEFAULTS.merge(options)
13
-
14
- format = options[:format] || FHIR::Formats::ResourceFormat::RESOURCE_XML
15
- fhir_headers = {
16
- 'User-Agent' => 'Ruby FHIR Client',
17
- 'Content-Type' => format + ';charset=' + DEFAULT_CHARSET,
18
- 'Accept-Charset' => DEFAULT_CHARSET
19
- }
20
- # remove the content-type header if the format is 'xml' or 'json' because
21
- # even those are valid _format parameter options, they are not valid MimeTypes.
22
- fhir_headers.delete('Content-Type') if %w(xml json).include?(format.downcase)
23
-
24
- if options[:category]
25
- # options[:category] should be an Array of FHIR::Tag objects
26
- tags = {
27
- 'Category' => options[:category].collect(&:to_header).join(',')
28
- }
29
- fhir_headers.merge!(tags)
30
- options.delete(:category)
9
+ DEFAULT_CHARSET = 'utf-8'.freeze
10
+ DEFAULT_CONTENT_TYPE = 'application/fhir+xml'.freeze # time to default to json?
11
+
12
+ #
13
+ # Normalize submitted header key value pairs
14
+ #
15
+ # 'content-type', 'Content-Type', and :content_type would all represent the content-type header
16
+ #
17
+ # Assumes symbols like :content_type to be "content-type"
18
+ # if for some odd reason the Header string representation contains underscores it would need to be specified
19
+ # as a string (i.e. options {"Underscore_Header" => 'why not hyphens'})
20
+ # Note that servers like apache or nginx consider that invalid anyways and drop them
21
+ # http://httpd.apache.org/docs/trunk/new_features_2_4.html
22
+ # http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers
23
+ def self.normalize_headers(to_be_normalized, to_symbol = true, capitalized = false)
24
+ to_be_normalized.inject({}) do |result, (key, value)|
25
+ key = key.to_s.downcase.split(/-|_/)
26
+ key.map!(&:capitalize) if capitalized
27
+ key = to_symbol ? key.join('_').to_sym : key.join('-')
28
+ result[key] = value.to_s
29
+ result
31
30
  end
31
+ end
32
32
 
33
- if use_format_param
34
- fhir_headers.delete('Accept')
35
- options.delete('Accept')
36
- options.delete(:accept)
37
- else
38
- fhir_headers['Accept'] = format
33
+ def self.convert_symbol_headers headers
34
+ headers.inject({}) do |result, (key, value)|
35
+ if key.is_a? Symbol
36
+ key = key.to_s.split(/_/).map(&:capitalize).join('-')
37
+ end
38
+ result[key] = value.to_s
39
+ result
39
40
  end
41
+ end
40
42
 
41
- fhir_headers.merge!(options) unless options.empty?
42
- fhir_headers[:operation] = options[:operation][:name] if options[:operation] && options[:operation][:name]
43
- fhir_headers.delete('id')
44
- fhir_headers.delete('resource')
43
+ # Returns normalized HTTP Headers
44
+ # header key value pairs can be supplied with keys specified as symbols or strings
45
+ # keys will be normalized to symbols.
46
+ # e.g. the keys :accept, "accept", and "Accept" all represent the Accept HTTP Header
47
+ # @param [Hash] options key value pairs for the http headerx
48
+ # @return [Hash] The normalized FHIR Headers
49
+ def self.fhir_headers(headers = {}, additional_headers = {}, format = DEFAULT_CONTENT_TYPE, use_accept_header = true, use_accept_charset = true)
50
+ # normalizes header names to be case-insensitive
51
+ # See relevant HTTP RFCs:
52
+ # https://tools.ietf.org/html/rfc2616#section-4.2
53
+ # https://tools.ietf.org/html/rfc7230#section-3.2
54
+ #
55
+ # https://tools.ietf.org/html/rfc7231#section-5.3.2
56
+ # optional white space before and
57
+ # https://tools.ietf.org/html/rfc2616#section-3.4
58
+ # utf-8 is case insensitive
59
+ #
60
+ headers ||= {}
61
+ additional_headers ||= {}
62
+
63
+ fhir_headers = {user_agent: 'Ruby FHIR Client'}
64
+
65
+ fhir_headers[:accept_charset] = DEFAULT_CHARSET if use_accept_charset
66
+
67
+ # https://www.hl7.org/fhir/DSTU2/http.html#mime-type
68
+ # could add option for ;charset=#{DEFAULT_CHARSET} in accept header
69
+ fhir_headers[:accept] = "#{format}" if use_accept_header
70
+
71
+ # maybe in a future update normalize everything to symbols
72
+ # Headers should be case insensitive anyways...
73
+ #headers = normalize_headers(headers) unless headers.empty?
74
+ #
75
+ fhir_headers = convert_symbol_headers(fhir_headers)
76
+ headers = convert_symbol_headers(headers)
77
+
78
+ # supplied headers will always be used, e.g. if @use_accept_header is false
79
+ # ,but an accept header is explicitly supplied then it will be used (or override the existing)
80
+ fhir_headers.merge!(headers) unless headers.empty?
81
+ fhir_headers.merge!(additional_headers)
45
82
  fhir_headers
46
83
  end
47
84
 
48
- def resource_url(options, use_format_param = false)
85
+ def self.resource_url(options, use_format_param = false)
49
86
  options = DEFAULTS.merge(options)
50
87
 
51
88
  params = {}
@@ -0,0 +1,8 @@
1
+ module FHIR
2
+ module Formats
3
+ module ReturnPreferences
4
+ MINIMAL = 'return=minimal'.freeze
5
+ REPRESENTATION = 'return=representation'.freeze
6
+ end
7
+ end
8
+ end
@@ -4,11 +4,47 @@ module FHIR
4
4
  #
5
5
  # Read the current state of a resource.
6
6
  #
7
- def read(klass, id, format = @default_format, summary = nil, options = {})
8
- options = { resource: klass, id: id, format: format }.merge(options)
7
+ def read(klass, id, format = nil, summary = nil, options = {})
8
+ options = { resource: klass, id: id, format: format || @default_format}.merge(options)
9
9
  options[:summary] = summary if summary
10
- reply = get resource_url(options), fhir_headers(options)
11
- reply.resource = parse_reply(klass, format, reply)
10
+ headers = { accept: "#{format}" } if format
11
+ reply = get resource_url(options), fhir_headers(headers)
12
+ reply.resource = parse_reply(klass, format || @default_format, reply)
13
+ reply.resource_class = klass
14
+ reply
15
+ end
16
+
17
+ #
18
+ # Conditionally Read the resource if it has been modified since the supplied date
19
+ #
20
+ # See If-Modified-Since RRC 7232 https://tools.ietf.org/html/rfc7232#section-3.3 and
21
+ # See HTTP-date https://tools.ietf.org/html/rfc7231#section-7.1.1.1
22
+ #
23
+ # @param klass the FHIR Resource class
24
+ # @param id the resource id
25
+ # @param since_date the date
26
+ def conditional_read_since(klass, id, since_date, options = {})
27
+
28
+ options = { resource: klass, id: id}.merge(options)
29
+ headers = { if_modified_since: since_date }
30
+ reply = get resource_url(options), fhir_headers(headers)
31
+ reply.resource = parse_reply(klass, @default_format, reply)
32
+ reply.resource_class = klass
33
+ reply
34
+ end
35
+
36
+ #
37
+ # Conditionally Read the resource based on the provided ETag
38
+ #
39
+ # @param klass the FHIR Resource class
40
+ # @param id the resource id
41
+ # @param version_id the version_id used for the ETag
42
+ def conditional_read_version(klass, id, version_id, options = {})
43
+
44
+ options = { resource: klass, id: id}.merge(options)
45
+ headers = { if_none_match: "W/#{version_id}" }
46
+ reply = get resource_url(options), fhir_headers(headers)
47
+ reply.resource = parse_reply(klass, @default_format, reply)
12
48
  reply.resource_class = klass
13
49
  reply
14
50
  end
@@ -16,9 +52,12 @@ module FHIR
16
52
  #
17
53
  # Read a resource bundle (an XML ATOM feed)
18
54
  #
19
- def read_feed(klass, format = @default_format)
20
- options = { resource: klass, format: format }
21
- reply = get resource_url(options), fhir_headers(options)
55
+ #
56
+ def read_feed(klass, format = nil)
57
+ headers = { accept: "#{format}" } if format
58
+ format ||= @default_format
59
+ options = { resource: klass, format: format}
60
+ reply = get resource_url(options), fhir_headers(headers)
22
61
  reply.resource = parse_reply(klass, format, reply)
23
62
  reply.resource_class = klass
24
63
  reply
@@ -27,33 +66,36 @@ module FHIR
27
66
  #
28
67
  # Read the state of a specific version of the resource
29
68
  #
30
- def vread(klass, id, version_id, format = @default_format)
69
+ def vread(klass, id, version_id, format = nil)
70
+ headers = { accept: "#{format}" } if format
71
+ format ||= @default_format
31
72
  options = { resource: klass, id: id, format: format, history: { id: version_id } }
32
- reply = get resource_url(options), fhir_headers(options)
73
+ reply = get resource_url(options), fhir_headers(headers)
33
74
  reply.resource = parse_reply(klass, format, reply)
34
75
  reply.resource_class = klass
35
76
  reply
36
77
  end
37
78
 
38
79
  def raw_read(options)
39
- get resource_url(options), fhir_headers(options)
80
+ get resource_url(options), fhir_headers
40
81
  end
41
82
 
42
- def raw_read_url(url, format = @default_format)
43
- get url, fhir_headers(format: format)
83
+ def raw_read_url(url, format = nil)
84
+ headers = { accept: "#{format}" } if format
85
+ get url, fhir_headers(headers)
44
86
  end
45
87
 
46
88
  #
47
89
  # Update an existing resource by its id or create it if it is a new resource, not present on the server
48
90
  #
49
- def update(resource, id, format = @default_format)
91
+ def update(resource, id, format = nil)
50
92
  base_update(resource, id, nil, format)
51
93
  end
52
94
 
53
95
  #
54
96
  # Update an existing resource by its id or create it if it is a new resource, not present on the server
55
97
  #
56
- def conditional_update(resource, id, search_params, format = @default_format)
98
+ def conditional_update(resource, id, search_params, format = nil)
57
99
  options = {
58
100
  search: {
59
101
  flag: false,
@@ -67,15 +109,29 @@ module FHIR
67
109
  base_update(resource, id, options, format)
68
110
  end
69
111
 
112
+ #
113
+ # Version Aware Update using version_id
114
+ # prevents Lost Update Problem https://www.w3.org/1999/04/Editing/
115
+ # @param resource the FHIR resource object
116
+ # @param id the resource id
117
+ def version_aware_update(resource, id, version_id, format = nil, options = {})
118
+ base_update(resource, id, options, format, {if_match: "W/#{version_id}"})
119
+ end
120
+
70
121
  #
71
122
  # Update an existing resource by its id or create it if it is a new resource, not present on the server
72
123
  #
73
- def base_update(resource, id, options, format)
124
+ def base_update(resource, id, options, format = nil, headers = nil)
125
+ headers ||= {}
126
+ headers[:accept] = "#{format}" if format
127
+ format ||= @default_format
128
+ headers[:content_type] = "#{format}"
129
+ headers[:prefer] = @return_preference if @use_return_preference
74
130
  options = {} if options.nil?
75
131
  options[:resource] = resource.class
76
132
  options[:format] = format
77
133
  options[:id] = id
78
- reply = put resource_url(options), resource, fhir_headers(options)
134
+ reply = put resource_url(options), resource, fhir_headers(headers)
79
135
  reply.resource = parse_reply(resource.class, format, reply) if reply.body.present?
80
136
  reply.resource_class = resource.class
81
137
  reply
@@ -84,18 +140,20 @@ module FHIR
84
140
  #
85
141
  # Partial update using a patchset (PATCH)
86
142
  #
87
- def partial_update(klass, id, patchset, options = {}, format = @default_format)
88
- options = { resource: klass, id: id, format: format }.merge options
89
-
143
+ def partial_update(klass, id, patchset, options = {}, format = nil)
144
+ headers = {}
145
+ headers[:accept] = "#{format}" if format
146
+ format ||= @default_format
147
+ options = { resource: klass, id: id, format: format}.merge options
90
148
  if [FHIR::Formats::ResourceFormat::RESOURCE_XML, FHIR::Formats::ResourceFormat::RESOURCE_XML_DSTU2].include?(format)
91
149
  options[:format] = FHIR::Formats::PatchFormat::PATCH_XML
92
- options[:Accept] = format
150
+ headers[:content_type] = "#{FHIR::Formats::PatchFormat::PATCH_XML}"
93
151
  elsif [FHIR::Formats::ResourceFormat::RESOURCE_JSON, FHIR::Formats::ResourceFormat::RESOURCE_JSON_DSTU2].include?(format)
94
152
  options[:format] = FHIR::Formats::PatchFormat::PATCH_JSON
95
- options[:Accept] = format
153
+ headers[:content_type] = "#{FHIR::Formats::PatchFormat::PATCH_JSON}"
96
154
  end
97
-
98
- reply = patch resource_url(options), patchset, fhir_headers(options)
155
+ headers[:prefer] = @return_preference if @use_return_preference
156
+ reply = patch resource_url(options), patchset, fhir_headers(headers)
99
157
  reply.resource = parse_reply(klass, format, reply)
100
158
  reply.resource_class = klass
101
159
  reply
@@ -106,9 +164,7 @@ module FHIR
106
164
  #
107
165
  def destroy(klass, id = nil, options = {})
108
166
  options = { resource: klass, id: id, format: @default_format }.merge options
109
- headers = fhir_headers(options)
110
- headers.delete('Content-Type')
111
- reply = delete resource_url(options), headers
167
+ reply = delete resource_url(options), fhir_headers
112
168
  reply.resource_class = klass
113
169
  reply
114
170
  end
@@ -117,33 +173,38 @@ module FHIR
117
173
  # Create a new resource with a server assigned id. Return the newly created
118
174
  # resource with the id the server assigned.
119
175
  #
120
- def create(resource, options = {}, format = @default_format)
176
+ def create(resource, options = {}, format = nil)
121
177
  base_create(resource, options, format)
122
178
  end
123
179
 
124
180
  #
125
181
  # Conditionally create a new resource with a server assigned id.
126
182
  #
127
- def conditional_create(resource, if_none_exist_parameters, format = @default_format)
183
+ def conditional_create(resource, if_none_exist_parameters, format = nil)
128
184
  query = ''
129
185
  if_none_exist_parameters.each do |key, value|
130
186
  query += "#{key}=#{value}&"
131
187
  end
132
188
  query = query[0..-2] # strip off the trailing ampersand
133
- options = {}
134
- options['If-None-Exist'] = query
135
- base_create(resource, options, format)
189
+ header = {if_none_exist: query}
190
+ base_create(resource, options, format, header)
136
191
  end
137
192
 
138
193
  #
139
194
  # Create a new resource with a server assigned id. Return the newly created
140
195
  # resource with the id the server assigned.
141
196
  #
142
- def base_create(resource, options, format)
197
+ def base_create(resource, options, format = nil, additional_header = {})
198
+ headers = {}
199
+ headers[:accept] = "#{format}" if format
200
+ format ||= @default_format
201
+ headers = {content_type: "#{format}"}
202
+ headers[:prefer] = @return_preference if @use_return_preference
203
+ headers.merge!(additional_header)
143
204
  options = {} if options.nil?
144
205
  options[:resource] = resource.class
145
- options[:format] = format
146
- reply = post resource_url(options), resource, fhir_headers(options)
206
+ options[:format] = format || @default_format
207
+ reply = post resource_url(options), resource, fhir_headers(headers)
147
208
  if [200, 201].include? reply.code
148
209
  type = reply.response[:headers].detect{|x, _y| x.downcase=='content-type'}.try(:last)
149
210
  if !type.nil?
@@ -10,7 +10,7 @@ module FHIR
10
10
  bundle = current.resource
11
11
  link = bundle.method(page).call
12
12
  return nil unless link
13
- reply = get strip_base(link.url), fhir_headers(format: @default_format)
13
+ reply = get strip_base(link.url), fhir_headers
14
14
  reply.resource = parse_reply(current.resource_class, @default_format, reply)
15
15
  reply.resource_class = current.resource_class
16
16
  reply
@@ -25,7 +25,7 @@ module FHIR
25
25
 
26
26
  def history(options)
27
27
  options = {format: @default_format}.merge(options)
28
- reply = get resource_url(options), fhir_headers(options).except(:history)
28
+ reply = get resource_url(options), fhir_headers
29
29
  reply.resource = parse_reply(options[:resource], options[:format], reply)
30
30
  reply.resource_class = options[:resource]
31
31
  reply
@@ -10,15 +10,19 @@ module FHIR
10
10
  # Fetch Patient Record [base]/Patient/$everything | [base]/Patient/[id]/$everything
11
11
  # http://hl7.org/implement/standards/FHIR-Develop/patient-operations.html#everything
12
12
  # Fetches resources for a given patient record, scoped by a start and end time, and returns a Bundle of results
13
- def fetch_patient_record(id = nil, startTime = nil, endTime = nil, method = 'GET', format = @default_format)
13
+ def fetch_patient_record(id = nil, startTime = nil, endTime = nil, method = 'GET', format = nil)
14
14
  fetch_record(id, [startTime, endTime], method, versioned_resource_class('Patient'), format)
15
15
  end
16
16
 
17
- def fetch_encounter_record(id = nil, method = 'GET', format = @default_format)
17
+ def fetch_encounter_record(id = nil, method = 'GET', format = nil)
18
18
  fetch_record(id, [nil, nil], method, versioned_resource_class('Encounter'), format)
19
19
  end
20
20
 
21
- def fetch_record(id = nil, time = [nil, nil], method = 'GET', klass = versioned_resource_class('Patient'), format = @default_format)
21
+ def fetch_record(id = nil, time = [nil, nil], method = 'GET', klass = versioned_resource_class('Patient'), format = nil)
22
+ headers = {}
23
+ headers[:accept] = "#{format}" if format
24
+ format ||= @default_format
25
+ headers[:content_type] = format
22
26
  options = { resource: klass, format: format, operation: { name: :fetch_patient_record, method: method } }
23
27
  options.deep_merge!(id: id) unless id.nil?
24
28
  options[:operation][:parameters] = {} if options[:operation][:parameters].nil?
@@ -26,7 +30,7 @@ module FHIR
26
30
  options[:operation][:parameters][:end] = { type: 'Date', value: time.last } unless time.last.nil?
27
31
 
28
32
  if options[:operation][:method] == 'GET'
29
- reply = get resource_url(options), fhir_headers(options)
33
+ reply = get resource_url(options), fhir_headers
30
34
  else
31
35
  # create Parameters body
32
36
  if options[:operation] && options[:operation][:parameters]
@@ -37,7 +41,7 @@ module FHIR
37
41
  p.parameter << parameter
38
42
  end
39
43
  end
40
- reply = post resource_url(options), p, fhir_headers(options)
44
+ reply = post resource_url(options), p, fhir_headers(headers)
41
45
  end
42
46
 
43
47
  reply.resource = parse_reply(versioned_resource_class('Bundle'), format, reply)
@@ -95,11 +99,14 @@ module FHIR
95
99
 
96
100
  def terminology_operation(params = {}, format = @default_format)
97
101
  options = { format: format }
102
+ headers = {}
103
+ headers[:accept] = "#{format}" if format
104
+ format ||= @default_format
98
105
  # params = [id, code, system, version, display, coding, codeableConcept, date, abstract]
99
106
  options.deep_merge!(params)
100
107
 
101
108
  if options[:operation][:method] == 'GET'
102
- reply = get resource_url(options), fhir_headers(options)
109
+ reply = get resource_url(options), fhir_headers(headers)
103
110
  else
104
111
  # create Parameters body
105
112
  if options[:operation] && options[:operation][:parameters]
@@ -110,7 +117,8 @@ module FHIR
110
117
  p.parameter << parameter
111
118
  end
112
119
  end
113
- reply = post resource_url(options), p, fhir_headers(options)
120
+ headers[:content_type] = "#{format}"
121
+ reply = post resource_url(options), p, fhir_headers(headers)
114
122
  end
115
123
 
116
124
  reply.resource = parse_reply(options[:resource], format, reply)
@@ -124,7 +132,8 @@ module FHIR
124
132
  add_resource_parameter(params, 'resource', resource)
125
133
  add_parameter(params, 'onlyCertainMatches', 'Boolean', options[:onlyCertainMatches]) unless options[:onlyCertainMatches].nil?
126
134
  add_parameter(params, 'count', 'Integer', options[:matchCount]) if options[:matchCount].is_a?(Integer)
127
- post resource_url(options), params, fhir_headers(options)
135
+ post resource_url(options), params, fhir_headers({content_type: "#{format || @default_format}",
136
+ accept: "#{format || @default_format}"})
128
137
  end
129
138
 
130
139
  #
@@ -139,18 +148,24 @@ module FHIR
139
148
 
140
149
  def validate(resource, options = {}, format = @default_format)
141
150
  options.merge!(resource: resource.class, validate: true, format: format)
151
+ headers = {}
152
+ headers[:accept] = "#{format}" if format
153
+ headers[:content_type] = "#{format}"
142
154
  params = versioned_resource_class('Parameters').new
143
155
  add_resource_parameter(params, 'resource', resource)
144
156
  add_parameter(params, 'profile', 'Uri', options[:profile_uri]) unless options[:profile_uri].nil?
145
- post resource_url(options), params, fhir_headers(options)
157
+ post resource_url(options), params, fhir_headers(headers)
146
158
  end
147
159
 
148
160
  def validate_existing(resource, id, options = {}, format = @default_format)
149
161
  options.merge!(resource: resource.class, id: id, validate: true, format: format)
162
+ headers = {}
163
+ headers[:accept] = "#{format}" if format
164
+ headers[:content_type] = "#{format}"
150
165
  params = versioned_resource_class('Parameters').new
151
166
  add_resource_parameter(params, 'resource', resource)
152
167
  add_parameter(params, 'profile', 'Uri', options[:profile_uri]) unless options[:profile_uri].nil?
153
- post resource_url(options), params, fhir_headers(options)
168
+ post resource_url(options), params, fhir_headers(headers)
154
169
  end
155
170
 
156
171
  private
@@ -13,9 +13,9 @@ module FHIR
13
13
  options[:format] = format
14
14
 
15
15
  reply = if options[:search] && options[:search][:flag]
16
- post resource_url(options), nil, fhir_headers(options)
16
+ post resource_url(options), nil, fhir_headers({content_type: 'application/x-www-form-urlencoded'})
17
17
  else
18
- get resource_url(options), fhir_headers(options)
18
+ get resource_url(options), fhir_headers
19
19
  end
20
20
  # reply = get resource_url(options), fhir_headers(options)
21
21
  reply.resource = parse_reply(klass, format, reply)
@@ -27,9 +27,9 @@ module FHIR
27
27
  options.merge!(resource: klass, id: id, format: format)
28
28
  # if options[:search][:flag]
29
29
  reply = if options[:search] && options[:search][:flag]
30
- post resource_url(options), nil, fhir_headers(options)
30
+ post resource_url(options), nil, fhir_headers({content_type: 'application/x-www-form-urlencoded'})
31
31
  else
32
- get resource_url(options), fhir_headers(options)
32
+ get resource_url(options), fhir_headers
33
33
  end
34
34
  reply.resource = parse_reply(klass, format, reply)
35
35
  reply.resource_class = klass
@@ -39,9 +39,9 @@ module FHIR
39
39
  def search_all(options = {}, format = @default_format)
40
40
  options[:format] = format
41
41
  reply = if options[:search] && options[:search][:flag]
42
- post resource_url(options), nil, fhir_headers(options)
42
+ post resource_url(options), nil, fhir_headers({content_type: 'application/x-www-form-urlencoded'})
43
43
  else
44
- get resource_url(options), fhir_headers(options)
44
+ get resource_url(options), fhir_headers
45
45
  end
46
46
  reply.resource = parse_reply(nil, format, reply)
47
47
  reply.resource_class = nil
@@ -61,8 +61,10 @@ module FHIR
61
61
  # @return FHIR::ClientReply
62
62
  #
63
63
  def end_batch(format = @default_format)
64
- options = { format: format, 'Prefer' => 'return=representation' }
65
- reply = post resource_url(options), @transaction_bundle, fhir_headers(options)
64
+ headers = {prefer: FHIR::Formats::ReturnPreferences::REPRESENTATION}
65
+ headers[:content_type] = "#{format}"
66
+ options = { format: format}
67
+ reply = post resource_url(options), @transaction_bundle, fhir_headers(headers)
66
68
  begin
67
69
  reply.resource = if format.downcase.include?('xml')
68
70
  versioned_resource_class('Xml').from_xml(reply.body)
@@ -1,5 +1,5 @@
1
1
  module FHIR
2
2
  class Client
3
- VERSION = '3.0.7'
3
+ VERSION = '3.1.0'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fhir_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.7
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andre Quina
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2018-12-10 00:00:00.000000000 Z
13
+ date: 2019-02-12 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -144,14 +144,14 @@ dependencies:
144
144
  requirements:
145
145
  - - "~>"
146
146
  - !ruby/object:Gem::Version
147
- version: '1.13'
147
+ version: '2.0'
148
148
  type: :development
149
149
  prerelease: false
150
150
  version_requirements: !ruby/object:Gem::Requirement
151
151
  requirements:
152
152
  - - "~>"
153
153
  - !ruby/object:Gem::Version
154
- version: '1.13'
154
+ version: '2.0'
155
155
  - !ruby/object:Gem::Dependency
156
156
  name: rake
157
157
  requirement: !ruby/object:Gem::Requirement
@@ -256,6 +256,7 @@ files:
256
256
  - lib/fhir_client/patch_format.rb
257
257
  - lib/fhir_client/resource_address.rb
258
258
  - lib/fhir_client/resource_format.rb
259
+ - lib/fhir_client/return_preferences.rb
259
260
  - lib/fhir_client/sections/crud.rb
260
261
  - lib/fhir_client/sections/feed.rb
261
262
  - lib/fhir_client/sections/history.rb