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,136 @@
1
+ require_relative 'fhir_resource_navigation'
2
+
3
+ module PacioInfernoCore
4
+ module ResourceSearchParamChecker
5
+ include Inferno::DSL::FHIRResourceNavigation
6
+
7
+ def search_param_paths(name)
8
+ paths = metadata.search_definitions[name.to_sym][:paths]
9
+ paths[0] = 'local_class' if paths.first == 'class'
10
+
11
+ paths
12
+ end
13
+
14
+ def element_has_valid_value?(element, include_system)
15
+ case element
16
+ when FHIR::Reference
17
+ element.reference.present?
18
+ when FHIR::CodeableConcept
19
+ if include_system
20
+ coding =
21
+ find_a_value_at(element, 'coding') { |coding| coding.code.present? && coding.system.present? }
22
+ coding.present?
23
+ else
24
+ find_a_value_at(element, 'coding.code').present?
25
+ end
26
+ when FHIR::Identifier
27
+ include_system ? element.value.present? && element.system.present? : element.value.present?
28
+ when FHIR::Coding
29
+ include_system ? element.code.present? && element.system.present? : element.code.present?
30
+ when FHIR::HumanName
31
+ (element.family || element.given&.first || element.text).present?
32
+ when FHIR::Address
33
+ (element.text || element.city || element.state || element.postalCode || element.country).present?
34
+ when FHIR::Element
35
+ false
36
+ else
37
+ true
38
+ end
39
+ end
40
+
41
+ def resource_matches_param?(resource, search_param_name, escaped_search_value, values_found = [])
42
+ search_value = unescape_search_value(escaped_search_value)
43
+ paths = search_param_paths(search_param_name)
44
+
45
+ match_found = false
46
+
47
+ paths.each do |path|
48
+ type = metadata.search_definitions[search_param_name.to_sym][:type]
49
+ values_found =
50
+ resolve_path(resource, path)
51
+ .map do |value|
52
+ if value.is_a? FHIR::Reference
53
+ value.reference
54
+ else
55
+ value
56
+ end
57
+ end
58
+
59
+ match_found =
60
+ case type
61
+ when 'Period', 'date', 'instant', 'dateTime'
62
+ values_found.any? { |date| validate_date_search(search_value, date) }
63
+ when 'HumanName'
64
+ # When a string search parameter refers to the types HumanName and Address,
65
+ # the search covers the elements of type string, and does not cover elements such as use and period
66
+ # https://www.hl7.org/fhir/search.html#string
67
+ search_value_downcase = search_value.downcase
68
+ values_found.any? do |name|
69
+ name&.text&.downcase&.start_with?(search_value_downcase) ||
70
+ name&.family&.downcase&.start_with?(search_value_downcase) ||
71
+ name&.given&.any? { |given| given.downcase.start_with?(search_value_downcase) } ||
72
+ name&.prefix&.any? { |prefix| prefix.downcase.start_with?(search_value_downcase) } ||
73
+ name&.suffix&.any? { |suffix| suffix.downcase.start_with?(search_value_downcase) }
74
+ end
75
+ when 'Address'
76
+ search_value_downcase = search_value.downcase
77
+ values_found.any? do |address|
78
+ address&.text&.downcase&.start_with?(search_value_downcase) ||
79
+ address&.city&.downcase&.start_with?(search_value_downcase) ||
80
+ address&.state&.downcase&.start_with?(search_value_downcase) ||
81
+ address&.postalCode&.downcase&.start_with?(search_value_downcase) ||
82
+ address&.country&.downcase&.start_with?(search_value_downcase)
83
+ end
84
+ when 'CodeableConcept'
85
+ # FHIR token search (https://www.hl7.org/fhir/search.html#token): "When in doubt, servers SHOULD
86
+ # treat tokens in a case-insensitive manner, on the grounds that including undesired data has
87
+ # less safety implications than excluding desired behavior".
88
+ codings = values_found.flat_map(&:coding)
89
+ if search_value.include? '|'
90
+ system = search_value.split('|').first
91
+ code = search_value.split('|').last
92
+ codings&.any? { |coding| coding.system == system && coding.code&.casecmp?(code) }
93
+ else
94
+ codings&.any? { |coding| coding.code&.casecmp?(search_value) }
95
+ end
96
+ when 'Coding'
97
+ if search_value.include? '|'
98
+ system = search_value.split('|').first
99
+ code = search_value.split('|').last
100
+ values_found.any? { |coding| coding.system == system && coding.code&.casecmp?(code) }
101
+ else
102
+ values_found.any? { |coding| coding.code&.casecmp?(search_value) }
103
+ end
104
+ when 'Identifier'
105
+ if search_value.include? '|'
106
+ values_found.any? { |identifier| "#{identifier.system}|#{identifier.value}" == search_value }
107
+ else
108
+ values_found.any? { |identifier| identifier.value == search_value }
109
+ end
110
+ when 'string'
111
+ searched_values = search_value.downcase.split(/(?<!\\\\),/).map { |string| string.gsub('\\,', ',') }
112
+ values_found.any? do |value_found|
113
+ searched_values.any? { |searched_value| value_found.downcase.starts_with? searched_value }
114
+ end
115
+ else
116
+ # searching by patient requires special case because we are searching by a resource identifier
117
+ # references can also be URLs, so we may need to resolve those URLs
118
+ if %w[subject patient].include? search_param_name.to_s
119
+ id = search_value.split('Patient/').last
120
+ possible_values = [id, "Patient/#{id}", "#{url}/Patient/#{id}"]
121
+ values_found.any? do |reference|
122
+ possible_values.include? reference
123
+ end
124
+ else
125
+ search_values = search_value.split(/(?<!\\\\),/).map { |string| string.gsub('\\,', ',') }
126
+ values_found.any? { |value_found| search_values.include? value_found }
127
+ end
128
+ end
129
+
130
+ break if match_found
131
+ end
132
+
133
+ match_found
134
+ end
135
+ end
136
+ end