Class: FHIR::Client

Inherits:
Object
  • Object
show all
Includes:
Sections::Crud, Sections::Feed, Sections::History, Sections::Operations, Sections::Search, Sections::Tags, Sections::Transactions, VersionManagement
Defined in:
lib/fhir_client/client.rb,
lib/fhir_client/version.rb

Constant Summary collapse

VERSION =
'6.0.0'.freeze

Constants included from Sections::Feed

Sections::Feed::BACKWARD, Sections::Feed::FIRST, Sections::Feed::FORWARD, Sections::Feed::LAST

Instance Attribute Summary collapse

Attributes included from Sections::Transactions

#transaction_bundle

Instance Method Summary collapse

Methods included from VersionManagement

#versioned_format_class, #versioned_resource_class

Methods included from Sections::Transactions

#add_batch_request, #add_transaction_request, #begin_batch, #begin_transaction, #end_batch, #end_transaction

Methods included from Sections::Operations

#closure_table_maintenance, #code_system_lookup, #concept_map_translate, #fetch_encounter_record, #fetch_patient_record, #fetch_record, #match, #terminology_operation, #validate, #validate_existing, #value_set_code_validation, #value_set_expansion

Methods included from Sections::Search

#search, #search_all, #search_existing

Methods included from Sections::Feed

#next_page

Methods included from Sections::Crud

#base_create, #base_update, #conditional_create, #conditional_read_since, #conditional_read_version, #conditional_update, #create, #destroy, #partial_update, #raw_read, #raw_read_url, #read, #read_feed, #update, #version_aware_update, #vread

Methods included from Sections::History

#all_history, #all_history_as_of, #history, #resource_history, #resource_history_as_of, #resource_instance_history, #resource_instance_history_as_of

Constructor Details

#initialize(base_service_url, default_format: FHIR::Formats::ResourceFormat::RESOURCE_JSON, proxy: nil) ⇒ Object

Call method to initialize FHIR client. This method must be invoked with a valid base server URL prior to using the client.

Parameters:

  • base_service_url

    Base service URL for FHIR Service.

  • default_format (defaults to: FHIR::Formats::ResourceFormat::RESOURCE_JSON)

    Default Format Mime type



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/fhir_client/client.rb', line 41

def initialize(base_service_url, default_format: FHIR::Formats::ResourceFormat::RESOURCE_JSON, proxy: nil)
  @base_service_url = base_service_url
  FHIR.logger.info "Initializing client with #{@base_service_url}"
  @use_format_param = false
  @use_accept_header = true
  @use_accept_charset = true
  @default_format = default_format
  @fhir_version = :r4
  @use_return_preference = false
  @return_preference = FHIR::Formats::ReturnPreferences::REPRESENTATION
  @exception_class = ClientException
  @proxy = proxy

  set_no_auth
end

Instance Attribute Details

#additional_headersObject

Returns the value of attribute additional_headers.



26
27
28
# File 'lib/fhir_client/client.rb', line 26

def additional_headers
  @additional_headers
end

#cached_capability_statementObject

Returns the value of attribute cached_capability_statement.



25
26
27
# File 'lib/fhir_client/client.rb', line 25

def cached_capability_statement
  @cached_capability_statement
end

#clientObject

Returns the value of attribute client.



21
22
23
# File 'lib/fhir_client/client.rb', line 21

def client
  @client
end

#default_formatObject

Returns the value of attribute default_format.



23
24
25
# File 'lib/fhir_client/client.rb', line 23

def default_format
  @default_format
end

#exception_classObject

Returns the value of attribute exception_class.



28
29
30
# File 'lib/fhir_client/client.rb', line 28

def exception_class
  @exception_class
end

#fhir_versionObject

Returns the value of attribute fhir_version.



24
25
26
# File 'lib/fhir_client/client.rb', line 24

def fhir_version
  @fhir_version
end

#proxyObject

Returns the value of attribute proxy.



