fhir_models 1.8.2 → 1.8.3

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.
@@ -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