-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FI-3440: Add IG entity and repository, integrated into Evaluate task #573
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
c16e436
FI-3440: Add IG class to DSL
dehall 5641c4c
FI-3440: Add IG spec
dehall ed5b8cf
cleanup
dehall 4028fcd
FI-3440 review feedback, part 1
dehall 6ac13ea
Fix reading from directory
dehall dafe3cb
add test for loading IG from directory
dehall 252796f
review feedback
dehall File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'fhir_models' | ||
require 'pathname' | ||
require 'rubygems/package' | ||
require 'zlib' | ||
|
||
require_relative '../repositories/igs' | ||
|
||
module Inferno | ||
module Entities | ||
# IG is a wrapper class around the relevant concepts inside an IG. | ||
# Not everything within an IG is currently used by Inferno. | ||
class IG < Entity | ||
ATTRIBUTES = [ | ||
:id, | ||
:profiles, | ||
:extensions, | ||
:value_sets, | ||
:search_params, | ||
:examples | ||
].freeze | ||
|
||
include Inferno::Entities::Attributes | ||
|
||
def initialize(**params) | ||
super(params, ATTRIBUTES) | ||
|
||
@profiles = [] | ||
@extensions = [] | ||
@value_sets = [] | ||
@examples = [] | ||
@search_params = [] | ||
end | ||
|
||
def self.from_file(ig_path) | ||
raise "#{ig_path} does not exist" unless File.exist?(ig_path) | ||
|
||
# fhir_models by default logs the entire content of non-FHIR files | ||
# which could be things like a package.json | ||
original_logger = FHIR.logger | ||
FHIR.logger = Logger.new('/dev/null') | ||
|
||
if File.directory?(ig_path) | ||
from_directory(ig_path) | ||
elsif ig_path.end_with? '.tgz' | ||
from_tgz(ig_path) | ||
else | ||
raise "Unable to load #{ig_path} as it does not appear to be a directory or a .tgz file" | ||
end | ||
ensure | ||
FHIR.logger = original_logger if defined? original_logger | ||
end | ||
|
||
def self.from_tgz(ig_path) | ||
tar = Gem::Package::TarReader.new( | ||
Zlib::GzipReader.open(ig_path) | ||
) | ||
|
||
ig = IG.new | ||
|
||
tar.each do |entry| | ||
next if skip_item?(entry.full_name, entry.directory?) | ||
|
||
begin | ||
resource = FHIR::Json.from_json(entry.read) | ||
next if resource.nil? | ||
|
||
ig.handle_resource(resource, entry.full_name) | ||
rescue StandardError | ||
next | ||
end | ||
end | ||
ig | ||
end | ||
|
||
def self.from_directory(ig_directory) | ||
ig = IG.new | ||
|
||
ig_path = Pathname.new(ig_directory) | ||
Dir.glob("#{ig_path}/**/*") do |f| | ||
relative_path = Pathname.new(f).relative_path_from(ig_path).to_s | ||
next if skip_item?(relative_path, File.directory?(f)) | ||
|
||
begin | ||
resource = FHIR::Json.from_json(File.read(f)) | ||
next if resource.nil? | ||
|
||
ig.handle_resource(resource, relative_path) | ||
rescue StandardError | ||
next | ||
end | ||
end | ||
ig | ||
end | ||
|
||
# These files aren't FHIR resources | ||
FILES_TO_SKIP = ['package.json', 'validation-summary.json'].freeze | ||
|
||
def self.skip_item?(relative_path, is_directory) | ||
return true if is_directory | ||
|
||
file_name = relative_path.split('/').last | ||
|
||
return true unless file_name.end_with? '.json' | ||
return true unless relative_path.start_with? 'package/' | ||
|
||
return true if file_name.start_with? '.' # ignore hidden files | ||
return true if file_name.end_with? '.openapi.json' | ||
return true if FILES_TO_SKIP.include? file_name | ||
|
||
false | ||
end | ||
|
||
def handle_resource(resource, relative_path) | ||
case resource.resourceType | ||
when 'StructureDefinition' | ||
if resource.type == 'Extension' | ||
extensions.push resource | ||
else | ||
profiles.push resource | ||
end | ||
when 'ValueSet' | ||
value_sets.push resource | ||
when 'SearchParameter' | ||
search_params.push resource | ||
when 'ImplementationGuide' | ||
@id = extract_package_id(resource) | ||
else | ||
examples.push(resource) if relative_path.start_with? 'package/example' | ||
end | ||
end | ||
|
||
def extract_package_id(ig_resource) | ||
"#{ig_resource.id}##{ig_resource.version || 'current'}" | ||
end | ||
|
||
# @private | ||
def add_self_to_repository | ||
repository.insert(self) | ||
end | ||
|
||
# @private | ||
def repository | ||
Inferno::Repositories::IGs.new | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
require_relative 'in_memory_repository' | ||
|
||
module Inferno | ||
module Repositories | ||
# Repository that deals with persistence for the `IG` entity. | ||
class IGs < InMemoryRepository | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module ExtractTGZHelper | ||
def extract_tgz(fixture) | ||
filename = File.basename(fixture, '.tgz') | ||
target_dir = Dir.mktmpdir(filename) | ||
system "mkdir -p #{target_dir}" | ||
system "tar -xzf #{fixture} --directory #{target_dir}" | ||
target_dir | ||
end | ||
|
||
def cleanup(target_dir) | ||
FileUtils.remove_entry(target_dir) | ||
end | ||
end |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
require 'extract_tgz_helper' | ||
|
||
RSpec.describe Inferno::Entities::IG do | ||
include ExtractTGZHelper | ||
|
||
let(:uscore3_package) { File.expand_path('../../fixtures/uscore311.tgz', __dir__) } | ||
let(:uscore3_untarred) { extract_tgz(uscore3_package) } | ||
|
||
after { cleanup(uscore3_untarred) } | ||
|
||
describe '#from_file' do | ||
it 'loads an IG from tgz file' do | ||
ig = described_class.from_file(uscore3_package) | ||
expect_uscore3_loaded_properly(ig) | ||
end | ||
|
||
it 'loads an IG from directory' do | ||
ig = described_class.from_file(uscore3_untarred) | ||
expect_uscore3_loaded_properly(ig) | ||
end | ||
|
||
def expect_uscore3_loaded_properly(ig) # rubocop:disable Naming/MethodParameterName, Metrics/CyclomaticComplexity | ||
# For each artifact type in the IG, check: | ||
# the right number are loaded, | ||
# they're all the expected type, | ||
# and spot check a couple IDs | ||
|
||
# https://www.hl7.org/fhir/us/core/STU3.1.1/profiles.html | ||
expect(ig.profiles.length).to eq(26) | ||
expect(ig.profiles.map(&:resourceType).uniq).to eq(['StructureDefinition']) | ||
expect(ig.profiles.map(&:id)).to include('us-core-patient', 'us-core-condition', | ||
'head-occipital-frontal-circumference-percentile') | ||
|
||
expect(ig.extensions.length).to eq(4) | ||
expect(ig.extensions.map(&:resourceType).uniq).to eq(['StructureDefinition']) | ||
expect(ig.extensions.map(&:type).uniq).to eq(['Extension']) | ||
expect(ig.extensions.map(&:id)).to include('us-core-race', 'us-core-ethnicity') | ||
|
||
# https://www.hl7.org/fhir/us/core/STU3.1.1/terminology.html | ||
expect(ig.value_sets.length).to eq(32) | ||
expect(ig.value_sets.map(&:resourceType).uniq).to eq(['ValueSet']) | ||
expect(ig.value_sets.map(&:id)).to include('us-core-usps-state', 'simple-language') | ||
|
||
# https://www.hl7.org/fhir/us/core/STU3.1.1/searchparameters.html | ||
expect(ig.search_params.length).to eq(74) | ||
expect(ig.search_params.map(&:resourceType).uniq).to eq(['SearchParameter']) | ||
expect(ig.search_params.map(&:id)).to include('us-core-patient-name', 'us-core-encounter-id') | ||
|
||
# https://www.hl7.org/fhir/us/core/STU3.1.1/all-examples.html | ||
expect(ig.examples.length).to eq(84) | ||
expect(ig.examples.map(&:id)).to include('child-example', 'self-tylenol') | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note I deleted this line because it makes things a lot more complicated when using this with the evaluate rake task. Since we're just downloading to a temp folder in that case, I don't want to say "Put its package.tgz in /var/folders/l1/d3cs21v54hb4djpb1yjnlzm40000gn/T/inferno-core/" or whatever a temp folder path looks like, because doing that and trying the same thing wouldn't work. I'm open to wordsmithing and adding more params if we want to keep a note like this in the error message