Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

feat: Compile IG StructureDefinitions #235

Merged
merged 3 commits into from
Mar 9, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@
"serverless-plugin-typescript": "^1.1.9",
"serverless-step-functions": "^2.27.1",
"sinon": "^9.0.2",
"tmp-promise": "^3.0.2",
"ts-jest": "^26.4.4",
"typescript": "^4.1.3",
"wait-for-expect": "^3.0.2",
"tmp-promise": "^3.0.2"
"wait-for-expect": "^3.0.2"
},
"resolutions": {
"dot-prop": "^5.1.1",
Expand Down
22 changes: 14 additions & 8 deletions scripts/compile-igs.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { join, dirname } from 'path';
import yargs from 'yargs';
import { SearchImplementationGuides } from 'fhir-works-on-aws-search-es';
import { join } from 'path';
import { existsSync, mkdirSync } from 'fs';
import { SearchImplementationGuides } from 'fhir-works-on-aws-search-es';
import { StructureDefinitionImplementationGuides } from 'fhir-works-on-aws-routing/lib/implementationGuides';
import { IGCompiler } from '../src/implementationGuides/IGCompiler';
import { COMPILED_IGS_DIRECTORY } from '../src/implementationGuides/loadCompiledIGs';

const PROJECT_DIR = join(__dirname, '..');

function parseCmdOptions() {
return yargs(process.argv.slice(2))
.usage('Usage: $0 [--ignoreVersion, -i ] [--igPath, -p IG pack directory] [--outputFile, -o output ]')
.usage('Usage: $0 [--ignoreVersion, -i ] [--igPath, -p IG pack directory] [--outputDir, -o output ]')
.describe('ignoreVersion', "Don't care whether version of dependency lines up with version of installed IG")
.boolean('ignoreVersion')
.default('ignoreVersion', false)
.alias('i', 'ignoreVersion')
.describe('igPath', 'Path to folder with IG pack sub folders')
.default('igPath', join(PROJECT_DIR, 'implementationGuides/'))
.alias('p', 'igPath')
.describe('outputFile', 'Path to compiled output JSON file')
.alias('o', 'outputFile')
.default('outputFile', join(PROJECT_DIR, 'compiledImplementationGuides/fhir-works-on-aws-search-es.json')).argv;
.describe('outputDir', 'Path to compiled output JSON file')
.alias('o', 'outputDir')
.default('outputDir', join(PROJECT_DIR, COMPILED_IGS_DIRECTORY)).argv;
}

/**
Expand All @@ -34,14 +36,18 @@ async function compileIGs() {
console.log(`IGs folder '${cmdArgs.igPath}' does not exist. No IGs found, exiting...`);
return;
}
const compiledIgsDir = dirname(cmdArgs.outputFile);
const compiledIgsDir = cmdArgs.outputDir.toString();
if (!existsSync(compiledIgsDir)) {
console.log(`folder for compiled IGs '${compiledIgsDir}' does not exist, creating it`);
mkdirSync(compiledIgsDir, { recursive: true });
}

try {
await new IGCompiler(SearchImplementationGuides, options).compileIGs(cmdArgs.igPath, cmdArgs.outputFile);
await new IGCompiler(
SearchImplementationGuides,
new StructureDefinitionImplementationGuides(),
options,
).compileIGs(cmdArgs.igPath, cmdArgs.outputDir);
} catch (ex) {
console.error('Exception: ', ex.message, ex.stack);
}
Expand Down
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ const baseResources = fhirVersion === '4.0.1' ? BASE_R4_RESOURCES : BASE_STU3_RE
const authService = IS_OFFLINE ? stubs.passThroughAuthz : new RBACHandler(RBACRules(baseResources), fhirVersion);
const dynamoDbDataService = new DynamoDbDataService(DynamoDb);
const dynamoDbBundleService = new DynamoDbBundleService(DynamoDb);

const esSearch = new ElasticSearchService(
[
{
Expand Down Expand Up @@ -79,6 +78,7 @@ export const fhirConfig: FhirConfig = {
profile: {
systemOperations: ['transaction'],
bundle: dynamoDbBundleService,
compiledImplementationGuides: loadImplementationGuides('fhir-works-on-aws-routing'),
systemHistory: stubs.history,
systemSearch: stubs.search,
bulkDataAccess: dynamoDbDataService,
Expand Down
33 changes: 24 additions & 9 deletions src/implementationGuides/IGCompiler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ interface IGVersion {
describe('IGCompiler tests', () => {
let workDir: DirectoryResult;
let igsDir: PathLike;
let output: string;
let outputDir: string;

async function createIGs(options: IGCompilerOptions, igs: { [key: string]: IGVersion }) {
for (const [igName, igInfo] of Object.entries(igs)) {
const igPath = join(igsDir.toString(), igName);
mkdirSync(igPath);
await storeJson(join(igPath, 'searchParam1.json'), { igName, name: 'param1' });
await storeJson(join(igPath, 'searchParam2.json'), { igName, name: 'param2' });
await storeJson(join(igPath, 'structureDefinition1.json'), { igName, name: 'structureDefinition1' });
const indexJson = {
files: [
{
Expand All @@ -44,7 +45,11 @@ describe('IGCompiler tests', () => {
},
{
resourceType: 'ValueSet',
filename: 'searchParam1.json',
filename: 'valueSet1.json',
},
{
resourceType: 'StructureDefinition',
filename: 'structureDefinition1.json',
},
],
};
Expand All @@ -62,7 +67,7 @@ describe('IGCompiler tests', () => {
});
}
const implementationGuides = new MockImplementationGuides();
const igCompiler = new IGCompiler(implementationGuides, options);
const igCompiler = new IGCompiler(implementationGuides, implementationGuides, options);
console.log('Done creating IGs');
return igCompiler;
}
Expand All @@ -71,7 +76,8 @@ describe('IGCompiler tests', () => {
workDir = await dir({ unsafeCleanup: true });
igsDir = join(workDir.path, 'igs');
mkdirSync(igsDir);
output = join(workDir.path, 'output.json');
outputDir = join(workDir.path, 'output');
mkdirSync(outputDir);
console.log('before each');
});

Expand Down Expand Up @@ -102,9 +108,9 @@ describe('IGCompiler tests', () => {
deps: ['[email protected]'],
},
});
await igCompiler.compileIGs(igsDir, output);
expect(existsSync(output)).toEqual(true);
expect(await loadJson(output)).toEqual({
await igCompiler.compileIGs(igsDir, outputDir);
expect(existsSync(outputDir)).toEqual(true);
expect(await loadJson(join(outputDir, 'fhir-works-on-aws-search-es.json'))).toEqual({
input: [
{
igName: 'hl7.fhir.us.carin-bb',
Expand Down Expand Up @@ -140,6 +146,15 @@ describe('IGCompiler tests', () => {
},
],
});

expect(await loadJson(join(outputDir, 'fhir-works-on-aws-routing.json'))).toEqual({
input: [
{ igName: 'hl7.fhir.us.carin-bb', name: 'structureDefinition1' },
{ igName: 'hl7.fhir.us.core', name: 'structureDefinition1' },
{ igName: 'hl7.fhir.us.davinci-pdex-plan-net', name: 'structureDefinition1' },
{ igName: 'us.nlm.vsac', name: 'structureDefinition1' },
],
});
});

it('missing dependencies', async () => {
Expand All @@ -156,7 +171,7 @@ describe('IGCompiler tests', () => {
deps: ['[email protected]', '[email protected]'],
},
});
await expect(igCompiler.compileIGs(igsDir, output)).rejects.toThrow('Missing dependency [email protected]');
await expect(igCompiler.compileIGs(igsDir, outputDir)).rejects.toThrow('Missing dependency [email protected]');
});

it('circular dependencies', async () => {
Expand Down Expand Up @@ -185,7 +200,7 @@ describe('IGCompiler tests', () => {
deps: ['[email protected]', '[email protected]'],
},
});
await expect(igCompiler.compileIGs(igsDir, output)).rejects.toThrow(
await expect(igCompiler.compileIGs(igsDir, outputDir)).rejects.toThrow(
'Circular dependency found: [email protected] -> [email protected] -> [email protected] -> [email protected]',
);
});
Expand Down
51 changes: 43 additions & 8 deletions src/implementationGuides/IGCompiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,21 @@ export interface IGInfo {
export class IGCompiler {
private options: IGCompilerOptions;

private readonly implementationGuides: ImplementationGuides;
private readonly searchImplementationGuides: ImplementationGuides;

constructor(implementationGuides: ImplementationGuides, options: IGCompilerOptions) {
private readonly structureDefinitionImplementationGuides: ImplementationGuides;

constructor(
searchImplementationGuides: ImplementationGuides,
structureDefinitionImplementationGuides: ImplementationGuides,
options: IGCompilerOptions,
) {
this.searchImplementationGuides = searchImplementationGuides;
this.structureDefinitionImplementationGuides = structureDefinitionImplementationGuides;
this.options = options;
this.implementationGuides = implementationGuides;
}

async collectResources(igDir: PathLike): Promise<any[]> {
async collectSearchParams(igDir: PathLike): Promise<any[]> {
const indexJson = path.join(igDir.toString(), '.index.json');
if (!existsSync(indexJson)) {
throw new Error(`'.index.json' not found in ${igDir}`);
Expand All @@ -83,6 +90,23 @@ export class IGCompiler {
return resources;
}

async collectStructureDefinitions(igDir: PathLike): Promise<any[]> {
const indexJson = path.join(igDir.toString(), '.index.json');
if (!existsSync(indexJson)) {
throw new Error(`'.index.json' not found in ${igDir}`);
}
const index: any = await loadJson(indexJson);
const resources = [];
for (const file of index.files) {
if (file.resourceType === 'StructureDefinition') {
const filePath = path.join(igDir.toString(), file.filename);
console.log(`Compiling ${filePath}`);
resources.push(await loadJson(filePath));
}
}
return resources;
}
rsmayda marked this conversation as resolved.
Show resolved Hide resolved

/**
* Main public function
rsmayda marked this conversation as resolved.
Show resolved Hide resolved
* @param igsDir
Expand All @@ -94,12 +118,23 @@ export class IGCompiler {
}
const igInfos = await this.collectIGInfos(igsDir);
this.validateDependencies(igInfos);
const resources: any[] = [];

const searchParams: any[] = [];
const structureDefinitions: any[] = [];
for (const igInfo of igInfos) {
resources.push(...(await this.collectResources(igInfo.path)));
searchParams.push(...(await this.collectSearchParams(igInfo.path)));
structureDefinitions.push(...(await this.collectStructureDefinitions(igInfo.path)));
}
const compiledResources = await this.implementationGuides.compile(resources);
await storeJson(outputPath, compiledResources);
const compiledSearchParams = await this.searchImplementationGuides.compile(searchParams);
const compiledStructureDefinitions = await this.structureDefinitionImplementationGuides.compile(
structureDefinitions,
);

await storeJson(path.join(outputPath.toString(), 'fhir-works-on-aws-search-es.json'), compiledSearchParams);
await storeJson(
path.join(outputPath.toString(), 'fhir-works-on-aws-routing.json'),
compiledStructureDefinitions,
);
}

createIGKey(name: string, version: string) {
Expand Down
19 changes: 12 additions & 7 deletions src/implementationGuides/loadCompiledIGs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@
*/

import path from 'path';
import { existsSync, readFileSync } from 'fs';
import { existsSync, PathLike, readFileSync } from 'fs';

const COMPILED_IGS_DIRECTORY = 'compiledImplementationGuides';
export const COMPILED_IGS_DIRECTORY = 'compiledImplementationGuides';

// eslint-disable-next-line import/prefer-default-export
export const loadImplementationGuides = (moduleName: string): any[] | undefined => {
const implementationGuidesPath = path.join(__dirname, '..', '..', COMPILED_IGS_DIRECTORY);
const searchIgsPath = path.join(implementationGuidesPath, `${moduleName}.json`);
export const loadImplementationGuides = (
moduleName: string,
rsmayda marked this conversation as resolved.
Show resolved Hide resolved
implementationGuidesPath?: PathLike,
): any[] | undefined => {
const resolvedImplementationGuidesPath =
implementationGuidesPath ?? path.join(__dirname, '..', '..', COMPILED_IGS_DIRECTORY);

if (existsSync(searchIgsPath)) {
return JSON.parse(readFileSync(searchIgsPath, { encoding: 'utf8' }));
const igsPath = path.join(resolvedImplementationGuidesPath.toString(), `${moduleName}.json`);

if (existsSync(igsPath)) {
return JSON.parse(readFileSync(igsPath, { encoding: 'utf8' }));
}
return undefined;
};