fhir_stu3_models 3.1.1 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +1 -1
- data/.github/workflows/ruby.yml +1 -1
- data/.gitignore +0 -1
- data/.rubocop.yml +5 -1
- data/.rubocop_todo.yml +49 -119
- data/.ruby-version +1 -0
- data/.simplecov +1 -5
- data/.tool-versions +1 -0
- data/Gemfile.lock +36 -32
- data/Guardfile +1 -1
- data/fhir_stu3_models.gemspec +2 -1
- data/lib/fhir_stu3_models/bootstrap/definitions.rb +14 -1
- data/lib/fhir_stu3_models/bootstrap/field.rb +2 -2
- data/lib/fhir_stu3_models/bootstrap/generator.rb +7 -5
- data/lib/fhir_stu3_models/bootstrap/hashable.rb +5 -4
- data/lib/fhir_stu3_models/bootstrap/json.rb +2 -2
- data/lib/fhir_stu3_models/bootstrap/model.rb +43 -31
- data/lib/fhir_stu3_models/bootstrap/preprocess.rb +23 -15
- data/lib/fhir_stu3_models/bootstrap/xml.rb +9 -7
- data/lib/fhir_stu3_models/deprecate.rb +1 -0
- data/lib/fhir_stu3_models/fhir.rb +8 -7
- data/lib/fhir_stu3_models/fhir_ext/element_definition.rb +4 -1
- data/lib/fhir_stu3_models/fhir_ext/structure_definition.rb +24 -17
- data/lib/fhir_stu3_models/fhir_ext/structure_definition_compare.rb +10 -0
- data/lib/fhir_stu3_models/fhir_ext/structure_definition_finding.rb +2 -2
- data/lib/fhir_stu3_models/fluentpath/expression.rb +1 -1
- data/lib/fhir_stu3_models/fluentpath/parse.rb +10 -7
- data/lib/fhir_stu3_models/version.rb +1 -1
- metadata +11 -9
@@ -39,11 +39,12 @@ module FHIR
|
|
39
39
|
key = key.to_s
|
40
40
|
meta = self.class::METADATA[key]
|
41
41
|
next if meta.nil?
|
42
|
+
|
42
43
|
local_name = key
|
43
44
|
local_name = meta['local_name'] if meta['local_name']
|
44
45
|
begin
|
45
46
|
instance_variable_set("@#{local_name}", value)
|
46
|
-
rescue
|
47
|
+
rescue StandardError
|
47
48
|
# TODO: this appears to be a dead code branch
|
48
49
|
nil
|
49
50
|
end
|
@@ -86,7 +87,7 @@ module FHIR
|
|
86
87
|
if child['resourceType'] && !klass::METADATA['resourceType']
|
87
88
|
klass = begin
|
88
89
|
FHIR::STU3.const_get(child['resourceType'])
|
89
|
-
rescue =>
|
90
|
+
rescue StandardError => _e
|
90
91
|
# TODO: this appears to be a dead code branch
|
91
92
|
# TODO: should this log / re-raise the exception if encountered instead of silently swallowing it?
|
92
93
|
FHIR::STU3.logger.error("Unable to identify embedded class #{child['resourceType']}\n#{exception.backtrace}")
|
@@ -95,10 +96,10 @@ module FHIR
|
|
95
96
|
end
|
96
97
|
begin
|
97
98
|
obj = klass.new(child)
|
98
|
-
rescue =>
|
99
|
+
rescue StandardError => e
|
99
100
|
# TODO: this appears to be a dead code branch
|
100
101
|
# TODO: should this re-raise the exception if encountered instead of silently swallowing it?
|
101
|
-
FHIR::STU3.logger.error("Unable to inflate embedded class #{klass}\n#{
|
102
|
+
FHIR::STU3.logger.error("Unable to inflate embedded class #{klass}\n#{e.backtrace}")
|
102
103
|
end
|
103
104
|
obj
|
104
105
|
end
|
@@ -7,7 +7,7 @@ module FHIR
|
|
7
7
|
# This module includes methods to serialize or deserialize FHIR resources to and from JSON.
|
8
8
|
#
|
9
9
|
|
10
|
-
def to_json
|
10
|
+
def to_json(*_args)
|
11
11
|
JSON.pretty_unparse(to_hash)
|
12
12
|
end
|
13
13
|
|
@@ -18,7 +18,7 @@ module FHIR
|
|
18
18
|
resource_type = hash['resourceType']
|
19
19
|
klass = Module.const_get("FHIR::STU3::#{resource_type}")
|
20
20
|
resource = klass.new(hash)
|
21
|
-
rescue => e
|
21
|
+
rescue StandardError => e
|
22
22
|
FHIR::STU3.logger.error("Failed to deserialize JSON:\n#{e.backtrace}")
|
23
23
|
FHIR::STU3.logger.debug("JSON:\n#{json}")
|
24
24
|
resource = nil
|
@@ -23,40 +23,39 @@ module FHIR
|
|
23
23
|
to_hash.hash
|
24
24
|
end
|
25
25
|
|
26
|
+
def respond_to_missing?(method_name, *)
|
27
|
+
(defined?(self.class::MULTIPLE_TYPES) && self.class::MULTIPLE_TYPES[method_name.to_s]) ||
|
28
|
+
(!@extension.nil? && !@extension.empty? && !find_extension(@extension, method_name).first.nil?) ||
|
29
|
+
(!@modifierExtension.nil? && !@modifierExtension.empty? && !find_extension(@modifierExtension, method_name).first.nil?) ||
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
26
33
|
# allow two FHIR models to be compared for equality
|
27
34
|
def ==(other)
|
28
35
|
self.class == other.class && to_hash == other.to_hash
|
29
36
|
end
|
30
37
|
alias eql? ==
|
31
38
|
|
32
|
-
def method_missing(
|
33
|
-
if defined?(self.class::MULTIPLE_TYPES) && self.class::MULTIPLE_TYPES[
|
34
|
-
self.class::MULTIPLE_TYPES[
|
39
|
+
def method_missing(method_name, *_args, &_block)
|
40
|
+
if defined?(self.class::MULTIPLE_TYPES) && self.class::MULTIPLE_TYPES[method_name.to_s]
|
41
|
+
self.class::MULTIPLE_TYPES[method_name.to_s].each do |type|
|
35
42
|
type[0] = type[0].upcase
|
36
|
-
value = send("#{
|
43
|
+
value = send("#{method_name}#{type}".to_sym)
|
37
44
|
return value unless value.nil?
|
38
45
|
end
|
39
46
|
return nil
|
40
47
|
elsif !@extension.nil? && !@extension.empty?
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
(method.to_s == name || method.to_s == anchor)
|
45
|
-
end
|
46
|
-
unless ext.first.nil?
|
47
|
-
return ext.first.value.nil? ? ext.first : ext.first.value
|
48
|
+
desired_extension = find_extension(@extension, method_name)
|
49
|
+
unless desired_extension.first.nil?
|
50
|
+
return desired_extension.first.value.nil? ? desired_extension.first : desired_extension.first.value
|
48
51
|
end
|
49
52
|
elsif !@modifierExtension.nil? && !@modifierExtension.empty?
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
(method.to_s == name || method.to_s == anchor)
|
54
|
-
end
|
55
|
-
unless ext.first.nil?
|
56
|
-
return ext.first.value.nil? ? ext.first : ext.first.value
|
53
|
+
desired_extension = find_extension(@modifierExtension, method_name)
|
54
|
+
unless desired_extension.first.nil?
|
55
|
+
return desired_extension.first.value.nil? ? desired_extension.first : desired_extension.first.value
|
57
56
|
end
|
58
57
|
end
|
59
|
-
raise NoMethodError.new("undefined method `#{
|
58
|
+
raise NoMethodError.new("undefined method `#{method_name}' for #{self.class.name}", method_name)
|
60
59
|
end
|
61
60
|
|
62
61
|
def to_reference
|
@@ -142,14 +141,14 @@ module FHIR
|
|
142
141
|
end # metadata.each
|
143
142
|
# check multiple types
|
144
143
|
multiple_types = begin
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
144
|
+
self.class::MULTIPLE_TYPES
|
145
|
+
rescue StandardError
|
146
|
+
{}
|
147
|
+
end
|
149
148
|
multiple_types.each do |prefix, suffixes|
|
150
149
|
present = []
|
151
150
|
suffixes.each do |suffix|
|
152
|
-
typename = "#{prefix}#{suffix[0].upcase}#{suffix[1
|
151
|
+
typename = "#{prefix}#{suffix[0].upcase}#{suffix[1..]}"
|
153
152
|
# check which multiple data types are actually present, not just errors
|
154
153
|
# actually, this might be allowed depending on cardinality
|
155
154
|
value = instance_variable_get("@#{typename}")
|
@@ -158,8 +157,9 @@ module FHIR
|
|
158
157
|
errors[prefix] = ["#{prefix}[x]: more than one type present."] if present.length > 1
|
159
158
|
# remove errors for suffixes that are not present
|
160
159
|
next unless present.length == 1
|
160
|
+
|
161
161
|
suffixes.each do |suffix|
|
162
|
-
typename = "#{prefix}#{suffix[0].upcase}#{suffix[1
|
162
|
+
typename = "#{prefix}#{suffix[0].upcase}#{suffix[1..]}"
|
163
163
|
errors.delete(typename) unless present.include?(typename)
|
164
164
|
end
|
165
165
|
end
|
@@ -222,10 +222,12 @@ module FHIR
|
|
222
222
|
# check binding
|
223
223
|
next unless meta['binding']
|
224
224
|
next unless meta['binding']['strength'] == 'required'
|
225
|
+
|
225
226
|
the_codes = [v]
|
226
|
-
|
227
|
+
case meta['type']
|
228
|
+
when 'Coding'
|
227
229
|
the_codes = [v.code]
|
228
|
-
|
230
|
+
when 'CodeableConcept'
|
229
231
|
the_codes = v.coding.map(&:code).compact
|
230
232
|
end
|
231
233
|
has_valid_code = false
|
@@ -248,6 +250,7 @@ module FHIR
|
|
248
250
|
def validate_reference_type(ref, meta, contained_here, errors)
|
249
251
|
return unless ref.reference && meta['type_profiles']
|
250
252
|
return if ref.reference.start_with?('urn:uuid:', 'urn:oid:')
|
253
|
+
|
251
254
|
matches_one_profile = false
|
252
255
|
meta['type_profiles'].each do |p|
|
253
256
|
basetype = p.split('/').last
|
@@ -259,7 +262,7 @@ module FHIR
|
|
259
262
|
matches_one_profile = true if meta['type_profiles'].include?('http://hl7.org/fhir/StructureDefinition/Resource')
|
260
263
|
if !matches_one_profile && ref.reference.start_with?('#')
|
261
264
|
# we need to look at the local contained resources
|
262
|
-
r = contained_here.find { |x| x.id == ref.reference[1
|
265
|
+
r = contained_here.find { |x| x.id == ref.reference[1..] }
|
263
266
|
if !r.nil?
|
264
267
|
meta['type_profiles'].each do |p|
|
265
268
|
p = p.split('/').last
|
@@ -280,12 +283,13 @@ module FHIR
|
|
280
283
|
|
281
284
|
def check_binding_uri(uri, value)
|
282
285
|
valid = false
|
283
|
-
|
286
|
+
case uri
|
287
|
+
when 'http://hl7.org/fhir/ValueSet/content-type', 'http://www.rfc-editor.org/bcp/bcp13.txt'
|
284
288
|
matches = MIME::Types[value]
|
285
289
|
json_or_xml = value.downcase.include?('xml') || value.downcase.include?('json')
|
286
290
|
known_weird = ['text/cql', 'application/cql+text'].include?(value)
|
287
291
|
valid = json_or_xml || known_weird || (!matches.nil? && !matches.empty?)
|
288
|
-
|
292
|
+
when 'http://hl7.org/fhir/ValueSet/languages', 'http://tools.ietf.org/html/bcp47'
|
289
293
|
has_region = !(value =~ /-/).nil?
|
290
294
|
valid = !BCP47::Language.identify(value.downcase).nil? && (!has_region || !BCP47::Region.identify(value.upcase).nil?)
|
291
295
|
else
|
@@ -315,7 +319,15 @@ module FHIR
|
|
315
319
|
self
|
316
320
|
end
|
317
321
|
|
318
|
-
|
322
|
+
def find_extension(extension_source, method_name)
|
323
|
+
extension_source.select do |extension|
|
324
|
+
name = extension.url.tr('-', '_').split('/').last
|
325
|
+
anchor = name.split('#').last
|
326
|
+
(method_name.to_s == name || method_name.to_s == anchor)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
private :validate_reference_type, :check_binding_uri, :validate_field, :find_extension
|
319
331
|
end
|
320
332
|
end
|
321
333
|
end
|
@@ -12,16 +12,17 @@ module FHIR
|
|
12
12
|
|
13
13
|
# Remove entries that do not interest us: CompartmentDefinitions, OperationDefinitions, Conformance statements
|
14
14
|
hash['entry'].select! do |entry|
|
15
|
-
|
15
|
+
['StructureDefinition', 'ValueSet', 'CodeSystem', 'SearchParameter'].include? entry['resource']['resourceType']
|
16
16
|
end
|
17
17
|
|
18
18
|
# Remove unnecessary elements from the hash
|
19
19
|
hash['entry'].each do |entry|
|
20
20
|
next unless entry['resource']
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
|
22
|
+
pre_process_structuredefinition(entry['resource']) if entry['resource']['resourceType'] == 'StructureDefinition'
|
23
|
+
pre_process_valueset(entry['resource']) if entry['resource']['resourceType'] == 'ValueSet'
|
24
|
+
pre_process_codesystem(entry['resource']) if entry['resource']['resourceType'] == 'CodeSystem'
|
25
|
+
pre_process_searchparam(entry['resource']) if entry['resource']['resourceType'] == 'SearchParameter'
|
25
26
|
remove_fhir_comments(entry['resource'])
|
26
27
|
end
|
27
28
|
|
@@ -35,26 +36,30 @@ module FHIR
|
|
35
36
|
|
36
37
|
def self.pre_process_structuredefinition(hash)
|
37
38
|
# Remove large HTML narratives and unused content
|
38
|
-
|
39
|
+
['text', 'publisher', 'contact', 'description', 'requirements', 'mapping'].each { |key| hash.delete(key) }
|
39
40
|
|
40
41
|
# Remove unused descriptions within the snapshot and differential elements
|
41
|
-
|
42
|
+
['snapshot', 'differential'].each do |key|
|
42
43
|
next unless hash[key]
|
44
|
+
|
43
45
|
hash[key]['element'].each do |element|
|
44
|
-
|
46
|
+
['short', 'definition', 'comments', 'requirements', 'alias', 'mapping'].each { |subkey| element.delete(subkey) }
|
45
47
|
end
|
46
48
|
end
|
47
49
|
end
|
48
50
|
|
49
51
|
def self.pre_process_valueset(hash)
|
50
52
|
# Remove large HTML narratives and unused content
|
51
|
-
|
53
|
+
['meta', 'text', 'publisher', 'contact', 'description', 'requirements'].each { |key| hash.delete(key) }
|
52
54
|
|
53
55
|
return unless hash['compose']
|
54
|
-
|
56
|
+
|
57
|
+
['include', 'exclude'].each do |key|
|
55
58
|
next unless hash['compose'][key]
|
59
|
+
|
56
60
|
hash['compose'][key].each do |element|
|
57
61
|
next unless element['concept']
|
62
|
+
|
58
63
|
element['concept'].each do |concept|
|
59
64
|
concept.delete('designation')
|
60
65
|
end
|
@@ -64,16 +69,18 @@ module FHIR
|
|
64
69
|
|
65
70
|
def self.pre_process_codesystem(hash)
|
66
71
|
# Remove large HTML narratives and unused content
|
67
|
-
|
72
|
+
['meta', 'text', 'publisher', 'contact', 'description', 'requirements'].each { |key| hash.delete(key) }
|
68
73
|
return unless hash['concept']
|
74
|
+
|
69
75
|
hash['concept'].each do |concept|
|
70
76
|
pre_process_codesystem_concept(concept)
|
71
77
|
end
|
72
78
|
end
|
73
79
|
|
74
80
|
def self.pre_process_codesystem_concept(hash)
|
75
|
-
|
81
|
+
['extension', 'definition', 'designation'].each { |key| hash.delete(key) }
|
76
82
|
return unless hash['concept']
|
83
|
+
|
77
84
|
hash['concept'].each do |concept|
|
78
85
|
pre_process_codesystem_concept(concept)
|
79
86
|
end
|
@@ -81,15 +88,16 @@ module FHIR
|
|
81
88
|
|
82
89
|
def self.pre_process_searchparam(hash)
|
83
90
|
# Remove large HTML narratives and unused content
|
84
|
-
|
91
|
+
['id', 'url', 'name', 'date', 'publisher', 'contact', 'description', 'xpathUsage'].each { |key| hash.delete(key) }
|
85
92
|
end
|
86
93
|
|
87
94
|
def self.remove_fhir_comments(hash)
|
88
95
|
hash.delete('fhir_comments')
|
89
96
|
hash.each do |_key, value|
|
90
|
-
|
97
|
+
case value
|
98
|
+
when Hash
|
91
99
|
remove_fhir_comments(value)
|
92
|
-
|
100
|
+
when Array
|
93
101
|
value.each do |v|
|
94
102
|
remove_fhir_comments(v) if v.is_a?(Hash)
|
95
103
|
end
|
@@ -25,7 +25,7 @@ module FHIR
|
|
25
25
|
# if hash contains resourceType
|
26
26
|
# create a child node with the name==resourceType
|
27
27
|
# fill that, and place the child under the above `node`
|
28
|
-
if hash['resourceType']
|
28
|
+
if hash['resourceType'].is_a?(String)
|
29
29
|
child_name = hash['resourceType']
|
30
30
|
hash.delete('resourceType')
|
31
31
|
child = hash_to_xml_node(child_name, hash, doc)
|
@@ -34,11 +34,13 @@ module FHIR
|
|
34
34
|
end
|
35
35
|
|
36
36
|
hash.each do |key, value|
|
37
|
-
next if
|
37
|
+
next if ['extension', 'modifierExtension'].include?(name) && key == 'url'
|
38
38
|
next if key == 'id' && !FHIR::STU3::RESOURCES.include?(name)
|
39
|
-
|
39
|
+
|
40
|
+
case value
|
41
|
+
when Hash
|
40
42
|
node.add_child(hash_to_xml_node(key, value, doc))
|
41
|
-
|
43
|
+
when Array
|
42
44
|
value.each do |v|
|
43
45
|
if v.is_a?(Hash)
|
44
46
|
node.add_child(hash_to_xml_node(key, v, doc))
|
@@ -63,7 +65,7 @@ module FHIR
|
|
63
65
|
node.add_child(child)
|
64
66
|
end
|
65
67
|
end
|
66
|
-
node.set_attribute('url', hash['url']) if
|
68
|
+
node.set_attribute('url', hash['url']) if ['extension', 'modifierExtension'].include?(name)
|
67
69
|
node.set_attribute('id', hash['id']) if hash['id'] && !FHIR::STU3::RESOURCES.include?(name)
|
68
70
|
node
|
69
71
|
end
|
@@ -79,7 +81,7 @@ module FHIR
|
|
79
81
|
resource_type = doc.root.name
|
80
82
|
klass = Module.const_get("FHIR::STU3::#{resource_type}")
|
81
83
|
resource = klass.new(hash)
|
82
|
-
rescue => e
|
84
|
+
rescue StandardError => e
|
83
85
|
FHIR::STU3.logger.error("Failed to deserialize XML:\n#{e.backtrace}")
|
84
86
|
FHIR::STU3.logger.debug("XML:\n#{xml}")
|
85
87
|
resource = nil
|
@@ -109,7 +111,7 @@ module FHIR
|
|
109
111
|
end
|
110
112
|
end
|
111
113
|
end
|
112
|
-
hash['url'] = node.get_attribute('url') if
|
114
|
+
hash['url'] = node.get_attribute('url') if ['extension', 'modifierExtension'].include?(node.name)
|
113
115
|
hash['id'] = node.get_attribute('id') if node.get_attribute('id') # Testscript fixture ids (applies to any BackboneElement)
|
114
116
|
hash['resourceType'] = node.name if FHIR::STU3::RESOURCES.include?(node.name)
|
115
117
|
|
@@ -11,6 +11,7 @@ module FHIR
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
return unless methods.include? new_method
|
14
|
+
|
14
15
|
(class << self; self; end).instance_eval do
|
15
16
|
define_method(old_method) do |*args, &block|
|
16
17
|
message = "DEPRECATED: `#{old_method}` has been deprecated. Use `#{new_method}` instead. Called from #{caller.first}"
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'nokogiri'
|
2
2
|
require 'logger'
|
3
|
+
require 'uri'
|
3
4
|
|
4
5
|
module FHIR
|
5
6
|
module STU3
|
@@ -12,7 +13,7 @@ module FHIR
|
|
12
13
|
end
|
13
14
|
|
14
15
|
def self.default_logger
|
15
|
-
@default_logger ||= Logger.new(ENV['FHIR_LOGGER'] ||
|
16
|
+
@default_logger ||= Logger.new(ENV['FHIR_LOGGER'] || $stdout)
|
16
17
|
end
|
17
18
|
|
18
19
|
def self.from_contents(contents)
|
@@ -35,11 +36,11 @@ module FHIR
|
|
35
36
|
when 'string', 'markdown'
|
36
37
|
value.is_a?(String)
|
37
38
|
when 'decimal'
|
38
|
-
!(value.to_s =~ /\A([-+]?(
|
39
|
+
!(value.to_s =~ /\A([-+]?(0|([1-9][0-9]*))(\.[0-9]+)?)\Z/).nil?
|
39
40
|
when 'uri'
|
40
41
|
begin
|
41
42
|
!URI.parse(value).nil?
|
42
|
-
rescue
|
43
|
+
rescue StandardError
|
43
44
|
false
|
44
45
|
end
|
45
46
|
when 'base64binary'
|
@@ -48,7 +49,7 @@ module FHIR
|
|
48
49
|
# whitespace is not significant so we strip it out before doing the regex so that we can be sure that
|
49
50
|
# the number of characters is a multiple of 4.
|
50
51
|
# https://tools.ietf.org/html/rfc4648
|
51
|
-
!(value.to_s.gsub(/\s/, '') =~ %r{\A(|[0-9a-zA-Z
|
52
|
+
!(value.to_s.gsub(/\s/, '') =~ %r{\A(|[0-9a-zA-Z+=/]{4}+)\Z}).nil?
|
52
53
|
when 'instant'
|
53
54
|
formatted_value = value.respond_to?(:xmlschema) ? value.xmlschema : value.to_s
|
54
55
|
!(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?
|
@@ -63,16 +64,16 @@ module FHIR
|
|
63
64
|
when 'time'
|
64
65
|
!(value.to_s =~ /\A(([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\.[0-9]+)?)\Z/).nil?
|
65
66
|
when 'code'
|
66
|
-
!(value.to_s =~ /\A[^\s]+(
|
67
|
+
!(value.to_s =~ /\A[^\s]+(\s?[^\s]+)*\Z/).nil?
|
67
68
|
when 'oid'
|
68
69
|
!(value.to_s =~ /\Aurn:oid:[0-2](\.[1-9]\d*)+\Z/).nil?
|
69
70
|
when 'id'
|
70
|
-
!(value.to_s =~ /\A[A-Za-z0-9
|
71
|
+
!(value.to_s =~ /\A[A-Za-z0-9\-.]{1,64}\Z/).nil?
|
71
72
|
when 'xhtml'
|
72
73
|
fragment = Nokogiri::HTML::DocumentFragment.parse(value)
|
73
74
|
value.is_a?(String) && fragment.errors.size.zero?
|
74
75
|
when 'unsignedint'
|
75
|
-
!(value.to_s =~ /\A(
|
76
|
+
!(value.to_s =~ /\A(0|([1-9][0-9]*))\Z/).nil?
|
76
77
|
when 'positiveint'
|
77
78
|
!(value.to_s =~ /\A+?[1-9][0-9]*\Z/).nil?
|
78
79
|
else
|
@@ -26,6 +26,7 @@ module FHIR
|
|
26
26
|
def keep_children(whitelist = [])
|
27
27
|
@marked_for_keeping = true if whitelist.include?(path)
|
28
28
|
return unless @children
|
29
|
+
|
29
30
|
@children.each do |child|
|
30
31
|
child.keep_children(whitelist)
|
31
32
|
end
|
@@ -33,14 +34,16 @@ module FHIR
|
|
33
34
|
|
34
35
|
def sweep_children
|
35
36
|
return unless @children
|
37
|
+
|
36
38
|
@children.each(&:sweep_children)
|
37
|
-
@children
|
39
|
+
@children.keep_if(&:marked_for_keeping)
|
38
40
|
@marked_for_keeping = !@children.empty? || @marked_for_keeping
|
39
41
|
end
|
40
42
|
|
41
43
|
def print_children(spaces = 0)
|
42
44
|
puts "#{' ' * spaces}+#{local_name || path}"
|
43
45
|
return nil unless @children
|
46
|
+
|
44
47
|
@children.each do |child|
|
45
48
|
child.print_children(spaces + 2)
|
46
49
|
end
|
@@ -60,7 +60,7 @@ module FHIR
|
|
60
60
|
if json.is_a? String
|
61
61
|
begin
|
62
62
|
json = JSON.parse(json)
|
63
|
-
rescue => e
|
63
|
+
rescue StandardError => e
|
64
64
|
@errors << "Failed to parse JSON: #{e.message} %n #{h} %n #{e.backtrace.join("\n")}"
|
65
65
|
return false
|
66
66
|
end
|
@@ -100,13 +100,15 @@ module FHIR
|
|
100
100
|
def get_json_nodes(json, path)
|
101
101
|
results = []
|
102
102
|
return [json] if path.nil?
|
103
|
+
|
103
104
|
steps = path.split('.')
|
104
105
|
steps.each.with_index do |step, index|
|
105
|
-
|
106
|
+
case json
|
107
|
+
when Hash
|
106
108
|
json = json[step]
|
107
|
-
|
109
|
+
when Array
|
108
110
|
json.each do |e|
|
109
|
-
results << get_json_nodes(e, steps[index
|
111
|
+
results << get_json_nodes(e, steps[index..].join('.'))
|
110
112
|
end
|
111
113
|
return results.flatten!
|
112
114
|
else
|
@@ -126,7 +128,7 @@ module FHIR
|
|
126
128
|
|
127
129
|
def verify_element(element, json)
|
128
130
|
path = element.local_name || element.path
|
129
|
-
path = path[(@hierarchy.path.size + 1)
|
131
|
+
path = path[(@hierarchy.path.size + 1)..] if path.start_with? @hierarchy.path
|
130
132
|
|
131
133
|
if element.type && !element.type.empty?
|
132
134
|
data_type_found = element.type.first.code
|
@@ -158,10 +160,11 @@ module FHIR
|
|
158
160
|
verify_cardinality(element, nodes)
|
159
161
|
|
160
162
|
return if nodes.empty?
|
163
|
+
|
161
164
|
# Check the datatype for each node, only if the element has one declared, and it isn't the root element
|
162
165
|
if !element.type.empty? && element.path != id
|
163
166
|
# element.type not being empty implies data_type_found != nil, for valid profiles
|
164
|
-
codeable_concept_pattern = element.pattern
|
167
|
+
codeable_concept_pattern = element.pattern&.is_a?(FHIR::STU3::CodeableConcept)
|
165
168
|
codeable_concept_binding = element.binding
|
166
169
|
matching_pattern = false
|
167
170
|
nodes.each do |value|
|
@@ -201,19 +204,20 @@ module FHIR
|
|
201
204
|
end
|
202
205
|
elsif data_type_found == 'CodeableConcept' && codeable_concept_binding
|
203
206
|
binding_issues =
|
204
|
-
|
207
|
+
case element.binding.strength
|
208
|
+
when 'extensible'
|
205
209
|
@warnings
|
206
|
-
|
210
|
+
when 'required'
|
207
211
|
@errors
|
208
212
|
else # e.g., example-strength or unspecified
|
209
213
|
[] # Drop issues errors on the floor, in throwaway array
|
210
214
|
end
|
211
215
|
|
212
|
-
valueset_uri = element.binding
|
216
|
+
valueset_uri = element.binding&.valueSetReference && element.binding.valueSetReference.reference
|
213
217
|
vcc = FHIR::STU3::CodeableConcept.new(value)
|
214
218
|
if valueset_uri && self.class.vs_validators[valueset_uri]
|
215
219
|
check_fn = self.class.vs_validators[valueset_uri]
|
216
|
-
has_valid_code = vcc.coding
|
220
|
+
has_valid_code = vcc.coding&.any? { |c| check_fn.call(c) }
|
217
221
|
unless has_valid_code
|
218
222
|
binding_issues << "#{describe_element(element)} has no codings from #{valueset_uri}. Codings evaluated: #{vcc.to_json}"
|
219
223
|
end
|
@@ -262,13 +266,14 @@ module FHIR
|
|
262
266
|
# elsewhere. There is no good way to determine "where" you should evaluate the expression.
|
263
267
|
element.constraint.each do |constraint|
|
264
268
|
next unless constraint.expression && !nodes.empty?
|
269
|
+
|
265
270
|
nodes.each do |node|
|
266
271
|
result = FluentPath::STU3.evaluate(constraint.expression, node)
|
267
272
|
if !result && constraint.severity == 'error'
|
268
273
|
@errors << "#{describe_element(element)}: FluentPath expression evaluates to false for #{name} invariant rule #{constraint.key}: #{constraint.human}"
|
269
274
|
@errors << node.to_s
|
270
275
|
end
|
271
|
-
rescue
|
276
|
+
rescue StandardError
|
272
277
|
@warnings << "#{describe_element(element)}: unable to evaluate FluentPath expression against JSON for #{name} invariant rule #{constraint.key}: #{constraint.human}"
|
273
278
|
@warnings << node.to_s
|
274
279
|
end
|
@@ -276,6 +281,7 @@ module FHIR
|
|
276
281
|
|
277
282
|
# check children if the element has any
|
278
283
|
return unless element.children
|
284
|
+
|
279
285
|
nodes.each do |node|
|
280
286
|
element.children.each do |child|
|
281
287
|
verify_element(child, node)
|
@@ -310,7 +316,7 @@ module FHIR
|
|
310
316
|
@errors += definition.errors
|
311
317
|
@warnings += definition.warnings
|
312
318
|
end
|
313
|
-
rescue
|
319
|
+
rescue StandardError
|
314
320
|
@errors << "Unable to verify #{data_type_code} as a FHIR Resource."
|
315
321
|
end
|
316
322
|
return ret_val
|
@@ -334,7 +340,7 @@ module FHIR
|
|
334
340
|
@errors += definition.errors
|
335
341
|
@warnings += definition.warnings
|
336
342
|
end
|
337
|
-
rescue
|
343
|
+
rescue StandardError
|
338
344
|
@errors << "Unable to verify #{resource_type} as a FHIR Resource."
|
339
345
|
end
|
340
346
|
ret_val
|
@@ -360,7 +366,7 @@ module FHIR
|
|
360
366
|
@errors += definition.errors
|
361
367
|
@warnings += definition.warnings
|
362
368
|
end
|
363
|
-
rescue
|
369
|
+
rescue StandardError
|
364
370
|
@errors << "Unable to verify #{data_type_code} as a FHIR type."
|
365
371
|
end
|
366
372
|
ret_val
|
@@ -378,13 +384,13 @@ module FHIR
|
|
378
384
|
|
379
385
|
matching_type = 0
|
380
386
|
|
381
|
-
if
|
387
|
+
if ['http://hl7.org/fhir/ValueSet/content-type', 'http://www.rfc-editor.org/bcp/bcp13.txt'].include?(vs_uri)
|
382
388
|
matches = MIME::Types[value]
|
383
389
|
if (matches.nil? || matches.size.zero?) && !some_type_of_xml_or_json?(value)
|
384
390
|
@errors << "#{element.path} has invalid mime-type: '#{value}'"
|
385
391
|
matching_type -= 1 if element.binding.strength == 'required'
|
386
392
|
end
|
387
|
-
elsif
|
393
|
+
elsif ['http://hl7.org/fhir/ValueSet/languages', 'http://tools.ietf.org/html/bcp47'].include?(vs_uri)
|
388
394
|
has_region = !(value =~ /-/).nil?
|
389
395
|
valid = !BCP47::Language.identify(value.downcase).nil? && (!has_region || !BCP47::Region.identify(value.upcase).nil?)
|
390
396
|
unless valid
|
@@ -417,9 +423,10 @@ module FHIR
|
|
417
423
|
|
418
424
|
def some_type_of_xml_or_json?(code)
|
419
425
|
m = code.downcase
|
420
|
-
return true if
|
426
|
+
return true if ['xml', 'json'].include?(m)
|
421
427
|
return true if m.start_with?('application/', 'text/') && m.end_with?('json', 'xml')
|
422
428
|
return true if m.start_with?('application/xml', 'text/xml', 'application/json', 'text/json')
|
429
|
+
|
423
430
|
false
|
424
431
|
end
|
425
432
|
deprecate :is_some_type_of_xml_or_json, :some_type_of_xml_or_json?
|