Skip to content
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

Use new FPL API #277

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,508 changes: 952 additions & 556 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@
"diff": "^7.0.0",
"diff2html": "^3.4.48",
"fhir": "^4.12.0",
"fhir-package-loader": "^1.0.0",
"fhir-package-loader": "^2.0.0-beta.3",
"flat": "^5.0.2",
"fs-extra": "^11.2.0",
"fsh-sushi": "^3.12.1",
"fsh-sushi": "file:../fsh-sushi-new-fpl.tgz",
"ini": "^5.0.0",
"lodash": "^4.17.21",
"readline-sync": "^1.4.10",
Expand Down
2 changes: 2 additions & 0 deletions src/api/FhirToFsh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ export async function fhirToFsh(

// Set up the FHIRProcessor
const lake = new LakeOfFHIR(docs);
await lake.prepareDefs();
const defs = new FHIRDefinitions();
await defs.initialize();
const fisher = new MasterFisher(lake, defs);
const processor = new FHIRProcessor(lake, fisher);

Expand Down
3 changes: 2 additions & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ async function app() {

// Load dependencies
const defs = new FHIRDefinitions();
await defs.initialize();

// Trim empty spaces from command line dependencies
const dependencies = programOptions.dependency?.map((dep: string) => dep.trim());
Expand Down Expand Up @@ -197,7 +198,7 @@ async function app() {
alias: programOptions.alias
} as ProcessingOptions;

const processor = getFhirProcessor(inDir, defs, fileType);
const processor = await getFhirProcessor(inDir, defs, fileType);
const config = processor.processConfig(dependencies, specifiedFHIRVersion);

// Load dependencies from config for GoFSH processing
Expand Down
9 changes: 2 additions & 7 deletions src/processor/FHIRProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,9 @@ import { InstanceProcessor } from './InstanceProcessor';
export class FHIRProcessor {
constructor(
private readonly lake: LakeOfFHIR,
private readonly fisher?: MasterFisher,
private readonly fisher: MasterFisher,
private readonly igPath: string = null
) {
// If no fisher was passed in, just use the built-in lake fisher (usually for testing only)
if (fisher == null) {
fisher = new MasterFisher(lake);
}
}
) {}

getFisher(): MasterFisher {
return this.fisher;
Expand Down
38 changes: 30 additions & 8 deletions src/processor/LakeOfFHIR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ import { CONFORMANCE_AND_TERMINOLOGY_RESOURCES } from './InstanceProcessor';
import { StructureDefinitionProcessor } from './StructureDefinitionProcessor';
import { ValueSetProcessor } from './ValueSetProcessor';
import { WildFHIR } from './WildFHIR';
import { FHIRDefinitions, logger } from '../utils';
import { FHIRDefinitions, logger, logMessage } from '../utils';
import { InMemoryVirtualPackage } from 'fhir-package-loader';

// Like FSHTank in SUSHI, but it doesn't contain FSH, it contains FHIR. And who ever heard of a tank of FHIR? But a lake of FHIR...
export class LakeOfFHIR implements utils.Fishable {
constructor(public docs: WildFHIR[]) {}
readonly defs: FHIRDefinitions;

constructor(public docs: WildFHIR[]) {
this.defs = new FHIRDefinitions();
}

/**
* Gets all non-instance structure definitions (profiles, extensions, logicals, and resources) in the lake
Expand Down Expand Up @@ -111,24 +116,41 @@ export class LakeOfFHIR implements utils.Fishable {
}
}

async prepareDefs() {
// In theory, this.docs can be modified at any time. In practice, GoFSH only modifies it with calls to
// removeDuplicateDefinitions and assignMissingIds, which are called before the lake is ever used.
// So, it's reasonably safe to provide this function to be called right after those modification functions.
await this.defs.initialize();
const lakeMap = new Map<string, any>();
this.docs.forEach(wildFHIR => {
lakeMap.set(wildFHIR.path, wildFHIR.content);
});
const lakePackage = new InMemoryVirtualPackage(
{ name: 'wild-fhir', version: '1.0.0' },
lakeMap,
{
log: (level: string, message: string) => {
logMessage(level, message);
}
}
);
return this.defs.loadVirtualPackage(lakePackage);
}

fishForFHIR(item: string, ...types: utils.Type[]) {
// The simplest approach is just to re-use the FHIRDefinitions fisher. But since this.docs can be modified by anyone at any time
// the only safe way to do this is by rebuilding a FHIRDefinitions object each time we need it. If this becomes a performance
// concern, we can optimize it later -- but performance isn't a huge concern in GoFSH. Note also that this approach may need to be
// updated if we ever need to support fishing for Instances.
const defs = new FHIRDefinitions();
this.docs.forEach(d => defs.add(d.content));
return defs.fishForFHIR(item, ...types);
return this.defs.fishForFHIR(item, ...types);
}

fishForMetadata(item: string, ...types: utils.Type[]): utils.Metadata {
// The simplest approach is just to re-use the FHIRDefinitions fisher. But since this.docs can be modified by anyone at any time
// the only safe way to do this is by rebuilding a FHIRDefinitions object each time we need it. If this becomes a performance
// concern, we can optimize it later -- but performance isn't a huge concern in GoFSH. Note also that this approach may need to be
// updated if we ever need to support fishing for Instances.
const defs = new FHIRDefinitions();
this.docs.forEach(d => defs.add(d.content));
return defs.fishForMetadata(item, ...types);
return this.defs.fishForMetadata(item, ...types);
}

/**
Expand Down
7 changes: 3 additions & 4 deletions src/utils/FHIRDefinitions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { FHIRDefinitions as BaseFHIRDefinitions, Type } from 'fhir-package-loader';
import { utils } from 'fsh-sushi';
import { utils, fhirdefs } from 'fsh-sushi';

export class FHIRDefinitions extends BaseFHIRDefinitions implements utils.Fishable {
fishForMetadata(item: string, ...types: Type[]): utils.Metadata | undefined {
export class FHIRDefinitions extends fhirdefs.FHIRDefinitions implements utils.Fishable {
fishForMetadata(item: string, ...types: utils.Type[]): utils.Metadata | undefined {
const result = this.fishForFHIR(item, ...types);
if (result) {
return {
Expand Down
12 changes: 9 additions & 3 deletions src/utils/GoFSHLogger.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { createLogger, format, transports } from 'winston';
import { TransformableInfo } from 'logform';
import chalk from 'chalk';

const { combine, printf } = format;

interface LoggerInfo extends TransformableInfo {
file?: string;
location?: string;
}

const incrementCounts = format(info => {
switch (info.level) {
case 'info':
Expand All @@ -24,19 +30,19 @@ const incrementCounts = format(info => {
return info;
});

const trackErrorsAndWarnings = format(info => {
const trackErrorsAndWarnings = format((info: LoggerInfo) => {
if (!errorsAndWarnings.shouldTrack) {
return info;
}
if (info.level === 'error') {
errorsAndWarnings.errors.push({
message: info.message,
message: info.message as string,
location: info.location,
input: info.file
});
} else if (info.level === 'warn') {
errorsAndWarnings.warnings.push({
message: info.message,
message: info.message as string,
location: info.location,
input: info.file
});
Expand Down
10 changes: 6 additions & 4 deletions src/utils/MasterFisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { FHIRDefinitions } from '../utils';
*/
export class MasterFisher implements utils.Fishable {
constructor(
public lakeOfFHIR = new LakeOfFHIR([]),
public external = new FHIRDefinitions()
public lakeOfFHIR: LakeOfFHIR,
public external: FHIRDefinitions
) {}

fishForStructureDefinition(item: string) {
Expand All @@ -31,13 +31,15 @@ export class MasterFisher implements utils.Fishable {
}

fishForFHIR(item: string, ...types: utils.Type[]) {
return this.lakeOfFHIR.fishForFHIR(item, ...types) ?? this.external.fishForFHIR(item, ...types);
return (
this.lakeOfFHIR.fishForFHIR(item, ...types) ?? this.external?.fishForFHIR(item, ...types)
);
}

fishForMetadata(item: string, ...types: utils.Type[]): utils.Metadata {
return (
this.lakeOfFHIR.fishForMetadata(item, ...types) ??
this.external.fishForMetadata(item, ...types)
this.external?.fishForMetadata(item, ...types)
);
}
}
34 changes: 17 additions & 17 deletions src/utils/Processing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import fs from 'fs-extra';
import path from 'path';
import ini from 'ini';
import readlineSync from 'readline-sync';
import { mergeDependency } from 'fhir-package-loader';
import { logger, logMessage } from './GoFSHLogger';
import { logger } from './GoFSHLogger';
import { fhirtypes, utils } from 'fsh-sushi';
import {
Package,
Expand Down Expand Up @@ -58,14 +57,19 @@ export function ensureOutputDir(output = path.join('.', 'gofsh')): string {
return output;
}

export function getFhirProcessor(inDir: string, defs: FHIRDefinitions, fileType: string) {
export async function getFhirProcessor(inDir: string, defs: FHIRDefinitions, fileType: string) {
const lake = getLakeOfFHIR(inDir, fileType);

// Assign any missing ids where we can before filtering out duplicates so that all
// the definitions with the same resourceType without an id don't get filtered out.
lake.assignMissingIds();
lake.removeDuplicateDefinitions();
await lake.prepareDefs();

if (defs == null) {
defs = new FHIRDefinitions();
}
await defs.initialize();
const igIniIgPath = getIgPathFromIgIni(inDir);
const fisher = new MasterFisher(lake, defs);
return new FHIRProcessor(lake, fisher, igIniIgPath);
Expand Down Expand Up @@ -131,20 +135,19 @@ export async function loadExternalDependencies(
// @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));
await loadConfiguredDependencies(defs, allDependencies);
}

export function loadConfiguredDependencies(
export async function loadConfiguredDependencies(
defs: FHIRDefinitions,
dependencies: string[] = []
): Promise<FHIRDefinitions | void>[] {
): Promise<FHIRDefinitions | void> {
// Automatically include FHIR R4 if no other versions of FHIR are already included
if (!dependencies.some(dep => /hl7\.fhir\.r(4|5|4b|6)\.core/.test(dep))) {
dependencies.push('hl7.fhir.r4.core@4.0.1');
}

// Load dependencies
const dependencyDefs: Promise<FHIRDefinitions | void>[] = [];
for (const dep of dependencies) {
const [packageId, version] = dep.split('@');
if (version == null) {
Expand All @@ -154,17 +157,14 @@ export function loadConfiguredDependencies(
);
continue;
}
dependencyDefs.push(
mergeDependency(packageId, version, defs, undefined, logMessage)
.then((def: FHIRDefinitions) => {
return def;
})
.catch(e => {
logger.error(`Failed to load ${dep}: ${e.message}`);
})
);
await defs.loadPackage(packageId, version).catch(e => {
logger.error(`Failed to load ${packageId}#${version}: ${e.message}`);
if (e.stack) {
logger.debug(e.stack);
}
});
}
return dependencyDefs;
return defs;
}

export function getLakeOfFHIR(inDir: string, fileType: string): LakeOfFHIR {
Expand Down
17 changes: 14 additions & 3 deletions test/api/FhirToFsh.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { FHIRDefinitions, logger } from '../../src/utils';
import { fhirToFsh, ResourceMap } from '../../src/api';
import { loggerSpy } from '../helpers/loggerSpy';
import { EOL } from 'os';
import { InMemoryVirtualPackage } from 'fhir-package-loader';
describe('fhirToFsh', () => {
let loadSpy: jest.SpyInstance;
let defaultConfig: fshtypes.Configuration;
Expand Down Expand Up @@ -37,9 +38,19 @@ describe('fhirToFsh', () => {
'utf-8'
)
);
loadSpy = jest.spyOn(processing, 'loadExternalDependencies').mockImplementation(defs => {
defs.add(sd);
defs.add(patient);

loadSpy = jest.spyOn(processing, 'loadExternalDependencies').mockImplementation(async defs => {
const resourceMap: Map<string, any> = new Map();
resourceMap.set('StructureDefinition', sd);
resourceMap.set('Patient', patient);
const testPackage = new InMemoryVirtualPackage(
{
name: 'fhir-to-fsh-test',
version: '1.0.0'
},
resourceMap
);
await defs.loadVirtualPackage(testPackage);
return Promise.resolve();
});
defaultConfig = {
Expand Down
4 changes: 2 additions & 2 deletions test/extractor/CardRuleExtractor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('CardRuleExtractor', () => {
let looseSDWithInheritedSlices: any;
let defs: FHIRDefinitions;

beforeAll(() => {
beforeAll(async () => {
looseSD = JSON.parse(
fs.readFileSync(path.join(__dirname, 'fixtures', 'card-profile.json'), 'utf-8').trim()
);
Expand All @@ -30,7 +30,7 @@ describe('CardRuleExtractor', () => {
)
.trim()
);
defs = loadTestDefinitions();
defs = await loadTestDefinitions();
});
describe('#process', () => {
it('should extract a card rule with a min and a max', () => {
Expand Down
4 changes: 2 additions & 2 deletions test/extractor/CaretValueRuleExtractor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ describe('CaretValueRuleExtractor', () => {
let config: fshtypes.Configuration;
let defs: FHIRDefinitions;

beforeAll(() => {
defs = loadTestDefinitions();
beforeAll(async () => {
defs = await loadTestDefinitions();
config = {
canonical: 'http://hl7.org/fhir/sushi-test',
fhirVersion: ['4.0.1']
Expand Down
4 changes: 2 additions & 2 deletions test/extractor/ContainsRuleExtractor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ describe('ContainsRuleExtractor', () => {
let looseSD: any;
let defs: FHIRDefinitions;

beforeAll(() => {
beforeAll(async () => {
looseSD = JSON.parse(
fs.readFileSync(path.join(__dirname, 'fixtures', 'contains-profile.json'), 'utf-8').trim()
);
defs = loadTestDefinitions();
defs = await loadTestDefinitions();
});

it('should extract a ContainsRule with cardinality', () => {
Expand Down
4 changes: 2 additions & 2 deletions test/extractor/InvariantExtractor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ describe('InvariantExtractor', () => {
let defs: FHIRDefinitions;
let looseSD: ProcessableStructureDefinition;

beforeAll(() => {
defs = loadTestDefinitions();
beforeAll(async () => {
defs = await loadTestDefinitions();
looseSD = JSON.parse(
fs.readFileSync(path.join(__dirname, 'fixtures', 'obeys-profile.json'), 'utf-8').trim()
);
Expand Down
4 changes: 2 additions & 2 deletions test/extractor/MappingExtractor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ describe('MappingExtractor', () => {
let defs: FHIRDefinitions;
let elements: ProcessableElementDefinition[];

beforeAll(() => {
defs = loadTestDefinitions();
beforeAll(async () => {
defs = await loadTestDefinitions();
looseSD = JSON.parse(
fs.readFileSync(path.join(__dirname, 'fixtures', 'mapping-profile.json'), 'utf-8').trim()
);
Expand Down
Loading
Loading