Skip to content

Commit

Permalink
Inline extensions are not allowed on profiles (#665)
Browse files Browse the repository at this point in the history
Log an error when an inline extension would be used on a profile. Many
existing test cases were using inline extensions on profiles, so those
tests are updated such that they still test the same code. Inline
extensions are still allowed on extensions.
  • Loading branch information
mint-thompson authored Nov 13, 2020
1 parent 3a4859a commit 17701c1
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 26 deletions.
4 changes: 4 additions & 0 deletions src/export/StructureDefinitionExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,10 @@ export class StructureDefinitionExporter implements Fishable {
this
);
urlElement.assignValue(slice.sliceName, true);
// Inline extensions should not be used on profiles
if (fshDefinition instanceof Profile) {
logger.error('Inline extensions should not be used on profiles.', rule.sourceInfo);
}
}
});
}
Expand Down
17 changes: 17 additions & 0 deletions test/export/StructureDefinition.ExtensionExporter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { loggerSpy } from '../testhelpers/loggerSpy';
import { TestFisher } from '../testhelpers';
import path from 'path';
import { minimalConfig } from '../utils/minimalConfig';
import { ContainsRule } from '../../src/fshtypes/rules';

describe('ExtensionExporter', () => {
let defs: FHIRDefinitions;
Expand Down Expand Up @@ -145,4 +146,20 @@ describe('ExtensionExporter', () => {
expect(exported[1].baseDefinition === exported[0].url);
expect(exported[2].baseDefinition === exported[1].url);
});

it('should not log an error when an inline extension is used', () => {
loggerSpy.reset();
const extension = new Extension('MyExtension');
const containsRule = new ContainsRule('extension')
.withFile('MyExtension.fsh')
.withLocation([3, 8, 3, 25]);
containsRule.items.push({
name: 'SomeExtension'
});
extension.rules.push(containsRule);
doc.extensions.set(extension.name, extension);
exporter.export();

expect(loggerSpy.getAllLogs('error')).toHaveLength(0);
});
});
21 changes: 20 additions & 1 deletion test/export/StructureDefinition.ProfileExporter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Profile, Instance } from '../../src/fshtypes';
import { loggerSpy } from '../testhelpers/loggerSpy';
import { TestFisher } from '../testhelpers';
import { minimalConfig } from '../utils/minimalConfig';
import { CaretValueRule } from '../../src/fshtypes/rules';
import { CaretValueRule, ContainsRule } from '../../src/fshtypes/rules';

describe('ProfileExporter', () => {
let defs: FHIRDefinitions;
Expand Down Expand Up @@ -157,4 +157,23 @@ describe('ProfileExporter', () => {
expect(exporter.deferredRules.get(exported[0]).length).toBe(1);
expect(exporter.deferredRules.get(exported[0])).toContain(caretValueRule);
});

it('should log an error when an inline extension is used', () => {
const profile = new Profile('MyObservation');
profile.parent = 'Observation';
const containsRule = new ContainsRule('extension')
.withFile('MyObservation.fsh')
.withLocation([3, 8, 3, 25]);
containsRule.items.push({
name: 'SomeExtension'
});
profile.rules.push(containsRule);
doc.profiles.set(profile.name, profile);
exporter.export();

expect(loggerSpy.getLastMessage('error')).toMatch(/File: MyObservation\.fsh.*Line: 3\D*/s);
expect(loggerSpy.getLastMessage('error')).toMatch(
/Inline extensions should not be used on profiles/s
);
});
});
56 changes: 31 additions & 25 deletions test/export/StructureDefinitionExporter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2136,13 +2136,11 @@ describe('StructureDefinitionExporter', () => {
});