27
28
29
# File 'lib/fhir_client/client.rb', line 27

def proxy
  @proxy
end

#replyObject

Returns the value of attribute reply.



16
17
18
# File 'lib/fhir_client/client.rb', line 16

def reply
  @reply
end

#security_headersObject

Returns the value of attribute security_headers.



20
21
22
# File 'lib/fhir_client/client.rb', line 20

def security_headers
  @security_headers
end

#use_accept_charsetObject

Returns the value of attribute use_accept_charset.



31
32
33
# File 'lib/fhir_client/client.rb', line 31

def use_accept_charset
  @use_accept_charset
end

#use_accept_headerObject

Returns the value of attribute use_accept_header.



30
31
32
# File 'lib/fhir_client/client.rb', line 30

def use_accept_header
  @use_accept_header
end

#use_basic_authObject

Returns the value of attribute use_basic_auth.



18
19
20
# File 'lib/fhir_client/client.rb', line 18

def use_basic_auth
  @use_basic_auth
end

#use_format_paramObject

Returns the value of attribute use_format_param.



17
18
19
# File 'lib/fhir_client/client.rb', line 17

def use_format_param
  @use_format_param
end

#use_oauth2_authObject

Returns the value of attribute use_oauth2_auth.



19
20
21
# File 'lib/fhir_client/client.rb', line 19

def use_oauth2_auth
  @use_oauth2_auth
end

#use_return_preferenceObject

Returns the value of attribute use_return_preference.



32
33
34
# File 'lib/fhir_client/client.rb', line 32

def use_return_preference
  @use_return_preference
end

Instance Method Details

#capability_statement(format = @default_format) ⇒ Object

Method returns a capability statement for the system queried.



259
260
261
# File 'lib/fhir_client/client.rb', line 259

def capability_statement(format = @default_format)
  conformance_statement(format)
end

#conformance_statement(format = @default_format) ⇒ Object

Method returns a conformance statement for the system queried.

Returns:



265
266
267
268
269
270
# File 'lib/fhir_client/client.rb', line 265

def conformance_statement(format = @default_format)
  if @cached_capability_statement.nil? || format != @default_format
    try_conformance_formats(format)
  end
  @cached_capability_statement
end

#default_jsonObject



57
58
59
# File 'lib/fhir_client/client.rb', line 57

def default_json
  @default_format = versioned_format_class(:json)
end

#default_xmlObject



61
62
63
# File 'lib/fhir_client/client.rb', line 61

def default_xml
  @default_format = versioned_format_class(:xml)
end

#detect_versionObject



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/fhir_client/client.rb', line 104

def detect_version
  cap = capability_statement
  if cap.is_a?(FHIR::CapabilityStatement)
    use_r4
  elsif cap.is_a?(FHIR::R4B::CapabilityStatement)
    use_r4b
  elsif cap.is_a?(FHIR::R5::CapabilityStatement)
    use_r5
  elsif cap.is_a?(FHIR::STU3::CapabilityStatement)
    use_stu3
  elsif cap.is_a?(FHIR::DSTU2::Conformance)
    use_dstu2
  else
    use_r4
  end
  # Should update the default_format when changing fhir_version
  @default_format = versioned_format_class
  FHIR.logger.info("Detecting server FHIR version as #{@fhir_version} via metadata")
  @fhir_version
end

#fhir_headers(options = {}) ⇒ Object



341
342
343
# File 'lib/fhir_client/client.rb', line 341

def fhir_headers(options = {})
  FHIR::ResourceAddress.fhir_headers(options, additional_headers, @default_format, @use_accept_header, @use_accept_charset)
end

#full_resource_url(options) ⇒ Object



337
338
339
# File 'lib/fhir_client/client.rb', line 337

def full_resource_url(options)
  @base_service_url + resource_url(options)
end

#get_oauth2_metadata_from_conformance(strict = true) ⇒ Object

