fhir_models 1.8.2 → 1.8.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,22 @@
1
+ module FHIR
2
+ # add support for deprecating instance and class methods
3
+ module Deprecate
4
+ def deprecate(old_method, new_method)
5
+ if instance_methods.include? new_method
6
+ define_method(old_method) do |*args, &block|
7
+ message = "DEPRECATED: `#{old_method}` has been deprecated. Use `#{new_method}` instead. Called from #{caller.first}"
8
+ FHIR.logger.warn message
9
+ send(new_method, *args, &block)
10
+ end
11
+ end
12
+ return unless methods.include? new_method
13
+ (class << self; self; end).instance_eval do
14
+ define_method(old_method) do |*args, &block|
15
+ message = "DEPRECATED: `#{old_method}` has been deprecated. Use `#{new_method}` instead. Called from #{caller.first}"
16
+ FHIR.logger.warn message
17
+ send(new_method, *args, &block)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,5 +1,6 @@
1
1
  require 'nokogiri'
2
2
  require 'logger'
3
+
3
4
  module FHIR
4
5
  def self.logger
5
6
  @logger || default_logger
@@ -21,4 +22,61 @@ module FHIR
21
22
  FHIR::Json.from_json(contents)
22
23
  end
23
24
  end
25
+
26
+ # TODO: pull regexes from metadata
27
+ def self.primitive?(datatype:, value:)
28
+ # Remaining data types: handle special cases before checking type StructureDefinitions
29
+ case datatype.downcase
30
+ when 'boolean'
31
+ !(value.to_s =~ /\A(true|false)\Z/).nil?
32
+ when 'integer'
33
+ !(value.to_s =~ /\A(0|[-+]?[1-9][0-9]*)\Z/).nil?
34
+ when 'string', 'markdown'
35
+ value.is_a?(String)
36
+ when 'decimal'
37
+ !(value.to_s =~ /\A([-+]?([0]|([1-9][0-9]*))(\.[0-9]+)?)\Z/).nil?
38
+ when 'uri'
39
+ begin
40
+ !URI.parse(value).nil?
41
+ rescue
42
+ false
43
+ end
44
+ when 'base64binary'
45
+ # According to RFC-4648 base64binary encoding includes digits 0-9, a-z, A-Z, =, +, /, and whitespace
46
+ # an empty string is considered valid
47
+ # whitespace is not significant so we strip it out before doing the regex so that we can be sure that
48
+ # the number of characters is a multiple of 4.
49
+ # https://tools.ietf.org/html/rfc4648
50
+ !(value.to_s.gsub(/\s/, '') =~ %r{\A(|[0-9a-zA-Z\+=/]{4}+)\Z}).nil?
51
+ when 'instant'
52
+ formatted_value = value.respond_to?(:xmlschema) ? value.xmlschema : value.to_s
53
+ !(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?
54
+ when 'date'
55
+ !(value.to_s =~ /\A(-?[0-9]{4}(-(0[1-9]|1[0-2])(-(0[0-9]|[1-2][0-9]|3[0-1]))?)?)\Z/).nil?
56
+ # NOTE: we don't try to instantiate and verify a Date because ruby does not natively suppport
57
+ # partial dates, which the FHIR standard allows.
58
+ when 'datetime'
59
+ !(value.to_s =~ /\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?
60
+ # NOTE: we don't try to instantiate and verify a DateTime because ruby does not natively suppport
61
+ # partial dates, which the FHIR standard allows.
62
+ when 'time'
63
+ !(value.to_s =~ /\A(([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\.[0-9]+)?)\Z/).nil?
64
+ when 'code'
65
+ !(value.to_s =~ /\A[^\s]+([\s]?[^\s]+)*\Z/).nil?
66
+ when 'oid'
67
+ !(value.to_s =~ /\Aurn:oid:[0-2](\.[1-9]\d*)+\Z/).nil?
68
+ when 'id'
69
+ !(value.to_s =~ /\A[A-Za-z0-9\-\.]{1,64}\Z/).nil?
70
+ when 'xhtml'
71
+ fragment = Nokogiri::HTML::DocumentFragment.parse(value)
72
+ value.is_a?(String) && fragment.errors.size.zero?
73
+ when 'unsignedint'
74
+ !(value.to_s =~ /\A([0]|([1-9][0-9]*))\Z/).nil?
75
+ when 'positiveint'
76
+ !(value.to_s =~ /\A+?[1-9][0-9]*\Z/).nil?
77
+ else
78
+ FHIR.logger.warn "Unable to check #{value} for datatype #{datatype}"
79
+ false
80
+ end
81
+ end
24
82
  end
@@ -21,6 +21,6 @@ module FHIR
21
21
  'xhtml' => {'type'=>'string'}
22
22
  }
23
23
  TYPES = ['Reference', 'Quantity', 'Period', 'Attachment', 'Duration', 'Count', 'Range', 'Annotation', 'Money', 'Identifier', 'Coding', 'Signature', 'SampledData', 'Ratio', 'Distance', 'Age', 'CodeableConcept', 'Extension', 'BackboneElement', 'Narrative', 'Element', 'Meta', 'Address', 'TriggerDefinition', 'Contributor', 'DataRequirement', 'RelatedArtifact', 'ContactDetail', 'HumanName', 'ContactPoint', 'UsageContext', 'Timing', 'ElementDefinition', 'DosageInstruction', 'ParameterDefinition']
24
- RESOURCES = ['CodeSystem', 'ValueSet', 'DomainResource', 'Parameters', 'Resource', 'Account', 'ActivityDefinition', 'AllergyIntolerance', 'Appointment', 'AppointmentResponse', 'AuditEvent', 'Basic', 'Binary', 'BodySite', 'Bundle', 'CapabilityStatement', 'CarePlan', 'CareTeam', 'Claim', 'ClaimResponse', 'ClinicalImpression', 'Communication', 'CommunicationRequest', 'CompartmentDefinition', 'Composition', 'ConceptMap', 'Condition', 'Consent', 'Contract', 'Coverage', 'DataElement', 'DetectedIssue', 'Device', 'DeviceComponent', 'DeviceMetric', 'DeviceUseRequest', 'DeviceUseStatement', 'DiagnosticReport', 'DiagnosticRequest', 'DocumentManifest', 'DocumentReference', 'EligibilityRequest', 'EligibilityResponse', 'Encounter', 'Endpoint', 'EnrollmentRequest', 'EnrollmentResponse', 'EpisodeOfCare', 'ExpansionProfile', 'ExplanationOfBenefit', 'FamilyMemberHistory', 'Flag', 'Goal', 'Group', 'GuidanceResponse', 'HealthcareService', 'ImagingManifest', 'ImagingStudy', 'Immunization', 'ImmunizationRecommendation', 'ImplementationGuide', 'Library', 'Linkage', 'List', 'Location', 'Measure', 'MeasureReport', 'Media', 'Medication', 'MedicationAdministration', 'MedicationDispense', 'MedicationRequest', 'MedicationStatement', 'MessageDefinition', 'MessageHeader', 'NamingSystem', 'NutritionRequest', 'Observation', 'OperationDefinition', 'OperationOutcome', 'Organization', 'Patient', 'PaymentNotice', 'PaymentReconciliation', 'Person', 'PlanDefinition', 'Practitioner', 'PractitionerRole', 'Procedure', 'ProcedureRequest', 'ProcessRequest', 'ProcessResponse', 'Provenance', 'Questionnaire', 'QuestionnaireResponse', 'ReferralRequest', 'RelatedPerson', 'RequestGroup', 'ResearchStudy', 'ResearchSubject', 'RiskAssessment', 'Schedule', 'SearchParameter', 'Sequence', 'ServiceDefinition', 'Slot', 'Specimen', 'StructureDefinition', 'StructureMap', 'Subscription', 'Substance', 'SupplyDelivery', 'SupplyRequest', 'Task', 'TestReport', 'TestScript', 'VisionPrescription', 'MetadataResource']
24
+ RESOURCES = ['CodeSystem', 'ValueSet', 'DomainResource', 'Parameters', 'Resource', 'Account', 'ActivityDefinition', 'AllergyIntolerance', 'Appointment', 'AppointmentResponse', 'AuditEvent', 'Basic', 'Binary', 'BodySite', 'Bundle', 'CapabilityStatement', 'CarePlan', 'CareTeam', 'Claim', 'ClaimResponse', 'ClinicalImpression', 'Communication', 'CommunicationRequest', 'CompartmentDefinition', 'Composition', 'ConceptMap', 'Condition', 'Consent', 'Contract', 'Coverage', 'DataElement', 'DetectedIssue', 'Device', 'DeviceComponent', 'DeviceMetric', 'DeviceUseRequest', 'DeviceUseStatement', 'DiagnosticReport', 'DiagnosticRequest', 'DocumentManifest', 'DocumentReference', 'EligibilityRequest', 'EligibilityResponse', 'Encounter', 'Endpoint', 'EnrollmentRequest', 'EnrollmentResponse', 'EpisodeOfCare', 'ExpansionProfile', 'ExplanationOfBenefit', 'FamilyMemberHistory', 'Flag', 'Goal', 'Group', 'GuidanceResponse', 'HealthcareService', 'ImagingManifest', 'ImagingStudy', 'Immunization', 'ImmunizationRecommendation', 'ImplementationGuide', 'Library', 'Linkage', 'List', 'Location', 'Measure', 'MeasureReport', 'Media', 'Medication', 'MedicationAdministration', 'MedicationDispense', 'MedicationRequest', 'MedicationStatement', 'MessageDefinition', 'MessageHeader', 'NamingSystem', 'NutritionRequest', 'Observation', 'OperationDefinition', 'OperationOutcome', 'Organization', 'Patient', 'PaymentNotice', 'PaymentReconciliation', 'Person', 'PlanDefinition', 'Practitioner', 'PractitionerRole', 'Procedure', 'ProcedureRequest', 'ProcessRequest', 'ProcessResponse', 'Provenance', 'Questionnaire', 'QuestionnaireResponse', 'ReferralRequest', 'RelatedPerson', 'RequestGroup', 'ResearchStudy', 'ResearchSubject', 'RiskAssessment', 'Schedule', 'SearchParameter', 'Sequence', 'ServiceDefinition', 'Slot', 'Specimen', 'StructureDefinition', 'StructureMap', 'Subscription', 'Substance', 'SupplyDelivery', 'SupplyRequest', 'Task', 'TestReport', 'TestScript', 'VisionPrescription']
25
25
 
26
26
  end
@@ -0,0 +1,47 @@
1
+ module FHIR
2
+ # Extend ElementDefinition for profile validation code
3
+ class ElementDefinition < FHIR::Model
4
+ # children is used to hierarchically arrange elements
5
+ # so profile validation is easier to compute
6
+ attr_accessor :children
7
+ attr_accessor :marked_for_keeping
8
+
9
+ def add_descendent(element)
10
+ @children = [] if @children.nil?
11
+ if @children.last && element.path.start_with?(@children.last.path)
12
+ if element.path == @children.last.path
13
+ # slicing
14
+ @children << element
15
+ else
16
+ @children.last.add_descendent(element)
17
+ end
18
+ else
19
+ @children << element
20
+ end
21
+ end
22
+
23
+ def keep_children(whitelist = [])
24
+ @marked_for_keeping = true if whitelist.include?(path)
25
+ return unless @children
26
+ @children.each do |child|
27
+ child.keep_children(whitelist)
28
+ end
29
+ end
30
+
31
+ def sweep_children
32
+ return unless @children
33
+ @children.each(&:sweep_children)
34
+ @children = @children.keep_if(&:marked_for_keeping)
35
+ @marked_for_keeping = !@children.empty? || @marked_for_keeping
36
+ end
37
+
38
+ def print_children(spaces = 0)
39
+ puts "#{' ' * spaces}+#{path}"
40
+ return nil unless @children
41
+ @children.each do |child|
42
+ child.print_children(spaces + 2)
43
+ end
44
+ nil
45
+ end
46
+ end
47
+ end
@@ -1,381 +1,15 @@
1
+ # Extend StructureDefinition for profile validation code
1
2
  require 'nokogiri'
2
3
  require 'yaml'
3
4
  require 'bcp47'
4
5
 
5
6
  module FHIR
6
7
  class StructureDefinition
8
+ extend FHIR::Deprecate
7
9
  attr_accessor :finding
8
10
  attr_accessor :errors
9
11
  attr_accessor :warnings
10
-
11
- # -------------------------------------------------------------------------
12
- # Profile Comparison
13
- # -------------------------------------------------------------------------
14
-
15
- # Checks whether or not "another_definition" is compatible with this definition.
16
- # If they have conflicting elements, restrictions, bindings, modifying extensions, etc.
17
- def is_compatible?(another_definition)
18
- @errors = []
19
- @warnings = []
20
-
21
- @finding = FHIR::StructureDefinitionFinding.new
22
- @finding.resourceType = snapshot.element[0].path
23
- @finding.profileIdA = id
24
- @finding.profileIdB = another_definition.id if another_definition.respond_to?(:id)
25
-
26
- if !(another_definition.is_a? FHIR::StructureDefinition)
27
- @errors << @finding.error('', '', 'Not a StructureDefinition', 'StructureDefinition', another_definition.class.name.to_s)
28
- return false
29
- elsif another_definition.snapshot.element[0].path != snapshot.element[0].path
30
- @errors << @finding.error('', '', 'Incompatible resourceType', @finding.resourceType, another_definition.snapshot.element[0].path.to_s)
31
- return false
32
- end
33
-
34
- left_elements = Array.new(snapshot.element)
35
- right_elements = Array.new(another_definition.snapshot.element)
36
-
37
- left_paths = left_elements.map(&:path)
38
- right_paths = right_elements.map(&:path)
39
-
40
- # StructureDefinitions don't always include all base attributes (for example, of a ContactPoint)
41
- # if nothing is modified from the base definition, so we have to add them in if they are missing.
42
- base_definition = FHIR::Definitions.get_resource_definition(snapshot.element[0].path)
43
- base_elements = base_definition.snapshot.element
44
-
45
- left_missing = right_paths - left_paths
46
- # left_missing_roots = left_missing.map{|e| e.split('.')[0..-2].join('.') }.uniq
47
- add_missing_elements(id, left_missing, left_elements, base_elements)
48
-
49
- right_missing = left_paths - right_paths
50
- # right_missing_roots = right_missing.map{|e| e.split('.')[0..-2].join('.') }.uniq
51
- add_missing_elements(another_definition.id, right_missing, right_elements, base_elements)
52
-
53
- # update paths
54
- left_paths = left_elements.map(&:path)
55
- right_paths = right_elements.map(&:path)
56
-
57
- # recalculate the missing attributes
58
- left_missing = right_paths - left_paths
59
- right_missing = left_paths - right_paths
60
-
61
- # generate warnings for missing fields (ignoring extensions)
62
- left_missing.each do |e|
63
- next if e.include? 'extension'
64
- elem = get_element_by_path(e, right_elements)
65
- if !elem.min.nil? && elem.min > 0
66
- @errors << @finding.error(e, 'min', 'Missing REQUIRED element', 'Missing', elem.min.to_s)
67
- elsif elem.isModifier == true
68
- @errors << @finding.error(e, 'isModifier', 'Missing MODIFIER element', 'Missing', elem.isModifier.to_s)
69
- else
70
- @warnings << @finding.warning(e, '', 'Missing element', 'Missing', 'Defined')
71
- end
72
- end
73
- right_missing.each do |e|
74
- next if e.include? 'extension'
75
- elem = get_element_by_path(e, left_elements)
76
- if !elem.min.nil? && elem.min > 0
77
- @errors << @finding.error(e, 'min', 'Missing REQUIRED element', elem.min.to_s, 'Missing')
78
- elsif elem.isModifier == true
79
- @errors << @finding.error(e, 'isModifier', 'Missing MODIFIER element', elem.isModifier.to_s, 'Missing')
80
- else
81
- @warnings << @finding.warning(e, '', 'Missing element', 'Defined', 'Missing')
82
- end
83
- end
84
-
85
- left_extensions = []
86
- right_extensions = []
87
-
88
- # compare elements, starting with the elements in this definition
89
- left_elements.each do |x|
90
- if x.path.include? 'extension'
91
- # handle extensions separately
92
- left_extensions << x
93
- else
94
- y = get_element_by_path(x.path, right_elements)
95
- compare_element_definitions(x, y, another_definition)
96
- end
97
- end
98
-
99
- # now compare elements defined in the other definition, if we haven't already looked at them
100
- right_elements.each do |y|
101
- if y.path.include? 'extension'
102
- # handle extensions separately
103
- right_extensions << y
104
- elsif left_missing.include? y.path
105
- x = get_element_by_path(y.path, left_elements)
106
- compare_element_definitions(x, y, another_definition)
107
- end
108
- end
109
-
110
- # finally, compare the extensions.
111
- checked_extensions = []
112
- left_extensions.each do |x|
113
- y = get_extension(x.name, right_extensions)
114
- unless y.nil?
115
- # both profiles share an extension with the same name
116
- checked_extensions << x.name
117
- compare_extension_definition(x, y, another_definition)
118
- end
119
- y = get_extension(x.type[0].profile, right_extensions)
120
- if !y.nil? && x.name != y.name
121
- # both profiles share the same extension definition but with a different name
122
- checked_extensions << x.name
123
- checked_extensions << y.name
124
- compare_element_definitions(x, y, another_definition)
125
- end
126
- end
127
- right_extensions.each do |y|
128
- next if checked_extensions.include?(y.name)
129
- x = get_extension(y.name, left_extensions)
130
- unless x.nil?
131
- # both profiles share an extension with the same name
132
- checked_extensions << y.name
133
- compare_extension_definition(x, y, another_definition)
134
- end
135
- x = get_extension(y.type[0].profile, left_extensions)
136
- if !x.nil? && x.name != y.name && !checked_extensions.include?(x.name)
137
- # both profiles share the same extension definition but with a different name
138
- checked_extensions << x.name
139
- checked_extensions << y.name
140
- compare_element_definitions(x, y, another_definition)
141
- end
142
- end
143
- @errors.flatten!
144
- @warnings.flatten!
145
- @errors.size.zero?
146
- end
147
-
148
- def get_element_by_path(path, elements = snapshot.element)
149
- elements.detect { |element| element.path == path }
150
- end
151
-
152
- def get_extension(extension, elements = snapshot.element)
153
- elements.each do |element|
154
- if element.path.include?('extension') || element.type.map(&:code).include?('Extension')
155
- return element if element.name == extension || element.type.map(&:profile).include?(extension)
156
- end
157
- end
158
- nil
159
- end
160
-
161
- # private
162
- # name -- name of the profile we're fixing
163
- # missing_paths -- list of paths that we're adding
164
- # elements -- list of elements currently defined in the profile
165
- # base_elements -- list of elements defined in the base resource the profile extends
166
- def add_missing_elements(_name, missing_paths, elements, base_elements)
167
- variable_paths = elements.map(&:path).grep(/\[x\]/).map { |e| e[0..-4] }
168
- variable_paths << base_elements.map(&:path).grep(/\[x\]/).map { |e| e[0..-4] }
169
- variable_paths.flatten!.uniq!
170
-
171
- missing_paths.each do |path|
172
- # Skip extensions
173
- next if path.include? 'extension'
174
-
175
- # Skip the variable paths that end with "[x]"
176
- next if variable_paths.any? { |variable| path.starts_with?(variable) }
177
-
178
- elem = get_element_by_path(path, base_elements)
179
- unless elem.nil?
180
- # _DEEP_ copy
181
- elements << FHIR::ElementDefinition.from_fhir_json(elem.to_fhir_json)
182
- next
183
- end
184
-
185
- x = path.split('.')
186
- root = x.first(x.size - 1).join('.')
187
- next unless root.include? '.'
188
- # get the root element to fill in the details
189
- elem = get_element_by_path(root, elements)
190
- # get the data type definition to fill in the details
191
- # assume missing elements are from first data type (gross)
192
- next if elem.type.nil? || elem.type.empty?
193
- type_def = FHIR::Definitions.get_type_definition(elem.type[0].code)
194
- next if type_def.nil?
195
- type_elements = Array.new(type_def.snapshot.element)
196
- # _DEEP_ copy
197
- type_elements.map! do |e| # {|e| FHIR::ElementDefinition.from_fhir_json(e.to_fhir_json) }
198
- FHIR::ElementDefinition.from_fhir_json(e.to_fhir_json)
199
- end
200
- # Fix path names
201
- type_root = String.new(type_elements[0].path)
202
- type_elements.each { |e| e.path.gsub!(type_root, root) }
203
- # finally, add the missing element definitions
204
- # one by one -- only if they are not already present (i.e. do not override)
205
- type_elements.each do |z|
206
- y = get_element_by_path(z.path, elements)
207
- next unless y.nil?
208
- elements << z
209
- # else
210
- # @warnings << "StructureDefinition #{name} already contains #{z.path}"
211
- end
212
- elements.uniq!
213
- # else
214
- # @warnings << "StructureDefinition #{name} missing -- #{path}"
215
- end
216
- end
217
-
218
- # private
219
- def compare_extension_definition(x, y, another_definition)
220
- x_profiles = x.type.map(&:profile)
221
- y_profiles = y.type.map(&:profile)
222
- x_only = x_profiles - y_profiles
223
- shared = x_profiles - x_only
224
-
225
- if !shared.nil? && shared.size.zero?
226
- # same name, but different profiles
227
- # maybe the profiles are the same, just with different URLs...
228
- # ... so we have to compare them, if we can.
229
- @warnings << @finding.warning("#{x.path} (#{x.name})", 'type.profile', 'Different Profiles', x_profiles.to_s, y_profiles.to_s)
230
- x_extension = FHIR::Definitions.get_extension_definition(x.type[0].profile)
231
- y_extension = FHIR::Definitions.get_extension_definition(y.type[0].profile)
232
- if !x_extension.nil? && !y_extension.nil?
233
- x_extension.is_compatible?(y_extension)
234
- @errors << x_extension.errors
235
- @warnings << x_extension.warnings
236
- else
237
- @warnings << @finding.warning("#{x.path} (#{x.name})", '', 'Could not find extension definitions to compare.', '', '')
238
- end
239
- else
240
- compare_element_definitions(x, y, another_definition)
241
- end
242
- end
243
-
244
- # private
245
- def compare_element_definitions(x, y, another_definition)
246
- return if x.nil? || y.nil? || another_definition.nil?
247
-
248
- # check cardinality
249
- x_min = x.min || 0
250
- x_max = x.max == '*' ? Float::INFINITY : x.max.to_i
251
- y_min = y.min || 0
252
- y_max = y.max == '*' ? Float::INFINITY : y.max.to_i
253
-
254
- if x_min.nil? || x.max.nil? || y_min.nil? || y.max.nil?
255
- @errors << @finding.error(x.path.to_s, 'min/max', 'Unknown cardinality', "#{x_min}..#{x.max}", "#{y_min}..#{y.max}")
256
- elsif (x_min > y_max) || (x_max < y_min)
257
- @errors << @finding.error(x.path.to_s, 'min/max', 'Incompatible cardinality', "#{x_min}..#{x.max}", "#{y_min}..#{y.max}")
258
- elsif (x_min != y_min) || (x_max != y_max)
259
- @warnings << @finding.warning(x.path.to_s, 'min/max', 'Inconsistent cardinality', "#{x_min}..#{x.max}", "#{y_min}..#{y.max}")
260
- end
261
-
262
- # check data types
263
- x_types = x.type.map(&:code)
264
- y_types = y.type.map(&:code)
265
- x_only = x_types - y_types
266
- y_only = y_types - x_types
267
- shared = x_types - x_only
268
-
269
- if !shared.nil? && shared.size.zero? && !x_types.empty? && !y_types.empty? && !x.constraint.empty? && !y.constraint.empty?
270
- @errors << @finding.error(x.path.to_s, 'type.code', 'Incompatible data types', x_types.to_s, y_types.to_s)
271
- end
272
- if !x_only.nil? && !x_only.empty?
273
- @warnings << @finding.warning(x.path.to_s, 'type.code', 'Allows additional data types', x_only.to_s, 'not allowed')
274
- end
275
- if !y_only.nil? && !y_only.empty?
276
- @warnings << @finding.warning(x.path.to_s, 'type.code', 'Allows additional data types', 'not allowed', y_only.to_s)
277
- end
278
-
279
- # check bindings
280
- if x.binding.nil? && !y.binding.nil?
281
- val = y.binding.valueSetUri || y.binding.valueSetReference.try(:reference) || y.binding.description
282
- @warnings << @finding.warning(x.path.to_s, 'binding', 'Inconsistent binding', '', val)
283
- elsif !x.binding.nil? && y.binding.nil?
284
- val = x.binding.valueSetUri || x.binding.valueSetReference.try(:reference) || x.binding.description
285
- @warnings << @finding.warning(x.path.to_s, 'binding', 'Inconsistent binding', val, '')
286
- elsif !x.binding.nil? && !y.binding.nil?
287
- x_vs = x.binding.valueSetUri || x.binding.valueSetReference.try(:reference)
288
- y_vs = y.binding.valueSetUri || y.binding.valueSetReference.try(:reference)
289
- if x_vs != y_vs
290
- if x.binding.strength == 'required' || y.binding.strength == 'required'
291
- @errors << @finding.error(x.path.to_s, 'binding.strength', 'Incompatible bindings', "#{x.binding.strength} #{x_vs}", "#{y.binding.strength} #{y_vs}")
292
- else
293
- @warnings << @finding.warning(x.path.to_s, 'binding.strength', 'Inconsistent bindings', "#{x.binding.strength} #{x_vs}", "#{y.binding.strength} #{y_vs}")
294
- end
295
- end
296
- end
297
-
298
- # check default values
299
- if x.defaultValue.try(:type) != y.defaultValue.try(:type)
300
- @errors << @finding.error(x.path.to_s, 'defaultValue', 'Incompatible default type', x.defaultValue.try(:type).to_s, y.defaultValue.try(:type).to_s)
301
- end
302
- if x.defaultValue.try(:value) != y.defaultValue.try(:value)
303
- @errors << @finding.error(x.path.to_s, 'defaultValue', 'Incompatible default value', x.defaultValue.try(:value).to_s, y.defaultValue.try(:value).to_s)
304
- end
305
-
306
- # check meaning when missing
307
- if x.meaningWhenMissing != y.meaningWhenMissing
308
- @errors << @finding.error(x.path.to_s, 'meaningWhenMissing', 'Inconsistent missing meaning', x.meaningWhenMissing.tr(',', ';').to_s, y.meaningWhenMissing.tr(',', ';').to_s)
309
- end
310
-
311
- # check fixed values
312
- if x.fixed.try(:type) != y.fixed.try(:type)
313
- @errors << @finding.error(x.path.to_s, 'fixed', 'Incompatible fixed type', x.fixed.try(:type).to_s, y.fixed.try(:type).to_s)
314
- end
315
- if x.fixed != y.fixed
316
- xfv = x.fixed.try(:value)
317
- xfv = xfv.to_xml.delete(/\n/) if x.fixed.try(:value).methods.include?(:to_xml)
318
- yfv = y.fixed.try(:value)
319
- yfv = yfv.to_xml.delete(/\n/) if y.fixed.try(:value).methods.include?(:to_xml)
320
- @errors << @finding.error(x.path.to_s, 'fixed', 'Incompatible fixed value', xfv.to_s, yfv.to_s)
321
- end
322
-
323
- # check min values
324
- if x.min.try(:type) != y.min.try(:type)
325
- @errors << @finding.error(x.path.to_s, 'min', 'Incompatible min type', x.min.try(:type).to_s, y.min.try(:type).to_s)
326
- end
327
- if x.min.try(:value) != y.min.try(:value)
328
- @errors << @finding.error(x.path.to_s, 'min', 'Incompatible min value', x.min.try(:value).to_s, y.min.try(:value).to_s)
329
- end
330
-
331
- # check max values
332
- if x.max.try(:type) != y.max.try(:type)
333
- @errors << @finding.error(x.path.to_s, 'max', 'Incompatible max type', x.max.try(:type).to_s, y.max.try(:type).to_s)
334
- end
335
- if x.max.try(:value) != y.max.try(:value)
336
- @errors << @finding.error(x.path.to_s, 'max', 'Incompatible max value', x.max.try(:value).to_s, y.max.try(:value).to_s)
337
- end
338
-
339
- # check pattern values
340
- if x.pattern.try(:type) != y.pattern.try(:type)
341
- @errors << @finding.error(x.path.to_s, 'pattern', 'Incompatible pattern type', x.pattern.try(:type).to_s, y.pattern.try(:type).to_s)
342
- end
343
- if x.pattern.try(:value) != y.pattern.try(:value)
344
- @errors << @finding.error(x.path.to_s, 'pattern', 'Incompatible pattern value', x.pattern.try(:value).to_s, y.pattern.try(:value).to_s)
345
- end
346
-
347
- # maxLength (for Strings)
348
- if x.maxLength != y.maxLength
349
- @warnings << @finding.warning(x.path.to_s, 'maxLength', 'Inconsistent maximum length', x.maxLength.to_s, y.maxLength.to_s)
350
- end
351
-
352
- # constraints
353
- x_constraints = x.constraint.map(&:xpath)
354
- y_constraints = y.constraint.map(&:xpath)
355
- x_only = x_constraints - y_constraints
356
- y_only = y_constraints - x_constraints
357
- shared = x_constraints - x_only
358
-
359
- if !shared.nil? && shared.size.zero? && !x.constraint.empty? && !y.constraint.empty?
360
- @errors << @finding.error(x.path.to_s, 'constraint.xpath', 'Incompatible constraints', x_constraints.map { |z| z.tr(',', ';') }.join(' && ').to_s, y_constraints.map { |z| z.tr(',', ';') }.join(' && ').to_s)
361
- end
362
- if !x_only.nil? && !x_only.empty?
363
- @errors << @finding.error(x.path.to_s, 'constraint.xpath', 'Additional constraints', x_constraints.map { |z| z.tr(',', ';') }.join(' && ').to_s, '')
364
- end
365
- if !y_only.nil? && !y_only.empty?
366
- @errors << @finding.error(x.path.to_s, 'constraint.xpath', 'Additional constraints', '', y_constraints.map { |z| z.tr(',', ';') }.join(' && ').to_s)
367
- end
368
-
369
- # mustSupports
370
- if x.mustSupport != y.mustSupport
371
- @warnings << @finding.warning(x.path.to_s, 'mustSupport', 'Inconsistent mustSupport', (x.mustSupport || false).to_s, (y.mustSupport || false).to_s)
372
- end
373
-
374
- # isModifier
375
- if x.isModifier != y.isModifier
376
- @errors << @finding.error(x.path.to_s, 'isModifier', 'Incompatible isModifier', (x.isModifier || false).to_s, (y.isModifier || false).to_s)
377
- end
378
- end
12
+ attr_accessor :hierarchy
379
13
 
380
14
  # -------------------------------------------------------------------------
381
15
  # Profile Validation
@@ -388,14 +22,26 @@ module FHIR
388
22
  def validate_resource(resource)
389
23
  @errors = []
390
24
  @warnings = []
391
- @errors << "#{resource.class} is not a resource." unless resource.is_a?(FHIR::Model)
392
- is_valid_json?(resource.to_json) if resource
25
+ if resource.is_a?(FHIR::Model)
26
+ valid_json?(resource.to_json) if resource
27
+ else
28
+ @errors << "#{resource.class} is not a resource."
29
+ end
30
+ @errors
31
+ end
32
+
33
+ def validates_hash?(hash)
34
+ @errors = []
35
+ @warnings = []
36
+ valid_json?(hash) if hash
393
37
  @errors
394
38
  end
395
39
 
396
40
  # Checks whether or not the "json" is valid according to this definition.
397
41
  # json == the raw json for a FHIR resource
398
- def is_valid_json?(json)
42
+ def valid_json?(json)
43
+ build_hierarchy if @hierarchy.nil?
44
+
399
45
  if json.is_a? String
400
46
  begin
401
47
  json = JSON.parse(json)
@@ -405,100 +51,35 @@ module FHIR
405
51
  end
406
52
  end
407
53
 
408
- resource_type = json['resourceType']
409
- base_type = snapshot.element[0].path
410
- snapshot.element.each do |element|
411
- path = element.path
412
- path = path[(base_type.size + 1)..-1] if path.start_with? base_type
413
-
414
- nodes = get_json_nodes(json, path)
415
-
416
- # special filtering on extension urls
417
- extension_profile = element.type.find { |t| t.code == 'Extension' && !t.profile.nil? && !t.profile.empty? }
418
- if extension_profile
419
- nodes.keep_if { |x| extension_profile.profile.include?(x['url']) }
420
- end
421
-
422
- # Check the cardinality
423
- min = element.min
424
- max =
425
- if element.max == '*'
426
- Float::INFINITY
427
- else
428
- element.max.to_i
429
- end
430
- if (nodes.size < min) && (nodes.size > max)
431
- @errors << "#{element.path} failed cardinality test (#{min}..#{max}) -- found #{nodes.size}"
432
- end
54
+ @hierarchy.children.each do |element|
55
+ verify_element(element, json)
56
+ end
433
57
 
434
- # Check the datatype for each node, only if the element has one declared, and it isn't the root element
435
- if !element.type.empty? && element.path != id
436
- nodes.each do |value|
437
- matching_type = 0
438
-
439
- # the element is valid, if it matches at least one of the datatypes
440
- temp_messages = []
441
- element.type.each do |type|
442
- data_type_code = type.code
443
- verified_extension = false
444
- if data_type_code == 'Extension' && !type.profile.empty?
445
- extension_def = FHIR::Definitions.get_extension_definition(value['url'])
446
- if extension_def
447
- verified_extension = extension_def.validates_resource?(FHIR::Extension.new(deep_copy(value)))
448
- end
449
- end
450
- if verified_extension || is_data_type?(data_type_code, value)
451
- matching_type += 1
452
- if data_type_code == 'code' # then check the binding
453
- unless element.binding.nil?
454
- matching_type += check_binding(element, value)
455
- end
456
- elsif data_type_code == 'CodeableConcept' && !element.pattern.nil? && element.pattern.type == 'CodeableConcept'
457
- # TODO: check that the CodeableConcept matches the defined pattern
458
- @warnings << "Ignoring defined patterns on CodeableConcept #{element.path}"
459
- elsif data_type_code == 'String' && !element.maxLength.nil? && (value.size > element.maxLength)
460
- @errors << "#{element.path} exceed maximum length of #{element.maxLength}: #{value}"
461
- end
462
- else
463
- temp_messages << "#{element.path} is not a valid #{data_type_code}: '#{value}'"
464
- end
465
- end
466
- if matching_type <= 0
467
- @errors += temp_messages
468
- @errors << "#{element.path} did not match one of the valid data types: #{element.type.map(&:code)}"
469
- else
470
- @warnings += temp_messages
471
- end
472
- if !element.fixed.nil? && element.fixed != value
473
- @errors << "#{element.path} value of '#{value}' did not match fixed value: #{element.fixed}"
474
- end
475
- end
476
- end
58
+ @errors.size.zero?
59
+ end
60
+ deprecate :is_valid_json?, :valid_json?
477
61
 
478
- # Check FluentPath invariants 'constraint.xpath' constraints...
479
- # This code is not very robust, and is likely to be throwing *many* exceptions.
480
- # This is partially because the FluentPath evaluator is not complete, and partially
481
- # because the context of an expression (element.constraint.expression) is not always
482
- # consistent with the current context (element.path). For example, sometimes expressions appear to be
483
- # written to be evaluated within the element, other times at the resource level, or perhaps
484
- # elsewhere. There is no good way to determine "where" you should evaluate the expression.
485
- unless element.constraint.empty?
486
- element.constraint.each do |constraint|
487
- if constraint.expression && !nodes.empty?
488
- begin
489
- result = FluentPath.evaluate(constraint.expression, json)
490
- if !result && constraint.severity == 'error'
491
- @errors << "#{element.path}: FluentPath expression evaluates to false for #{name} invariant rule #{constraint.key}: #{constraint.human}"
492
- end
493
- rescue
494
- @warnings << "#{element.path}: unable to evaluate FluentPath expression against JSON for #{name} invariant rule #{constraint.key}: #{constraint.human}"
495
- end
496
- end
497
- end
62
+ def build_hierarchy
63
+ @hierarchy = nil
64
+ snapshot.element.each do |element|
65
+ if @hierarchy.nil?
66
+ @hierarchy = element
67
+ else
68
+ @hierarchy.add_descendent(element)
498
69
  end
499
70
  end
71
+ changelist = differential.element.map(&:path)
72
+ @hierarchy.keep_children(changelist)
73
+ @hierarchy.sweep_children
74
+ @hierarchy
75
+ end
500
76
 
501
- @errors.size.zero?
77
+ def describe_element(element)
78
+ if element.path.end_with?('.extension', '.modifierExtension') && element.sliceName
79
+ "#{element.path} (#{element.sliceName})"
80
+ else
81
+ element.path
82
+ end
502
83
  end
503
84
 
504
85
  def get_json_nodes(json, path)
@@ -528,21 +109,151 @@ module FHIR
528
109
  results
529
110
  end
530
111
 
531
- def deep_copy(thing)
532
- JSON.parse(JSON.unparse(thing))
112
+ def verify_element(element, json)
113
+ path = element.path
114
+ path = path[(@hierarchy.path.size + 1)..-1] if path.start_with? @hierarchy.path
115
+
116
+ begin
117
+ data_type_found = element.type.first.code
118
+ rescue
119
+ data_type_found = nil
120
+ end
121
+
122
+ # get the JSON nodes associated with this element path
123
+ if path.end_with?('[x]')
124
+ nodes = []
125
+ element.type.each do |type|
126
+ data_type_found = type.code
127
+ capcode = type.code.clone
128
+ capcode[0] = capcode[0].upcase
129
+ nodes = get_json_nodes(json, path.gsub('[x]', capcode))
130
+ break unless nodes.empty?
131
+ end
132
+ else
133
+ nodes = get_json_nodes(json, path)
134
+ end
135
+
136
+ # special filtering on extension urls
137
+ extension_profile = element.type.find { |t| t.code == 'Extension' && !t.profile.nil? }
138
+ if extension_profile
139
+ nodes.keep_if { |x| extension_profile.profile == x['url'] }
140
+ end
141
+
142
+ # Check the cardinality
143
+ min = element.min
144
+ max = element.max == '*' ? Float::INFINITY : element.max.to_i
145
+ if (nodes.size < min) || (nodes.size > max)
146
+ @errors << "#{describe_element(element)} failed cardinality test (#{min}..#{max}) -- found #{nodes.size}"
147
+ end
148
+
149
+ return if nodes.empty?
150
+ # Check the datatype for each node, only if the element has one declared, and it isn't the root element
151
+ if !element.type.empty? && element.path != id
152
+ codeable_concept_pattern = element.pattern && element.pattern.is_a?(FHIR::CodeableConcept)
153
+ matching_pattern = false
154
+ nodes.each do |value|
155
+ matching_type = 0
156
+
157
+ # the element is valid, if it matches at least one of the datatypes
158
+ temp_messages = []
159
+ verified_extension = false
160
+ verified_data_type = false
161
+ if data_type_found == 'Extension' # && !type.profile.nil?
162
+ verified_extension = true
163
+ # TODO: should verify extensions
164
+ # extension_def = FHIR::Definitions.get_extension_definition(value['url'])
165
+ # if extension_def
166
+ # verified_extension = extension_def.validates_resource?(FHIR::Extension.new(deep_copy(value)))
167
+ # end
168
+ elsif data_type_found
169
+ temp = @errors
170
+ @errors = []
171
+ verified_data_type = data_type?(data_type_found, value)
172
+ temp_messages << @errors
173
+ @errors = temp
174
+ end
175
+ if data_type_found && (verified_extension || verified_data_type)
176
+ matching_type += 1
177
+ if data_type_found == 'code' # then check the binding
178
+ unless element.binding.nil?
179
+ matching_type += check_binding(element, value)
180
+ end
181
+ elsif data_type_found == 'CodeableConcept' && codeable_concept_pattern
182
+ vcc = FHIR::CodeableConcept.new(value)
183
+ pattern = element.pattern.coding
184
+ pattern.each do |pcoding|
185
+ vcc.coding.each do |vcoding|
186
+ matching_pattern = true if vcoding.system == pcoding.system && vcoding.code == pcoding.code
187
+ end
188
+ end
189
+ elsif data_type_found == 'String' && !element.maxLength.nil? && (value.size > element.maxLength)
190
+ @errors << "#{describe_element(element)} exceed maximum length of #{element.maxLength}: #{value}"
191
+ end
192
+ elsif data_type_found
193
+ temp_messages << "#{describe_element(element)} is not a valid #{data_type_found}: '#{value}'"
194
+ else
195
+ # we don't know the data type... so we say "OK"
196
+ matching_type += 1
197
+ @warnings >> "Unable to guess data type for #{describe_element(element)}"
198
+ end
199
+
200
+ if matching_type <= 0
201
+ @errors += temp_messages
202
+ @errors << "#{describe_element(element)} did not match one of the valid data types: #{element.type.map(&:code)}"
203
+ else
204
+ @warnings += temp_messages
205
+ end
206
+ if !element.fixed.nil? && element.fixed != value
207
+ @errors << "#{describe_element(element)} value of '#{value}' did not match fixed value: #{element.fixed}"
208
+ end
209
+ end
210
+ if codeable_concept_pattern && matching_pattern == false
211
+ @errors << "#{describe_element(element)} CodeableConcept did not match defined pattern: #{element.pattern.to_hash}"
212
+ end
213
+ end
214
+
215
+ # Check FluentPath invariants 'constraint.xpath' constraints...
216
+ # This code is not very robust, and is likely to be throwing *many* exceptions.
217
+ # This is partially because the FluentPath evaluator is not complete, and partially
218
+ # because the context of an expression (element.constraint.expression) is not always
219
+ # consistent with the current context (element.path). For example, sometimes expressions appear to be
220
+ # written to be evaluated within the element, other times at the resource level, or perhaps
221
+ # elsewhere. There is no good way to determine "where" you should evaluate the expression.
222
+ element.constraint.each do |constraint|
223
+ next unless constraint.expression && !nodes.empty?
224
+ nodes.each do |node|
225
+ begin
226
+ result = FluentPath.evaluate(constraint.expression, node)
227
+ if !result && constraint.severity == 'error'
228
+ @errors << "#{describe_element(element)}: FluentPath expression evaluates to false for #{name} invariant rule #{constraint.key}: #{constraint.human}"
229
+ @errors << node.to_s
230
+ end
231
+ rescue
232
+ @warnings << "#{describe_element(element)}: unable to evaluate FluentPath expression against JSON for #{name} invariant rule #{constraint.key}: #{constraint.human}"
233
+ @warnings << node.to_s
234
+ end
235
+ end
236
+ end
237
+
238
+ # check children if the element has any
239
+ return unless element.children
240
+ element.children.each do |child|
241
+ verify_element(child, json)
242
+ end
533
243
  end
534
244
 
535
245
  # data_type_code == a FHIR DataType code (see http://hl7.org/fhir/2015May/datatypes.html)
536
246
  # value == the representation of the value
537
- def is_data_type?(data_type_code, value)
247
+ def data_type?(data_type_code, value)
538
248
  # FHIR models covers any base Resources
539
249
  if FHIR::RESOURCES.include?(data_type_code)
540
- definition = FHIR::Definitions.get_resource_definition(data_type_code)
250
+ definition = FHIR::Definitions.resource_definition(data_type_code)
541
251
  unless definition.nil?
542
252
  ret_val = false
543
253
  begin
544
- klass = Module.const_get("FHIR::#{data_type_code}")
545
- ret_val = definition.validates_resource?(klass.new(deep_copy(value)))
254
+ # klass = Module.const_get("FHIR::#{data_type_code}")
255
+ # ret_val = definition.validates_resource?(klass.new(deep_copy(value)))
256
+ ret_val = definition.validates_hash?(value)
546
257
  unless ret_val
547
258
  @errors += definition.errors
548
259
  @warnings += definition.warnings
@@ -558,60 +269,15 @@ module FHIR
558
269
  case data_type_code.downcase
559
270
  when 'domainresource'
560
271
  true # we don't have to verify domain resource, because it will be included in the snapshot
561
- when 'boolean'
562
- value == true || value == false || value.downcase == 'true' || value.downcase == 'false'
563
- when 'code'
564
- value.is_a?(String) && value.size >= 1 && value.size == value.rstrip.size
565
- when 'string', 'markdown'
566
- value.is_a?(String)
567
- when 'xhtml'
568
- fragment = Nokogiri::HTML::DocumentFragment.parse(value)
569
- value.is_a?(String) && fragment.errors.size.zero?
570
- when 'base64binary'
571
- regex = /[^0-9\+\/\=A-Za-z\r\n ]/
572
- value.is_a?(String) && (regex =~ value).nil?
573
- when 'id'
574
- regex = /[^\d\w\-\.]/
575
- # the FHIR spec says IDs have a length limit of 36 characters. But it also says that OIDs
576
- # are valid IDs, and ISO OIDs have no length limitations.
577
- value.is_a?(String) && (regex =~ value).nil? # && value.size<=36
578
- when 'oid'
579
- regex = /[^(urn:oid:)[\d\.]]/
580
- value.is_a?(String) && (regex =~ value).nil?
581
- when 'uri'
582
- is_valid_uri = false
583
- begin
584
- is_valid_uri = !URI.parse(value).nil?
585
- rescue
586
- is_valid_uri = false
587
- end
588
- is_valid_uri
589
- when 'instant'
590
- regex = /\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/
591
- value.is_a?(String) && !(regex =~ value).nil?
592
- when 'date'
593
- regex = /\A[0-9]{4}(-(0[1-9]|1[0-2])(-(0[0-9]|[1-2][0-9]|3[0-1]))?)?\Z/
594
- value.is_a?(String) && !(regex =~ value).nil?
595
- when 'datetime'
596
- regex = /\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/
597
- value.is_a?(String) && !(regex =~ value).nil?
598
- when 'time'
599
- regex = /\A([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\.[0-9]+)?\Z/
600
- value.is_a?(String) && !(regex =~ value).nil?
601
- when 'integer', 'unsignedint'
602
- (!Integer(value).nil? rescue false)
603
- when 'positiveint'
604
- (!Integer(value).nil? rescue false) && (Integer(value) >= 0)
605
- when 'decimal'
606
- (!Float(value).nil? rescue false)
607
272
  when 'resource'
608
273
  resource_type = value['resourceType']
609
- definition = FHIR::Definitions.get_resource_definition(resource_type)
274
+ definition = FHIR::Definitions.resource_definition(resource_type)
610
275
  if !definition.nil?
611
276
  ret_val = false
612
277
  begin
613
- klass = Module.const_get("FHIR::#{resource_type}")
614
- ret_val = definition.validates_resource?(klass.new(deep_copy(value)))
278
+ # klass = Module.const_get("FHIR::#{resource_type}")
279
+ # ret_val = definition.validates_resource?(klass.new(deep_copy(value)))
280
+ ret_val = definition.validates_hash?(value)
615
281
  unless ret_val
616
282
  @errors += definition.errors
617
283
  @warnings += definition.warnings
@@ -624,17 +290,20 @@ module FHIR
624
290
  @errors << "Unable to find base Resource definition: #{resource_type}"
625
291
  false
626
292
  end
293
+ when *FHIR::PRIMITIVES.keys.map(&:downcase)
294
+ FHIR.primitive?(datatype: data_type_code, value: value)
627
295
  else
628
296
  # Eliminate endless loop on Element is an Element
629
297
  return true if data_type_code == 'Element' && id == 'Element'
630
298
 
631
- definition = FHIR::Definitions.get_type_definition(data_type_code)
632
- definition = FHIR::Definitions.get_resource_definition(data_type_code) if definition.nil?
299
+ definition = FHIR::Definitions.type_definition(data_type_code)
300
+ definition = FHIR::Definitions.resource_definition(data_type_code) if definition.nil?
633
301
  if !definition.nil?
634
302
  ret_val = false
635
303
  begin
636
- klass = Module.const_get("FHIR::#{data_type_code}")
637
- ret_val = definition.validates_resource?(klass.new(deep_copy(value)))
304
+ # klass = Module.const_get("FHIR::#{data_type_code}")
305
+ # ret_val = definition.validates_resource?(klass.new(deep_copy(value)))
306
+ ret_val = definition.validates_hash?(value)
638
307
  unless ret_val
639
308
  @errors += definition.errors
640
309
  @warnings += definition.warnings
@@ -649,6 +318,7 @@ module FHIR
649
318
  end
650
319
  end
651
320
  end
321
+ deprecate :is_data_type?, :data_type?
652
322
 
653
323
  def check_binding(element, value)
654
324
  vs_uri = element.binding.valueSetUri || element.binding.valueSetReference.reference
@@ -658,7 +328,7 @@ module FHIR
658
328
 
659
329
  if vs_uri == 'http://hl7.org/fhir/ValueSet/content-type' || vs_uri == 'http://www.rfc-editor.org/bcp/bcp13.txt'
660
330
  matches = MIME::Types[value]
661
- if (matches.nil? || matches.size.zero?) && !is_some_type_of_xml_or_json(value)
331
+ if (matches.nil? || matches.size.zero?) && !some_type_of_xml_or_json?(value)
662
332
  @errors << "#{element.path} has invalid mime-type: '#{value}'"
663
333
  matching_type -= 1 if element.binding.strength == 'required'
664
334
  end
@@ -671,7 +341,15 @@ module FHIR
671
341
  end
672
342
  elsif valueset.nil?
673
343
  @warnings << "#{element.path} has unknown ValueSet: '#{vs_uri}'"
674
- matching_type -= 1 if element.binding.strength == 'required'
344
+ if element.binding.strength == 'required'
345
+ if element.short
346
+ @warnings << "#{element.path} guessing codes for ValueSet: '#{vs_uri}'"
347
+ guess_codes = element.short.split(' | ')
348
+ matching_type -= 1 unless guess_codes.include?(value)
349
+ else
350
+ matching_type -= 1
351
+ end
352
+ end
675
353
  elsif !valueset.values.flatten.include?(value)
676
354
  message = "#{element.path} has invalid code '#{value}' from #{valueset}"
677
355
  if element.binding.strength == 'required'
@@ -685,7 +363,7 @@ module FHIR
685
363
  matching_type
686
364
  end
687
365
 
688
- def is_some_type_of_xml_or_json(code)
366
+ def some_type_of_xml_or_json?(code)
689
367
  m = code.downcase
690
368
  return true if m == 'xml' || m == 'json'
691
369
  return true if (m.starts_with?('application/') || m.starts_with?('text/')) && (m.ends_with?('json') || m.ends_with?('xml'))
@@ -693,7 +371,8 @@ module FHIR
693
371
  return true if m.starts_with?('application/json') || m.starts_with?('text/json')
694
372
  false
695
373
  end
374
+ deprecate :is_some_type_of_xml_or_json, :some_type_of_xml_or_json?
696
375
 
697
- private :is_valid_json?, :get_json_nodes, :is_data_type?, :check_binding, :add_missing_elements, :compare_element_definitions
376
+ private :valid_json?, :get_json_nodes, :build_hierarchy, :verify_element, :check_binding
698
377
  end
699
378
  end