fhir_models 4.2.2 → 4.3.0
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 +4 -4
- data/.github/workflows/ruby.yml +1 -1
- data/.gitignore +0 -1
- data/.rubocop.yml +5 -1
- data/.rubocop_todo.yml +49 -116
- data/.ruby-version +1 -0
- data/.simplecov +1 -5
- data/.tool-versions +1 -0
- data/Gemfile.lock +29 -23
- data/Guardfile +1 -1
- data/fhir_models.gemspec +2 -1
- data/lib/fhir_models/bootstrap/definitions.rb +16 -3
- data/lib/fhir_models/bootstrap/field.rb +2 -2
- data/lib/fhir_models/bootstrap/generator.rb +9 -7
- data/lib/fhir_models/bootstrap/hashable.rb +9 -7
- data/lib/fhir_models/bootstrap/json.rb +1 -1
- data/lib/fhir_models/bootstrap/model.rb +43 -32
- data/lib/fhir_models/bootstrap/preprocess.rb +25 -17
- data/lib/fhir_models/bootstrap/xml.rb +9 -7
- data/lib/fhir_models/deprecate.rb +1 -0
- data/lib/fhir_models/fhir.rb +7 -6
- data/lib/fhir_models/fhir_ext/element_definition.rb +4 -1
- data/lib/fhir_models/fhir_ext/structure_definition.rb +24 -20
- data/lib/fhir_models/fhir_ext/structure_definition_compare.rb +12 -2
- data/lib/fhir_models/fhir_ext/structure_definition_finding.rb +2 -2
- data/lib/fhir_models/fhirpath/expression.rb +4 -6
- data/lib/fhir_models/fhirpath/parse.rb +10 -7
- data/lib/fhir_models/version.rb +1 -1
- metadata +11 -9
@@ -25,13 +25,14 @@ module FHIR
|
|
25
25
|
# Inspired by active-support `blank` but in our case false isn't blank ...
|
26
26
|
# https://github.com/rails/rails/blob/v5.2.3/activesupport/lib/active_support/core_ext/object/blank.rb
|
27
27
|
blank = ->(obj) { obj.respond_to?(:empty?) ? obj.empty? : obj.nil? }
|
28
|
-
|
28
|
+
case thing
|
29
|
+
when Array
|
29
30
|
return nil if thing.empty?
|
30
31
|
|
31
32
|
thing
|
32
33
|
.map { |i| prune(i) }
|
33
34
|
.reject(&blank)
|
34
|
-
|
35
|
+
when Hash
|
35
36
|
return {} if thing.empty?
|
36
37
|
|
37
38
|
new_thing = {}
|
@@ -59,11 +60,12 @@ module FHIR
|
|
59
60
|
key = key.to_s
|
60
61
|
meta = self.class::METADATA[key]
|
61
62
|
next if meta.nil?
|
63
|
+
|
62
64
|
local_name = key
|
63
65
|
local_name = meta['local_name'] if meta['local_name']
|
64
66
|
begin
|
65
67
|
instance_variable_set("@#{local_name}", value)
|
66
|
-
rescue
|
68
|
+
rescue StandardError
|
67
69
|
# TODO: this appears to be a dead code branch
|
68
70
|
nil
|
69
71
|
end
|
@@ -102,7 +104,7 @@ module FHIR
|
|
102
104
|
if child['resourceType'] && !klass::METADATA['resourceType']
|
103
105
|
klass = begin
|
104
106
|
FHIR.const_get(child['resourceType'])
|
105
|
-
rescue =>
|
107
|
+
rescue StandardError => _e
|
106
108
|
# TODO: this appears to be a dead code branch
|
107
109
|
# TODO: should this log / re-raise the exception if encountered instead of silently swallowing it?
|
108
110
|
FHIR.logger.error("Unable to identify embedded class #{child['resourceType']}\n#{exception.backtrace}")
|
@@ -111,9 +113,9 @@ module FHIR
|
|
111
113
|
end
|
112
114
|
begin
|
113
115
|
obj = klass.new(child)
|
114
|
-
rescue =>
|
116
|
+
rescue StandardError => e
|
115
117
|
# TODO: should this re-raise the exception if encountered instead of silently swallowing it?
|
116
|
-
FHIR.logger.error("Unable to inflate embedded class #{klass}\n#{
|
118
|
+
FHIR.logger.error("Unable to inflate embedded class #{klass}\n#{e.backtrace}")
|
117
119
|
end
|
118
120
|
obj
|
119
121
|
end
|
@@ -125,7 +127,7 @@ module FHIR
|
|
125
127
|
if meta['type'] == 'boolean'
|
126
128
|
rval = value.strip == 'true'
|
127
129
|
elsif FHIR::PRIMITIVES.include?(meta['type'])
|
128
|
-
if
|
130
|
+
if ['decimal', 'integer', 'positiveInt', 'unsignedInt'].include?(meta['type'])
|
129
131
|
rval = BigDecimal(value.to_s)
|
130
132
|
rval = rval.frac.zero? ? rval.to_i : rval.to_f
|
131
133
|
end # primitive is number
|
@@ -17,7 +17,7 @@ module FHIR
|
|
17
17
|
resource_type = hash['resourceType']
|
18
18
|
klass = Module.const_get("FHIR::#{resource_type}")
|
19
19
|
resource = klass.new(hash)
|
20
|
-
rescue => e
|
20
|
+
rescue StandardError => e
|
21
21
|
FHIR.logger.error("Failed to deserialize JSON:\n#{e.backtrace}")
|
22
22
|
FHIR.logger.debug("JSON:\n#{json}")
|
23
23
|
resource = nil
|
@@ -22,40 +22,39 @@ module FHIR
|
|
22
22
|
to_hash.hash
|
23
23
|
end
|
24
24
|
|
25
|
+
def respond_to_missing?(method_name, *)
|
26
|
+
(defined?(self.class::MULTIPLE_TYPES) && self.class::MULTIPLE_TYPES[method_name.to_s]) ||
|
27
|
+
(!@extension.nil? && !@extension.empty? && !find_extension(@extension, method_name).first.nil?) ||
|
28
|
+
(!@modifierExtension.nil? && !@modifierExtension.empty? && !find_extension(@modifierExtension, method_name).first.nil?) ||
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
25
32
|
# allow two FHIR models to be compared for equality
|
26
33
|
def ==(other)
|
27
34
|
self.class == other.class && to_hash == other.to_hash
|
28
35
|
end
|
29
36
|
alias eql? ==
|
30
37
|
|
31
|
-
def method_missing(
|
32
|
-
if defined?(self.class::MULTIPLE_TYPES) && self.class::MULTIPLE_TYPES[
|
33
|
-
self.class::MULTIPLE_TYPES[
|
38
|
+
def method_missing(method_name, *_args, &_block)
|
39
|
+
if defined?(self.class::MULTIPLE_TYPES) && self.class::MULTIPLE_TYPES[method_name.to_s]
|
40
|
+
self.class::MULTIPLE_TYPES[method_name.to_s].each do |type|
|
34
41
|
type[0] = type[0].upcase
|
35
|
-
value = send("#{
|
42
|
+
value = send("#{method_name}#{type}".to_sym)
|
36
43
|
return value unless value.nil?
|
37
44
|
end
|
38
45
|
return nil
|
39
46
|
elsif !@extension.nil? && !@extension.empty?
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
(method.to_s == name || method.to_s == anchor)
|
44
|
-
end
|
45
|
-
unless ext.first.nil?
|
46
|
-
return ext.first.value.nil? ? ext.first : ext.first.value
|
47
|
+
desired_extension = find_extension(@extension, method_name)
|
48
|
+
unless desired_extension.first.nil?
|
49
|
+
return desired_extension.first.value.nil? ? desired_extension.first : desired_extension.first.value
|
47
50
|
end
|
48
51
|
elsif !@modifierExtension.nil? && !@modifierExtension.empty?
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
(method.to_s == name || method.to_s == anchor)
|
53
|
-
end
|
54
|
-
unless ext.first.nil?
|
55
|
-
return ext.first.value.nil? ? ext.first : ext.first.value
|
52
|
+
desired_extension = find_extension(@modifierExtension, method_name)
|
53
|
+
unless desired_extension.first.nil?
|
54
|
+
return desired_extension.first.value.nil? ? desired_extension.first : desired_extension.first.value
|
56
55
|
end
|
57
56
|
end
|
58
|
-
raise NoMethodError.new("undefined method `#{
|
57
|
+
raise NoMethodError.new("undefined method `#{method_name}' for #{self.class.name}", method_name)
|
59
58
|
end
|
60
59
|
|
61
60
|
def to_reference
|
@@ -141,14 +140,14 @@ module FHIR
|
|
141
140
|
end # metadata.each
|
142
141
|
# check multiple types
|
143
142
|
multiple_types = begin
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
143
|
+
self.class::MULTIPLE_TYPES
|
144
|
+
rescue StandardError
|
145
|
+
{}
|
146
|
+
end
|
148
147
|
multiple_types.each do |prefix, suffixes|
|
149
148
|
present = []
|
150
149
|
suffixes.each do |suffix|
|
151
|
-
typename = "#{prefix}#{suffix[0].upcase}#{suffix[1
|
150
|
+
typename = "#{prefix}#{suffix[0].upcase}#{suffix[1..]}"
|
152
151
|
# check which multiple data types are actually present, not just errors
|
153
152
|
# actually, this might be allowed depending on cardinality
|
154
153
|
value = instance_variable_get("@#{typename}")
|
@@ -157,8 +156,9 @@ module FHIR
|
|
157
156
|
errors[prefix] = ["#{prefix}[x]: more than one type present."] if present.length > 1
|
158
157
|
# remove errors for suffixes that are not present
|
159
158
|
next unless present.length == 1
|
159
|
+
|
160
160
|
suffixes.each do |suffix|
|
161
|
-
typename = "#{prefix}#{suffix[0].upcase}#{suffix[1
|
161
|
+
typename = "#{prefix}#{suffix[0].upcase}#{suffix[1..]}"
|
162
162
|
errors.delete(typename) unless present.include?(typename)
|
163
163
|
end
|
164
164
|
end
|
@@ -221,10 +221,12 @@ module FHIR
|
|
221
221
|
# check binding
|
222
222
|
next unless meta['binding']
|
223
223
|
next unless meta['binding']['strength'] == 'required'
|
224
|
+
|
224
225
|
the_codes = [v]
|
225
|
-
|
226
|
+
case meta['type']
|
227
|
+
when 'Coding'
|
226
228
|
the_codes = [v.code]
|
227
|
-
|
229
|
+
when 'CodeableConcept'
|
228
230
|
the_codes = v.coding.map(&:code).compact
|
229
231
|
end
|
230
232
|
has_valid_code = false
|
@@ -247,6 +249,7 @@ module FHIR
|
|
247
249
|
def validate_reference_type(ref, meta, contained_here, errors)
|
248
250
|
return unless ref.reference && meta['type_profiles']
|
249
251
|
return if ref.reference.start_with?('urn:uuid:', 'urn:oid:')
|
252
|
+
|
250
253
|
matches_one_profile = false
|
251
254
|
meta['type_profiles'].each do |p|
|
252
255
|
basetype = p.split('/').last
|
@@ -258,7 +261,7 @@ module FHIR
|
|
258
261
|
matches_one_profile = true if meta['type_profiles'].include?('http://hl7.org/fhir/StructureDefinition/Resource')
|
259
262
|
if !matches_one_profile && ref.reference.start_with?('#')
|
260
263
|
# we need to look at the local contained resources
|
261
|
-
r = contained_here.find { |x| x.id == ref.reference[1
|
264
|
+
r = contained_here.find { |x| x.id == ref.reference[1..] }
|
262
265
|
if !r.nil?
|
263
266
|
meta['type_profiles'].each do |p|
|
264
267
|
p = p.split('/').last
|
@@ -280,15 +283,15 @@ module FHIR
|
|
280
283
|
def check_binding_uri(uri, value)
|
281
284
|
valid = false
|
282
285
|
# Strip off the |4.0.0 or |4.0.1 or |2014-03-26 or similar from the ends of URLs
|
283
|
-
uri&.gsub!(/\|[A-Za-z0-9
|
286
|
+
uri&.gsub!(/\|[A-Za-z0-9.\-]*/, '')
|
284
287
|
valueset = FHIR::Definitions.get_codes(uri)
|
285
288
|
|
286
|
-
if
|
289
|
+
if ['http://hl7.org/fhir/ValueSet/mimetypes', 'http://www.rfc-editor.org/bcp/bcp13.txt'].include?(uri)
|
287
290
|
matches = MIME::Types[value]
|
288
291
|
json_or_xml = value.downcase.include?('xml') || value.downcase.include?('json')
|
289
292
|
known_weird = ['text/cql', 'application/cql+text', 'application/hl7-v2'].include?(value)
|
290
293
|
valid = json_or_xml || known_weird || (!matches.nil? && !matches.empty?)
|
291
|
-
elsif
|
294
|
+
elsif ['http://hl7.org/fhir/ValueSet/languages', 'http://tools.ietf.org/html/bcp47'].include?(uri)
|
292
295
|
has_region = !(value =~ /-/).nil?
|
293
296
|
valid = !BCP47::Language.identify(value.downcase).nil? && (!has_region || !BCP47::Region.identify(value.upcase).nil?)
|
294
297
|
elsif valueset.nil?
|
@@ -321,6 +324,14 @@ module FHIR
|
|
321
324
|
self
|
322
325
|
end
|
323
326
|
|
324
|
-
|
327
|
+
def find_extension(extension_source, method_name)
|
328
|
+
extension_source.select do |extension|
|
329
|
+
name = extension.url.tr('-', '_').split('/').last
|
330
|
+
anchor = name.split('#').last
|
331
|
+
(method_name.to_s == name || method_name.to_s == anchor)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
private :validate_reference_type, :check_binding_uri, :validate_field, :find_extension
|
325
336
|
end
|
326
337
|
end
|
@@ -11,16 +11,17 @@ module FHIR
|
|
11
11
|
|
12
12
|
# Remove entries that do not interest us: CompartmentDefinitions, OperationDefinitions, Conformance statements
|
13
13
|
hash['entry'].select! do |entry|
|
14
|
-
|
14
|
+
['StructureDefinition', 'ValueSet', 'CodeSystem', 'SearchParameter'].include? entry['resource']['resourceType']
|
15
15
|
end
|
16
16
|
|
17
17
|
# Remove unnecessary elements from the hash
|
18
18
|
hash['entry'].each do |entry|
|
19
19
|
next unless entry['resource']
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
|
21
|
+
pre_process_structuredefinition(entry['resource']) if entry['resource']['resourceType'] == 'StructureDefinition'
|
22
|
+
pre_process_valueset(entry['resource']) if entry['resource']['resourceType'] == 'ValueSet'
|
23
|
+
pre_process_codesystem(entry['resource']) if entry['resource']['resourceType'] == 'CodeSystem'
|
24
|
+
pre_process_searchparam(entry['resource']) if entry['resource']['resourceType'] == 'SearchParameter'
|
24
25
|
remove_fhir_comments(entry['resource'])
|
25
26
|
end
|
26
27
|
|
@@ -34,26 +35,30 @@ module FHIR
|
|
34
35
|
|
35
36
|
def self.pre_process_structuredefinition(hash)
|
36
37
|
# Remove large HTML narratives and unused content
|
37
|
-
|
38
|
+
['text', 'publisher', 'contact', 'description', 'requirements', 'mapping'].each { |key| hash.delete(key) }
|
38
39
|
|
39
40
|
# Remove unused descriptions within the snapshot and differential elements
|
40
|
-
|
41
|
+
['snapshot', 'differential'].each do |key|
|
41
42
|
next unless hash[key]
|
43
|
+
|
42
44
|
hash[key]['element'].each do |element|
|
43
|
-
|
45
|
+
['short', 'definition', 'comments', 'requirements', 'alias', 'mapping'].each { |subkey| element.delete(subkey) }
|
44
46
|
end
|
45
47
|
end
|
46
48
|
end
|
47
49
|
|
48
50
|
def self.pre_process_valueset(hash)
|
49
51
|
# Remove large HTML narratives and unused content
|
50
|
-
|
52
|
+
['meta', 'text', 'publisher', 'contact', 'description', 'requirements'].each { |key| hash.delete(key) }
|
51
53
|
|
52
54
|
return unless hash['compose']
|
53
|
-
|
55
|
+
|
56
|
+
['include', 'exclude'].each do |key|
|
54
57
|
next unless hash['compose'][key]
|
58
|
+
|
55
59
|
hash['compose'][key].each do |element|
|
56
60
|
next unless element['concept']
|
61
|
+
|
57
62
|
element['concept'].each do |concept|
|
58
63
|
concept.delete('designation')
|
59
64
|
end
|
@@ -63,16 +68,18 @@ module FHIR
|
|
63
68
|
|
64
69
|
def self.pre_process_codesystem(hash)
|
65
70
|
# Remove large HTML narratives and unused content
|
66
|
-
|
71
|
+
['meta', 'text', 'publisher', 'contact', 'description', 'requirements'].each { |key| hash.delete(key) }
|
67
72
|
return unless hash['concept']
|
73
|
+
|
68
74
|
hash['concept'].each do |concept|
|
69
75
|
pre_process_codesystem_concept(concept)
|
70
76
|
end
|
71
77
|
end
|
72
78
|
|
73
79
|
def self.pre_process_codesystem_concept(hash)
|
74
|
-
|
80
|
+
['extension', 'definition', 'designation'].each { |key| hash.delete(key) }
|
75
81
|
return unless hash['concept']
|
82
|
+
|
76
83
|
hash['concept'].each do |concept|
|
77
84
|
pre_process_codesystem_concept(concept)
|
78
85
|
end
|
@@ -80,15 +87,16 @@ module FHIR
|
|
80
87
|
|
81
88
|
def self.pre_process_searchparam(hash)
|
82
89
|
# Remove large HTML narratives and unused content
|
83
|
-
|
90
|
+
['id', 'url', 'name', 'date', 'publisher', 'contact', 'description', 'xpathUsage'].each { |key| hash.delete(key) }
|
84
91
|
end
|
85
92
|
|
86
93
|
def self.remove_fhir_comments(hash)
|
87
94
|
hash.delete('fhir_comments')
|
88
95
|
hash.each do |_key, value|
|
89
|
-
|
96
|
+
case value
|
97
|
+
when Hash
|
90
98
|
remove_fhir_comments(value)
|
91
|
-
|
99
|
+
when Array
|
92
100
|
value.each do |v|
|
93
101
|
remove_fhir_comments(v) if v.is_a?(Hash)
|
94
102
|
end
|
@@ -126,7 +134,7 @@ module FHIR
|
|
126
134
|
hash = JSON.parse(json)
|
127
135
|
|
128
136
|
# Remove narratives
|
129
|
-
|
137
|
+
['text'].each { |key| hash.delete(key) }
|
130
138
|
|
131
139
|
# Output the post processed file
|
132
140
|
f = File.open(filename, 'w:UTF-8')
|
@@ -156,7 +164,7 @@ module FHIR
|
|
156
164
|
# Remove the weird parantheses on xml example filenames
|
157
165
|
# we do this so they match the names of the json examples
|
158
166
|
if filename.include?('(') && filename.include?(')')
|
159
|
-
rename = filename.gsub(/\([A-Za-z0-9
|
167
|
+
rename = filename.gsub(/\([A-Za-z0-9\-.]*\)/, '')
|
160
168
|
File.rename(filename, rename)
|
161
169
|
filename = rename
|
162
170
|
end
|
@@ -24,7 +24,7 @@ module FHIR
|
|
24
24
|
# if hash contains resourceType
|
25
25
|
# create a child node with the name==resourceType
|
26
26
|
# fill that, and place the child under the above `node`
|
27
|
-
if hash['resourceType']
|
27
|
+
if hash['resourceType'].is_a?(String) && name != 'instance'
|
28
28
|
child_name = hash['resourceType']
|
29
29
|
hash.delete('resourceType')
|
30
30
|
child = hash_to_xml_node(child_name, hash, doc)
|
@@ -33,11 +33,13 @@ module FHIR
|
|
33
33
|
end
|
34
34
|
|
35
35
|
hash.each do |key, value|
|
36
|
-
next if
|
36
|
+
next if ['extension', 'modifierExtension'].include?(name) && key == 'url'
|
37
37
|
next if key == 'id' && !FHIR::RESOURCES.include?(name)
|
38
|
-
|
38
|
+
|
39
|
+
case value
|
40
|
+
when Hash
|
39
41
|
node.add_child(hash_to_xml_node(key, value, doc))
|
40
|
-
|
42
|
+
when Array
|
41
43
|
value.each do |v|
|
42
44
|
if v.is_a?(Hash)
|
43
45
|
node.add_child(hash_to_xml_node(key, v, doc))
|
@@ -62,7 +64,7 @@ module FHIR
|
|
62
64
|
node.add_child(child)
|
63
65
|
end
|
64
66
|
end
|
65
|
-
node.set_attribute('url', hash['url']) if
|
67
|
+
node.set_attribute('url', hash['url']) if ['extension', 'modifierExtension'].include?(name)
|
66
68
|
node.set_attribute('id', hash['id']) if hash['id'] && !FHIR::RESOURCES.include?(name)
|
67
69
|
node
|
68
70
|
end
|
@@ -78,7 +80,7 @@ module FHIR
|
|
78
80
|
resource_type = doc.root.name
|
79
81
|
klass = Module.const_get("FHIR::#{resource_type}")
|
80
82
|
resource = klass.new(hash)
|
81
|
-
rescue => e
|
83
|
+
rescue StandardError => e
|
82
84
|
FHIR.logger.error("Failed to deserialize XML:\n#{e.backtrace}")
|
83
85
|
FHIR.logger.debug("XML:\n#{xml}")
|
84
86
|
resource = nil
|
@@ -108,7 +110,7 @@ module FHIR
|
|
108
110
|
end
|
109
111
|
end
|
110
112
|
end
|
111
|
-
hash['url'] = node.get_attribute('url') if
|
113
|
+
hash['url'] = node.get_attribute('url') if ['extension', 'modifierExtension'].include?(node.name)
|
112
114
|
hash['id'] = node.get_attribute('id') if node.get_attribute('id') # Testscript fixture ids (applies to any BackboneElement)
|
113
115
|
hash['resourceType'] = node.name if FHIR::RESOURCES.include?(node.name)
|
114
116
|
|
@@ -10,6 +10,7 @@ module FHIR
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
return unless methods.include? new_method
|
13
|
+
|
13
14
|
(class << self; self; end).instance_eval do
|
14
15
|
define_method(old_method) do |*args, &block|
|
15
16
|
message = "DEPRECATED: `#{old_method}` has been deprecated. Use `#{new_method}` instead. Called from #{caller.first}"
|
data/lib/fhir_models/fhir.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'nokogiri'
|
2
2
|
require 'logger'
|
3
|
+
require 'uri'
|
3
4
|
|
4
5
|
module FHIR
|
5
6
|
def self.logger
|
@@ -11,7 +12,7 @@ module FHIR
|
|
11
12
|
end
|
12
13
|
|
13
14
|
def self.default_logger
|
14
|
-
@default_logger ||= Logger.new(ENV['FHIR_LOGGER'] ||
|
15
|
+
@default_logger ||= Logger.new(ENV['FHIR_LOGGER'] || $stdout)
|
15
16
|
end
|
16
17
|
|
17
18
|
def self.from_contents(contents)
|
@@ -38,7 +39,7 @@ module FHIR
|
|
38
39
|
when 'uri'
|
39
40
|
begin
|
40
41
|
!URI.parse(value).nil?
|
41
|
-
rescue
|
42
|
+
rescue StandardError
|
42
43
|
false
|
43
44
|
end
|
44
45
|
when 'base64binary'
|
@@ -47,7 +48,7 @@ module FHIR
|
|
47
48
|
# whitespace is not significant so we strip it out before doing the regex so that we can be sure that
|
48
49
|
# the number of characters is a multiple of 4.
|
49
50
|
# https://tools.ietf.org/html/rfc4648
|
50
|
-
!(value.to_s.gsub(/\s/, '') =~ %r{\A(|[0-9a-zA-Z
|
51
|
+
!(value.to_s.gsub(/\s/, '') =~ %r{\A(|[0-9a-zA-Z+=/]{4}+)\Z}).nil?
|
51
52
|
when 'instant'
|
52
53
|
formatted_value = value.respond_to?(:xmlschema) ? value.xmlschema : value.to_s
|
53
54
|
!(formatted_value =~ /\A([0-9]{4}(-(0[1-9]|1[0-2])(-(0[0-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\.[0-9]+)?(Z|(\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))))))\Z/).nil?
|
@@ -62,16 +63,16 @@ module FHIR
|
|
62
63
|
when 'time'
|
63
64
|
!(value.to_s =~ /\A(([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\.[0-9]+)?)\Z/).nil?
|
64
65
|
when 'code'
|
65
|
-
!(value.to_s =~ /\A[^\s]+(
|
66
|
+
!(value.to_s =~ /\A[^\s]+(\s?[^\s]+)*\Z/).nil?
|
66
67
|
when 'oid'
|
67
68
|
!(value.to_s =~ /\Aurn:oid:[0-2](\.[1-9]\d*)+\Z/).nil?
|
68
69
|
when 'id'
|
69
|
-
!(value.to_s =~ /\A[A-Za-z0-9
|
70
|
+
!(value.to_s =~ /\A[A-Za-z0-9\-.]{1,64}\Z/).nil?
|
70
71
|
when 'xhtml'
|
71
72
|
fragment = Nokogiri::HTML::DocumentFragment.parse(value)
|
72
73
|
value.is_a?(String) && fragment.errors.size.zero?
|
73
74
|
when 'unsignedint'
|
74
|
-
!(value.to_s =~ /\A(
|
75
|
+
!(value.to_s =~ /\A(0|([1-9][0-9]*))\Z/).nil?
|
75
76
|
when 'positiveint'
|
76
77
|
!(value.to_s =~ /\A+?[1-9][0-9]*\Z/).nil?
|
77
78
|
else
|
@@ -25,6 +25,7 @@ module FHIR
|
|
25
25
|
def keep_children(whitelist = [])
|
26
26
|
@marked_for_keeping = true if whitelist.include?(path)
|
27
27
|
return unless @children
|
28
|
+
|
28
29
|
@children.each do |child|
|
29
30
|
child.keep_children(whitelist)
|
30
31
|
end
|
@@ -32,14 +33,16 @@ module FHIR
|
|
32
33
|
|
33
34
|
def sweep_children
|
34
35
|
return unless @children
|
36
|
+
|
35
37
|
@children.each(&:sweep_children)
|
36
|
-
@children
|
38
|
+
@children.keep_if(&:marked_for_keeping)
|
37
39
|
@marked_for_keeping = !@children.empty? || @marked_for_keeping
|
38
40
|
end
|
39
41
|
|
40
42
|
def print_children(spaces = 0)
|
41
43
|
puts "#{' ' * spaces}+#{local_name || path}"
|
42
44
|
return nil unless @children
|
45
|
+
|
43
46
|
@children.each do |child|
|
44
47
|
child.print_children(spaces + 2)
|
45
48
|
end
|
@@ -59,7 +59,7 @@ module FHIR
|
|
59
59
|
if json.is_a? String
|
60
60
|
begin
|
61
61
|
json = JSON.parse(json)
|
62
|
-
rescue => e
|
62
|
+
rescue StandardError => e
|
63
63
|
@errors << "Failed to parse JSON: #{e.message} %n #{h} %n #{e.backtrace.join("\n")}"
|
64
64
|
return false
|
65
65
|
end
|
@@ -99,13 +99,15 @@ module FHIR
|
|
99
99
|
def get_json_nodes(json, path)
|
100
100
|
results = []
|
101
101
|
return [json] if path.nil?
|
102
|
+
|
102
103
|
steps = path.split('.')
|
103
104
|
steps.each.with_index do |step, index|
|
104
|
-
|
105
|
+
case json
|
106
|
+
when Hash
|
105
107
|
json = json[step]
|
106
|
-
|
108
|
+
when Array
|
107
109
|
json.each do |e|
|
108
|
-
results << get_json_nodes(e, steps[index
|
110
|
+
results << get_json_nodes(e, steps[index..].join('.'))
|
109
111
|
end
|
110
112
|
return results.flatten!
|
111
113
|
else
|
@@ -125,7 +127,7 @@ module FHIR
|
|
125
127
|
|
126
128
|
def verify_element(element, json)
|
127
129
|
path = element.local_name || element.path
|
128
|
-
path = path[(@hierarchy.path.size + 1)
|
130
|
+
path = path[(@hierarchy.path.size + 1)..] if path.start_with? @hierarchy.path
|
129
131
|
|
130
132
|
if element.type && !element.type.empty?
|
131
133
|
data_type_found = element.type.first.code
|
@@ -157,10 +159,11 @@ module FHIR
|
|
157
159
|
verify_cardinality(element, nodes)
|
158
160
|
|
159
161
|
return if nodes.empty?
|
162
|
+
|
160
163
|
# Check the datatype for each node, only if the element has one declared, and it isn't the root element
|
161
164
|
if !element.type.empty? && element.path != id
|
162
165
|
# element.type not being empty implies data_type_found != nil, for valid profiles
|
163
|
-
codeable_concept_pattern = element.pattern
|
166
|
+
codeable_concept_pattern = element.pattern&.is_a?(FHIR::CodeableConcept)
|
164
167
|
matching_pattern = false
|
165
168
|
nodes.each do |value|
|
166
169
|
matching_type = 0
|
@@ -197,7 +200,7 @@ module FHIR
|
|
197
200
|
matching_pattern = true if vcoding.system == pcoding.system && vcoding.code == pcoding.code
|
198
201
|
end
|
199
202
|
end
|
200
|
-
elsif
|
203
|
+
elsif ['CodeableConcept', 'Coding', 'Quantity'].include? data_type_found
|
201
204
|
required_strength = element&.binding&.strength == 'required'
|
202
205
|
binding_issues = required_strength ? @errors : @warnings
|
203
206
|
|
@@ -275,20 +278,20 @@ module FHIR
|
|
275
278
|
# elsewhere. There is no good way to determine "where" you should evaluate the expression.
|
276
279
|
element.constraint.each do |constraint|
|
277
280
|
next unless constraint.expression && !nodes.empty?
|
281
|
+
|
278
282
|
nodes.each do |node|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
@errors << "#{describe_element(element)}: FHIRPath expression evaluates to false for #{name} (containing: #{node}) invariant rule #{constraint.key}: #{constraint.human}"
|
283
|
-
end
|
284
|
-
rescue
|
285
|
-
@warnings << "#{describe_element(element)}: unable to evaluate FHIRPath expression against JSON for #{name} (containing: #{node}) invariant rule #{constraint.key}: #{constraint.human}"
|
283
|
+
result = FHIRPath.evaluate(constraint.expression, node)
|
284
|
+
if !result && constraint.severity == 'error'
|
285
|
+
@errors << "#{describe_element(element)}: FHIRPath expression evaluates to false for #{name} (containing: #{node}) invariant rule #{constraint.key}: #{constraint.human}"
|
286
286
|
end
|
287
|
+
rescue StandardError
|
288
|
+
@warnings << "#{describe_element(element)}: unable to evaluate FHIRPath expression against JSON for #{name} (containing: #{node}) invariant rule #{constraint.key}: #{constraint.human}"
|
287
289
|
end
|
288
290
|
end
|
289
291
|
|
290
292
|
# check children if the element has any
|
291
293
|
return unless element.children
|
294
|
+
|
292
295
|
nodes.each do |node|
|
293
296
|
element.children.each do |child|
|
294
297
|
verify_element(child, node)
|
@@ -323,7 +326,7 @@ module FHIR
|
|
323
326
|
@errors += definition.errors
|
324
327
|
@warnings += definition.warnings
|
325
328
|
end
|
326
|
-
rescue
|
329
|
+
rescue StandardError
|
327
330
|
@errors << "Unable to verify #{data_type_code} as a FHIR Resource."
|
328
331
|
end
|
329
332
|
return ret_val
|
@@ -347,7 +350,7 @@ module FHIR
|
|
347
350
|
@errors += definition.errors
|
348
351
|
@warnings += definition.warnings
|
349
352
|
end
|
350
|
-
rescue
|
353
|
+
rescue StandardError
|
351
354
|
@errors << "Unable to verify #{resource_type} as a FHIR Resource."
|
352
355
|
end
|
353
356
|
ret_val
|
@@ -373,7 +376,7 @@ module FHIR
|
|
373
376
|
@errors += definition.errors
|
374
377
|
@warnings += definition.warnings
|
375
378
|
end
|
376
|
-
rescue
|
379
|
+
rescue StandardError
|
377
380
|
@errors << "Unable to verify #{data_type_code} as a FHIR type."
|
378
381
|
end
|
379
382
|
ret_val
|
@@ -395,14 +398,14 @@ module FHIR
|
|
395
398
|
|
396
399
|
matching_type = 0
|
397
400
|
|
398
|
-
if
|
401
|
+
if ['http://hl7.org/fhir/ValueSet/mimetypes', 'http://www.rfc-editor.org/bcp/bcp13.txt'].include?(vs_uri)
|
399
402
|
matches = MIME::Types[value]
|
400
403
|
known_weird = ['text/cql', 'application/cql+text', 'application/hl7-v2'].include?(value)
|
401
404
|
if (matches.nil? || matches.size.zero? || known_weird) && !some_type_of_xml_or_json?(value)
|
402
405
|
@errors << "#{element.path} has invalid mime-type: '#{value}'"
|
403
406
|
matching_type -= 1 if element.binding.strength == 'required'
|
404
407
|
end
|
405
|
-
elsif
|
408
|
+
elsif ['http://hl7.org/fhir/ValueSet/languages', 'http://tools.ietf.org/html/bcp47'].include?(vs_uri)
|
406
409
|
has_region = !(value =~ /-/).nil?
|
407
410
|
valid = !BCP47::Language.identify(value.downcase).nil? && (!has_region || !BCP47::Region.identify(value.upcase).nil?)
|
408
411
|
unless valid
|
@@ -435,9 +438,10 @@ module FHIR
|
|
435
438
|
|
436
439
|
def some_type_of_xml_or_json?(code)
|
437
440
|
m = code.downcase
|
438
|
-
return true if
|
441
|
+
return true if ['xml', 'json'].include?(m)
|
439
442
|
return true if m.start_with?('application/', 'text/') && m.end_with?('json', 'xml')
|
440
443
|
return true if m.start_with?('application/xml', 'text/xml', 'application/json', 'text/json')
|
444
|
+
|
441
445
|
false
|
442
446
|
end
|
443
447
|
deprecate :is_some_type_of_xml_or_json, :some_type_of_xml_or_json?
|