This repository has been archived by the owner on Apr 13, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: allow sorting by date type parameters (#60)
- Loading branch information
Showing
7 changed files
with
189 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -111,3 +111,5 @@ export const buildQueryForAllSearchParameters = ( | |
}, | ||
}; | ||
}; | ||
|
||
export { buildSortClause } from './sort'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { InvalidSearchParameterError } from 'fhir-works-on-aws-interface'; | ||
import { buildSortClause, parseSortParameter } from './sort'; | ||
import { FHIRSearchParametersRegistry } from '../FHIRSearchParametersRegistry'; | ||
|
||
const fhirSearchParametersRegistry = new FHIRSearchParametersRegistry('4.0.1'); | ||
|
||
describe('parseSortParameter', () => { | ||
test('status,-date,category', () => { | ||
expect(parseSortParameter('status,-date,category')).toMatchInlineSnapshot(` | ||
Array [ | ||
Object { | ||
"order": "asc", | ||
"searchParam": "status", | ||
}, | ||
Object { | ||
"order": "desc", | ||
"searchParam": "date", | ||
}, | ||
Object { | ||
"order": "asc", | ||
"searchParam": "category", | ||
}, | ||
] | ||
`); | ||
}); | ||
}); | ||
|
||
describe('buildSortClause', () => { | ||
test('valid date params', () => { | ||
expect(buildSortClause(fhirSearchParametersRegistry, 'Patient', '-_lastUpdated,birthdate')) | ||
.toMatchInlineSnapshot(` | ||
Array [ | ||
Object { | ||
"meta.lastUpdated": Object { | ||
"order": "desc", | ||
"unmapped_type": "long", | ||
}, | ||
}, | ||
Object { | ||
"meta.lastUpdated.end": Object { | ||
"order": "desc", | ||
"unmapped_type": "long", | ||
}, | ||
}, | ||
Object { | ||
"birthDate": Object { | ||
"order": "asc", | ||
"unmapped_type": "long", | ||
}, | ||
}, | ||
Object { | ||
"birthDate.start": Object { | ||
"order": "asc", | ||
"unmapped_type": "long", | ||
}, | ||
}, | ||
] | ||
`); | ||
}); | ||
|
||
test('invalid params', () => { | ||
[ | ||
'notAPatientParam', | ||
'_lastUpdated,notAPatientParam', | ||
'+birthdate', | ||
'#$%/., symbols and stuff', | ||
'valid params must match a param name from fhirSearchParametersRegistry, so most strings are invalid...', | ||
'name', // This is actually a valid param but right now we only allow sorting by date params | ||
].forEach(p => | ||
expect(() => buildSortClause(fhirSearchParametersRegistry, 'Patient', p)).toThrow( | ||
InvalidSearchParameterError, | ||
), | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
*/ | ||
|
||
import { InvalidSearchParameterError } from 'fhir-works-on-aws-interface'; | ||
import { FHIRSearchParametersRegistry } from '../FHIRSearchParametersRegistry'; | ||
|
||
interface SortParameter { | ||
order: 'asc' | 'desc'; | ||
searchParam: string; | ||
} | ||
|
||
export const parseSortParameter = (param: string): SortParameter[] => { | ||
const parts = param.split(','); | ||
return parts.map(s => { | ||
const order = s.startsWith('-') ? 'desc' : 'asc'; | ||
return { | ||
order, | ||
searchParam: s.replace(/^-/, ''), | ||
}; | ||
}); | ||
}; | ||
|
||
const elasticsearchSort = (field: string, order: 'asc' | 'desc') => ({ | ||
[field]: { | ||
order, | ||
// unmapped_type makes queries more fault tolerant. Since we are using dynamic mapping there's no guarantee | ||
// that the mapping exists at query time. This ignores the unmapped field instead of failing | ||
unmapped_type: 'long', | ||
}, | ||
}); | ||
|
||
// eslint-disable-next-line import/prefer-default-export | ||
export const buildSortClause = ( | ||
fhirSearchParametersRegistry: FHIRSearchParametersRegistry, | ||
resourceType: string, | ||
sortQueryParam: string | string[], | ||
): any[] => { | ||
if (Array.isArray(sortQueryParam)) { | ||
throw new InvalidSearchParameterError('_sort parameter cannot be used multiple times on a search query'); | ||
} | ||
const sortParams = parseSortParameter(sortQueryParam); | ||
|
||
return sortParams.flatMap(sortParam => { | ||
const searchParameter = fhirSearchParametersRegistry.getSearchParameter(resourceType, sortParam.searchParam); | ||
if (searchParameter === undefined) { | ||
throw new InvalidSearchParameterError( | ||
`Unknown _sort parameter value: ${sortParam.searchParam}. Sort parameters values must use a valid Search Parameter`, | ||
); | ||
} | ||
if (searchParameter.type !== 'date') { | ||
throw new InvalidSearchParameterError( | ||
`Invalid _sort parameter: ${sortParam.searchParam}. Only date type parameters can currently be used for sorting`, | ||
); | ||
} | ||
return searchParameter.compiled.flatMap(compiledParam => { | ||
return [ | ||
elasticsearchSort(compiledParam.path, sortParam.order), | ||
|
||
// Date search params may target fields of type Period, so we add a sort clause for them. | ||
// The FHIR spec does not fully specify how to sort by Period, but it makes sense that the most recent | ||
// record is the one with the most recent "end" date and vice versa. | ||
elasticsearchSort( | ||
sortParam.order === 'desc' ? `${compiledParam.path}.end` : `${compiledParam.path}.start`, | ||
sortParam.order, | ||
), | ||
]; | ||
}); | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters