inferno_core 0.6.4 → 0.6.5

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/lib/inferno/apps/cli/evaluate.rb +1 -30
  3. data/lib/inferno/apps/cli/new.rb +1 -2
  4. data/lib/inferno/apps/cli/templates/.env.development +1 -0
  5. data/lib/inferno/apps/cli/templates/.env.production +1 -0
  6. data/lib/inferno/apps/cli/templates/.gitignore +1 -0
  7. data/lib/inferno/apps/cli/templates/data/igs/.keep +0 -0
  8. data/lib/inferno/apps/cli/templates/docker-compose.background.yml.tt +2 -2
  9. data/lib/inferno/apps/web/controllers/controller.rb +3 -1
  10. data/lib/inferno/apps/web/router.rb +12 -6
  11. data/lib/inferno/config/boot/ig_files.rb +47 -0
  12. data/lib/inferno/config/boot/validator.rb +1 -0
  13. data/lib/inferno/config/boot/web.rb +6 -2
  14. data/lib/inferno/dsl/assertions.rb +26 -0
  15. data/lib/inferno/dsl/fhir_client_builder.rb +1 -0
  16. data/lib/inferno/dsl/fhir_evaluation/rules/all_must_supports_present.rb +13 -308
  17. data/lib/inferno/dsl/fhir_resource_validation.rb +34 -2
  18. data/lib/inferno/dsl/fhir_validation.rb +13 -0
  19. data/lib/inferno/dsl/must_support_assessment.rb +365 -0
  20. data/lib/inferno/dsl/results.rb +36 -4
  21. data/lib/inferno/dsl/runnable.rb +71 -0
  22. data/lib/inferno/dsl.rb +3 -1
  23. data/lib/inferno/entities/ig.rb +4 -1
  24. data/lib/inferno/exceptions.rb +6 -0
  25. data/lib/inferno/public/bundle.js +34 -34
  26. data/lib/inferno/public/bundle.js.LICENSE.txt +3 -3
  27. data/lib/inferno/repositories/igs.rb +122 -0
  28. data/lib/inferno/repositories/in_memory_repository.rb +7 -0
  29. data/lib/inferno/utils/ig_downloader.rb +17 -6
  30. data/lib/inferno/version.rb +1 -1
  31. data/spec/shared/test_kit_examples.rb +69 -0
  32. metadata +5 -2
@@ -61,7 +61,7 @@
61
61
  */
62
62
 
63
63
  /**
64
- * @remix-run/router v1.19.2
64
+ * @remix-run/router v1.21.0
65
65
  *
66
66
  * Copyright (c) Remix Software Inc.
67
67
  *
@@ -72,7 +72,7 @@
72
72
  */
73
73
 
74
74
  /**
75
- * React Router DOM v6.26.2
75
+ * React Router DOM v6.28.1
76
76
  *
77
77
  * Copyright (c) Remix Software Inc.
78
78
  *
@@ -83,7 +83,7 @@
83
83
  */
84
84
 
85
85
  /**
86
- * React Router v6.26.2
86
+ * React Router v6.28.1
87
87
  *
88
88
  * Copyright (c) Remix Software Inc.
89
89
  *
@@ -1,9 +1,131 @@
1
1
  require_relative 'in_memory_repository'
2
2
 
3
+ require_relative '../utils/ig_downloader'
4
+
3
5
  module Inferno
4
6
  module Repositories
5
7
  # Repository that deals with persistence for the `IG` entity.
6
8
  class IGs < InMemoryRepository
9
+ include Inferno::Utils::IgDownloader
10
+
11
+ # Get the instance of the IG specified by either identifier or file path.
12
+ # An in-memory instance will be returned if already loaded, otherwise
13
+ # the IG will be retrieved from the user package cache (~/.fhir/packages)
14
+ # or from the package server and then loaded into the repository.
15
+ # @param id_or_path [String] either an identifier, eg "hl7.fhir.us.core#3.1.1"
16
+ # or a file path, eg "./igs/uscore.tgz"
17
+ # @return [Inferno::Entities::IG]
18
+ def find_or_load(id_or_path)
19
+ return find(id_or_path) if exists?(id_or_path)
20
+
21
+ ig_by_path = find_by_path(id_or_path)
22
+
23
+ return ig_by_path if ig_by_path
24
+
25
+ load(id_or_path)
26
+ end
27
+
28
+ # Get the instance of the already-loaded IG specified by file path.
29
+ # @param path [String] file path, eg "./igs/uscore.tgz"
30
+ # @return [Inferno::Entities::IG]
31
+ def find_by_path(path)
32
+ all.find { |ig| ig.source_path == path }
33
+ end
34
+
35
+ # @private
36
+ def load(ig_path)
37
+ local_ig_file = find_local_file(ig_path)
38
+ if local_ig_file
39
+ ig = Inferno::Entities::IG.from_file(local_ig_file)
40
+ # To match the HL7 FHIR validator, DO NOT cache igs loaded from file
41
+ elsif in_user_package_cache?(ig_path.sub('@', '#'))
42
+ # NPM syntax for a package identifier is id@version (eg, hl7.fhir.us.core@3.1.1)
43
+ # but in the cache the separator is # (hl7.fhir.us.core#3.1.1)
44
+ cache_directory = File.join(user_package_cache, ig_path.sub('@', '#'))
45
+ ig = Inferno::Entities::IG.from_file(cache_directory)
46
+ else
47
+ Tempfile.create(['package', '.tgz']) do |temp_file|
48
+ load_ig(ig_path, nil, temp_file.path)
49
+ cache_directory = add_package_to_cache(ig_path.sub('@', '#'), temp_file.path)
50
+ ig = Inferno::Entities::IG.from_file(cache_directory)
51
+ end
52
+ end
53
+ ig.add_self_to_repository
54
+ ig
55
+ end
56
+
57
+ # @private
58
+ def find_local_file(ig_path)
59
+ return nil unless ['.tgz', '.tar.gz'].any? { |ext| ig_path.downcase.end_with?(ext) }
60
+
61
+ return ig_path if File.exist?(ig_path)
62
+
63
+ # IG packages are copied to ./data/igs to be used by the validator,
64
+ # and if referenced by file in the validator block the path given must be "igs/{filename}.tgz"
65
+ data_igs_path = File.join('data', ig_path)
66
+ return data_igs_path if File.exist?(data_igs_path)
67
+
68
+ # Last resort, try to find the file under the current working directory,
69
+ # eg, given ig_path: 'igs/package123.tgz'
70
+ # this would find 'lib/my_test_kit/igs/package123.tgz'
71
+ Dir.glob(File.join('**', ig_path))[0]
72
+ end
73
+
74
+ # @private
75
+ def user_package_cache
76
+ File.join(Dir.home, '.fhir', 'packages')
77
+ end
78
+
79
+ # @private
80
+ def in_user_package_cache?(ig_identifier)
81
+ File.directory?(File.join(user_package_cache, ig_identifier))
82
+ end
83
+
84
+ # @private
85
+ def add_package_to_cache(ig_identifier, temp_tgz)
86
+ return temp_tgz if ENV['READ_ONLY_FHIR_PACKAGE_CACHE'].present?
87
+
88
+ # In the HL7 FHIR validator, this is handled by the FilesystemPackageCacheManager:
89
+ # https://github.com/hapifhir/org.hl7.fhir.core/blob/3cf2a06e7abda7dc32cdc052d3a356c1201139cf/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/FilesystemPackageCacheManager.java#L558
90
+ # We try to follow the same approach here
91
+
92
+ lockfile_path = File.join(user_package_cache, "#{ig_identifier}.lock")
93
+ lockfile = File.open(lockfile_path, 'w+')
94
+ lockfile.flock(File::LOCK_EX)
95
+
96
+ target_cache_dir = File.join(user_package_cache, ig_identifier)
97
+
98
+ # Break early 1 - something else already created it while we got the lock
99
+ return target_cache_dir if in_user_package_cache?(ig_identifier)
100
+
101
+ # 1. Extract to a temp folder under the cache
102
+ temp_dir = File.join(user_package_cache, SecureRandom.uuid)
103
+ FileUtils.mkdir_p(temp_dir)
104
+
105
+ system "tar -xzf #{temp_tgz} --directory #{temp_dir}"
106
+
107
+ # Break early 2 - something else already created it
108
+ return target_cache_dir if in_user_package_cache?(ig_identifier)
109
+
110
+ # 2. Rename the temp folder to the correct name
111
+ File.rename(temp_dir, target_cache_dir)
112
+
113
+ # return the path in cache
114
+ target_cache_dir
115
+ rescue StandardError => e
116
+ Application['logger'].error(e.full_message)
117
+ # Don't leave a half extracted package behind
118
+ FileUtils.remove_dir(temp_dir, true)
119
+
120
+ # Return the tgz so that processing can continue
121
+ temp_tgz
122
+ ensure
123
+ if lockfile.present?
124
+ lockfile.flock(File::LOCK_UN)
125
+ lockfile.close
126
+ File.delete(lockfile)
127
+ end
128
+ end
7
129
  end
8
130
  end
9
131
  end
@@ -16,6 +16,13 @@ module Inferno
16
16
  entity
17
17
  end
18
18
 
19
+ def remove(entity)
20
+ return unless exists?(entity.id)
21
+
22
+ all.delete(entity)
23
+ all_by_id.delete(entity.id.to_s)
24
+ end
25
+
19
26
  def find(id)
20
27
  all_by_id[id.to_s]
21
28
  end
@@ -1,3 +1,5 @@
1
+ require 'open-uri'
2
+
1
3
  module Inferno
2
4
  module Utils
3
5
  module IgDownloader
@@ -14,7 +16,7 @@ module Inferno
14
16
  File.join(ig_path, suffix ? "package_#{suffix}.tgz" : 'package.tgz')
15
17
  end
16
18
 
17
- def load_ig(ig_input, idx = nil, thor_config = { verbose: true }, output_path = nil)
19
+ def load_ig(ig_input, idx = nil, output_path = nil)
18
20
  case ig_input
19
21
  when FHIR_PACKAGE_NAME_REG_EX
20
22
  uri = ig_registry_url(ig_input)
@@ -29,19 +31,28 @@ module Inferno
29
31
  end
30
32
 
31
33
  destination = output_path || ig_file(idx)
32
- # use Thor's get to support CLI options config
33
- get(uri, destination, thor_config)
34
+ download_file(uri, destination)
34
35
  uri
35
36
  end
36
37
 
38
+ def download_file(uri, destination)
39
+ # Inspired by Thor `get`
40
+ # https://github.com/rails/thor/blob/3178667e1727504bf4fb693bf4ac74a5ca6c691e/lib/thor/actions/file_manipulation.rb#L81
41
+ download = URI.send(:open, uri)
42
+ IO.copy_stream(download, destination)
43
+ end
44
+
37
45
  def ig_registry_url(ig_npm_style)
38
- unless ig_npm_style.include? '@'
46
+ if ig_npm_style.include?('@')
47
+ package_name, version = ig_npm_style.split('@')
48
+ elsif ig_npm_style.include?('#')
49
+ package_name, version = ig_npm_style.split('#')
50
+ else
39
51
  raise StandardError, <<~NO_VERSION
40
- No IG version specified for #{ig_npm_style}; you must specify one with '@'. I.e: hl7.fhir.us.core@6.1.0
52
+ No IG version specified for #{ig_npm_style}; you must specify one with '@' or '#'. I.e: hl7.fhir.us.core@6.1.0
41
53
  NO_VERSION
42
54
  end
43
55
 
44
- package_name, version = ig_npm_style.split('@')
45
56
  "https://packages.fhir.org/#{package_name}/-/#{package_name}-#{version}.tgz"
46
57
  end
47
58
 
@@ -1,4 +1,4 @@
1
1
  module Inferno
2
2
  # Standard patterns for gem versions: https://guides.rubygems.org/patterns/
3
- VERSION = '0.6.4'.freeze
3
+ VERSION = '0.6.5'.freeze
4
4
  end
@@ -70,6 +70,62 @@ RSpec.shared_examples 'platform_deployable_test_kit' do
70
70
 
71
71
  expect(dockerfile_contents.lines.first.chomp).to eq('FROM ruby:3.3.6')
72
72
  end
73
+
74
+ context 'when it contains a validator service' do
75
+ it 'has a data/igs/.keep file' do
76
+ docker_compose_file_path = File.join(base_path, 'docker-compose.background.yml')
77
+ docker_compose_contents = YAML.load_file(docker_compose_file_path)
78
+
79
+ validator_service_images = [
80
+ 'infernocommunity/fhir-validator-service',
81
+ 'infernocommunity/inferno-resource-validator'
82
+ ]
83
+ validator_service =
84
+ docker_compose_contents['services']
85
+ .any? { |_name, service| validator_service_images.include? service['image'] }
86
+
87
+ if validator_service.present?
88
+ igs_keep_file = File.join(base_path, 'data', 'igs', '.keep')
89
+ error_message =
90
+ "Create a 'data/igs/.keep' file and commit it"
91
+ expect(File.exist?(igs_keep_file)).to be(true), error_message
92
+ end
93
+ end
94
+
95
+ it 'uses data/igs as the path for test kit IGs in the hl7 validator service' do
96
+ docker_compose_file_path = File.join(base_path, 'docker-compose.background.yml')
97
+ docker_compose_contents = YAML.load_file(docker_compose_file_path)
98
+
99
+ hl7_validator_service =
100
+ docker_compose_contents['services']
101
+ .find { |_name, service| service['image'] == 'infernocommunity/inferno-resource-validator' }
102
+ if hl7_validator_service.present?
103
+ hl7_validator_ig_volume =
104
+ hl7_validator_service[1]['volumes']&.find { |volume| volume.end_with? 'igs' }
105
+
106
+ expected_hl7_validator_volume = './data/igs:/app/igs'
107
+ error_message =
108
+ "Update the hl7 validator service IG volume from '#{hl7_validator_ig_volume}' " \
109
+ "to '#{expected_hl7_validator_volume}'"
110
+ expect(hl7_validator_ig_volume).to eq(expected_hl7_validator_volume), error_message
111
+ end
112
+
113
+ standalone_validator_service =
114
+ docker_compose_contents['services']
115
+ .find { |_name, service| service['image'] == 'infernocommunity/fhir-validator-service' }
116
+
117
+ if standalone_validator_service.present?
118
+ standalone_validator_ig_volume =
119
+ standalone_validator_service[1]['volumes']&.find { |volume| volume.end_with? 'igs' }
120
+
121
+ expected_standalone_validator_volume = './data/igs:/home/igs'
122
+ error_message =
123
+ "Update the validator service IG volume from '#{standalone_validator_ig_volume}' " \
124
+ "to '#{expected_standalone_validator_volume}'"
125
+ expect(standalone_validator_ig_volume).to eq(expected_standalone_validator_volume), error_message
126
+ end
127
+ end
128
+ end
73
129
  end
74
130
 
75
131
  describe 'suites' do
@@ -99,6 +155,19 @@ RSpec.shared_examples 'platform_deployable_test_kit' do
99
155
  expect(link_labels).to include(*expected_labels), error_message
100
156
  end
101
157
  end
158
+
159
+ it 'does not rely on the deprecated `Inferno::DSL::FHIRValidation`' do
160
+ suites.each do |suite|
161
+ suite.fhir_validators.each do |name, validators|
162
+ validators.each do |validator|
163
+ error_message =
164
+ "Validator '#{name}' in Suite '#{suite.id}' should be changed to a " \
165
+ 'fhir_resource_validator'
166
+ expect(validator).to_not be_an_instance_of(Inferno::DSL::FHIRValidation::Validator), error_message
167
+ end
168
+ end
169
+ end
170
+ end
102
171
  end
103
172
 
104
173
  describe 'presets' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inferno_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.4
4
+ version: 0.6.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen MacVicar
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2025-02-14 00:00:00.000000000 Z
13
+ date: 2025-03-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -441,6 +441,7 @@ files:
441
441
  - lib/inferno/apps/cli/templates/config/nginx.conf.tt
442
442
  - lib/inferno/apps/cli/templates/config/puma.rb.tt
443
443
  - lib/inferno/apps/cli/templates/data/.keep
444
+ - lib/inferno/apps/cli/templates/data/igs/.keep
444
445
  - lib/inferno/apps/cli/templates/data/redis/.keep
445
446
  - lib/inferno/apps/cli/templates/docker-compose.background.yml.tt
446
447
  - lib/inferno/apps/cli/templates/docker-compose.yml.tt
@@ -501,6 +502,7 @@ files:
501
502
  - lib/inferno/config/boot.rb
502
503
  - lib/inferno/config/boot/db.rb
503
504
  - lib/inferno/config/boot/executor.rb
505
+ - lib/inferno/config/boot/ig_files.rb
504
506
  - lib/inferno/config/boot/logging.rb
505
507
  - lib/inferno/config/boot/presets.rb
506
508
  - lib/inferno/config/boot/sidekiq.rb
@@ -546,6 +548,7 @@ files:
546
548
  - lib/inferno/dsl/jwks.rb
547
549
  - lib/inferno/dsl/links.rb
548
550
  - lib/inferno/dsl/messages.rb
551
+ - lib/inferno/dsl/must_support_assessment.rb
549
552
  - lib/inferno/dsl/must_support_metadata_extractor.rb
550
553
  - lib/inferno/dsl/oauth_credentials.rb
551
554
  - lib/inferno/dsl/primitive_type.rb