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.
data/lib/model/tag.rb ADDED
@@ -0,0 +1,57 @@
1
+ module FHIR
2
+ class Tag
3
+ # Each Tag is part of an HTTP header named "Category" with three parts: term, scheme, and label.
4
+ # Each Tag can be in an individual "Category" header, or they can all be concatentated (with comma
5
+ # separation) inside a single "Category" header.
6
+
7
+ # Term is a URI:
8
+ # General tags:
9
+ # Bundle / FHIR Documents: "http://hl7.org/fhir/tag/document"
10
+ # Bundle / FHIR Messages: "http://hl7.org/fhir/tag/message"
11
+ # Profile tags: URL that references a profile resource.
12
+ attr_accessor :term
13
+
14
+ # Scheme is a URI:
15
+ # "http://hl7.org/fhir/tag" A general tag
16
+ # "http://hl7.org/fhir/tag/profile" A profile tag - a claim that the Resource conforms to the profile identified in the term
17
+ # "http://hl7.org/fhir/tag/security" A security label
18
+ attr_accessor :scheme
19
+
20
+ # Label is an OPTIONAL human-readable label for the tag for use when displaying in end-user applications
21
+ attr_accessor :label
22
+
23
+ def to_header
24
+ s = "#{term}; scheme=#{scheme}"
25
+ s += "; label=#{label}" if !label.nil?
26
+ s
27
+ end
28
+
29
+ # Parses a string named "header" and returns a Tag object.
30
+ def self.parse_tag(header)
31
+ h = FHIR::Tag.new
32
+ regex = /\s*;\s*/
33
+ tokens = header.strip.split(regex)
34
+ h.term = tokens.shift
35
+ tokens.each do |token|
36
+ if !token.strip.index('scheme').nil?
37
+ token.strip =~ %r{(?<=scheme)(\s*)=(\s*)([\".:_\-\/\w]+)}
38
+ h.scheme = $3
39
+ elsif !token.strip.index('label').nil?
40
+ token.strip =~ %r{(?<=label)(\s*)=(\s*)([\".:_\-\/\w\s]+)}
41
+ h.label = $3
42
+ end
43
+ end
44
+ h
45
+ end
46
+
47
+ # Parses a string named "header" and returns an Array of Tag objects.
48
+ def self.parse_tags(header)
49
+ tags = []
50
+ regex = /\s*,\s*/
51
+ tokens = header.strip.split(regex)
52
+ tokens.each { |token| tags << FHIR::Tag.parse_tag(token) }
53
+ tags
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,10 @@
1
+ module FHIR
2
+ module Formats
3
+ class PatchFormat
4
+
5
+ PATCH_XML = "application/xml-patch+xml"
6
+ PATCH_JSON = "application/json-patch+json"
7
+
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,133 @@
1
+ module FHIR
2
+ class ResourceAddress
3
+
4
+ DEFAULTS = {
5
+ id: nil,
6
+ resource: nil,
7
+ format: 'application/xml+fhir',
8
+ }
9
+
10
+ DEFAULT_CHARSET = 'UTF-8'
11
+
12
+ def fhir_headers(options, use_format_param=false)
13
+ options = DEFAULTS.merge(options)
14
+
15
+ format = options[:format] || FHIR::Formats::ResourceFormat::RESOURCE_XML
16
+ fhir_headers = {
17
+ 'User-Agent' => 'Ruby FHIR Client',
18
+ 'Content-Type' => format + ';charset=' + DEFAULT_CHARSET,
19
+ 'Accept-Charset' => DEFAULT_CHARSET
20
+ }
21
+ # remove the content-type header if the format is 'xml' or 'json' because
22
+ # even those are valid _format parameter options, they are not valid MimeTypes.
23
+ fhir_headers.delete('Content-Type') if ['xml','json'].include?(format.downcase)
24
+
25
+ if(options[:category])
26
+ # options[:category] should be an Array of FHIR::Tag objects
27
+ tags = {
28
+ 'Category' => options[:category].collect { |h| h.to_header }.join(',')
29
+ }
30
+ fhir_headers.merge!(tags)
31
+ options.delete(:category)
32
+ end
33
+
34
+ if use_format_param
35
+ fhir_headers.delete('Accept')
36
+ options.delete('Accept')
37
+ options.delete(:accept)
38
+ else
39
+ fhir_headers['Accept'] = format
40
+ end
41
+
42
+ fhir_headers.merge!(options) unless options.empty?
43
+ fhir_headers[:operation] = options[:operation][:name] if options[:operation] && options[:operation][:name]
44
+ fhir_headers
45
+ end
46
+
47
+ def resource_url(options, use_format_param=false)
48
+ options = DEFAULTS.merge(options)
49
+
50
+ params = {}
51
+ url = ""
52
+ # handle requests for resources by class or string; useful for testing nonexistent resource types
53
+ url += "/#{ options[:resource].try(:name).try(:demodulize) || options[:resource].split("::").last }" if options[:resource]
54
+ url += "/#{options[:id]}" if options[:id]
55
+ url += "/$validate" if options[:validate]
56
+
57
+ if(options[:operation])
58
+ opr = options[:operation]
59
+ p = opr[:parameters]
60
+ p = p.each{|k,v|p[k]=v[:value]} if p
61
+ params.merge!(p) if p && opr[:method]=='GET'
62
+
63
+ if (opr[:name] == :fetch_patient_record)
64
+ url += "/$everything"
65
+ elsif (opr[:name] == :value_set_expansion)
66
+ url += "/$expand"
67
+ elsif (opr && opr[:name]== :value_set_based_validation)
68
+ url += "/$validate-code"
69
+ elsif (opr && opr[:name]== :code_system_lookup)
70
+ url += "/$lookup"
71
+ elsif (opr && opr[:name]== :concept_map_translate)
72
+ url += "/$translate"
73
+ end
74
+ end
75
+
76
+ if (options[:history])
77
+ history = options[:history]
78
+ url += "/_history/#{history[:id]}"
79
+ params[:_count] = history[:count] if history[:count]
80
+ params[:_since] = history[:since].iso8601 if history[:since]
81
+ end
82
+
83
+ if(options[:search])
84
+ search_options = options[:search]
85
+ url += '/_search' if search_options[:flag]
86
+ url += "/#{search_options[:compartment]}" if search_options[:compartment]
87
+
88
+ if search_options[:parameters]
89
+ search_options[:parameters].each do |key,value|
90
+ params[key.to_sym] = value
91
+ end
92
+ end
93
+ end
94
+
95
+ # options[:params] is simply appended at the end of a url and is used by testscripts
96
+ url += options[:params] if options[:params]
97
+
98
+ if(options[:summary])
99
+ params[:_summary] = options[:summary]
100
+ end
101
+
102
+ if use_format_param && options[:format]
103
+ params[:_format] = options[:format]
104
+ end
105
+
106
+ uri = Addressable::URI.parse(url)
107
+ # params passed in options takes precidence over params calculated in this method
108
+ # for use by testscript primarily
109
+ uri.query_values = params unless options[:params] && options[:params].include?("?")
110
+ uri.normalize.to_str
111
+ end
112
+
113
+ # Get the resource ID out of the URL (e.g. Bundle.entry.response.location)
114
+ def self.pull_out_id(resourceType,url)
115
+ id = nil
116
+ if !resourceType.nil? && !url.nil?
117
+ token = "#{resourceType}/"
118
+ start = url.index(token) + token.length
119
+ t = url[start..-1]
120
+ stop = (t.index("/") || 0)-1
121
+ stop = -1 if stop.nil?
122
+ id = t[0..stop]
123
+ end
124
+ id
125
+ end
126
+
127
+ def self.append_forward_slash_to_path(path)
128
+ path += '/' unless path.last == '/'
129
+ path
130
+ end
131
+
132
+ end
133
+ end
@@ -0,0 +1,10 @@
1
+ module FHIR
2
+ module Formats
3
+ class ResourceFormat
4
+
5
+ RESOURCE_XML = "application/xml+fhir"
6
+ RESOURCE_JSON = "application/json+fhir"
7
+
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,170 @@
1
+ module FHIR
2
+ module Sections
3
+ module Crud
4
+
5
+ #
6
+ # Read the current state of a resource.
7
+ #
8
+ def read(klass, id, format=@default_format, summary=nil, options = {})
9
+ options = { resource: klass, id: id, format: format }.merge(options)
10
+ options[:summary] = summary if summary
11
+ reply = get resource_url(options), fhir_headers(options)
12
+ reply.resource = parse_reply(klass, format, reply)
13
+ reply.resource_class = klass
14
+ reply
15
+ end
16
+
17
+ #
18
+ # Read a resource bundle (an XML ATOM feed)
19
+ #
20
+ def read_feed(klass, format=@default_format_bundle)
21
+ options = { resource: klass, format: format }
22
+ reply = get resource_url(options), fhir_headers(options)
23
+ reply.resource = parse_reply(klass, format, reply)
24
+ reply.resource_class = klass
25
+ reply
26
+ end
27
+
28
+ #
29
+ # Read the state of a specific version of the resource
30
+ #
31
+ def vread(klass, id, version_id, format=@default_format)
32
+ options = { resource: klass, id: id, format: format, history: {id: version_id} }
33
+ reply = get resource_url(options), fhir_headers(options)
34
+ reply.resource = parse_reply(klass, format, reply)
35
+ reply.resource_class = klass
36
+ reply
37
+ end
38
+
39
+ def raw_read(options)
40
+ reply = get resource_url(options), fhir_headers(options)
41
+ reply.body
42
+ end
43
+
44
+ def raw_read_url(url)
45
+ reply = get url, fhir_headers({})
46
+ reply.body
47
+ end
48
+
49
+ #
50
+ # Update an existing resource by its id or create it if it is a new resource, not present on the server
51
+ #
52
+ def update(resource, id, format=@default_format)
53
+ base_update(resource, id, nil, format)
54
+ end
55
+
56
+ #
57
+ # Update an existing resource by its id or create it if it is a new resource, not present on the server
58
+ #
59
+ def conditional_update(resource, id, searchParams, format=@default_format)
60
+ options = {
61
+ :search => {
62
+ :flag => false,
63
+ :compartment => nil,
64
+ :parameters => {}
65
+ }
66
+ }
67
+ searchParams.each do |key,value|
68
+ options[:search][:parameters][key] = value
69
+ end
70
+ base_update(resource, id, options, format)
71
+ end
72
+
73
+ #
74
+ # Update an existing resource by its id or create it if it is a new resource, not present on the server
75
+ #
76
+ def base_update(resource, id, options, format)
77
+ options = {} if options.nil?
78
+ options[:resource] = resource.class
79
+ options[:format] = format
80
+ options[:id] = id
81
+ reply = put resource_url(options), resource, fhir_headers(options)
82
+ reply.resource = parse_reply(resource.class, format, reply)
83
+ reply.resource_class = resource.class
84
+ reply
85
+ end
86
+
87
+ #
88
+ # Partial update using a patchset (PATCH)
89
+ #
90
+ def partial_update(klass, id, patchset, options={}, format=@default_format)
91
+ options = { resource: klass, id: id, format: format }.merge options
92
+
93
+ if (format == FHIR::Formats::ResourceFormat::RESOURCE_XML)
94
+ options[:format] = FHIR::Formats::PatchFormat::PATCH_XML
95
+ options[:Accept] = format
96
+ elsif (format == FHIR::Formats::ResourceFormat::RESOURCE_JSON)
97
+ options[:format] = FHIR::Formats::PatchFormat::PATCH_JSON
98
+ options[:Accept] = format
99
+ end
100
+
101
+ reply = patch resource_url(options), patchset, fhir_headers(options)
102
+ reply.resource = parse_reply(klass, format, reply)
103
+ reply.resource_class = klass
104
+ reply
105
+ end
106
+
107
+ #
108
+ # Delete the resource with the given ID.
109
+ #
110
+ def destroy(klass, id=nil, options={})
111
+ options = { resource: klass, id: id, format: nil }.merge options
112
+ reply = delete resource_url(options), fhir_headers(options)
113
+ reply.resource_class = klass
114
+ reply
115
+ end
116
+
117
+ #
118
+ # Create a new resource with a server assigned id. Return the newly created
119
+ # resource with the id the server assigned.
120
+ #
121
+ def create(resource, format=@default_format)
122
+ base_create(resource, nil ,format)
123
+ end
124
+
125
+ #
126
+ # Conditionally create a new resource with a server assigned id.
127
+ #
128
+ def conditional_create(resource, ifNoneExistParameters, format=@default_format)
129
+ query = ''
130
+ ifNoneExistParameters.each do |key,value|
131
+ query += "#{key.to_s}=#{value.to_s}&"
132
+ end
133
+ query = query[0..-2] # strip off the trailing ampersand
134
+ options = {}
135
+ options['If-None-Exist'] = query
136
+ base_create(resource, options, format)
137
+ end
138
+
139
+ #
140
+ # Create a new resource with a server assigned id. Return the newly created
141
+ # resource with the id the server assigned.
142
+ #
143
+ def base_create(resource, options, format)
144
+ options = {} if options.nil?
145
+ options[:resource] = resource.class
146
+ options[:format] = format
147
+ reply = post resource_url(options), resource, fhir_headers(options)
148
+ if [200,201].include? reply.code
149
+ type = reply.response[:headers][:content_type]
150
+ if !type.nil?
151
+ if type.include?('xml') && !reply.body.empty?
152
+ reply.resource = resource.class.from_xml(reply.body)
153
+ elsif type.include?('json') && !reply.body.empty?
154
+ reply.resource = resource.class.from_fhir_json(reply.body)
155
+ else
156
+ reply.resource = resource # just send back the submitted resource
157
+ end
158
+ else
159
+ reply.resource = resource # don't know the content type, so return the resource provided
160
+ end
161
+ else
162
+ reply.resource = resource # just send back the submitted resource
163
+ end
164
+ reply.resource_class = resource.class
165
+ reply
166
+ end
167
+
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,23 @@
1
+ module FHIR
2
+ module Sections
3
+ module Feed
4
+
5
+ FORWARD = :next_link
6
+ BACKWARD = :previous_link
7
+ FIRST = :first_link
8
+ LAST = :last_link
9
+
10
+ def next_page(current, page=FORWARD)
11
+ bundle = current.resource
12
+ link = bundle.method(page).call
13
+ return nil unless link
14
+ reply = get strip_base(link.url), fhir_headers
15
+ reply.resource = parse_reply(current.resource_class, @default_format_bundle, reply)
16
+ reply.resource_class = current.resource_class
17
+ reply
18
+ end
19
+
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,78 @@
1
+ module FHIR
2
+ module Sections
3
+ module History
4
+ #
5
+ # Create a new resource with a server assigned id. Return the newly created
6
+ # resource with the id the server assigned. Associates tags with newly created resource.
7
+ #
8
+ # @param resourceClass
9
+ # @param resource
10
+ # @return
11
+ #
12
+ # public <T extends Resource> AtomEntry<OperationOutcome> create(Class<T> resourceClass, T resource, List<AtomCategory> tags);
13
+
14
+ #
15
+ # Retrieve the update history for a resource with given id since last update time.
16
+ # Last update may be null TODO - ensure this is the case.
17
+ #
18
+ # @param lastUpdate
19
+ # @param resourceClass
20
+ # @param id
21
+ # @return
22
+ #
23
+ # public <T extends Resource> AtomFeed history(Calendar lastUpdate, Class<T> resourceClass, String id);
24
+ # public <T extends Resource> AtomFeed history(DateAndTime lastUpdate, Class<T> resourceClass, String id);
25
+
26
+ def history(options)
27
+ reply = get resource_url(options), fhir_headers(options).except(:history)
28
+ reply.resource = parse_reply(options[:resource], @default_format_bundle, reply)
29
+ reply.resource_class = options[:resource]
30
+ reply
31
+ end
32
+
33
+ #
34
+ # Retrieve the entire update history for a resource with the given id.
35
+ # Last update may be null TODO - ensure this is the case.
36
+ #
37
+ # @param resourceClass
38
+ # @param id
39
+ # @param lastUpdate
40
+ # @return
41
+ #
42
+ def resource_instance_history_as_of(klass, id, lastUpdate)
43
+ history(resource: klass, id: id, history:{since: lastUpdate})
44
+ end
45
+
46
+ def resource_instance_history(klass, id)
47
+ history(resource: klass, id: id, history:{})
48
+ end
49
+
50
+ def resource_history(klass)
51
+ history(resource: klass, history:{})
52
+ end
53
+
54
+ def resource_history_as_of(klass, lastUpdate)
55
+ history(resource: klass, history:{since: lastUpdate})
56
+ end
57
+
58
+ #
59
+ # Retrieve the update history for all resource types since the start of server records.
60
+ #
61
+ def all_history
62
+ history(history:{})
63
+ end
64
+
65
+ #
66
+ # Retrieve the update history for all resource types since a specific last update date/time.
67
+ #
68
+ # Note:
69
+ # @param lastUpdate
70
+ # @return
71
+ #
72
+ def all_history_as_of(lastUpdate)
73
+ history(history:{since: lastUpdate})
74
+ end
75
+
76
+ end
77
+ end
78
+ end