fhir_stu3_models 3.1.1 → 3.2.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/.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?
|