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