it('should apply a ContainsRule on an extension with defined slicing', () => {
// Profile: Foo
// Parent: resprate
// Extension: Foo
// * extension contains bar
// * extension[bar] ^slicing.discriminator.type = #pattern
// * extension[bar] contains barReslice
const profile = new Profile('Foo');
profile.parent = 'resprate';
const extension = new Extension('Foo');

const sliceRule = new ContainsRule('extension');
sliceRule.items = [{ name: 'bar' }];
Expand All @@ -2151,13 +2149,13 @@ describe('StructureDefinitionExporter', () => {
caretRule.value = new FshCode('pattern');
const resliceRule = new ContainsRule('extension[bar]');
resliceRule.items = [{ name: 'barReslice' }];
profile.rules.push(sliceRule, caretRule, resliceRule);
extension.rules.push(sliceRule, caretRule, resliceRule);

exporter.exportStructDef(profile);
const sd = pkg.profiles[0];
const baseStructDef = fisher.fishForStructureDefinition('resprate');
exporter.exportStructDef(extension);
const sd = pkg.extensions[0];
const baseStructDef = fisher.fishForStructureDefinition('Extension');

const barReslice = sd.elements.find(e => e.id === 'Observation.extension:bar/barReslice');
const barReslice = sd.elements.find(e => e.id === 'Extension.extension:bar/barReslice');

expect(sd.elements.length).toBe(baseStructDef.elements.length + 6);
expect(barReslice).toBeDefined();
Expand Down Expand Up @@ -2322,32 +2320,31 @@ describe('StructureDefinitionExporter', () => {
// for the code type. This test initially failed and was assigned by changing the code that handles extension
// slices to only look for Extension resolutions (as opposed to all types).
// NOTE: This test is mainly irrelevant now that we switched to a syntax that distinguishes slice name from type.
const profile = new Profile('Foo');
profile.parent = 'Observation';
const extension = new Extension('Foo');

const containsRule = new ContainsRule('extension');
containsRule.items = [{ name: 'code' }];
profile.rules.push(containsRule);
extension.rules.push(containsRule);

const onlyRule = new OnlyRule('extension[code].value[x]');
onlyRule.types = [{ type: 'Quantity' }];
profile.rules.push(onlyRule);
extension.rules.push(onlyRule);

exporter.exportStructDef(profile);
const sd = pkg.profiles[0];
exporter.exportStructDef(extension);
const sd = pkg.extensions[0];

expect(loggerSpy.getAllLogs('error')).toHaveLength(0);

const extension = sd.elements.find(e => e.id === 'Observation.extension');
const extensionSlice = sd.elements.find(e => e.id === 'Observation.extension:code');
const extensionSliceUrl = sd.elements.find(e => e.id === 'Observation.extension:code.url');
const innerExtension = sd.elements.find(e => e.id === 'Extension.extension');
const extensionSlice = sd.elements.find(e => e.id === 'Extension.extension:code');
const extensionSliceUrl = sd.elements.find(e => e.id === 'Extension.extension:code.url');
const extensionSliceValueX = sd.elements.find(
e => e.id === 'Observation.extension:code.value[x]'
e => e.id === 'Extension.extension:code.value[x]'
);

expect(extension.slicing).toBeDefined();
expect(extension.slicing.discriminator.length).toBe(1);
expect(extension.slicing.discriminator[0]).toEqual({ type: 'value', path: 'url' });
expect(innerExtension.slicing).toBeDefined();
expect(innerExtension.slicing.discriminator.length).toBe(1);
expect(innerExtension.slicing.discriminator[0]).toEqual({ type: 'value', path: 'url' });
expect(extensionSlice).toBeDefined();
expect(extensionSliceUrl).toBeDefined();
expect(extensionSliceUrl.fixedUri).toBe('code');
Expand Down Expand Up @@ -3469,12 +3466,15 @@ describe('StructureDefinitionExporter', () => {
patientProfile.parent = 'Patient';

const containsRule = new ContainsRule('maritalStatus.extension');
containsRule.items = [{ name: 'maritalSlice' }];
containsRule.items = [{ name: 'maritalSlice', type: 'MaritalExtension' }];
const sliceCardRule = new CardRule('maritalStatus.extension[maritalSlice].extension');
sliceCardRule.min = 1;
sliceCardRule.max = '2';
patientProfile.rules.push(containsRule, sliceCardRule);

const extension = new Extension('MaritalExtension');
doc.extensions.set(extension.name, extension);

exporter.exportStructDef(patientProfile);
const sd = pkg.profiles[0];
const valueElement = sd.findElement('Patient.maritalStatus.extension:maritalSlice.value[x]');
Expand All @@ -3489,11 +3489,14 @@ describe('StructureDefinitionExporter', () => {
profile.parent = 'Observation';

const containsRule = new ContainsRule('extension');
containsRule.items = [{ name: 'EvidenceType' }];
containsRule.items = [{ name: 'EvidenceType', type: 'EvidenceExtension' }];
const onlyRule = new OnlyRule('value[x]');
onlyRule.types = [{ type: 'string' }];
profile.rules.push(containsRule, onlyRule);

const extension = new Extension('EvidenceExtension');
doc.extensions.set(extension.name, extension);

exporter.exportStructDef(profile);
const sd = pkg.profiles[0];

Expand All @@ -3512,12 +3515,15 @@ describe('StructureDefinitionExporter', () => {
profile.parent = 'Patient';

const containsRule = new ContainsRule('extension');
containsRule.items = [{ name: 'PatientNote' }];
containsRule.items = [{ name: 'PatientNote', type: 'NoteExtension' }];
const cardRule = new CardRule('extension[PatientNote].extension');
cardRule.min = 1;
cardRule.max = '1';
profile.rules.push(containsRule, cardRule);

const noteExtension = new Extension('NoteExtension');
doc.extensions.set(noteExtension.name, noteExtension);

exporter.exportStructDef(profile);
const sd = pkg.profiles[0];

Expand Down

0 comments on commit 17701c1

Please sign in to comment.