pacio_inferno_core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/lib/pacio_inferno_core/custom_groups/capability_statement/conformance_support_test.rb +41 -0
  4. data/lib/pacio_inferno_core/custom_groups/capability_statement/fhir_version_test.rb +15 -0
  5. data/lib/pacio_inferno_core/custom_groups/capability_statement/instantiate_test.rb +23 -0
  6. data/lib/pacio_inferno_core/custom_groups/capability_statement/json_support_test.rb +40 -0
  7. data/lib/pacio_inferno_core/date_search_validation.rb +112 -0
  8. data/lib/pacio_inferno_core/fhir_resource_navigation.rb +7 -0
  9. data/lib/pacio_inferno_core/generator/group_generator.rb +161 -0
  10. data/lib/pacio_inferno_core/generator/group_metadata.rb +114 -0
  11. data/lib/pacio_inferno_core/generator/group_metadata_extractor.rb +301 -0
  12. data/lib/pacio_inferno_core/generator/ig_loader.rb +118 -0
  13. data/lib/pacio_inferno_core/generator/ig_metadata.rb +60 -0
  14. data/lib/pacio_inferno_core/generator/ig_metadata_extractor.rb +88 -0
  15. data/lib/pacio_inferno_core/generator/ig_resources.rb +88 -0
  16. data/lib/pacio_inferno_core/generator/must_support_metadata_extractor.rb +8 -0
  17. data/lib/pacio_inferno_core/generator/must_support_test_generator.rb +138 -0
  18. data/lib/pacio_inferno_core/generator/naming.rb +50 -0
  19. data/lib/pacio_inferno_core/generator/read_test_generator.rb +102 -0
  20. data/lib/pacio_inferno_core/generator/reference_resolution_test_generator.rb +96 -0
  21. data/lib/pacio_inferno_core/generator/search_definition_metadata_extractor.rb +228 -0
  22. data/lib/pacio_inferno_core/generator/search_metadata_extractor.rb +78 -0
  23. data/lib/pacio_inferno_core/generator/search_test_generator.rb +298 -0
  24. data/lib/pacio_inferno_core/generator/special_cases.rb +61 -0
  25. data/lib/pacio_inferno_core/generator/suite_generator.rb +105 -0
  26. data/lib/pacio_inferno_core/generator/templates/group.rb.erb +27 -0
  27. data/lib/pacio_inferno_core/generator/templates/must_support.rb.erb +36 -0
  28. data/lib/pacio_inferno_core/generator/templates/read.rb.erb +34 -0
  29. data/lib/pacio_inferno_core/generator/templates/reference_resolution.rb.erb +40 -0
  30. data/lib/pacio_inferno_core/generator/templates/search.rb.erb +40 -0
  31. data/lib/pacio_inferno_core/generator/templates/validation.rb.erb +36 -0
  32. data/lib/pacio_inferno_core/generator/terminology_binding_metadata_extractor.rb +116 -0
  33. data/lib/pacio_inferno_core/generator/validation_test_generator.rb +146 -0
  34. data/lib/pacio_inferno_core/generator/value_extractor.rb +152 -0
  35. data/lib/pacio_inferno_core/generator.rb +130 -0
  36. data/lib/pacio_inferno_core/must_support_test.rb +20 -0
  37. data/lib/pacio_inferno_core/primitive_type.rb +5 -0
  38. data/lib/pacio_inferno_core/read_test.rb +103 -0
  39. data/lib/pacio_inferno_core/reference_resolution_test.rb +181 -0
  40. data/lib/pacio_inferno_core/request_logger.rb +46 -0
  41. data/lib/pacio_inferno_core/resource_search_param_checker.rb +136 -0
  42. data/lib/pacio_inferno_core/search_test.rb +859 -0
  43. data/lib/pacio_inferno_core/search_test_properties.rb +56 -0
  44. data/lib/pacio_inferno_core/validation_test.rb +55 -0
  45. data/lib/pacio_inferno_core/version.rb +4 -0
  46. data/lib/pacio_inferno_core/well_known_code_systems.rb +21 -0
  47. metadata +165 -0
