fhir_client 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +38 -0
- data/.travis.yml +10 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +98 -0
- data/LICENSE +201 -0
- data/README.md +135 -0
- data/Rakefile +25 -0
- data/fhir_client.gemspec +21 -0
- data/lib/client_interface.rb +525 -0
- data/lib/feed_format.rb +10 -0
- data/lib/fhir_api_validation.json +360 -0
- data/lib/fhir_client.rb +41 -0
- data/lib/model/bundle.rb +32 -0
- data/lib/model/client_reply.rb +172 -0
- data/lib/model/tag.rb +57 -0
- data/lib/patch_format.rb +10 -0
- data/lib/resource_address.rb +133 -0
- data/lib/resource_format.rb +10 -0
- data/lib/sections/crud.rb +170 -0
- data/lib/sections/feed.rb +23 -0
- data/lib/sections/history.rb +78 -0
- data/lib/sections/operations.rb +118 -0
- data/lib/sections/search.rb +53 -0
- data/lib/sections/tags.rb +64 -0
- data/lib/sections/transactions.rb +83 -0
- data/lib/sections/validate.rb +49 -0
- data/lib/tasks/tasks.rake +73 -0
- data/test/fixtures/bundle-example.xml +79 -0
- data/test/fixtures/parameters-example.json +18 -0
- data/test/fixtures/parameters-example.xml +17 -0
- data/test/simplecov.rb +17 -0
- data/test/test_helper.rb +8 -0
- data/test/unit/basic_test.rb +17 -0
- data/test/unit/bundle_test.rb +21 -0
- data/test/unit/parameters_test.rb +44 -0
- metadata +177 -0
@@ -0,0 +1,118 @@
|
|
1
|
+
module FHIR
|
2
|
+
module Sections
|
3
|
+
module Operations
|
4
|
+
|
5
|
+
# DSTU 2 Operations: http://hl7.org/implement/standards/FHIR-Develop/operations.html
|
6
|
+
|
7
|
+
# Concept Translation [base]/ConceptMap/$translate | [base]/ConceptMap/[id]/$translate
|
8
|
+
|
9
|
+
# Closure Table Maintenance [base]/$closure
|
10
|
+
|
11
|
+
# Fetch Patient Record [base]/Patient/$everything | [base]/Patient/[id]/$everything
|
12
|
+
# http://hl7.org/implement/standards/FHIR-Develop/patient-operations.html#everything
|
13
|
+
# Fetches resources for a given patient record, scoped by a start and end time, and returns a Bundle of results
|
14
|
+
def fetch_patient_record(id=nil, startTime=nil, endTime=nil, method='GET', format=@default_format)
|
15
|
+
fetch_record(id,startTime,endTime,method,FHIR::Patient,format)
|
16
|
+
end
|
17
|
+
|
18
|
+
def fetch_encounter_record(id=nil, method='GET', format=@default_format)
|
19
|
+
fetch_record(id,nil,nil,method,FHIR::Encounter,format)
|
20
|
+
end
|
21
|
+
|
22
|
+
def fetch_record(id=nil, startTime=nil, endTime=nil, method='GET', klass=FHIR::Patient, format=@default_format)
|
23
|
+
options = { resource: klass, format: format, operation: { name: :fetch_patient_record, method: method } }
|
24
|
+
options.deep_merge!({id: id}) if !id.nil?
|
25
|
+
options[:operation][:parameters] = {} if options[:operation][:parameters].nil?
|
26
|
+
options[:operation][:parameters].merge!({start: { type: 'Date', value: startTime}}) if !startTime.nil?
|
27
|
+
options[:operation][:parameters].merge!({end: { type: 'Date', value:endTime}}) if !endTime.nil?
|
28
|
+
|
29
|
+
if options[:operation][:method]=='GET'
|
30
|
+
reply = get resource_url(options), fhir_headers(options)
|
31
|
+
else
|
32
|
+
# create Parameters body
|
33
|
+
body = nil
|
34
|
+
if(options[:operation] && options[:operation][:parameters])
|
35
|
+
p = FHIR::Parameters.new
|
36
|
+
options[:operation][:parameters].each do |key,value|
|
37
|
+
parameter = FHIR::Parameters::Parameter.new.from_hash(name: key.to_s)
|
38
|
+
parameter.method("value#{value[:type]}=").call(value[:value])
|
39
|
+
p.parameter << parameter
|
40
|
+
end
|
41
|
+
body = p.to_xml
|
42
|
+
end
|
43
|
+
reply = post resource_url(options), p, fhir_headers(options)
|
44
|
+
end
|
45
|
+
|
46
|
+
reply.resource = parse_reply(FHIR::Bundle, format, reply)
|
47
|
+
reply.resource_class = options[:resource]
|
48
|
+
reply
|
49
|
+
end
|
50
|
+
|
51
|
+
# Build Questionnaire [base]/Profile/$questionnaire | [base]/Profile/[id]/$questionnaire
|
52
|
+
|
53
|
+
# Populate Questionnaire [base]/Questionnaire/$populate | [base]/Questionnaire/[id]/$populate
|
54
|
+
|
55
|
+
# Value Set Expansion [base]/ValueSet/$expand | [base]/ValueSet/[id]/$expand
|
56
|
+
# http://hl7.org/implement/standards/FHIR-Develop/valueset-operations.html#expand
|
57
|
+
# The definition of a value set is used to create a simple collection of codes suitable for use for data entry or validation.
|
58
|
+
def value_set_expansion(params={}, format=@default_format)
|
59
|
+
options = { resource: FHIR::ValueSet, operation: { name: :value_set_expansion } }
|
60
|
+
options.deep_merge!(params)
|
61
|
+
terminology_operation(options, format)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Value Set based Validation [base]/ValueSet/$validate | [base]/ValueSet/[id]/$validate
|
65
|
+
# http://hl7.org/implement/standards/FHIR-Develop/valueset-operations.html#validate
|
66
|
+
# Validate that a coded value is in the set of codes allowed by a value set.
|
67
|
+
def value_set_code_validation(params={}, format=@default_format)
|
68
|
+
options = { resource: FHIR::ValueSet, operation: { name: :value_set_based_validation } }
|
69
|
+
options.deep_merge!(params)
|
70
|
+
terminology_operation(options, format)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Concept Look Up [base]/CodeSystem/$lookup
|
74
|
+
def code_system_lookup(params={}, format=@default_format)
|
75
|
+
options = { resource: FHIR::CodeSystem, operation: { name: :code_system_lookup } }
|
76
|
+
options.deep_merge!(params)
|
77
|
+
terminology_operation(options, format)
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
def concept_map_translate(params={}, format=@default_format)
|
82
|
+
options = { resource: FHIR::ConceptMap, operation: { name: :concept_map_translate } }
|
83
|
+
options.deep_merge!(params)
|
84
|
+
terminology_operation(options, format)
|
85
|
+
end
|
86
|
+
|
87
|
+
def terminology_operation(params={}, format=@default_format)
|
88
|
+
options = { format: format }
|
89
|
+
# params = [id, code, system, version, display, coding, codeableConcept, date, abstract]
|
90
|
+
options.deep_merge!(params)
|
91
|
+
|
92
|
+
if options[:operation][:method]=='GET'
|
93
|
+
reply = get resource_url(options), fhir_headers(options)
|
94
|
+
else
|
95
|
+
# create Parameters body
|
96
|
+
body = nil
|
97
|
+
if(options[:operation] && options[:operation][:parameters])
|
98
|
+
p = FHIR::Parameters.new
|
99
|
+
options[:operation][:parameters].each do |key,value|
|
100
|
+
parameter = FHIR::Parameters::Parameter.new.from_hash(name: key.to_s)
|
101
|
+
parameter.method("value#{value[:type]}=").call(value[:value])
|
102
|
+
p.parameter << parameter
|
103
|
+
end
|
104
|
+
body = p.to_xml
|
105
|
+
end
|
106
|
+
reply = post resource_url(options), p, fhir_headers(options)
|
107
|
+
end
|
108
|
+
|
109
|
+
reply.resource = parse_reply(options[:resource], format, reply)
|
110
|
+
reply.resource_class = options[:resource]
|
111
|
+
reply
|
112
|
+
end
|
113
|
+
|
114
|
+
# Batch Mode Validation [base]/ValueSet/$batch
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module FHIR
|
2
|
+
module Sections
|
3
|
+
module Search
|
4
|
+
|
5
|
+
#
|
6
|
+
# Search a set of resources of a given type.
|
7
|
+
#
|
8
|
+
# @param klass The type of resource to be searched.
|
9
|
+
# @param options A hash of options used to construct the search query.
|
10
|
+
# @return FHIR::ClientReply
|
11
|
+
#
|
12
|
+
def search(klass, options={}, format=@default_format_bundle)
|
13
|
+
options.merge!({ resource: klass, format: format })
|
14
|
+
|
15
|
+
if options[:search] && options[:search][:flag]
|
16
|
+
reply = post resource_url(options), nil, fhir_headers(options)
|
17
|
+
else
|
18
|
+
reply = get resource_url(options), fhir_headers(options)
|
19
|
+
end
|
20
|
+
# reply = get resource_url(options), fhir_headers(options)
|
21
|
+
reply.resource = parse_reply(klass, format, reply)
|
22
|
+
reply.resource_class = klass
|
23
|
+
reply
|
24
|
+
end
|
25
|
+
|
26
|
+
def search_existing(klass, id, options={}, format=@default_format_bundle)
|
27
|
+
options.merge!({ resource: klass, id: id, format: format })
|
28
|
+
#if options[:search][:flag]
|
29
|
+
if options[:search] && options[:search][:flag]
|
30
|
+
reply = post resource_url(options), nil, fhir_headers(options)
|
31
|
+
else
|
32
|
+
reply = get resource_url(options), fhir_headers(options)
|
33
|
+
end
|
34
|
+
reply.resource = parse_reply(klass, format, reply)
|
35
|
+
reply.resource_class = klass
|
36
|
+
reply
|
37
|
+
end
|
38
|
+
|
39
|
+
def search_all(options={}, format=@default_format_bundle)
|
40
|
+
options.merge!({ format: format })
|
41
|
+
if options[:search] && options[:search][:flag]
|
42
|
+
reply = post resource_url(options), nil, fhir_headers(options)
|
43
|
+
else
|
44
|
+
reply = get resource_url(options), fhir_headers(options)
|
45
|
+
end
|
46
|
+
reply.resource = parse_reply(nil, format, reply)
|
47
|
+
reply.resource_class = nil
|
48
|
+
reply
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module FHIR
|
2
|
+
module Sections
|
3
|
+
module Tags
|
4
|
+
#
|
5
|
+
# Get a list of all tags on server
|
6
|
+
#
|
7
|
+
# GET [base]/_tags
|
8
|
+
#
|
9
|
+
# public List<AtomCategory> getAllTags();
|
10
|
+
|
11
|
+
#
|
12
|
+
# Get a list of all tags used for the nominated resource type
|
13
|
+
#
|
14
|
+
# GET [base]/[type]/_tags
|
15
|
+
#
|
16
|
+
# public <T extends Resource> List<AtomCategory> getAllTagsForResourceType(Class<T> resourceClass);
|
17
|
+
|
18
|
+
#
|
19
|
+
# Get a list of all tags affixed to the nominated resource. This duplicates the HTTP header entries
|
20
|
+
#
|
21
|
+
# GET [base]/[type]/[id]/_tags
|
22
|
+
#
|
23
|
+
# public <T extends Resource> List<AtomCategory> getTagsForResource(Class<T> resource, String id);
|
24
|
+
|
25
|
+
#
|
26
|
+
# Get a list of all tags affixed to the nominated version of the resource. This duplicates the HTTP header entries
|
27
|
+
#
|
28
|
+
# GET [base]/[type]/[id]/_history/[vid]/_tags
|
29
|
+
#
|
30
|
+
# public <T extends Resource> List<AtomCategory> getTagsForResourceVersion(Class<T> resource, String id, String versionId);
|
31
|
+
|
32
|
+
#
|
33
|
+
# Remove all tags in the provided list from the list of tags for the nominated resource
|
34
|
+
#
|
35
|
+
# DELETE [base]/[type]/[id]/_tags
|
36
|
+
#
|
37
|
+
# //public <T extends Resource> boolean deleteTagsForResource(Class<T> resourceClass, String id);
|
38
|
+
|
39
|
+
#
|
40
|
+
# Remove tags in the provided list from the list of tags for the nominated version of the resource
|
41
|
+
#
|
42
|
+
# DELETE [base]/[type]/[id]/_history/[vid]/_tags
|
43
|
+
#
|
44
|
+
# public <T extends Resource> List<AtomCategory> deleteTags(List<AtomCategory> tags, Class<T> resourceClass, String id, String version);
|
45
|
+
|
46
|
+
#
|
47
|
+
# Affix tags in the list to the nominated resource
|
48
|
+
#
|
49
|
+
# POST [base]/[type]/[id]/_tags
|
50
|
+
# @return
|
51
|
+
#
|
52
|
+
# public <T extends Resource> List<AtomCategory> createTags(List<AtomCategory> tags, Class<T> resourceClass, String id);
|
53
|
+
|
54
|
+
#
|
55
|
+
# Affix tags in the list to the nominated version of the resource
|
56
|
+
#
|
57
|
+
# POST [base]/[type]/[id]/_history/[vid]/_tags
|
58
|
+
#
|
59
|
+
# @return
|
60
|
+
#
|
61
|
+
# public <T extends Resource> List<AtomCategory> createTags(List<AtomCategory> tags, Class<T> resourceClass, String id, String version);
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module FHIR
|
2
|
+
module Sections
|
3
|
+
module Transactions
|
4
|
+
|
5
|
+
attr_accessor :transaction_bundle
|
6
|
+
|
7
|
+
def begin_transaction
|
8
|
+
@transaction_bundle = FHIR::Bundle.new
|
9
|
+
@transaction_bundle.type = 'transaction'
|
10
|
+
@transaction_bundle.entry ||= []
|
11
|
+
@transaction_bundle
|
12
|
+
end
|
13
|
+
|
14
|
+
def begin_batch
|
15
|
+
@transaction_bundle = FHIR::Bundle.new
|
16
|
+
@transaction_bundle.type = 'batch'
|
17
|
+
@transaction_bundle.entry ||= []
|
18
|
+
@transaction_bundle
|
19
|
+
end
|
20
|
+
|
21
|
+
# syntactic sugar for add_batch_request
|
22
|
+
# @param method one of ['GET','POST','PUT','DELETE']
|
23
|
+
# @param url relative path, such as 'Patient/123'. Do not include the [base]
|
24
|
+
# @param resource The resource if a POST or PUT
|
25
|
+
def add_transaction_request(method, url, resource=nil, if_none_exist=nil)
|
26
|
+
add_batch_request(method, url, resource, if_none_exist)
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_batch_request(method, url, resource=nil, if_none_exist=nil)
|
30
|
+
request = FHIR::Bundle::Entry::Request.new
|
31
|
+
if FHIR::Bundle::Entry::Request::METADATA['method']['valid_codes'].values.first.include?(method.upcase)
|
32
|
+
request.local_method = method.upcase
|
33
|
+
else
|
34
|
+
request.local_method = 'POST'
|
35
|
+
end
|
36
|
+
request.ifNoneExist = if_none_exist if !if_none_exist.nil?
|
37
|
+
if url.nil? && !resource.nil?
|
38
|
+
options = Hash.new
|
39
|
+
options[:resource] = resource.class
|
40
|
+
options[:id] = resource.id if request.local_method != 'POST'
|
41
|
+
request.url = resource_url(options)
|
42
|
+
request.url = request.url[1..-1] if request.url.starts_with?('/')
|
43
|
+
else
|
44
|
+
request.url = url
|
45
|
+
end
|
46
|
+
|
47
|
+
entry = FHIR::Bundle::Entry.new
|
48
|
+
entry.resource = resource
|
49
|
+
entry.request = request
|
50
|
+
|
51
|
+
@transaction_bundle.entry << entry
|
52
|
+
entry
|
53
|
+
end
|
54
|
+
|
55
|
+
# syntactic sugar for end_batch
|
56
|
+
def end_transaction(format=@default_format)
|
57
|
+
end_batch(format)
|
58
|
+
end
|
59
|
+
|
60
|
+
# submit the batch/transaction to the server
|
61
|
+
# @param format
|
62
|
+
# @return FHIR::ClientReply
|
63
|
+
#
|
64
|
+
def end_batch(format=@default_format)
|
65
|
+
options = { format: format, 'Prefer' => 'return=representation' }
|
66
|
+
reply = post resource_url(options), @transaction_bundle, fhir_headers(options)
|
67
|
+
begin
|
68
|
+
if(format.downcase.include?('xml'))
|
69
|
+
reply.resource = FHIR::Xml.from_xml(reply.body)
|
70
|
+
else
|
71
|
+
reply.resource = FHIR::Json.from_json(reply.body)
|
72
|
+
end
|
73
|
+
rescue Exception => e
|
74
|
+
reply.resource = nil
|
75
|
+
end
|
76
|
+
reply.resource_class = reply.resource.class
|
77
|
+
reply
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module FHIR
|
2
|
+
module Sections
|
3
|
+
module Validate
|
4
|
+
#
|
5
|
+
# Validate resource payload.
|
6
|
+
#
|
7
|
+
# @param resourceClass
|
8
|
+
# @param resource
|
9
|
+
# @param id
|
10
|
+
# @return
|
11
|
+
#
|
12
|
+
# public <T extends Resource> AtomEntry<OperationOutcome> validate(Class<T> resourceClass, T resource, String id);
|
13
|
+
def validate(resource, options={}, format=@default_format)
|
14
|
+
options.merge!({ resource: resource.class, validate: true, format: format })
|
15
|
+
params = FHIR::Parameters.new
|
16
|
+
add_resource_parameter(params,'resource',resource)
|
17
|
+
add_parameter(params,'profile','Uri',options[:profile_uri]) if !options[:profile_uri].nil?
|
18
|
+
post resource_url(options), params, fhir_headers(options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def validate_existing(resource, id, options={}, format=@default_format)
|
22
|
+
options.merge!({ resource: resource.class, id: id, validate: true, format: format })
|
23
|
+
params = FHIR::Parameters.new
|
24
|
+
add_resource_parameter(params,'resource',resource)
|
25
|
+
add_parameter(params,'profile','Uri',options[:profile_uri]) if !options[:profile_uri].nil?
|
26
|
+
post resource_url(options), params, fhir_headers(options)
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
private
|
31
|
+
def add_parameter(params,name,type,value)
|
32
|
+
params.parameter ||= []
|
33
|
+
parameter = FHIR::Parameters::Parameter.new.from_hash(name: name)
|
34
|
+
parameter.method("value#{type}=").call(value)
|
35
|
+
params.parameter << parameter
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_resource_parameter(params, name,resource)
|
39
|
+
params.parameter ||= []
|
40
|
+
parameter = FHIR::Parameters::Parameter.new.from_hash(name: name)
|
41
|
+
parameter.resource = resource
|
42
|
+
params.parameter << parameter
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
namespace :fhir do
|
2
|
+
|
3
|
+
desc 'console'
|
4
|
+
task :console, [] do |t, args|
|
5
|
+
binding.pry
|
6
|
+
end
|
7
|
+
|
8
|
+
#
|
9
|
+
# Prerequisites & Assumptions:
|
10
|
+
#
|
11
|
+
# 1. Running SMART-on-FHIR (DSTU2 branch) server [http://example/fhir]
|
12
|
+
# 2. Running OpenID Connect server [http://example:8080/openid-connect-server-webapp]
|
13
|
+
# 3. Configured client under OpenID Connect server:
|
14
|
+
# a. Create a system scope if necessary. SMART-on-FHIR requires 'fhir_complete' scope.
|
15
|
+
# b. Add the scope created in (a) to client.
|
16
|
+
# c. Add the FHIR server URL to the list of allowed redirect URIs (required?)
|
17
|
+
# d. Ensure client has 'client credentials' grant type
|
18
|
+
# d. Whitelist the client
|
19
|
+
# 4. 'client_id' and 'client_secret' variables are the name and secret of the client created in (3)
|
20
|
+
# 5. :authorize_url is the authorization endpoint of the OpenID connect server
|
21
|
+
# 6. :token_url is the token endpoint of the OpenID connect server
|
22
|
+
#
|
23
|
+
desc 'OAuth2 Example'
|
24
|
+
task :oauth2, [:url,:client_id,:client_secret] do |t, args|
|
25
|
+
client = FHIR::Client.new(args.url)
|
26
|
+
client_id = args.client_id
|
27
|
+
client_secret = args.client_secret
|
28
|
+
options = client.get_oauth2_metadata_from_conformance
|
29
|
+
if options.empty?
|
30
|
+
puts 'This server does not support the expected OAuth2 extensions.'
|
31
|
+
else
|
32
|
+
client.set_oauth2_auth(client_id,client_secret,options[:authorize_url],options[:token_url])
|
33
|
+
reply = client.read_feed(FHIR::Patient)
|
34
|
+
puts reply.body
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
desc 'count all resources for a given server'
|
39
|
+
task :count, [:url] do |t, args|
|
40
|
+
client = FHIR::Client.new(args.url)
|
41
|
+
counts = {}
|
42
|
+
fhir_resources.map do | klass |
|
43
|
+
reply = client.read_feed(klass)
|
44
|
+
counts["#{klass.name.demodulize}"] = reply.resource.total unless reply.resource.nil?
|
45
|
+
end
|
46
|
+
printf " %-30s %5s\n", 'Resource', 'Count'
|
47
|
+
printf " %-30s %5s\n", '--------', '-----'
|
48
|
+
counts.each do |key,value|
|
49
|
+
# puts "#{key} #{value}"
|
50
|
+
printf " %-30s %5s\n", key, value
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
desc 'delete all resources for a given server'
|
55
|
+
task :clean, [:url] do |t, args|
|
56
|
+
client = FHIR::Client.new(args.url)
|
57
|
+
fhir_resources.map do | klass |
|
58
|
+
reply = client.read_feed(klass)
|
59
|
+
while !reply.nil? && !reply.resource.nil? && reply.resource.total > 0
|
60
|
+
reply.resource.entry.each do |entry|
|
61
|
+
client.destroy(klass,entry.resource.id) unless entry.resource.nil?
|
62
|
+
end
|
63
|
+
reply = client.read_feed(klass)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
Rake::Task['fhir:count'].invoke(args.url)
|
67
|
+
end
|
68
|
+
|
69
|
+
def fhir_resources
|
70
|
+
Mongoid.models.select {|c| c.name.include?('FHIR') && !c.included_modules.find_index(FHIR::Resource).nil?}
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|