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

Commit

Permalink
fix: chain parameters should inspect conditions to narrow down possib…
Browse files Browse the repository at this point in the history
…le target types (#168)
  • Loading branch information
ssvegaraju authored Feb 3, 2022
1 parent 55e80c2 commit bc805cb
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 9 deletions.
118 changes: 116 additions & 2 deletions src/QueryBuilder/chain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
*/

import { InvalidSearchParameterError } from 'fhir-works-on-aws-interface';
import parseChainedParameters from './chain';
import { FHIRSearchParametersRegistry } from '../FHIRSearchParametersRegistry';
import parseChainedParameters, { getUniqueTarget } from './chain';
import { FHIRSearchParametersRegistry, SearchParam } from '../FHIRSearchParametersRegistry';

const fhirSearchParametersRegistry = new FHIRSearchParametersRegistry('4.0.1');

Expand Down Expand Up @@ -93,4 +93,118 @@ describe('parseChainedParameters', () => {
),
);
});

test('search param with conditions that narrow down target type', () => {
expect(
parseChainedParameters(fhirSearchParametersRegistry, 'DocumentReference', {
'patient.identifier': '2.16.840.1.113883.3.1579|8889154591540',
}),
).toMatchInlineSnapshot(`
Array [
Object {
"chain": Array [
Object {
"resourceType": "Patient",
"searchParam": "identifier",
},
Object {
"resourceType": "DocumentReference",
"searchParam": "patient",
},
],
"initialValue": Array [
"2.16.840.1.113883.3.1579|8889154591540",
],
},
]
`);
});

test('get unique target edge cases', () => {
const errorCases: SearchParam[] = [
{
name: 'patient',
url: 'http://hl7.org/fhir/SearchParameter/Person-patient',
type: 'reference',
description: 'The Person links to this Patient',
base: 'Person',
target: ['Patient', 'Practitioner'],
compiled: [
{
resourceType: 'Person',
path: 'link.target',
condition: ['link.target', 'resolve', 'Patient'],
},
{
resourceType: 'Person',
path: 'link.target',
condition: ['link.something', 'resolve', 'Practitioner'],
},
],
},
{
name: 'patient',
url: 'http://hl7.org/fhir/SearchParameter/Person-patient',
type: 'reference',
description: 'The Person links to this Patient',
base: 'Person',
target: ['Patient', 'Practitioner'],
compiled: [
{
resourceType: 'Person',
path: 'link.target',
condition: ['link.target', 'resolve', 'Patient'],
},
{
resourceType: 'Person',
path: 'xxx.target',
},
],
},
{
name: 'patient',
url: 'http://hl7.org/fhir/SearchParameter/Person-patient',
type: 'reference',
description: 'The Person links to this Patient',
base: 'Person',
target: ['Patient', 'Practitioner'],
compiled: [
{
resourceType: 'Person',
path: 'link.target',
condition: ['link.target', 'resolve', 'Observation'],
},
{
resourceType: 'Person',
path: 'link.target',
condition: ['link.something', 'resolve', 'Observation'],
},
],
},
];

const successCase: SearchParam = {
name: 'patient',
url: 'http://hl7.org/fhir/SearchParameter/Person-patient',
type: 'reference',
description: 'The Person links to this Patient',
base: 'Person',
target: ['Patient', 'Practitioner'],
compiled: [
{
resourceType: 'Person',
path: 'link.target',
condition: ['link.target', 'resolve', 'Patient'],
},
{
resourceType: 'Person',
path: 'link.target',
condition: ['link.something', 'resolve', 'Patient'],
},
],
};

expect(getUniqueTarget(successCase)).toMatchInlineSnapshot(`"Patient"`);
errorCases.forEach((errorCase) => expect(getUniqueTarget(errorCase)).toBeUndefined());
});
});
48 changes: 41 additions & 7 deletions src/QueryBuilder/chain.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,46 @@
// eslint-disable-next-line import/prefer-default-export
import { InvalidSearchParameterError } from 'fhir-works-on-aws-interface';
import { FHIRSearchParametersRegistry } from '../FHIRSearchParametersRegistry';
import { NON_SEARCHABLE_PARAMETERS } from '../constants';
import { FHIRSearchParametersRegistry, SearchParam } from '../FHIRSearchParametersRegistry';
import { COMPILED_CONDITION_OPERATOR_RESOLVE, NON_SEARCHABLE_PARAMETERS } from '../constants';
import { parseSearchModifiers, normalizeQueryParams, isChainedParameter } from '../FhirQueryParser/util';

export interface ChainParameter {
chain: { resourceType: string; searchParam: string }[];
initialValue: string[];
}

export function getUniqueTarget(fhirSearchParam: SearchParam): string | undefined {
if (!fhirSearchParam.target) {
return undefined;
}
if (fhirSearchParam.target.length === 1) {
return fhirSearchParam.target[0];
}
let target: string | undefined;
for (let i = 0; i < fhirSearchParam.compiled.length; i += 1) {
// check compiled[].condition for resolution
const compiled = fhirSearchParam.compiled[i]; // we can use ! since we checked length before
// condition's format is defined in `../FHIRSearchParamtersRegistry/index.ts`
if (compiled.condition && compiled.condition[1] === COMPILED_CONDITION_OPERATOR_RESOLVE) {
if (!target) {
// eslint-disable-next-line prefer-destructuring
target = compiled.condition[2];
} else if (target !== compiled.condition[2]) {
// case where two compiled resolve to different resource types
return undefined;
}
} else {
// if there is no resolve condition, we have multiple resources pointed to.
return undefined;
}
}
// case for resolution to resource type that isn't contained in the target group
if (target && !fhirSearchParam.target.includes(target)) {
return undefined;
}
return target;
}

const parseChainedParameters = (
fhirSearchParametersRegistry: FHIRSearchParametersRegistry,
resourceType: string,
Expand Down Expand Up @@ -54,16 +86,18 @@ const parseChainedParameters = (
`Chained search parameter '${searchModifier.parameterName}' for resource type ${currentResourceType} does not point to resource type ${searchModifier.modifier}.`,
);
}
} else if (fhirSearchParam.target?.length !== 1) {
throw new InvalidSearchParameterError(
`Chained search parameter '${searchModifier.parameterName}' for resource type ${currentResourceType} points to multiple resource types, please specify.`,
);
} else {
const target = getUniqueTarget(fhirSearchParam);
if (!target) {
throw new InvalidSearchParameterError(
`Chained search parameter '${searchModifier.parameterName}' for resource type ${currentResourceType} points to multiple resource types, please specify.`,
);
}
organizedChain.push({
resourceType: currentResourceType,
searchParam: searchModifier.parameterName,
});
[nextResourceType] = fhirSearchParam.target;
nextResourceType = target;
}
currentResourceType = nextResourceType;
});
Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const ITERATIVE_INCLUSION_PARAMETERS = ['_include:iterate', '_revinclude:
export const INCLUSION_PARAMETERS = ['_include', '_revinclude', ...ITERATIVE_INCLUSION_PARAMETERS];
export const UNSUPPORTED_GENERAL_PARAMETERS = ['_format', '_pretty', '_summary', '_elements'];
export const SORT_PARAMETER = '_sort';
export const COMPILED_CONDITION_OPERATOR_RESOLVE = 'resolve';
export const NON_SEARCHABLE_PARAMETERS = [
SORT_PARAMETER,
SEARCH_PAGINATION_PARAMS.PAGES_OFFSET,
Expand Down

0 comments on commit bc805cb

Please sign in to comment.