@@ -0,0 +1,152 @@
1
+ module PacioInfernoCore
2
+ class Generator
3
+ class ValueExactor
4
+ attr_accessor :ig_resources, :resource, :profile_elements
5
+
6
+ def initialize(ig_resources, resource, profile_elements)
7
+ self.ig_resources = ig_resources
8
+ self.resource = resource
9
+ self.profile_elements = profile_elements
10
+ end
11
+
12
+ def values_from_fixed_codes(profile_element, type)
13
+ return [] unless type == 'CodeableConcept'
14
+
15
+ elements = profile_elements.select do |element|
16
+ element.path == "#{profile_element.path}.coding.code" && element.fixedCode.present?
17
+ end
18
+
19
+ elements.map(&:fixedCode)
20
+ end
21
+
22
+ def values_from_pattern_coding(profile_element, type)
23
+ return [] unless type == 'CodeableConcept'
24
+
25
+ elements = profile_elements.select do |element|
26
+ element.path == "#{profile_element.path}.coding" && element.patternCoding.present?
27
+ end
28
+
29
+ elements.map { |element| element.patternCoding.code }
30
+ end
31
+
32
+ def values_from_pattern_codeable_concept(profile_element, type)
33
+ return [] unless type == 'CodeableConcept'
34
+
35
+ elements = profile_elements.select do |element|
36
+ element.path == profile_element.path && element.patternCodeableConcept.present? && element.min.positive?
37
+ end
38
+
39
+ elements.map { |element| element.patternCodeableConcept.coding.first.code }
40
+ end
41
+
42
+ def value_set_binding(the_element)
43
+ the_element&.binding
44
+ end
45
+
46
+ def value_set(the_element)
47
+ binding = value_set_binding(the_element)
48
+ target_valueset = binding&.valueSet
49
+
50
+ additional_binding_ext = binding&.extension&.find do |ext|
51
+ ext.url == 'http://hl7.org/fhir/tools/StructureDefinition/additional-binding' &&
52
+ ext.extension.any? { |sub_ext| sub_ext.url == 'purpose' && sub_ext.valueCode == 'minimum' }
53
+ end
54
+
55
+ min_valueset_ext = binding&.extension&.find do |ext|
56
+ ext.url == 'http://hl7.org/fhir/StructureDefinition/elementdefinition-minValueSet'
57
+ end
58
+
59
+ if additional_binding_ext.present?
60
+ min_valueset = additional_binding_ext.extension.find { |ext| ext.url == 'valueSet' }
61
+
62
+ target_valueset = min_valueset.valueCanonical if min_valueset.present?
63
+ elsif min_valueset_ext.present?
64
+ target_valueset = min_valueset_ext.valueCanonical
65
+ end
66
+
67
+ ig_resources.value_set_by_url(target_valueset)
68
+ end
69
+
70
+ def bound_systems(the_element)
71
+ bound_systems_from_valueset(value_set(the_element))
72
+ end
73
+
74
+ def bound_systems_from_valueset(value_set)
75
+ value_set&.compose&.include&.map do |include_element|
76
+ if include_element.concept.present?
77
+ include_element
78
+ elsif include_element.system.present? && include_element.filter&.empty?
79
+ # Cannot process intensional value set with filters
80
+ ig_resources.code_system_by_url(include_element.system)
81
+ elsif include_element.valueSet.present?
82
+ include_element.valueSet.map do |vs|
83
+ a_value_set = ig_resources.value_set_by_url(vs)
84
+ bound_systems_from_valueset(a_value_set)
85
+ end
86
+ end
87
+ end&.flatten&.compact
88
+ end
89
+
90
+ def codes_from_value_set_binding(the_element)
91
+ codes_from_system_code_pair(codings_from_value_set_binding(the_element))
92
+ end
93
+
94
+ def codes_from_system_code_pair(codings)
95
+ codings.present? ? codings.map { |coding| coding[:code] }.compact.uniq : []
96
+ end
97
+
98
+ def codings_from_value_set_binding(the_element)
99
+ return [] if the_element.nil?
100
+
101
+ bound_systems = bound_systems(the_element)
102
+
103
+ return codings_from_bound_systems(bound_systems) if bound_systems.present?
104
+
105
+ expansion_contains = value_set_expansion_contains(the_element)
106
+
107
+ return [] if expansion_contains.blank?
108
+
109
+ expansion_contains.map { |contains| { system: contains.system, code: contains.code } }.compact.uniq
110
+ end
111
+
112
+ def codings_from_bound_systems(bound_systems)
113
+ return [] unless bound_systems.present?
114
+
115
+ bound_systems.flat_map do |bound_system|
116
+ case bound_system
117
+ when FHIR::ValueSet::Compose::Include
118
+ bound_system.concept.map { |concept| { system: bound_system.system, code: concept.code } }
119
+ when FHIR::CodeSystem
120
+ bound_system.concept.map { |concept| { system: bound_system.url, code: concept.code } }
121
+ else
122
+ []
123
+ end
124
+ end.uniq
125
+ end
126
+
127
+ def value_set_expansion_contains(element)
128
+ value_set(element)&.expansion&.contains
129
+ end
130
+
131
+ def fhir_metadata(current_path)
132
+ FHIR.const_get(resource)::METADATA[current_path]
133
+ end
134
+
135
+ def values_from_resource_metadata(paths)
136
+ values = []
137
+
138
+ paths.each do |current_path|
139
+ current_metadata = fhir_metadata(current_path)
140
+
141
+ next unless current_metadata&.dig('valid_codes').present?
142
+
143
+ values += current_metadata['valid_codes'].flat_map do |system, codes|
144
+ codes.map { |code| { system:, code: } }
145
+ end
146
+ end
147
+
148
+ values
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,130 @@
1
+ require 'fhir_models'
2
+ require 'inferno/ext/fhir_models'
3
+
4
+ require_relative 'generator/ig_loader'
5
+ require_relative 'generator/ig_metadata_extractor'
6
+ require_relative 'generator/group_generator'
7
+ require_relative 'generator/must_support_test_generator'
8
+ require_relative 'generator/read_test_generator'
9
+ require_relative 'generator/reference_resolution_test_generator'
10
+ require_relative 'generator/search_test_generator'
11
+ require_relative 'generator/suite_generator'
12
+ require_relative 'generator/validation_test_generator'
13
+
14
+ module PacioInfernoCore
15
+ class Generator
16
+ # def self.generate
17
+ # ig_packages = Dir.glob(File.join(Dir.pwd, 'lib', 'us_core_test_kit', 'igs', '*.tgz'))
18
+
19
+ # ig_packages.each do |ig_package|
20
+ # new(ig_package).generate
21
+ # end
22
+ # end
23
+
24
+ attr_accessor :ig_resources, :ig_metadata, :ig_file_name
25
+
26
+ def initialize(ig_file_name)
27
+ self.ig_file_name = ig_file_name
28
+ end
29
+
30
+ def generate
31
+ puts "Generating tests for IG #{File.basename(ig_file_name)}"
32
+ load_ig_package
33
+ extract_metadata
34
+ generate_search_tests
35
+ generate_read_tests
36
+ # TODO: generate_vread_tests
37
+ # TODO: generate_history_tests
38
+ generate_provenance_revinclude_search_tests
39
+ generate_validation_tests
40
+ generate_must_support_tests
41
+ generate_reference_resolution_tests
42
+ generate_practitioner_address_tests
43
+ generate_interpreter_required_extension_test_generator
44
+
45
+ generate_granular_scope_tests
46
+
47
+ generate_groups
48
+
49
+ generate_granular_scope_resource_type_groups
50
+
51
+ generate_granular_scope_groups
52
+
53
+ generate_suites
54
+
55
+ write_metadata
56
+ end
57
+
58
+ def extract_metadata
59
+ self.ig_metadata = IGMetadataExtractor.new(ig_resources).extract
60
+
61
+ FileUtils.mkdir_p(base_output_dir)
62
+ end
63
+
64
+ def write_metadata
65
+ File.write(File.join(base_output_dir, 'metadata.yml'), YAML.dump(ig_metadata.to_hash))
66
+ end
67
+
68
+ def base_output_dir
69
+ File.join(__dir__, 'generated', ig_metadata.ig_version)
70
+ end
71
+
72
+ def load_ig_package
73
+ FHIR.logger = Logger.new('/dev/null')
74
+ self.ig_resources = IGLoader.new(ig_file_name).load
75
+ end
76
+
77
+ def generate_reference_resolution_tests
78
+ ReferenceResolutionTestGenerator.generate(ig_metadata, base_output_dir)
79
+ end
80
+
81
+ def generate_must_support_tests
82
+ MustSupportTestGenerator.generate(ig_metadata, base_output_dir)
83
+ end
84
+
85
+ def generate_validation_tests
86
+ ValidationTestGenerator.generate(ig_metadata, base_output_dir)
87
+ end
88
+
89
+ def generate_read_tests
90
+ ReadTestGenerator.generate(ig_metadata, base_output_dir)
91
+ end
92
+
93
+ def generate_search_tests
94
+ SearchTestGenerator.generate(ig_metadata, base_output_dir)
95
+ end
96
+
97
+ def generate_provenance_revinclude_search_tests
98
+ ProvenanceRevincludeSearchTestGenerator.generate(ig_metadata, base_output_dir)
99
+ end
100
+
101
+ def generate_granular_scope_tests
102
+ GranularScopeTestGenerator.generate(ig_metadata, base_output_dir)
103
+ GranularScopeReadTestGenerator.generate(ig_metadata, base_output_dir)
104
+ end
105
+
106
+ def generate_groups
107
+ GroupGenerator.generate(ig_metadata, base_output_dir)
108
+ end
109
+
110
+ def generate_granular_scope_resource_type_groups
111
+ GranularScopeResourceTypeGroupGenerator.generate(ig_metadata, base_output_dir)
112
+ end
113
+
114
+ def generate_granular_scope_groups
115
+ GranularScopeGroupGenerator.generate(ig_metadata, base_output_dir)
116
+ end
117
+
118
+ def generate_practitioner_address_tests
119
+ PractitionerAddressTestGenerator.generate(ig_metadata, base_output_dir)
120
+ end
121
+
122
+ def generate_interpreter_required_extension_test_generator
123
+ InterpreterRequiredExtensionTestGenerator.generate(ig_metadata, base_output_dir)
124
+ end
125
+
126
+ def generate_suites
127
+ SuiteGenerator.generate(ig_metadata, base_output_dir)
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,20 @@
1
+ require_relative 'fhir_resource_navigation'
2
+
3
+ module PacioInfernoCore
4
+ module MustSupportTest
5
+ include Inferno::DSL::FHIRResourceNavigation
6
+ extend Forwardable
7
+
8
+ def_delegators 'self.class', :metadata
9
+
10
+ def all_scratch_resources
11
+ scratch_resources[:all]
12
+ end
13
+
14
+ def perform_must_support_test(resources)
15
+ skip_if resources.blank?, "No #{resource_type} resources were found"
16
+
17
+ skip { assert_must_support_elements_present(resources, nil, metadata:) }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ module PacioInfernoCore
2
+ # This functionality has been moved to Inferno Core, but this reference is
3
+ # retained to avoid a breaking change.
4
+ PrimitiveType = Inferno::DSL::PrimitiveType
5
+ end
@@ -0,0 +1,103 @@
1
+ module PacioInfernoCore
2
+ module ReadTest
3
+ def all_scratch_resources
4
+ scratch_resources[:all] ||= []
5
+ end
6
+
7
+ def perform_read_test(resources, _reply_handler = nil, delayed_reference: false)
8
+ skip_if resources.blank?, no_resources_skip_message
9
+
10
+ resources_to_read = if delayed_reference
11
+ readable_references(resources)
12
+ else
13
+ readable_resources(resources)
14
+ end
15
+
16
+ assert resources_to_read.present?, "No #{resource_type} id found."
17
+
18
+ if config.options[:read_all_resources]
19
+ if delayed_reference
20
+ all_referencing_resources = referencing_resources(resources_to_read)
21
+ info %(
22
+ The #{resource_type} references used for this test were pulled from the following resources:
23
+ #{all_referencing_resources}
24
+ )
25
+
26
+ resources_to_read.map! { |resource| resource[:reference] }
27
+ end
28
+
29
+ resources_to_read.each do |resource|
30
+ read_and_validate(resource)
31
+ end
32
+ else
33
+ first_resource = resources_to_read.first
34
+ if delayed_reference.present?
35
+ info %(
36
+ The #{resource_type} reference used for this test was pulled from resource
37
+ #{first_resource[:referencing_resource]}
38
+ )
39
+ first_resource = first_resource[:reference]
40
+ end
41
+ read_and_validate(first_resource)
42
+ end
43
+ end
44
+
45
+ def referencing_resources(readable_resources)
46
+ readable_resources
47
+ .map { |resource| resource[:referencing_resource] }
48
+ .join(', ')
49
+ end
50
+
51
+ def readable_references(resources)
52
+ resources
53
+ .filter_map do |resource|
54
+ next unless resource[:reference].present? && resource[:reference].is_a?(FHIR::Reference)
55
+
56
+ reference_id = resource[:reference].reference&.split('/')&.last
57
+ next unless reference_id&.present?
58
+
59
+ resource
60
+ end
61
+ .uniq { |resource| resource[:reference].reference.split('/').last }
62
+ end
63
+
64
+ def readable_resources(resources)
65
+ resources
66
+ .select { |resource| resource.is_a?(resource_class) || resource.is_a?(FHIR::Reference) }
67
+ .select { |resource| (resource.is_a?(FHIR::Reference) ? resource.reference.split('/').last : resource.id).present? }
68
+ .compact
69
+ .uniq { |resource| resource.is_a?(FHIR::Reference) ? resource.reference.split('/').last : resource.id }
70
+ end
71
+
72
+ def read_and_validate(resource_to_read)
73
+ id = resource_id(resource_to_read)
74
+
75
+ fhir_read resource_type, id
76
+
77
+ assert_response_status(200)
78
+ assert_resource_type(resource_type)
79
+ assert resource.id.present? && resource.id == id, bad_resource_id_message(id)
80
+
81
+ return unless resource_to_read.is_a? FHIR::Reference
82
+
83
+ all_scratch_resources << resource
84
+ end
85
+
86
+ def resource_id(resource)
87
+ resource.is_a?(FHIR::Reference) ? resource.reference.split('/').last : resource.id
88
+ end
89
+
90
+ def no_resources_skip_message
91
+ "No #{resource_type} resources appear to be available. " \
92
+ 'Please use patients with more information.'
93
+ end
94
+
95
+ def bad_resource_id_message(expected_id)
96
+ "Expected resource to have id: `#{expected_id.inspect}`, but found `#{resource.id.inspect}`"
97
+ end
98
+
99
+ def resource_class
100
+ FHIR.const_get(resource_type)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,181 @@
1
+ require_relative 'fhir_resource_navigation'
2
+
3
+ module PacioInfernoCore
4
+ module ReferenceResolutionTest
5
+ include Inferno::DSL::FHIRResourceNavigation
6
+ extend Forwardable
7
+
8
+ def_delegators 'self.class', :metadata
9
+
10
+ def perform_reference_resolution_test(resources)
11
+ skip_if resources.blank?, no_resources_skip_message
12
+
13
+ pass if unresolved_references(resources).length.zero?
14
+
15
+ skip "Could not resolve and validate any Must Support references for #{unresolved_references_strings.join(', ')}"
16
+ end
17
+
18
+ def unresolved_references_strings
19
+ unresolved_reference_hash =
20
+ unresolved_references.each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |missing, hash|
21
+ hash[missing[:path]] << missing[:target_profile]
22
+ end
23
+ unresolved_reference_hash.map do |path, profiles|
24
+ "#{path} element: Reference#{"(#{profiles.join('|')})" unless profiles.first.empty?}"
25
+ end
26
+ end
27
+
28
+ def record_resolved_reference(reference, target_profile)
29
+ saved_reference = resolved_references.find { |item| item[:reference] == reference.reference }
30
+
31
+ if saved_reference.present?
32
+ if target_profile.present? && !saved_reference[:profiles].include?(target_profile)
33
+ saved_reference[:profiles] << target_profile
34
+ end
35
+ else
36
+ saved_reference = {
37
+ reference: reference.reference,
38
+ profiles: []
39
+ }
40
+
41
+ saved_reference[:profiles] << target_profile if target_profile.present?
42
+ resolved_references.add(saved_reference)
43
+ end
44
+ end
45
+
46
+ def is_reference_resolved?(reference, target_profile)
47
+ resolved_references.any? do |item|
48
+ item[:reference] == reference.reference &&
49
+ (
50
+ target_profile.blank? || item[:profiles].include?(target_profile)
51
+ )
52
+ end
53
+ end
54
+
55
+ def resolved_references
56
+ scratch[:resolved_references] ||= Set.new
57
+ end
58
+
59
+ def no_resources_skip_message
60
+ "No #{resource_type} resources appear to be available. " \
61
+ 'Please use patients with more information.'
62
+ end
63
+
64
+ def must_support_references
65
+ metadata.must_supports[:elements].select do |element_definition|
66
+ element_definition[:types]&.include?('Reference')
67
+ end
68
+ end
69
+
70
+ def must_support_references_with_target_profile
71
+ # mapping array of target_profiles to array of {path, target_profile} pair
72
+ must_support_references.map do |element_definition|
73
+ (element_definition[:target_profiles] || ['']).map do |target_profile|
74
+ {
75
+ path: element_definition[:path],
76
+ target_profile: target_profile
77
+ }
78
+ end
79
+ end.flatten
80
+ end
81
+
82
+ def unresolved_references(resources = [])
83
+ @unresolved_references ||=
84
+ must_support_references_with_target_profile.select do |reference_path_profile_pair|
85
+ path = reference_path_profile_pair[:path]
86
+ target_profile = reference_path_profile_pair[:target_profile]
87
+
88
+ found_one_reference = false
89
+
90
+ resolve_one_reference = resources.any? do |resource|
91
+ value_found = resolve_path(resource, path)
92
+ next if value_found.empty?
93
+
94
+ found_one_reference = true
95
+
96
+ value_found.any? do |reference|
97
+ validate_reference_resolution(resource, reference, target_profile)
98
+ end
99
+ end
100
+
101
+ found_one_reference && !resolve_one_reference
102
+ end
103
+
104
+ if metadata.must_supports[:choices].present?
105
+ @unresolved_references.delete_if do |reference|
106
+ choice_profiles = metadata.must_supports[:choices].find do |choice|
107
+ choice[:target_profiles]&.include?(reference[:target_profile])
108
+ end
109
+
110
+ choice_profiles.present? &&
111
+ choice_profiles[:target_profiles]&.any? do |profile|
112
+ @unresolved_references.none? do |element|
113
+ element[:target_profile] == profile
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ @unresolved_references
120
+ end
121
+
122
+ def validate_reference_resolution(resource, reference, target_profile)
123
+ return true if is_reference_resolved?(reference, target_profile)
124
+
125
+ if reference.contained?
126
+ # if reference_id is blank it is referring to itself, so we know it exists
127
+ return true if reference.reference_id.blank?
128
+
129
+ return resource.contained.any? do |contained_resource|
130
+ contained_resource&.id == reference.reference_id &&
131
+ resource_is_valid_with_target_profile?(contained_resource, target_profile)
132
+ end
133
+ end
134
+
135
+ reference_type = reference.resource_type
136
+ reference_id = reference.reference_id
137
+
138
+ resolved_resource = resolve_reference(reference)
139
+
140
+ return false if resolved_resource.nil?
141
+
142
+ return false unless resolved_resource.resourceType == reference_type && resolved_resource.id == reference_id
143
+
144
+ return false unless resource_is_valid_with_target_profile?(resolved_resource, target_profile)
145
+
146
+ record_resolved_reference(reference, target_profile)
147
+ true
148
+ end
149
+
150
+ def resolve_reference(reference)
151
+ reference_type = reference.resource_type
152
+ reference_id = reference.reference_id
153
+
154
+ begin
155
+ if reference.relative?
156
+ begin
157
+ reference.resource_class
158
+ rescue NameError
159
+ return nil
160
+ end
161
+
162
+ fhir_read(reference_type, reference_id)&.resource
163
+ elsif reference.base_uri.chomp('/') == fhir_client.instance_variable_get(:@base_service_url).chomp('/')
164
+ fhir_read(reference_type, reference_id)&.resource
165
+ else
166
+ get(reference.reference)&.resource
167
+ end
168
+ rescue StandardError => e
169
+ Inferno::Application['logger'].error("Unable to resolve reference #{reference.reference}")
170
+ Inferno::Application['logger'].error(e.full_message)
171
+ nil
172
+ end
173
+ end
174
+
175
+ def resource_is_valid_with_target_profile?(resource, target_profile)
176
+ return true if target_profile.blank?
177
+
178
+ resource_is_valid?(resource:, profile_url: target_profile, add_messages_to_runnable: false)
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,46 @@
1
+ module Inferno
2
+ module Utils
3
+ # @private
4
+ module Middleware
5
+ class RequestLogger
6
+ def log_response(response, start_time, end_time, exception = nil)
7
+ elapsed = end_time - start_time
8
+ status, _response_headers, body = response if response
9
+ status, = response if exception
10
+
11
+ logger.info("#{status} in #{elapsed.in_milliseconds} ms")
12
+ return unless body.present?
13
+
14
+ body = body.join if body.is_a?(Array)
15
+
16
+ if body.length > 100
17
+ logger.info("#{body[0..100]}...")
18
+ else
19
+ logger.info(body)
20
+ end
21
+ end
22
+
23
+ def log_request(env)
24
+ method = env['REQUEST_METHOD']
25
+ scheme = env['rack.url_scheme']
26
+ host = env['HTTP_HOST']
27
+ path = env['REQUEST_URI']
28
+ query = env['rack.request.query_string']
29
+ body = env['rack.input']
30
+ body = body.instance_of?(Puma::NullIO) ? nil : body.string
31
+ query_string = query.blank? ? '' : "?#{query}"
32
+
33
+ logger.info("#{method} #{scheme}://#{host}#{path}#{query_string}")
34
+
35
+ return unless body.present?
36
+
37
+ if body.length > 100
38
+ logger.info("#{body[0..100]}...")
39
+ else
40
+ logger.info(body)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end