Skip to content

Commit

Permalink
Improve Handling of ElementDefinition.binding without valueSet (#1313)
Browse files Browse the repository at this point in the history
* Tests for bindings w/ no value set

* Improve handling of bindings without a valueSet
  • Loading branch information
cmoesel authored Jul 26, 2023
1 parent 1aa7826 commit 5a4976b
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 10 deletions.
2 changes: 1 addition & 1 deletion src/fhirdefs/impliedExtensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ function applyToValueXElement(
// Handle different representation in DSTU2 and STU3
fromED.binding.valueSet ??
fromED.binding.valueSetUri ??
fromED.binding.valueSetReference.reference,
fromED.binding.valueSetReference?.reference,
fromED.binding.strength
);
if (fromED.binding.description) {
Expand Down
24 changes: 15 additions & 9 deletions src/fhirtypes/ElementDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1677,16 +1677,22 @@ export class ElementDefinition {
) {
throw new BindingStrengthError(listElement?.binding?.strength, strength);
}
// Canonical URLs may include | to specify version: https://www.hl7.org/fhir/references.html#canonical
if (!isUri(vsURI.split('|')[0])) {
throw new InvalidUriError(vsURI);
}

// We're good. Bind it.
this.binding = {
strength,
valueSet: vsURI
};
if (vsURI == null) {
// Just bind the strength since valueSet is allowed to be 0..1
this.binding = { strength };
} else {
// Canonical URLs may include | to specify version: https://www.hl7.org/fhir/references.html#canonical
if (!isUri(vsURI.split('|')[0])) {
throw new InvalidUriError(vsURI);
}

// We're good. Bind it.
this.binding = {
strength,
valueSet: vsURI
};
}
}

/**
Expand Down
93 changes: 93 additions & 0 deletions test/fhirdefs/impliedExtension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,50 @@ describe('impliedExtensions', () => {
expect(loggerSpy.getAllLogs('error')).toHaveLength(0);
});

it('should materialize a simple R5 extension with missing valueSet in binding (because it apparently happens)', () => {
// See: https://github.com/FHIR/sushi/issues/1312
const ext = materializeImpliedExtension(
'http://hl7.org/fhir/5.0/StructureDefinition/extension-Group.code',
defs
);
expect(ext).toBeDefined();
expect(ext).toMatchObject({
resourceType: 'StructureDefinition',
id: 'extension-Group.code',
url: 'http://hl7.org/fhir/5.0/StructureDefinition/extension-Group.code',
version: '5.0.0',
name: 'Extension_Group_code',
title: 'Implied extension for Group.code',
status: 'active',
description: 'Implied extension for Group.code',
fhirVersion: '4.0.1',
kind: 'complex-type',
abstract: false,
context: [{ type: 'element', expression: 'Element' }],
type: 'Extension',
baseDefinition: 'http://hl7.org/fhir/StructureDefinition/Extension',
derivation: 'constraint'
});

// We don't need to check everything, as other tests already do that. We're mainly interested
// in the binding because the R5 definition does not specify a valueSet in the binding.
const diffValue = ext.differential?.element?.find((e: any) => e.id === 'Extension.value[x]');
expect(diffValue).toEqual({
id: 'Extension.value[x]',
path: 'Extension.value[x]',
type: [{ code: 'CodeableConcept' }],
binding: {
strength: 'example',
description: 'Kind of particular resource; e.g. cow, syringe, lake, etc.'
}
});
const snapValue = ext.snapshot?.element?.find((e: any) => e.id === 'Extension.value[x]');
expect(snapValue).toMatchObject(diffValue);

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

it('should materialize a complex R5 extension', () => {
const ext = materializeImpliedExtension(
'http://hl7.org/fhir/5.0/StructureDefinition/extension-MedicationRequest.substitution',
Expand Down Expand Up @@ -1144,6 +1188,55 @@ describe('impliedExtensions', () => {
expect(loggerSpy.getAllLogs('error')).toHaveLength(0);
});

it('should materialize a complex R5 extension for CodeableReference with missing valueSet in binding (because it apparently happens)', () => {
// See: https://github.com/FHIR/sushi/issues/1312
const ext = materializeImpliedExtension(
'http://hl7.org/fhir/5.0/StructureDefinition/extension-Specimen.collection.device',
defs
);
expect(ext).toBeDefined();
expect(ext).toMatchObject({
resourceType: 'StructureDefinition',
id: 'extension-Specimen.collection.device',
url: 'http://hl7.org/fhir/5.0/StructureDefinition/extension-Specimen.collection.device',
version: '5.0.0',
name: 'Extension_Specimen_collection_device',
title: 'Implied extension for Specimen.collection.device',
status: 'active',
description: 'Implied extension for Specimen.collection.device',
fhirVersion: '4.0.1',
kind: 'complex-type',
abstract: false,
context: [{ type: 'element', expression: 'Element' }],
type: 'Extension',
baseDefinition: 'http://hl7.org/fhir/StructureDefinition/Extension',
derivation: 'constraint'
});

// We don't need to check everything, as other tests already do that. We're mainly interested
// in the binding because the R5 definition does not specify a valueSet in the binding.
const diffConceptValue = ext.differential?.element?.find(
(e: any) => e.id === 'Extension.extension:concept.value[x]'
);
expect(diffConceptValue).toEqual({
id: 'Extension.extension:concept.value[x]',
path: 'Extension.extension.value[x]',
type: [{ code: 'CodeableConcept' }],
binding: {
strength: 'example',
description:
'The device that was used to obtain the specimen (e.g. a catheter or catheter part used to draw the blood via a central line).'
}
});
const snapConceptValue = ext.snapshot?.element?.find(
(e: any) => e.id === 'Extension.extension:concept.value[x]'
);
expect(snapConceptValue).toMatchObject(diffConceptValue);

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

it('should automatically convert references to resources that have been renamed', () => {
const ext = materializeImpliedExtension(
'http://hl7.org/fhir/1.0/StructureDefinition/extension-MedicationAdministration.prescription',
Expand Down
32 changes: 32 additions & 0 deletions test/fhirtypes/ElementDefinition.bindToVS.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ describe('ElementDefinition', () => {
expect(concept.binding.strength).toBe('required');
});

it('should allow a binding with no valueset specified (because it apparently happens)', () => {
// See: https://github.com/FHIR/sushi/issues/1312
const concept = observation.elements.find(e => e.id === 'Observation.code');
concept.bindToVS(null, 'required');
expect(concept.binding.valueSet).toBeUndefined();
expect(concept.binding.strength).toBe('required');
});

it('should throw CodedTypeNotFoundError when binding to an unsupported type', () => {
const instant = observation.elements.find(e => e.id === 'Observation.issued');
const clone = cloneDeep(instant);
Expand Down Expand Up @@ -177,6 +185,30 @@ describe('ElementDefinition', () => {
expect(clone.binding.strength).toBe('required');
});
});

it('should still follow strength rules even when no value set is supplied (because it apparently happens)', () => {
// See: https://github.com/FHIR/sushi/issues/1312
const interpretation = observation.elements.find(e => e.id === 'Observation.interpretation');
expect(interpretation.binding.strength).toBe('extensible');
let clone = cloneDeep(interpretation);
clone.bindToVS(null, 'extensible');
expect(clone.binding.valueSet).toBeUndefined();
expect(clone.binding.strength).toBe('extensible');
clone = cloneDeep(interpretation);
clone.bindToVS(null, 'required');
expect(clone.binding.valueSet).toBeUndefined();
expect(clone.binding.strength).toBe('required');
clone = cloneDeep(interpretation);
expect(() => {
interpretation.bindToVS(null, 'preferred');
}).toThrow(/extensible.*preferred/);
expect(clone).toEqual(interpretation);
clone = cloneDeep(interpretation);
expect(() => {
interpretation.bindToVS(null, 'example');
}).toThrow(/extensible.*example/);
expect(clone).toEqual(interpretation);
});
});

describe('ElementDefinition R5', () => {
Expand Down

Large diffs are not rendered by default.

0 comments on commit 5a4976b

Please sign in to comment.