Get the OAuth2 server and endpoints from the capability statement (the server should not require OAuth2 or other special security to access the capability statement). <rest>

<mode value="server"/>
<documentation value="All the functionality defined in FHIR"/>
<security>
<extension url="http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris">
  <extension url="register">
    <valueUri value="https://authorize-dstu2.smarthealthit.org/register"/>
  </extension>
  <extension url="authorize">
    <valueUri value="https://authorize-dstu2.smarthealthit.org/authorize"/>
  </extension>
  <extension url="token">
    <valueUri value="https://authorize-dstu2.smarthealthit.org/token"/>
  </extension>
</extension>
<service>
  <coding>
    <system value="http://hl7.org/fhir/vs/restful-security-service"/>
    <code value="OAuth2"/>
  </coding>
  <text value="OAuth version 2 (see oauth.net)."/>
</service>
<description value="SMART on FHIR uses OAuth2 for authorization"/>

</security>



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/fhir_client/client.rb', line 209

def (strict=true)
  options = {
    authorize_url: nil,
    token_url: nil
  }
  begin
    capability_statement.rest.each do |rest|
      if strict
        rest.security.service.each do |service|
          service.coding.each do |coding|
            next unless coding.code == 'SMART-on-FHIR'
             options.merge! (rest)
          end
        end
      else
        options.merge! (rest)
      end
    end
  rescue => e
    FHIR.logger.error "Failed to locate SMART-on-FHIR OAuth2 Security Extensions: #{e.message}"
  end
  options.delete_if { |_k, v| v.nil? }
  options.clear if options.keys.size != 2
  options
end

#get_oauth2_metadata_from_service_definition(rest) ⇒ Object



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/fhir_client/client.rb', line 235

def (rest)
  oauth_extension = 'http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris'
  authorize_extension = 'authorize'
  token_extension = 'token'
  options = {
    authorize_url: nil,
    token_url: nil
  }
  rest.security.extension.find{|x| x.url == oauth_extension}.extension.each do |ext|
    case ext.url
    when authorize_extension
      options[:authorize_url] = ext.value
    when "#{oauth_extension}\##{authorize_extension}"
      options[:authorize_url] = ext.value
    when token_extension
      options[:token_url] = ext.value
    when "#{oauth_extension}\##{token_extension}"
      options[:token_url] = ext.value
    end
  end
  options
end

#parse_reply(klass, format, response) ⇒ Object



345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/fhir_client/client.rb', line 345

def parse_reply(klass, format, response)
  FHIR.logger.debug "Parsing response with {klass: #{klass}, format: #{format}, code: #{response.code}}."
  return nil unless [200, 201].include? response.code
  res =
    begin
      if(@fhir_version == :dstu2 || klass < FHIR::DSTU2::Model)
        if(format.include?('xml'))
          FHIR::DSTU2::Xml.from_xml(response.body)
        else
          FHIR::DSTU2::Json.from_json(response.body)
        end
      elsif(@fhir_version == :stu3 || klass < FHIR::STU3::Model)
        if(format.include?('xml'))
          FHIR::STU3::Xml.from_xml(response.body)
        else
          FHIR::STU3::Json.from_json(response.body)
        end
      elsif(@fhir_version == :r4b || klass < FHIR::R4B::Model)
        if(format.include?('xml'))
          FHIR::R4B::Xml.from_xml(response.body)
        else
          FHIR::R4B::Json.from_json(response.body)
        end
      elsif(@fhir_version == :r5 || klass < FHIR::R5::Model)
        if(format.include?('xml'))
          FHIR::R5::Xml.from_xml(response.body)
        else
          FHIR::R5::Json.from_json(response.body)
        end
      else
        if(format.include?('xml'))
          FHIR::Xml.from_xml(response.body)
        else
          FHIR::Json.from_json(response.body)
        end
      end
    rescue => e
      FHIR.logger.error "Failed to parse #{format} as resource #{klass}: #{e.message}"
      nil
    end
  set_client_on_resource(res) unless res.nil?
  res
