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 =
'4.0.0'

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.



245
246
247
# File 'lib/fhir_client/client.rb', line 245

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:



251
252
253
254
255
256
# File 'lib/fhir_client/client.rb', line 251

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



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/fhir_client/client.rb', line 94

def detect_version
  cap = capability_statement
  if cap.is_a?(FHIR::CapabilityStatement)
    use_r4
  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



311
312
313
# File 'lib/fhir_client/client.rb', line 311

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



307
308
309
# File 'lib/fhir_client/client.rb', line 307

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>



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/fhir_client/client.rb', line 195

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



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/fhir_client/client.rb', line 221

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



315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/fhir_client/client.rb', line 315

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 = nil
  begin
    res = if(@fhir_version == :dstu2 || klass&.ancestors&.include?(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 == :r4 || klass&.ancestors&.include?(FHIR::Model))
            if(format.include?('xml'))
              FHIR::Xml.from_xml(response.body)
            else
              FHIR::Json.from_json(response.body)
            end
          else
            if(format.include?('xml'))
              FHIR::STU3::Xml.from_xml(response.body)
            else
              FHIR::STU3::Json.from_json(response.body)
            end
          end
    res.client = self unless res.nil?
    FHIR.logger.warn "Expected #{klass} but got #{res.class}" if res.class != klass
  rescue => e
    FHIR.logger.error "Failed to parse #{format} as resource #{klass}: #{e.message}"
    res = nil
  end
  res
end

#reissue_request(request) ⇒ Object



352
353
354
355
356
357
358
359
360
361
# File 'lib/fhir_client/client.rb', line 352

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



303
304
305
# File 'lib/fhir_client/client.rb', line 303

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



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

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



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

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_no_authObject

Set the client to use no authentication mechanisms



112
113
114
115
116
117
118
119
120
# File 'lib/fhir_client/client.rb', line 112

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



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/fhir_client/client.rb', line 152

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



348
349
350
# File 'lib/fhir_client/client.rb', line 348

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

#try_conformance_formats(default_format) ⇒ Object



258
259
260
261
262
263
264
265
266
267
268
269
270
271
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
# File 'lib/fhir_client/client.rb', line 258

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')
      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
    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



82
83
84
85
# File 'lib/fhir_client/client.rb', line 82

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_representation_preferenceObject

Instructs the client to specify the representation Prefer Header where applicable



89
90
91
92
# File 'lib/fhir_client/client.rb', line 89

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