Repository: FHIR/sushi
Pull request #698 (2020-12-17 00:10:41)

Support for loading FHIR R5 pre-releases
This allows users to specify FHIR versions > 4.0.1.  In that case, the user will receive a warning that support for pre-releases is experimental.

NOTE: this does not add support for new datatypes like integer64 or CodeableReference.

To test this on a real SUSHI project, checkout the [r5-experimental](https://github.com/HL7/fhir-mCODE-ig/tree/r5-experimental) branch on mCODE.  You can also change its FHIR version to `current` to test w/ current FHIR build.  Note that I did not have success building w/ the FHIR IG Publisher, but the errors did not seem to be related to faults in what SUSHI was doing.


Patch:
diff --git a/src/app.ts b/src/app.ts
index ee58f92..1072d36 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -8,7 +8,7 @@ import { pad, padStart, sample, padEnd } from 'lodash';
 import { FSHTank, RawFSH } from './import';
 import { exportFHIR, Package } from './export';
 import { IGExporter } from './ig';
-import { logger, stats, Type } from './utils';
+import { logger, stats, isSupportedFHIRVersion, Type } from './utils';
 import { loadCustomResources } from './fhirdefs';
 import { FHIRDefinitions } from './fhirdefs';
 import { Configuration } from './fshtypes';
@@ -153,9 +153,9 @@ async function app() {
 
   // Check for StructureDefinition
   const structDef = defs.fishForFHIR('StructureDefinition', Type.Resource);
-  if (structDef?.version !== '4.0.1') {
+  if (structDef == null || !isSupportedFHIRVersion(structDef.version)) {
     logger.error(
-      'StructureDefinition resource not found for v4.0.1. The FHIR R4 package in local cache' +
+      'Valid StructureDefinition resource not found. The FHIR package in your local cache' +
         ' may be corrupt. Local FHIR cache can be found at <home-directory>/.fhir/packages.' +
         ' For more information, see https://wiki.hl7.org/FHIR_Package_Cache#Location.'
     );
diff --git a/src/fhirdefs/load.ts b/src/fhirdefs/load.ts
index e39946f..526dc45 100644
--- a/src/fhirdefs/load.ts
+++ b/src/fhirdefs/load.ts
@@ -50,10 +50,19 @@ export async function loadDependency(
     loadedPackage = loadFromPath(loadPath, fullPackageName, FHIRDefs);
   }
 
-  // Even if a local current package is loaded, we must still check that the local package date matches
-  // the date on the most recent version on build.fhir.org. If the date does not match, we re-download to the cache
   let packageUrl;
-  if (version === 'current') {
+  if (packageName.startsWith('hl7.fhir.r5.') && version === 'current') {
+    packageUrl = `http://build.fhir.org/${packageName}.tgz`;
+    // TODO: Figure out how to determine if the cached package is current
+    // See: https://chat.fhir.org/#narrow/stream/179252-IG-creation/topic/Registry.20for.20FHIR.20Core.20packages.20.3E.204.2E0.2E1
+    if (loadedPackage) {
+      logger.info(
+        `Downloading ${fullPackageName} since SUSHI cannot determine if the version in the local cache is the most recent build.`
+      );
+    }
+  } else if (version === 'current') {
+    // Even if a local current package is loaded, we must still check that the local package date matches
+    // the date on the most recent version on build.fhir.org. If the date does not match, we re-download to the cache
     const baseUrl = 'http://build.fhir.org/ig';
     const res = await axios.get(`${baseUrl}/qas.json`);
     const qaData: { 'package-id': string; date: string; repo: string }[] = res?.data;
@@ -104,8 +113,13 @@ export async function loadDependency(
     }
   } else if (!loadedPackage) {
     // If the package is not locally cached, and it is not a current or dev version, we attempt to get it
-    // from packages.fhir.org
-    packageUrl = `http://packages.fhir.org/${packageName}/${version}`;
+    // from packages.fhir.org or packages2.fhir.org
+    if (packageName.startsWith('hl7.fhir.r5.')) {
+      // Temporary.  See: https://chat.fhir.org/#narrow/stream/179252-IG-creation/topic/Registry.20for.20FHIR.20Core.20packages.20.3E.204.2E0.2E1
+      packageUrl = `http://packages2.fhir.org/packages/${packageName}/${version}`;
+    } else {
+      packageUrl = `http://packages.fhir.org/${packageName}/${version}`;
+    }
   }
 
   // If the packageUrl is set, we must download the package from that url, and extract it to our local cache
diff --git a/src/fhirtypes/ElementDefinition.ts b/src/fhirtypes/ElementDefinition.ts
index 470c406..e364f07 100644
--- a/src/fhirtypes/ElementDefinition.ts
+++ b/src/fhirtypes/ElementDefinition.ts
@@ -65,7 +65,8 @@ export class ElementDefinitionType {
     const fhirTypeExtension = this.extension?.find(
       ext => ext.url === 'http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type'
     );
-    return fhirTypeExtension?.valueUrl ?? this._code;
+    // R4 uses valueUrl; R5 uses valueUri
+    return fhirTypeExtension?.valueUrl ?? fhirTypeExtension?.valueUri ?? this._code;
   }
 
   set code(c: string) {
@@ -2033,6 +2034,7 @@ export type ElementDefinitionExtension = {
   url: string;
   // TODO: support all the value[x]
   valueUrl?: string;
+  valueUri?: string;
 };
 
 export type ElementDefinitionExample = {
diff --git a/src/import/YAMLConfiguration.ts b/src/import/YAMLConfiguration.ts
index 794fc7c..692bbab 100644
--- a/src/import/YAMLConfiguration.ts
+++ b/src/import/YAMLConfiguration.ts
@@ -78,7 +78,7 @@ export type YAMLConfiguration = {
   license?: ImplementationGuide['license']; // string
 
   // Although fhirVersions is 0..* in the ImplementationGuide resource, it can be a single item OR
-  // an array here (but so far SUSHI only supports 4.0.1 anyway).
+  // an array here (NOTE: SUSHI only supports FHIR 4.x versions).
   fhirVersion:
     | ImplementationGuide['fhirVersion'][0] // string
     | ImplementationGuide['fhirVersion']; // string[]
diff --git a/src/import/ensureConfigurationFile.ts b/src/import/ensureConfigurationFile.ts
index 6d3247c..4a4fb03 100644
--- a/src/import/ensureConfigurationFile.ts
+++ b/src/import/ensureConfigurationFile.ts
@@ -209,15 +209,22 @@ function generateConfiguration(root: string, allowFromScratch: boolean): string
     contents.add(new YAMLPair('license', packageJSON.license ?? igIni.license));
   }
   // fhirVersion
-  contents.add(new YAMLPair('fhirVersion', '4.0.1'));
+  const fhirDependency =
+    packageJSON.dependencies?.['hl7.fhir.r4.core'] ??
+    packageJSON.dependencies?.['hl7.fhir.r5.core'] ??
+    DEFAULT_PACKAGE_JSON.dependencies['hl7.fhir.r4.core'];
+  contents.add(new YAMLPair('fhirVersion', fhirDependency));
   // dependencies
   const packageWithDeps =
     packageJSON.dependencies &&
-    Object.keys(packageJSON.dependencies)?.some(k => k !== 'hl7.fhir.r4.core')
+    Object.keys(packageJSON.dependencies)?.some(
+      k => k !== 'hl7.fhir.r4.core' && k !== 'hl7.fhir.r5.core'
+    )
       ? packageJSON
       : DEFAULT_PACKAGE_JSON;
   const dependencies = cloneDeep(packageWithDeps.dependencies);
   delete dependencies['hl7.fhir.r4.core'];
+  delete dependencies['hl7.fhir.r5.core'];
   if (packageWithDeps !== DEFAULT_PACKAGE_JSON) {
     contents.add(new YAMLPair('dependencies', dependencies));
   }
@@ -638,7 +645,7 @@ const DEFAULT_PACKAGE_LIST: any = {
     },
     {
       version: DEFAULT_PACKAGE_JSON.version,
-      fhirversion: '4.0.1',
+      fhirversion: DEFAULT_PACKAGE_JSON.dependencies['hl7.fhir.r4.core'],
       date: '2099-01-01',
       desc: 'Initial STU ballot (Mmm yyyy Ballot)',
       path: 'http://example.org/fhir/STU1',
diff --git a/src/run/FshToFhir.ts b/src/run/FshToFhir.ts
index 04cef8f..e9406bb 100644
--- a/src/run/FshToFhir.ts
+++ b/src/run/FshToFhir.ts
@@ -15,7 +15,7 @@ import {
  * NOTE: This function is not safe for true asynchronous usage. If two calls of this function are running at once, the error and warnings reported
  * will be inconsistent. Always ensure a given call to this function completes before making a new call.
  * @param {string|string[]} input - A string or array of strings containing FSH
- * @param {fshToFhirOptions} options - An object containing options for processing. Options include canonical, version, dependencies, and logLevel
+ * @param {fshToFhirOptions} options - An object containing options for processing. Options include canonical, version, fhirVersion, dependencies, and logLevel
  * @returns {Promise<{fhir: any[]; errors: ErrorsAndWarnings['errors']; warnings: ErrorsAndWarnings['warnings'];}>} - Object containing generated fhir, and errors/warnings from processing
  */
 export async function fshToFhir(
@@ -52,7 +52,7 @@ export async function fshToFhir(
   const config = {
     canonical: options.canonical ?? 'http://example.org',
     FSHOnly: true,
-    fhirVersion: ['4.0.1'],
+    fhirVersion: [options.fhirVersion ?? '4.0.1'],
     dependencies: options.dependencies,
     version: options.version
   };
@@ -93,6 +93,7 @@ export async function fshToFhir(
 type fshToFhirOptions = {
   canonical?: string;
   version?: string;
+  fhirVersion?: string;
   dependencies?: ImplementationGuideDependsOn[];
   logLevel?: Level;
 };
diff --git a/src/utils/Processing.ts b/src/utils/Processing.ts
index 4835d35..a70d39d 100644
--- a/src/utils/Processing.ts
+++ b/src/utils/Processing.ts
@@ -22,6 +22,12 @@ import {
 import { Configuration } from '../fshtypes';
 import { loadConfigurationFromIgResource } from '../import/loadConfigurationFromIgResource';
 
+export function isSupportedFHIRVersion(version: string): boolean {
+  // For now, allow current or any 4.x version of FHIR except 4.0.0. This is a quick check; not a guarantee.  If a user passes
+  // in an invalid version that passes this test (e.g., 4.99.0), it is still expected to fail when we load dependencies.
+  return /current|4\.0\.1|4\.[1-9]\d*.\d+/.test(version);
+}
+
 export function ensureInputDir(input: string): string {
   // If no input folder is specified, set default to current directory
   if (!input) {
@@ -153,10 +159,12 @@ export function readConfig(input: string, isLegacyIgPubContext: boolean): Config
     );
     throw Error;
   }
-  if (!config.fhirVersion.includes('4.0.1')) {
+  if (!config.fhirVersion.some(v => isSupportedFHIRVersion(v))) {
     logger.error(
-      `The ${path.basename(config.filePath)} must specify FHIR R4 as a fhirVersion. Be sure to` +
-        ` add "fhirVersion: 4.0.1" to the ${path.basename(config.filePath)} file.`
+      `The ${path.basename(config.filePath)} must specify a supported version of FHIR. Be sure to` +
+        ` add "fhirVersion: 4.0.1" (or 4.x.y, as appropriate) to the ${path.basename(
+          config.filePath
+        )} file.`
     );
     throw Error;
   }
@@ -167,9 +175,16 @@ export function loadExternalDependencies(
   defs: FHIRDefinitions,
   config: Configuration
 ): Promise<FHIRDefinitions | void>[] {
-  // Add FHIR R4 to the dependencies so it is loaded
+  // Add FHIR to the dependencies so it is loaded
   const dependencies = (config.dependencies ?? []).slice(); // slice so we don't modify actual config;
-  dependencies.push({ packageId: 'hl7.fhir.r4.core', version: '4.0.1' });
+  const fhirVersion = config.fhirVersion.find(v => isSupportedFHIRVersion(v));
+  const fhirPackageId = fhirVersion.startsWith('4.0') ? 'hl7.fhir.r4.core' : 'hl7.fhir.r5.core';
+  if (fhirPackageId === 'hl7.fhir.r5.core') {
+    logger.warn(
+      'SUSHI support for pre-release versions of FHIR is experimental. Use at your own risk!'
+    );
+  }
+  dependencies.push({ packageId: fhirPackageId, version: fhirVersion });
 
   // Load dependencies
   const dependencyDefs: Promise<FHIRDefinitions | void>[] = [];


Test patch:
diff --git a/test/fhirdefs/load.test.ts b/test/fhirdefs/load.test.ts
index fcf477d..6369d47 100644
--- a/test/fhirdefs/load.test.ts
+++ b/test/fhirdefs/load.test.ts
@@ -135,7 +135,10 @@ describe('#loadDependency()', () => {
       } else if (
         uri === 'http://packages.fhir.org/sushi-test/0.2.0' ||
         uri === 'http://build.fhir.org/ig/sushi/sushi-test-old/package.tgz' ||
-        uri === 'http://build.fhir.org/ig/HL7/US-Core-R4/package.tgz'
+        uri === 'http://build.fhir.org/ig/HL7/US-Core-R4/package.tgz' ||
+        uri === 'http://build.fhir.org/hl7.fhir.r5.core.tgz' ||
+        uri === 'http://packages2.fhir.org/packages/hl7.fhir.r5.core/4.5.0' ||
+        uri === 'http://packages.fhir.org/hl7.fhir.r4.core/4.0.1'
       ) {
         return {
           data: {
@@ -186,6 +189,28 @@ describe('#loadDependency()', () => {
     expect(tarSpy.mock.calls[0][0].cwd).toBe(path.join('foo', 'sushi-test#0.2.0'));
   });
 
+  it('should try to load FHIR R4 (4.0.1) from packages.fhir.org when it is not cached', async () => {
+    await expect(loadDependency('hl7.fhir.r4.core', '4.0.1', defs, 'foo')).rejects.toThrow(
+      'The package hl7.fhir.r4.core#4.0.1 could not be loaded locally or from the FHIR package registry'
+    ); // the package is never actually added to the cache, since tar is mocked
+    expect(axiosSpy.mock.calls).toEqual([
+      ['http://packages.fhir.org/hl7.fhir.r4.core/4.0.1', { responseType: 'arraybuffer' }]
+    ]);
+    expect(ensureDirSpy.mock.calls[0]).toEqual([path.join('foo', 'hl7.fhir.r4.core#4.0.1')]);
+    expect(tarSpy.mock.calls[0][0].cwd).toBe(path.join('foo', 'hl7.fhir.r4.core#4.0.1'));
+  });
+
+  it('should try to load FHIR R5 (4.5.0) from packages2.fhir.org when it is not cached', async () => {
+    await expect(loadDependency('hl7.fhir.r5.core', '4.5.0', defs, 'foo')).rejects.toThrow(
+      'The package hl7.fhir.r5.core#4.5.0 could not be loaded locally or from the FHIR package registry'
+    ); // the package is never actually added to the cache, since tar is mocked
+    expect(axiosSpy.mock.calls).toEqual([
+      ['http://packages2.fhir.org/packages/hl7.fhir.r5.core/4.5.0', { responseType: 'arraybuffer' }]
+    ]);
+    expect(ensureDirSpy.mock.calls[0]).toEqual([path.join('foo', 'hl7.fhir.r5.core#4.5.0')]);
+    expect(tarSpy.mock.calls[0][0].cwd).toBe(path.join('foo', 'hl7.fhir.r5.core#4.5.0'));
+  });
+
   it('should throw PackageLoadError when a package with a non-current version is not cached or available on packages.fhir.org', async () => {
     await expect(loadDependency('sushi-test', '0.3.0', defs, 'foo')).rejects.toThrow(
       'The package sushi-test#0.3.0 could not be loaded locally or from the FHIR package registry'
@@ -243,6 +268,17 @@ describe('#loadDependency()', () => {
     expect(tarSpy.mock.calls[0][0].cwd).toBe(path.join(cachePath, 'sushi-test-old#current'));
   });
 
+  it('should try to load the latest FHIR R5 package from build.fhir.org when it is not locally cached', async () => {
+    await expect(loadDependency('hl7.fhir.r5.core', 'current', defs, 'foo')).rejects.toThrow(
+      'The package hl7.fhir.r5.core#current could not be loaded locally or from the FHIR package registry'
+    ); // the package is never actually added to the cache, since tar is mocked
+    expect(axiosSpy.mock.calls).toEqual([
+      ['http://build.fhir.org/hl7.fhir.r5.core.tgz', { responseType: 'arraybuffer' }]
+    ]);
+    expect(ensureDirSpy.mock.calls[0]).toEqual([path.join('foo', 'hl7.fhir.r5.core#current')]);
+    expect(tarSpy.mock.calls[0][0].cwd).toBe(path.join('foo', 'hl7.fhir.r5.core#current'));
+  });
+
   it('should revert to an old locally cached current version when a newer current version is not available for download', async () => {
     const expectedDefs = new FHIRDefinitions();
     loadFromPath(
diff --git a/test/fhirtypes/ElementDefinition.test.ts b/test/fhirtypes/ElementDefinition.test.ts
index 21d196f..693a5fd 100644
--- a/test/fhirtypes/ElementDefinition.test.ts
+++ b/test/fhirtypes/ElementDefinition.test.ts
@@ -6,6 +6,7 @@ import { TestFisher } from '../testhelpers';
 import { Type } from '../../src/utils/Fishable';
 import { Invariant, FshCode } from '../../src/fshtypes';
 import path from 'path';
+import { cloneDeep } from 'lodash';
 
 describe('ElementDefinition', () => {
   let defs: FHIRDefinitions;
@@ -653,12 +654,26 @@ describe('ElementDefinition', () => {
   });
 
   describe('#ElementDefinitionType get code', () => {
-    it('should return valueUrl value when extension of fhir-type', () => {
+    it('should return valueUrl value when extension of fhir-type (R4)', () => {
       const code = valueId.type[0].code;
       const trueCodeValue = valueId.type[0].getActualCode();
       expect(code).toEqual('string');
       expect(trueCodeValue).toEqual('http://hl7.org/fhirpath/System.String');
     });
+
+    it('should return valueUri value when extension of fhir-type (R5)', () => {
+      // Create an R5 representation of the valueId (using valueUri instead of valueUrl)
+      const jsonValueIdR5 = cloneDeep(jsonValueId);
+      delete jsonValueIdR5.type[0].extension[0].valueUrl;
+      jsonValueIdR5.type[0].extension[0].valueUri = 'string';
+      const valueIdR5 = ElementDefinition.fromJSON(jsonValueIdR5);
+
+      const code = valueIdR5.type[0].code;
+      const trueCodeValue = valueIdR5.type[0].getActualCode();
+      expect(code).toEqual('string');
+      expect(trueCodeValue).toEqual('http://hl7.org/fhirpath/System.String');
+    });
+
     it('should return code value when extension is not of fhir-type', () => {
       const code = valueX.type[0].code;
       expect(code).toEqual('Quantity');
diff --git a/test/import/ensureConfigurationFile.test.ts b/test/import/ensureConfigurationFile.test.ts
index fcb3785..9a504c4 100644
--- a/test/import/ensureConfigurationFile.test.ts
+++ b/test/import/ensureConfigurationFile.test.ts
@@ -358,6 +358,32 @@ describe('ensureConfigurationFile', () => {
     });
   });
 
+  it('should generate an appropriate config for a tank w/ R5 package.json', () => {
+    // Copy the fixture to a temp folder since we actually create files in the tank
+    const tank = temp.mkdirSync('sushi-test');
+    fs.copySync(path.join(__dirname, 'fixtures', 'package-json-only'), tank);
+
+    // Tweak the JSON to remove optional fields
+    const packageJSON = fs.readJsonSync(path.join(tank, 'package.json'));
+    delete packageJSON.url;
+    delete packageJSON.title;
+    delete packageJSON.description;
+    delete packageJSON.dependencies;
+    delete packageJSON.author;
+    delete packageJSON.maintainers;
+    delete packageJSON.license;
+    packageJSON.dependencies = { 'hl7.fhir.r5.core': '4.5.0' };
+    fs.writeJsonSync(path.join(tank, 'package.json'), packageJSON);
+
+    // ensureConfiguration
+    const configPath = ensureConfiguration(tank);
+    const configText = fs.readFileSync(configPath, 'utf8');
+    const configJSON = YAML.parse(configText);
+
+    // We've tested most of this already.  We only care about the fhirVersion.
+    expect(configJSON.fhirVersion).toEqual('4.5.0');
+  });
+
   it('should generate an appropriate config for a tank w/ a package.json and legacy ig.ini', () => {
     // Copy the fixture to a temp folder since we actually create files in the tank
     const tank = temp.mkdirSync('sushi-test');
diff --git a/test/run/FshToFhir.test.ts b/test/run/FshToFhir.test.ts
index c08cfcf..597a3f9 100644
--- a/test/run/FshToFhir.test.ts
+++ b/test/run/FshToFhir.test.ts
@@ -83,6 +83,7 @@ describe('#FshToFhir', () => {
       fshToFhir('', {
         canonical: 'http://mycanonical.org',
         dependencies: [{ packageId: 'hl7.fhir.test.core', version: '1.2.3' }],
+        fhirVersion: '4.5.0',
         version: '3.2.1'
       })
     ).resolves.toEqual({
@@ -90,6 +91,13 @@ describe('#FshToFhir', () => {
       warnings: [],
       fhir: []
     });
+    expect(loadSpy.mock.calls[0][1]).toEqual({
+      FSHOnly: true,
+      canonical: 'http://mycanonical.org',
+      dependencies: [{ packageId: 'hl7.fhir.test.core', version: '1.2.3' }],
+      fhirVersion: ['4.5.0'],
+      version: '3.2.1'
+    });
   });
 
   it('should load external dependencies', async () => {
diff --git a/test/utils/Processing.test.ts b/test/utils/Processing.test.ts
index a34cb6b..43c8876 100644
--- a/test/utils/Processing.test.ts
+++ b/test/utils/Processing.test.ts
@@ -6,6 +6,7 @@ import { minimalConfig } from './minimalConfig';
 import { loggerSpy } from '../testhelpers/loggerSpy';
 import readlineSync from 'readline-sync';
 import {
+  isSupportedFHIRVersion,
   ensureInputDir,
   findInputDir,
   ensureOutputDir,
@@ -25,6 +26,26 @@ import { cloneDeep } from 'lodash';
 describe('Processing', () => {
   temp.track();
 
+  describe('#isSupportedFHIRVersion', () => {
+    it('should support published version >= 4.0.1', () => {
+      expect(isSupportedFHIRVersion('4.0.1')).toBe(true);
+      expect(isSupportedFHIRVersion('4.2.0')).toBe(true);
+      expect(isSupportedFHIRVersion('4.4.0')).toBe(true);
+      expect(isSupportedFHIRVersion('4.5.0')).toBe(true);
+    });
+
+    it('should support current version', () => {
+      expect(isSupportedFHIRVersion('current')).toBe(true);
+    });
+
+    it('should not support published versions < 4.0.1', () => {
+      expect(isSupportedFHIRVersion('4.0.0')).toBe(false);
+      expect(isSupportedFHIRVersion('3.0.2')).toBe(false);
+      expect(isSupportedFHIRVersion('1.0.2')).toBe(false);
+      expect(isSupportedFHIRVersion('0.0.82')).toBe(false);
+    });
+  });
+
   describe('#findInputDir()', () => {
     let tempRoot: string;
 
@@ -226,6 +247,18 @@ describe('Processing', () => {
       });
     });
 
+    it('should allow FHIR R5', () => {
+      const input = path.join(__dirname, 'fixtures', 'fhir-r5');
+      const config = readConfig(input, false);
+      expect(config.fhirVersion).toEqual(['4.5.0']);
+    });
+
+    it('should allow FHIR current', () => {
+      const input = path.join(__dirname, 'fixtures', 'fhir-current');
+      const config = readConfig(input, false);
+      expect(config.fhirVersion).toEqual(['current']);
+    });
+
     it('should extract a configuration from an ImplementationGuide JSON when config.yaml is absent', () => {
       const input = path.join(__dirname, 'fixtures', 'ig-JSON-only');
       const config = readConfig(input, false);
@@ -262,12 +295,24 @@ describe('Processing', () => {
       expect(loggerSpy.getLastMessage('error')).toMatch(/not a valid YAML object/s);
     });
 
-    it('should log and throw an error when the configuration does not include a FHIR R4 dependency', () => {
+    it('should log and throw an error when the configuration uses an unsupported FHIR version (DSTU2)', () => {
       const input = path.join(__dirname, 'fixtures', 'fhir-dstu2');
       expect(() => {
         readConfig(input, false);
       }).toThrow();
-      expect(loggerSpy.getLastMessage('error')).toMatch(/must specify FHIR R4 as a fhirVersion/s);
+      expect(loggerSpy.getLastMessage('error')).toMatch(
+        /must specify a supported version of FHIR/s
+      );
+    });
+
+    it('should log and throw an error when the configuration uses an unsupported FHIR version (4.0.0)', () => {
+      const input = path.join(__dirname, 'fixtures', 'fhir-four-oh-oh');
+      expect(() => {
+        readConfig(input, false);
+      }).toThrow();
+      expect(loggerSpy.getLastMessage('error')).toMatch(
+        /must specify a supported version of FHIR/s
+      );
     });
   });
 
@@ -277,8 +322,12 @@ describe('Processing', () => {
         .spyOn(loadModule, 'loadDependency')
         .mockImplementation(
           async (packageName: string, version: string, FHIRDefs: FHIRDefinitions) => {
-            // the mock loader can find hl7.fhir.r4.core and hl7.fhir.us.core
-            if (packageName === 'hl7.fhir.r4.core' || packageName === 'hl7.fhir.us.core') {
+            // the mock loader can find hl7.fhir.r4.core, hl7.fhir.r5.core, and hl7.fhir.us.core
+            if (
+              packageName === 'hl7.fhir.r4.core' ||
+              packageName === 'hl7.fhir.r5.core' ||
+              packageName === 'hl7.fhir.us.core'
+            ) {
               FHIRDefs.packages.push(`${packageName}#${version}`);
               return Promise.resolve(FHIRDefs);
             } else {
@@ -300,6 +349,33 @@ describe('Processing', () => {
         expect(defs.packages.length).toBe(2);
         expect(defs.packages).toContain('hl7.fhir.r4.core#4.0.1');
         expect(defs.packages).toContain('hl7.fhir.us.core#3.1.0');
+        expect(loggerSpy.getAllLogs('warn')).toHaveLength(0);
+      });
+    });
+
+    it('should support FHIR R5 dependencies', () => {
+      const config = cloneDeep(minimalConfig);
+      config.fhirVersion = ['4.5.0'];
+      const defs = new FHIRDefinitions();
+      const dependencyDefs = loadExternalDependencies(defs, config);
+      return Promise.all(dependencyDefs).then(() => {
+        expect(defs.packages).toEqual(['hl7.fhir.r5.core#4.5.0']);
+        expect(loggerSpy.getLastMessage('warn')).toMatch(
+          /support for pre-release versions of FHIR is experimental/s
+        );
+      });
+    });
+
+    it('should support FHIR current dependencies', () => {
+      const config = cloneDeep(minimalConfig);
+      config.fhirVersion = ['current'];
+      const defs = new FHIRDefinitions();
+      const dependencyDefs = loadExternalDependencies(defs, config);
+      return Promise.all(dependencyDefs).then(() => {
+        expect(defs.packages).toEqual(['hl7.fhir.r5.core#current']);
+        expect(loggerSpy.getLastMessage('warn')).toMatch(
+          /support for pre-release versions of FHIR is experimental/s
+        );
       });
     });
 
diff --git a/test/utils/fixtures/fhir-current/sushi-config.yaml b/test/utils/fixtures/fhir-current/sushi-config.yaml
new file mode 100644
index 0000000..d0aeea7
--- /dev/null
+++ b/test/utils/fixtures/fhir-current/sushi-config.yaml
@@ -0,0 +1,9 @@
+id: fhir.us.minimal
+canonical: http://hl7.org/fhir/us/minimal
+name: MinimalIG
+status: draft
+version: 1.0.0
+fhirVersion: current
+copyrightYear: 2020+
+releaseLabel: Build CI
+template: hl7.fhir.template#0.0.5
\ No newline at end of file
diff --git a/test/utils/fixtures/fhir-four-oh-oh/sushi-config.yaml b/test/utils/fixtures/fhir-four-oh-oh/sushi-config.yaml
new file mode 100644
index 0000000..6f1d348
--- /dev/null
+++ b/test/utils/fixtures/fhir-four-oh-oh/sushi-config.yaml
@@ -0,0 +1,9 @@
+id: fhir.us.minimal
+canonical: http://hl7.org/fhir/us/minimal
+name: MinimalIG
+status: draft
+version: 1.0.0
+fhirVersion: 4.0.0
+copyrightYear: 2020+
+releaseLabel: Build CI
+template: hl7.fhir.template#0.0.5
\ No newline at end of file
diff --git a/test/utils/fixtures/fhir-r5/sushi-config.yaml b/test/utils/fixtures/fhir-r5/sushi-config.yaml
new file mode 100644
index 0000000..0633dc5
--- /dev/null
+++ b/test/utils/fixtures/fhir-r5/sushi-config.yaml
@@ -0,0 +1,9 @@
+id: fhir.us.minimal
+canonical: http://hl7.org/fhir/us/minimal
+name: MinimalIG
+status: draft
+version: 1.0.0
+fhirVersion: 4.5.0
+copyrightYear: 2020+
+releaseLabel: Build CI
+template: hl7.fhir.template#0.0.5
\ No newline at end of file