end

#reissue_request(request) ⇒ Object



404
405
406
407
408
409
410
411
412
413
# File 'lib/fhir_client/client.rb', line 404

def reissue_request(request)
  if [:get, :delete, :head].include?(request['method'])
    method(request['method']).call(request['url'], request['headers'])
  elsif [:post, :put].include?(request['method'])
    unless request['payload'].nil?
      resource = versioned_resource_class.from_contents(request['payload'])
    end
    method(request['method']).call(request['url'], resource, request['headers'])
  end
end

#resource_url(options) ⇒ Object



333
334
335
# File 'lib/fhir_client/client.rb', line 333

def resource_url(options)
  FHIR::ResourceAddress.resource_url(options, @use_format_param)
end

#set_basic_auth(client, secret) ⇒ Object

Set the client to use HTTP Basic Authentication



137
138
139
140
141
142
143
144
145
146
147
# File 'lib/fhir_client/client.rb', line 137

def set_basic_auth(client, secret)
  FHIR.logger.info 'Configuring the client to use HTTP Basic authentication.'
  token = Base64.encode64("#{client}:#{secret}")
  value = "Basic #{token}"
  @security_headers = { 'Authorization' => value }
  @use_oauth2_auth = false
  @use_basic_auth = true
  @client = RestClient
  @client.proxy = proxy unless proxy.nil?
  @client
end

#set_bearer_token(token) ⇒ Object

Set the client to use Bearer Token Authentication



150
151
152
153
154
155
156
157
158
159
# File 'lib/fhir_client/client.rb', line 150

def set_bearer_token(token)
  FHIR.logger.info 'Configuring the client to use Bearer Token authentication.'
  value = "Bearer #{token}"
  @security_headers = { 'Authorization' => value }
  @use_oauth2_auth = false
  @use_basic_auth = true
  @client = RestClient
  @client.proxy = proxy unless proxy.nil?
  @client
end

#set_client_on_resource(resource) ⇒ Object



389
390
391
392
393
394
395
396
397
398
# File 'lib/fhir_client/client.rb', line 389

def set_client_on_resource(resource)
  return if resource.nil?

  resource.client = self
  resource.each_element do |element, _, _|
    if element.is_a?(Reference) || element.is_a?(FHIR::R4B::Reference) || element.is_a?(FHIR::R5::Reference) || element.is_a?(STU3::Reference) || element.is_a?(DSTU2::Reference) || element.respond_to?(:resourceType)
      element.client = self
    end
  end
end

#set_no_authObject

Set the client to use no authentication mechanisms



126
127
128
129
130
131
132
133
134
# File 'lib/fhir_client/client.rb', line 126

def set_no_auth
  FHIR.logger.info 'Configuring the client to use no authentication.'
  @use_oauth2_auth = false
  @use_basic_auth = false
  @security_headers = {}
  @client = RestClient
  @client.proxy = proxy unless proxy.nil?
  @client
end

#set_oauth2_auth(client, secret, authorize_path, token_path, site = nil) ⇒ Object

Set the client to use OpenID Connect OAuth2 Authentication client – client id secret – client secret authorize_path – absolute path of authorization endpoint token_path – absolute path of token endpoint



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/fhir_client/client.rb', line 166

def set_oauth2_auth(client, secret, authorize_path, token_path, site = nil)
  FHIR.logger.info 'Configuring the client to use OpenID Connect OAuth2 authentication.'
  @use_oauth2_auth = true
  @use_basic_auth = false
  @security_headers = {}
  options = {
    site: site || @base_service_url,
    authorize_url: authorize_path,
    token_url: token_path,
    raise_errors: true
  }
  client = OAuth2::Client.new(client, secret, options)
  client.connection.proxy(proxy) unless proxy.nil?
  @client = client.client_credentials.get_token
end

#strip_base(path) ⇒ Object



