inferno_core 0.6.1 → 0.6.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.
- checksums.yaml +4 -4
- data/lib/inferno/apps/cli/evaluate.rb +22 -12
- data/lib/inferno/apps/cli/templates/Dockerfile.tt +0 -1
- data/lib/inferno/apps/cli/templates/README.md.tt +10 -0
- data/lib/inferno/apps/cli/templates/docs/Overview.md +7 -0
- data/lib/inferno/apps/cli/templates/docs/README.md.tt +10 -0
- data/lib/inferno/apps/cli/templates/docs/_Footer.md +5 -0
- data/lib/inferno/apps/cli/templates/docs/_Sidebar.md +5 -0
- data/lib/inferno/config/boot/presets.rb +1 -1
- data/lib/inferno/dsl/auth_info.rb +87 -1
- data/lib/inferno/dsl/configurable.rb +14 -1
- data/lib/inferno/dsl/fhir_client.rb +66 -0
- data/lib/inferno/dsl/fhir_evaluation/evaluation_context.rb +4 -2
- data/lib/inferno/dsl/fhir_evaluation/evaluator.rb +8 -3
- data/lib/inferno/dsl/fhir_evaluation/profile_conformance_helper.rb +66 -0
- data/lib/inferno/dsl/fhir_evaluation/reference_extractor.rb +61 -0
- data/lib/inferno/dsl/fhir_evaluation/rules/all_must_supports_present.rb +379 -0
- data/lib/inferno/dsl/fhir_evaluation/rules/all_references_resolve.rb +53 -0
- data/lib/inferno/dsl/fhir_evaluation/rules/all_resources_reachable.rb +63 -0
- data/lib/inferno/dsl/fhir_resource_navigation.rb +226 -0
- data/lib/inferno/dsl/input_output_handling.rb +1 -0
- data/lib/inferno/dsl/must_support_metadata_extractor.rb +366 -0
- data/lib/inferno/dsl/primitive_type.rb +9 -0
- data/lib/inferno/dsl/runnable.rb +13 -1
- data/lib/inferno/dsl/value_extractor.rb +136 -0
- data/lib/inferno/dsl.rb +1 -0
- data/lib/inferno/entities/ig.rb +46 -24
- data/lib/inferno/entities/input.rb +63 -3
- data/lib/inferno/public/bundle.js +16 -16
- data/lib/inferno/repositories/session_data.rb +2 -0
- data/lib/inferno/version.rb +1 -1
- data/spec/runnable_context.rb +8 -5
- data/spec/shared/test_kit_examples.rb +23 -1
- metadata +15 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: afdf870396c4f72a853bcbc56b7f134c827391a0b0b2e956b47e79aa36fccdf8
|
4
|
+
data.tar.gz: d77d91fc728001379862adb5d740039704d3dd81fe184b8e11d3b99a650e5218
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 03ff313b070ce6d09ae5ec35c0f677ed30c04010915189fdf9361ddb0e69b5df9ae0bdb7350a9e8db041a3352cc183d60173b0598fc729273ccfdf1386b03351
|
7
|
+
data.tar.gz: 37ea35ed4bad96e583f935763f22865244d01ae8daed58b43b0a82d9e2b74d5a39efcc6995bdc087c19fa620eddbc131f81a6365aa8ece6b12dcf8db319c9245
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require_relative '../../../inferno/dsl/fhir_evaluation/evaluator'
|
2
|
+
require_relative '../../../inferno/dsl/fhir_evaluation/config'
|
2
3
|
require_relative '../../../inferno/entities'
|
3
4
|
require_relative '../../utils/ig_downloader'
|
4
5
|
|
@@ -12,21 +13,22 @@ module Inferno
|
|
12
13
|
|
13
14
|
def evaluate(ig_path, data_path, _log_level)
|
14
15
|
validate_args(ig_path, data_path)
|
15
|
-
|
16
|
+
ig = get_ig(ig_path)
|
16
17
|
|
17
|
-
|
18
|
+
check_ig_version(ig)
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
data =
|
21
|
+
if data_path
|
22
|
+
DatasetLoader.from_path(File.join(__dir__, data_path))
|
23
|
+
else
|
24
|
+
ig.examples
|
25
|
+
end
|
24
26
|
|
25
|
-
|
26
|
-
# evaluator = Inferno::DSL::FHIREvaluation::Evaluator.new(data, config)
|
27
|
+
evaluator = Inferno::DSL::FHIREvaluation::Evaluator.new(ig)
|
27
28
|
|
28
|
-
|
29
|
-
|
29
|
+
config = Inferno::DSL::FHIREvaluation::Config.new
|
30
|
+
results = evaluator.evaluate(data, config)
|
31
|
+
output_results(results, options[:output])
|
30
32
|
end
|
31
33
|
|
32
34
|
def validate_args(ig_path, data_path)
|
@@ -46,7 +48,7 @@ module Inferno
|
|
46
48
|
cache_directory = File.join(user_package_cache, ig_path.sub('@', '#'))
|
47
49
|
ig = Inferno::Entities::IG.from_file(cache_directory)
|
48
50
|
else
|
49
|
-
Tempfile.create('package.tgz') do |temp_file|
|
51
|
+
Tempfile.create(['package', '.tgz']) do |temp_file|
|
50
52
|
load_ig(ig_path, nil, { force: true }, temp_file.path)
|
51
53
|
ig = Inferno::Entities::IG.from_file(temp_file.path)
|
52
54
|
end
|
@@ -55,6 +57,14 @@ module Inferno
|
|
55
57
|
ig
|
56
58
|
end
|
57
59
|
|
60
|
+
def check_ig_version(ig)
|
61
|
+
versions = ig.ig_resource.fhirVersion
|
62
|
+
|
63
|
+
return unless versions.any? { |v| v > '4.0.1' }
|
64
|
+
|
65
|
+
puts '**WARNING** The selected IG targets a FHIR version higher than 4.0.1, which is not supported by Inferno.'
|
66
|
+
end
|
67
|
+
|
58
68
|
def user_package_cache
|
59
69
|
File.join(Dir.home, '.fhir', 'packages')
|
60
70
|
end
|
@@ -6,7 +6,6 @@ RUN mkdir -p $INSTALL_PATH
|
|
6
6
|
|
7
7
|
WORKDIR $INSTALL_PATH
|
8
8
|
|
9
|
-
ADD lib/<%= library_name %>/metadata.rb $INSTALL_PATH/lib/<%= library_name %>/metadata.rb
|
10
9
|
ADD lib/<%= library_name %>/version.rb $INSTALL_PATH/lib/<%= library_name %>/version.rb
|
11
10
|
ADD *.gemspec $INSTALL_PATH
|
12
11
|
ADD Gemfile* $INSTALL_PATH
|
@@ -3,6 +3,16 @@
|
|
3
3
|
<%= human_name %> [Inferno](https://github.com/inferno-community/inferno-core) Test Kit
|
4
4
|
for FHIR testing.
|
5
5
|
|
6
|
+
## Getting Started
|
7
|
+
|
8
|
+
The quickest way to run this test kit locally is with [Docker](https://www.docker.com/).
|
9
|
+
|
10
|
+
- Install Docker
|
11
|
+
- Clone this repository, or download an [official release](/releases) if available.
|
12
|
+
- Run `./setup.sh` within the test kit directory to download necessary dependencies
|
13
|
+
- Run `./run.sh` within the test kit directory to start the application
|
14
|
+
- Navigate to `http://localhost`
|
15
|
+
|
6
16
|
## Instructions for Developing Your Test Kit
|
7
17
|
|
8
18
|
Refer to the Inferno documentation for information about [setting up
|
@@ -0,0 +1,7 @@
|
|
1
|
+
This file was generated from the Inferno template to demonstrate providing
|
2
|
+
documentation across multiple files, and has not yet customized for this test
|
3
|
+
kit.
|
4
|
+
|
5
|
+
For examples of test kit documentation, refer to the [US Core Test
|
6
|
+
Kit](/inferno-framework/us-core-test-kit/wiki) and the [ONC Certification
|
7
|
+
(g)(10) Test Kit](/inferno-framework/onc-certification-g10-test-kit/wiki).
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<%= human_name %> documentation. This file has been generated from the Inferno
|
2
|
+
test kit template and has not yet been customized for this test kit.
|
3
|
+
|
4
|
+
*Note to developer: After customizing this documentation, initialize a GitHub
|
5
|
+
wiki within this repository to enable automatic publishing to the wiki via the
|
6
|
+
included Publish Docs Wiki action.*
|
7
|
+
|
8
|
+
## Using this Test Kit (example)
|
9
|
+
* [Getting Started](../?tab=readme-ov-file#getting-started): Installation instructions for this test kit.
|
10
|
+
* [Test Kit Overview](Overview.md): An overview of the test kit and its tests.
|
@@ -0,0 +1,5 @@
|
|
1
|
+
_Test kit documentation is stored within the [./docs](../tree/main/docs) folder of
|
2
|
+
this repository and is automatically synchronized to this wiki with each update
|
3
|
+
to the `main` branch using the [Publish Docs Wiki
|
4
|
+
workflow](../actions/workflows/publish-docs-wiki.yml). Do not change content
|
5
|
+
within this wiki directly as changes will be overwritten._
|
@@ -22,8 +22,8 @@ Inferno::Application.register_provider(:presets) do
|
|
22
22
|
end
|
23
23
|
|
24
24
|
files_to_load.compact!
|
25
|
-
files_to_load.uniq!
|
26
25
|
files_to_load.map! { |path| File.realpath(path) }
|
26
|
+
files_to_load.uniq!
|
27
27
|
|
28
28
|
files_to_load.each do |path|
|
29
29
|
presets_repo.insert_from_file(path)
|
@@ -307,9 +307,95 @@ module Inferno
|
|
307
307
|
self.expires_in = expires_in
|
308
308
|
self.issue_time = DateTime.now
|
309
309
|
|
310
|
-
add_to_client(client)
|
310
|
+
add_to_client(client) if client
|
311
311
|
self
|
312
312
|
end
|
313
|
+
|
314
|
+
# Returns the default configuration for the "auth_type" component
|
315
|
+
# @return [Hash]
|
316
|
+
def self.default_auth_type_component
|
317
|
+
{
|
318
|
+
name: :auth_type,
|
319
|
+
options: {
|
320
|
+
list_options: [
|
321
|
+
{ label: 'Public', value: 'public' },
|
322
|
+
{ label: 'Confidential Symmetric', value: 'symmetric' },
|
323
|
+
{ label: 'Confidential Asymmetric', value: 'asymmetric' },
|
324
|
+
{ label: 'Backend Services', value: 'backend_services' }
|
325
|
+
]
|
326
|
+
}
|
327
|
+
}
|
328
|
+
end
|
329
|
+
|
330
|
+
# Returns the default configuration for the "auth_type" component without
|
331
|
+
# the option for backend services auth
|
332
|
+
# @return [Hash]
|
333
|
+
def self.default_auth_type_component_without_backend_services
|
334
|
+
{
|
335
|
+
name: :auth_type,
|
336
|
+
options: {
|
337
|
+
list_options: [
|
338
|
+
{ label: 'Public', value: 'public' },
|
339
|
+
{ label: 'Confidential Symmetric', value: 'symmetric' },
|
340
|
+
{ label: 'Confidential Asymmetric', value: 'asymmetric' }
|
341
|
+
]
|
342
|
+
}
|
343
|
+
}
|
344
|
+
end
|
345
|
+
|
346
|
+
# Returns true when using public auth
|
347
|
+
# @return [Boolean]
|
348
|
+
def public_auth?
|
349
|
+
auth_type&.casecmp? 'public'
|
350
|
+
end
|
351
|
+
|
352
|
+
# Returns true when using confidential symmetric auth
|
353
|
+
# @return [Boolean]
|
354
|
+
def symmetric_auth?
|
355
|
+
auth_type&.casecmp? 'symmetric'
|
356
|
+
end
|
357
|
+
|
358
|
+
# Returns true when using confidential asymmetric auth
|
359
|
+
# @return [Boolean]
|
360
|
+
def asymmetric_auth?
|
361
|
+
auth_type&.casecmp? 'asymmetric'
|
362
|
+
end
|
363
|
+
|
364
|
+
# Returns true when using backend services auth
|
365
|
+
# @return [Boolean]
|
366
|
+
def backend_services_auth?
|
367
|
+
auth_type&.casecmp? 'backend_services'
|
368
|
+
end
|
369
|
+
|
370
|
+
# Returns true when using GET as the authorization request method
|
371
|
+
# @return [Boolean]
|
372
|
+
def get_auth_request?
|
373
|
+
auth_request_method&.casecmp? 'get'
|
374
|
+
end
|
375
|
+
|
376
|
+
# Returns true when using POST as the authorization request method
|
377
|
+
# @return [Boolean]
|
378
|
+
def post_auth_request?
|
379
|
+
auth_request_method&.casecmp? 'post'
|
380
|
+
end
|
381
|
+
|
382
|
+
# Returns true when pkce is enabled
|
383
|
+
# @return [Boolean]
|
384
|
+
def pkce_enabled?
|
385
|
+
pkce_support&.casecmp? 'enabled'
|
386
|
+
end
|
387
|
+
|
388
|
+
# Returns true when using the S256 pkce code challenge method
|
389
|
+
# @return [Boolean]
|
390
|
+
def s256_code_challenge_method?
|
391
|
+
pkce_code_challenge_method&.casecmp? 'S256'
|
392
|
+
end
|
393
|
+
|
394
|
+
# Returns true when using the palin pkce code challenge method
|
395
|
+
# @return [Boolean]
|
396
|
+
def plain_code_challenge_method?
|
397
|
+
pkce_code_challenge_method&.casecmp? 'plain'
|
398
|
+
end
|
313
399
|
end
|
314
400
|
end
|
315
401
|
end
|
@@ -138,6 +138,19 @@ module Inferno
|
|
138
138
|
configuration[:inputs] ||= {}
|
139
139
|
end
|
140
140
|
|
141
|
+
# @private
|
142
|
+
# Recursively duplicate arrays/hashes to prevent them from being shared
|
143
|
+
# across different runnables
|
144
|
+
def deep_dup(value)
|
145
|
+
if value.is_a? Array
|
146
|
+
value.map { |element| deep_dup(element) }
|
147
|
+
elsif value.is_a? Hash
|
148
|
+
value.transform_values { |element| deep_dup(element) }
|
149
|
+
else
|
150
|
+
value.dup
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
141
154
|
# @private
|
142
155
|
def add_input(identifier, new_config = {})
|
143
156
|
existing_config = input(identifier)
|
@@ -148,7 +161,7 @@ module Inferno
|
|
148
161
|
|
149
162
|
inputs[identifier] =
|
150
163
|
Entities::Input
|
151
|
-
.new(**existing_config.to_hash)
|
164
|
+
.new(**deep_dup(existing_config.to_hash))
|
152
165
|
.merge(Entities::Input.new(**new_config))
|
153
166
|
end
|
154
167
|
|
@@ -327,6 +327,58 @@ module Inferno
|
|
327
327
|
end
|
328
328
|
end
|
329
329
|
|
330
|
+
# Fetch all resources from a paginated FHIR bundle
|
331
|
+
#
|
332
|
+
# @param resource_type [String] The expected resource type to fetch.
|
333
|
+
# @param bundle [FHIR::Bundle] The initial FHIR bundle to process. Defaults to `self.resource`.
|
334
|
+
# @param reply_handler [Proc, nil] A handler for processing replies. Optional.
|
335
|
+
# @param client [Symbol] Defaults to `:default`.
|
336
|
+
# @param max_pages [Integer] Maximum number of pages to fetch. Defaults to 20.
|
337
|
+
# @param additional_resource_types [Array<String>] Additional resource types acceptable in the results.
|
338
|
+
# @param tags [Array<String>] for request tagging. Optional.
|
339
|
+
#
|
340
|
+
# @return [Array<FHIR::Resource>] An array of fetched FHIR resources.
|
341
|
+
def fetch_all_bundled_resources( # rubocop:disable Metrics/CyclomaticComplexity
|
342
|
+
resource_type:,
|
343
|
+
bundle: resource,
|
344
|
+
reply_handler: nil,
|
345
|
+
client: :default,
|
346
|
+
max_pages: 20,
|
347
|
+
additional_resource_types: [],
|
348
|
+
tags: []
|
349
|
+
)
|
350
|
+
page_count = 1
|
351
|
+
resources = []
|
352
|
+
|
353
|
+
while bundle && page_count <= max_pages
|
354
|
+
resources += bundle.entry&.map { |entry| entry&.resource } || []
|
355
|
+
reply_handler&.call(response)
|
356
|
+
|
357
|
+
break if next_bundle_link(bundle).blank?
|
358
|
+
|
359
|
+
bundle = fetch_next_bundle(bundle, client, tags)
|
360
|
+
|
361
|
+
page_count += 1
|
362
|
+
end
|
363
|
+
|
364
|
+
valid_resource_types = [resource_type, 'OperationOutcome'].concat(additional_resource_types)
|
365
|
+
|
366
|
+
invalid_resource_types =
|
367
|
+
resources.reject { |entry| valid_resource_types.include? entry.resourceType }
|
368
|
+
.map(&:resourceType)
|
369
|
+
.uniq
|
370
|
+
if invalid_resource_types.any?
|
371
|
+
info "Received resource type(s) #{invalid_resource_types.join(', ')} in search bundle, " \
|
372
|
+
"but only expected resource types #{valid_resource_types.join(', ')}. " \
|
373
|
+
'This is unusual but allowed if the server believes additional resource types are relevant.'
|
374
|
+
end
|
375
|
+
|
376
|
+
resources
|
377
|
+
rescue JSON::ParserError
|
378
|
+
Inferno::Application[:logger].error "Could not resolve next bundle: #{next_bundle_link(bundle)}"
|
379
|
+
resources
|
380
|
+
end
|
381
|
+
|
330
382
|
# @todo Make this a FHIR class method? Something like
|
331
383
|
# FHIR.class_for(resource_type)
|
332
384
|
# @private
|
@@ -371,6 +423,20 @@ module Inferno
|
|
371
423
|
Inferno::Application[:logger].error "Unable to refresh token: #{e.message}"
|
372
424
|
end
|
373
425
|
|
426
|
+
# @private
|
427
|
+
def fetch_next_bundle(bundle, client, tags)
|
428
|
+
reply = fhir_client(client).raw_read_url(next_bundle_link(bundle))
|
429
|
+
store_request('outgoing', tags:) { reply }
|
430
|
+
return unless request.status == 200
|
431
|
+
|
432
|
+
fhir_client(client).parse_reply(FHIR::Bundle, fhir_client(client).default_format, reply)
|
433
|
+
end
|
434
|
+
|
435
|
+
# @private
|
436
|
+
def next_bundle_link(bundle)
|
437
|
+
bundle&.link&.find { |link| link.relation == 'next' }&.url
|
438
|
+
end
|
439
|
+
|
374
440
|
module ClassMethods
|
375
441
|
# @private
|
376
442
|
def fhir_client_definitions
|
@@ -6,14 +6,16 @@ module Inferno
|
|
6
6
|
# - The data being evaluated
|
7
7
|
# - A summary/characterization of the data
|
8
8
|
# - Evaluation results
|
9
|
+
# - A Validator instance, configured to point to the given IG
|
9
10
|
class EvaluationContext
|
10
|
-
attr_reader :ig, :data, :results, :config
|
11
|
+
attr_reader :ig, :data, :results, :config, :validator
|
11
12
|
|
12
|
-
def initialize(ig, data, config)
|
13
|
+
def initialize(ig, data, config, validator)
|
13
14
|
@ig = ig
|
14
15
|
@data = data
|
15
16
|
@results = []
|
16
17
|
@config = config
|
18
|
+
@validator = validator
|
17
19
|
end
|
18
20
|
|
19
21
|
def add_result(result)
|
@@ -6,18 +6,23 @@ require_relative 'evaluation_context'
|
|
6
6
|
require_relative 'evaluation_result'
|
7
7
|
require_relative 'dataset_loader'
|
8
8
|
|
9
|
+
Dir.glob(File.join(__dir__, 'rules', '*.rb')).each do |file|
|
10
|
+
require_relative file
|
11
|
+
end
|
12
|
+
|
9
13
|
module Inferno
|
10
14
|
module DSL
|
11
15
|
module FHIREvaluation
|
12
16
|
class Evaluator
|
13
|
-
attr_accessor :ig
|
17
|
+
attr_accessor :ig, :validator
|
14
18
|
|
15
|
-
def initialize(ig
|
19
|
+
def initialize(ig, validator = nil)
|
16
20
|
@ig = ig
|
21
|
+
@validator = validator
|
17
22
|
end
|
18
23
|
|
19
24
|
def evaluate(data, config = Config.new)
|
20
|
-
context = EvaluationContext.new(@ig, data, config)
|
25
|
+
context = EvaluationContext.new(@ig, data, config, validator)
|
21
26
|
|
22
27
|
active_rules = []
|
23
28
|
config.data['Rule'].each do |rulename, rule_details|
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Inferno
|
2
|
+
module DSL
|
3
|
+
module FHIREvaluation
|
4
|
+
# This module is used to decide whether a resource instantiates a given profile.
|
5
|
+
# Aligning resources to profiles is necessary when evaluating the comprehensiveness
|
6
|
+
# of the resources with respect to those profiles, unfortunately it's impossible to
|
7
|
+
# programmatically determine intent. (i.e, is this resource supposed to instantiate this profile?)
|
8
|
+
# This module offers some approaches to make that determination.
|
9
|
+
module ProfileConformanceHelper
|
10
|
+
DEFAULT_OPTIONS = {
|
11
|
+
considerMetaProfile: true,
|
12
|
+
considerValidationResults: false,
|
13
|
+
considerOnlyResourceType: false
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
# Check whether the given resource conforms to the given profile, using the given options
|
17
|
+
# to select which approaches are considered.
|
18
|
+
# Current options:
|
19
|
+
# - If the resource is the right resourceType
|
20
|
+
# - If the resource claims conformance in resource.meta.profile
|
21
|
+
# - If the resource validates against the profile using the FHIR validator (NOT YET IMPLEMENTED)
|
22
|
+
# - If the resource meets other criteria defined in the block.
|
23
|
+
# As an example, the block may look for the presence of certain codes, such as LOINC "8867-4"
|
24
|
+
# in an Observation category code suggests that the resource intended to conform to a "Heart Rate" profile
|
25
|
+
# @param resource [FHIR::Resource]
|
26
|
+
# @param profile [FHIR::StructureDefinition]
|
27
|
+
# @param options [Hash] Hash of boolean-valued options. See DEFAULT_OPTIONS for defaults and keys
|
28
|
+
# @param validator [Inferno::DSL::FHIRResourceValidation::Validator]
|
29
|
+
# @yieldparam resource [FHIR::Resource] The original resource
|
30
|
+
# @yieldreturn [Boolean]
|
31
|
+
# @return [Boolean]
|
32
|
+
def conforms_to_profile?(resource, profile, options = DEFAULT_OPTIONS, validator = nil) # rubocop:disable Metrics/CyclomaticComplexity
|
33
|
+
return false if resource.resourceType != profile.type
|
34
|
+
|
35
|
+
return true if options[:considerOnlyResourceType]
|
36
|
+
|
37
|
+
return true if options[:considerMetaProfile] && declares_meta_profile?(resource, profile)
|
38
|
+
|
39
|
+
return true if options[:considerValidationResults] && validates_profile?(resource, profile, validator)
|
40
|
+
|
41
|
+
return true if block_given? && yield(resource)
|
42
|
+
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
# Check if the given resource claims conformance to the profile, versioned or unversioned,
|
47
|
+
# based on resource.meta.profile.
|
48
|
+
# @param resource [FHIR::Resource]
|
49
|
+
# @param profile [FHIR::StructureDefinition]
|
50
|
+
def declares_meta_profile?(resource, profile)
|
51
|
+
declared_profiles = resource&.meta&.profile || []
|
52
|
+
profile_url = profile.url
|
53
|
+
versioned_url = "#{profile_url}|#{profile.version}"
|
54
|
+
|
55
|
+
declared_profiles.include?(profile_url) || declared_profiles.include?(versioned_url)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @private until implemented
|
59
|
+
def validates_profile?(_resource, _profile, _validator)
|
60
|
+
raise 'Profile validation is not yet implemented. ' \
|
61
|
+
'Set considerValidationResults=false.'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Inferno
|
2
|
+
module DSL
|
3
|
+
module FHIREvaluation
|
4
|
+
class ReferenceExtractor
|
5
|
+
attr_accessor :resource_type_ids, :references
|
6
|
+
|
7
|
+
def extract_resource_type_ids(resources)
|
8
|
+
@resource_type_ids = Hash.new { |type, id| type[id] = [] }
|
9
|
+
|
10
|
+
resources.each do |resource|
|
11
|
+
resource.each_element do |value, metadata, path|
|
12
|
+
next unless metadata['type'] == 'id'
|
13
|
+
next if path.include?('contained')
|
14
|
+
|
15
|
+
type = metadata['path'].partition('.').first.downcase
|
16
|
+
resource_type_ids[type] << value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
resource_type_ids
|
21
|
+
end
|
22
|
+
|
23
|
+
def extract_references(resources)
|
24
|
+
@references = Hash.new { |reference, id| reference[id] = [] }
|
25
|
+
|
26
|
+
resources.each do |resource|
|
27
|
+
extract_references_from_resource(resource)
|
28
|
+
end
|
29
|
+
|
30
|
+
references
|
31
|
+
end
|
32
|
+
|
33
|
+
def extract_references_from_resource(resource)
|
34
|
+
resource.each_element do |value, metadata, path|
|
35
|
+
if metadata['type'] == 'Reference' && !value.reference.nil?
|
36
|
+
if value.reference.start_with?('#')
|
37
|
+
next
|
38
|
+
elsif value.reference.include? '/'
|
39
|
+
add_parsed_reference(resource, value, path)
|
40
|
+
elsif value.reference.start_with? 'urn:uuid:'
|
41
|
+
references[resource.id] << { path: path, type: '', id: value.reference[9..] }
|
42
|
+
else
|
43
|
+
references[resource.id] << { path: path, type: '', id: value.reference }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_parsed_reference(resource, value, path)
|
50
|
+
type = value.reference.split('/')[-2].downcase
|
51
|
+
id = value.reference.split('/')[-1]
|
52
|
+
references[resource.id] << if resource_type_ids.key?(type)
|
53
|
+
{ path: path, type: type, id: id }
|
54
|
+
else
|
55
|
+
{ path: path, type: '', id: value.reference }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|