diff --git a/src/api/FhirToFsh.ts b/src/api/FhirToFsh.ts index 6a4a0afa..6b609719 100644 --- a/src/api/FhirToFsh.ts +++ b/src/api/FhirToFsh.ts @@ -1,14 +1,13 @@ -import { fhirtypes, fshtypes, utils } from 'fsh-sushi'; +import { fshtypes, utils } from 'fsh-sushi'; import { - determineCorePackageId, FHIRDefinitions, getResources, isProcessableContent, - loadExternalDependencies, MasterFisher, logger, errorsAndWarnings, - ErrorsAndWarnings + ErrorsAndWarnings, + loadExternalDependencies } from '../utils'; import { FHIRProcessor, LakeOfFHIR, WildFHIR } from '../processor'; import { FSHExporter } from '../export'; @@ -81,15 +80,7 @@ export async function fhirToFsh( ); // Load dependencies, including those inferred from an IG file, and those given as input - const dependencies = - configuration.config.dependencies?.map( - (dep: fhirtypes.ImplementationGuideDependsOn) => `${dep.packageId}@${dep.version}` - ) ?? []; - const fhirPackageId = determineCorePackageId(configuration.config.fhirVersion[0]); - if (!dependencies.includes(`${fhirPackageId}@${configuration.config.fhirVersion[0]}`)) { - dependencies.push(`${fhirPackageId}@${configuration.config.fhirVersion[0]}`); - } - await Promise.all(loadExternalDependencies(defs, dependencies)); + await loadExternalDependencies(defs, configuration); // Process the FHIR to rules, and then export to FSH const pkg = await getResources(processor, configuration, processingOptions); diff --git a/src/app.ts b/src/app.ts index f3d64936..8d073819 100644 --- a/src/app.ts +++ b/src/app.ts @@ -5,22 +5,21 @@ import fs from 'fs-extra'; import program from 'commander'; import chalk from 'chalk'; import { pad, padStart, padEnd } from 'lodash'; -import { fhirtypes, utils } from 'fsh-sushi'; +import { utils } from 'fsh-sushi'; import { - determineCorePackageId, ensureOutputDir, FHIRDefinitions, getInputDir, getAliasFile, getFhirProcessor, getResources, - loadExternalDependencies, writeFSH, logger, stats, fshingTrip, getRandomPun, - ProcessingOptions + ProcessingOptions, + loadExternalDependencies } from './utils'; import { Package, AliasProcessor } from './processor'; import { ExportableAlias } from './exportable'; @@ -202,21 +201,7 @@ async function app() { const config = processor.processConfig(dependencies, specifiedFHIRVersion); // Load dependencies from config for GoFSH processing - const allDependencies = - config.config.dependencies?.map( - (dep: fhirtypes.ImplementationGuideDependsOn) => `${dep.packageId}@${dep.version}` - ) ?? []; - const fhirPackageId = determineCorePackageId(config.config.fhirVersion[0]); - allDependencies.push(`${fhirPackageId}@${config.config.fhirVersion[0]}`); - await utils.loadAutomaticDependencies( - config.config.fhirVersion[0], - config.config.dependencies ?? [], - // @ts-ignore TODO: this can be removed once SUSHI changes the type signature for this function to use FPL's FHIRDefinitions type - defs - ); - const dependencyDefs = loadExternalDependencies(defs, allDependencies); - - await Promise.all(dependencyDefs); + await loadExternalDependencies(defs, config); let pkg: Package; try { diff --git a/src/utils/Processing.ts b/src/utils/Processing.ts index 1e3b2106..c3d7a7be 100644 --- a/src/utils/Processing.ts +++ b/src/utils/Processing.ts @@ -4,6 +4,7 @@ import ini from 'ini'; import readlineSync from 'readline-sync'; import { mergeDependency } from 'fhir-package-loader'; import { logger, logMessage } from './GoFSHLogger'; +import { fhirtypes, utils } from 'fsh-sushi'; import { Package, FHIRProcessor, @@ -112,7 +113,26 @@ export function writeFSH(resources: Package, outDir: string, style: string): voi } } -export function loadExternalDependencies( +export async function loadExternalDependencies( + defs: FHIRDefinitions, + config: ExportableConfiguration +) { + const allDependencies = + config.config.dependencies?.map( + (dep: fhirtypes.ImplementationGuideDependsOn) => `${dep.packageId}@${dep.version}` + ) ?? []; + const fhirPackageId = determineCorePackageId(config.config.fhirVersion[0]); + allDependencies.push(`${fhirPackageId}@${config.config.fhirVersion[0]}`); + await utils.loadAutomaticDependencies( + config.config.fhirVersion[0], + config.config.dependencies ?? [], + // @ts-ignore TODO: this can be removed once SUSHI changes the type signature for this function to use FPL's FHIRDefinitions type + defs + ); + await Promise.all(loadConfiguredDependencies(defs, allDependencies)); +} + +export function loadConfiguredDependencies( defs: FHIRDefinitions, dependencies: string[] = [] ): Promise[] { diff --git a/test/api/FhirToFsh.test.ts b/test/api/FhirToFsh.test.ts index 1a77fd49..51140e4b 100644 --- a/test/api/FhirToFsh.test.ts +++ b/test/api/FhirToFsh.test.ts @@ -40,7 +40,7 @@ describe('fhirToFsh', () => { loadSpy = jest.spyOn(processing, 'loadExternalDependencies').mockImplementation(defs => { defs.add(sd); defs.add(patient); - return [undefined]; + return Promise.resolve(); }); defaultConfig = { FSHOnly: true, @@ -111,52 +111,6 @@ describe('fhirToFsh', () => { expect(results.configuration).toEqual(defaultConfig); }); - it('should load dependencies from user input', async () => { - // Should be able to accept both # and @ style for dependencies - const results = await fhirToFsh([], { - dependencies: ['hl7.fhir.us.core#3.1.0', 'hl7.fhir.us.mcode@1.0.0'] - }); - expect(results.errors).toHaveLength(0); - expect(results.warnings).toHaveLength(1); - expect(results.warnings[0].message).toMatch('Could not determine FHIR version. Using 4.0.1.'); - expect(results.fsh).toEqual(''); - expect(results.configuration).toEqual({ - ...defaultConfig, - dependencies: [ - { packageId: 'hl7.fhir.us.core', version: '3.1.0' }, - { packageId: 'hl7.fhir.us.mcode', version: '1.0.0' } - ] - }); - - expect(loadSpy.mock.calls).toHaveLength(1); - expect(loadSpy.mock.calls[0][1]).toEqual([ - 'hl7.fhir.us.core@3.1.0', - 'hl7.fhir.us.mcode@1.0.0', - 'hl7.fhir.r4.core@4.0.1' - ]); - }); - - it('should load FHIR R4B when specified in config fhirVersion', async () => { - // Loads R4B from fhirVersion - const results = await fhirToFsh([ - JSON.stringify({ - resourceType: 'StructureDefinition', - name: 'Foo', - baseDefinition: 'http://hl7.org/fhir/StructureDefinition/Patient', - derivation: 'constraint', - fhirVersion: '4.3.0' // FHIR R4B - }) - ]); - expect(results.errors).toHaveLength(0); - expect(results.warnings).toHaveLength(0); - expect(results.configuration).toEqual({ - ...defaultConfig, - fhirVersion: ['4.3.0'] - }); - expect(loadSpy.mock.calls).toHaveLength(1); - expect(loadSpy.mock.calls[0][1]).toEqual(['hl7.fhir.r4b.core@4.3.0']); - }); - it('should parse a string input into JSON', async () => { const results = await fhirToFsh([ JSON.stringify({ diff --git a/test/utils/Processing.test.ts b/test/utils/Processing.test.ts index 2e985c35..6a74462c 100644 --- a/test/utils/Processing.test.ts +++ b/test/utils/Processing.test.ts @@ -9,7 +9,7 @@ import { ensureOutputDir, getInputDir, getResources, - loadExternalDependencies, + loadConfiguredDependencies, writeFSH, getIgPathFromIgIni, getFhirProcessor, @@ -25,7 +25,7 @@ import { ExportableProfile, ExportableAssignmentRule } from '../../src/exportable'; -import { FHIRDefinitions } from '../../src/utils'; +import { FHIRDefinitions, loadExternalDependencies } from '../../src/utils'; import * as loadOptimizers from '../../src/optimizer/loadOptimizers'; let loadedPackages: string[] = []; @@ -51,6 +51,22 @@ jest.mock('fhir-package-loader', () => { return newStyle; }); +jest.mock('fsh-sushi', () => { + const original = jest.requireActual('fsh-sushi'); + const newStyle = { + ...original, + utils: { + ...original.utils, + loadAutomaticDependencies: jest.fn(async () => { + // this is just one of the usual automatic depencies, as an example + loadedPackages.push('hl7.terminology.r4#1.0.0'); + return Promise.resolve(); + }) + } + }; + return newStyle; +}); + describe('Processing', () => { temp.track(); @@ -527,10 +543,55 @@ describe('Processing', () => { loadedPackages = []; }); + it('should load automatic and configured dependencies', async () => { + const config = new ExportableConfiguration({ + FSHOnly: true, + canonical: 'http://example.org', + fhirVersion: ['4.0.1'], + id: 'example', + name: 'Example', + applyExtensionMetadataToRoot: false, + dependencies: [{ packageId: 'hl7.fhir.us.core', version: '3.1.0' }] + }); + const defs = new FHIRDefinitions(); + await loadExternalDependencies(defs, config); + expect(loadedPackages).toHaveLength(3); + expect(loadedPackages).toContain('hl7.fhir.r4.core#4.0.1'); + expect(loadedPackages).toContain('hl7.fhir.us.core#3.1.0'); + expect(loadedPackages).toContain('hl7.terminology.r4#1.0.0'); + expect(loggerSpy.getAllMessages('error')).toHaveLength(0); + }); + + it('should load FHIR R4B when specified in config fhirVersion', async () => { + const config = new ExportableConfiguration({ + FSHOnly: true, + canonical: 'http://example.org', + fhirVersion: ['4.3.0'], + id: 'example', + name: 'Example', + applyExtensionMetadataToRoot: false, + dependencies: [{ packageId: 'hl7.fhir.us.core', version: '3.1.0' }] + }); + const defs = new FHIRDefinitions(); + await loadExternalDependencies(defs, config); + expect(loadedPackages).toHaveLength(3); + expect(loadedPackages).toContain('hl7.fhir.r4b.core#4.3.0'); + expect(loadedPackages).toContain('hl7.fhir.us.core#3.1.0'); + expect(loadedPackages).toContain('hl7.terminology.r4#1.0.0'); + expect(loggerSpy.getAllMessages('error')).toHaveLength(0); + }); + }); + + describe('loadConfiguredDependencies', () => { + beforeEach(() => { + loggerSpy.reset(); + loadedPackages = []; + }); + it('should load specified dependencies', () => { const defs = new FHIRDefinitions(); const dependencies = ['hl7.fhir.us.core@3.1.0']; - const dependencyDefs = loadExternalDependencies(defs, dependencies); + const dependencyDefs = loadConfiguredDependencies(defs, dependencies); return Promise.all(dependencyDefs).then(() => { expect(loadedPackages).toHaveLength(2); expect(loadedPackages).toContain('hl7.fhir.r4.core#4.0.1'); @@ -542,7 +603,7 @@ describe('Processing', () => { it('should log an error when it fails to load a dependency', () => { const defs = new FHIRDefinitions(); const badDependencies = ['hl7.does.not.exist@current']; - const dependencyDefs = loadExternalDependencies(defs, badDependencies); + const dependencyDefs = loadConfiguredDependencies(defs, badDependencies); return Promise.all(dependencyDefs).then(() => { expect(loadedPackages).toHaveLength(1); expect(loadedPackages).toContain('hl7.fhir.r4.core#4.0.1'); @@ -556,7 +617,7 @@ describe('Processing', () => { it('should log an error when a dependency has no specified version', () => { const defs = new FHIRDefinitions(); const badDependencies = ['hl7.fhir.us.core']; // No version - const dependencyDefs = loadExternalDependencies(defs, badDependencies); + const dependencyDefs = loadConfiguredDependencies(defs, badDependencies); return Promise.all(dependencyDefs).then(() => { expect(loadedPackages).toHaveLength(1); expect(loadedPackages).toContain('hl7.fhir.r4.core#4.0.1'); @@ -570,7 +631,7 @@ describe('Processing', () => { it('should load only FHIR if no dependencies specified', () => { const defs = new FHIRDefinitions(); // No dependencies specified on CLI will pass in undefined - const dependencyDefs = loadExternalDependencies(defs, undefined); + const dependencyDefs = loadConfiguredDependencies(defs, undefined); return Promise.all(dependencyDefs).then(() => { expect(loadedPackages).toHaveLength(1); expect(loadedPackages).toContain('hl7.fhir.r4.core#4.0.1'); @@ -581,7 +642,7 @@ describe('Processing', () => { it('should load FHIR R4B if specified', () => { const defs = new FHIRDefinitions(); const dependencies = ['hl7.fhir.r4b.core@4.3.0-snapshot1']; - const dependencyDefs = loadExternalDependencies(defs, dependencies); + const dependencyDefs = loadConfiguredDependencies(defs, dependencies); return Promise.all(dependencyDefs).then(() => { expect(loadedPackages).toHaveLength(1); expect(loadedPackages).toContain('hl7.fhir.r4b.core#4.3.0-snapshot1'); // Only contains r4b, doesn't load r4