400
401
402
# File 'lib/fhir_client/client.rb', line 400

def strip_base(path)
  path.gsub(@base_service_url, '')
end

#try_conformance_formats(default_format) ⇒ Object



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/fhir_client/client.rb', line 272

def try_conformance_formats(default_format)
  formats = [FHIR::Formats::ResourceFormat::RESOURCE_XML,
             FHIR::Formats::ResourceFormat::RESOURCE_JSON,
             FHIR::Formats::ResourceFormat::RESOURCE_XML_DSTU2,
             FHIR::Formats::ResourceFormat::RESOURCE_JSON_DSTU2,
             'application/xml',
             'application/json']
  formats.insert(0, default_format)

  @cached_capability_statement = nil

  formats.each do |frmt|
    reply = get 'metadata', fhir_headers({accept: "#{frmt}"})
    next unless reply.code == 200
    use_r4
    begin
      @cached_capability_statement = parse_reply(FHIR::CapabilityStatement, frmt, reply)
    rescue
      @cached_capability_statement = nil
    end
    if @cached_capability_statement.nil? || !@cached_capability_statement.fhirVersion.starts_with?('4.0')
      use_r4b
      begin
        @cached_capability_statement = parse_reply(FHIR::R4B::CapabilityStatement, frmt, reply)
      rescue
        @cached_capability_statement = nil
      end
      if @cached_capability_statement.nil? || !@cached_capability_statement.fhirVersion.starts_with?('4')
        use_r5
        begin
          @cached_capability_statement = parse_reply(FHIR::R5::CapabilityStatement, frmt, reply)
        rescue
          @cached_capability_statement = nil
        end
        if @cached_capability_statement.nil? || !@cached_capability_statement.fhirVersion.starts_with?('5')
          use_stu3
          begin
            @cached_capability_statement = parse_reply(FHIR::STU3::CapabilityStatement, frmt, reply)
          rescue
            @cached_capability_statement = nil
          end
          unless @cached_capability_statement
            use_dstu2
            begin
              @cached_capability_statement = parse_reply(FHIR::DSTU2::Conformance, frmt, reply)
            rescue
              @cached_capability_statement = nil
            end
          end
        end
      end
    end
    if @cached_capability_statement
      @default_format = frmt
      break
    end
  end
  @default_format = default_format if @default_format.nil?
  @default_format
end

#use_dstu2Object



70
71
72
73
# File 'lib/fhir_client/client.rb', line 70

def use_dstu2
  @fhir_version = :dstu2
  @default_format = versioned_format_class
end

#use_minimal_preferenceObject

Instructs the client to specify the minimal Prefer Header where applicable



92
93
94
95
# File 'lib/fhir_client/client.rb', line 92

def use_minimal_preference
  @use_return_preference = true
  @return_preference = FHIR::Formats::ReturnPreferences::MINIMAL
end

#use_r4Object



75
76
77
78
# File 'lib/fhir_client/client.rb', line 75

def use_r4
  @fhir_version = :r4
  @default_format = versioned_format_class
end

#use_r4bObject



80
81
82
83
# File 'lib/fhir_client/client.rb', line 80

def use_r4b
  @fhir_version = :r4b
  @default_format = versioned_format_class
end

#use_r5Object



85
86
87
88
# File 'lib/fhir_client/client.rb', line 85

def use_r5
  @fhir_version = :r5
  @default_format = versioned_format_class
end

#use_representation_preferenceObject

Instructs the client to specify the representation Prefer Header where applicable



99
100
101
102
# File 'lib/fhir_client/client.rb', line 99

def use_representation_preference
  @use_return_preference = true
  @return_preference = FHIR::Formats::ReturnPreferences::REPRESENTATION
end

#use_stu3Object



65
66
67
68
# File 'lib/fhir_client/client.rb', line 65

def use_stu3
  @fhir_version = :stu3
  @default_format = versioned_format_class
end