Skip to content

Commit

Permalink
Merge pull request #50 from mcode/parse-query-parameters
Browse files Browse the repository at this point in the history
Parse query parameters in the library
  • Loading branch information
zlister authored Jun 26, 2024
2 parents 8d0d668 + 9562f8c commit d5d1d12
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 13 deletions.
177 changes: 177 additions & 0 deletions spec/query-parameters.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { Bundle, BundleEntry, Parameters } from 'fhir/r4';
import { parseQueryParameters } from '../src/query-parameters';

describe('parseQueryParameters', () => {
it('extracts passed properties', () => {
expect(
parseQueryParameters({
resourceType: 'Bundle',
type: 'collection',
entry: [
{
resource: {
resourceType: 'Parameters',
parameter: [
{
name: 'zipCode',
valueString: '01730'
},
{
name: 'travelRadius',
valueString: '25'
},
{
name: 'phase',
valueString: 'phase-1'
},
{
name: 'recruitmentStatus',
valueString: 'approved'
}
]
}
}
]
})
).toEqual({
zipCode: '01730',
travelRadius: 25,
phase: 'phase-1',
recruitmentStatus: 'approved'
});
});
it('extracts travel radius from valueDecimal', () => {
expect(
parseQueryParameters({
resourceType: 'Bundle',
type: 'collection',
entry: [
{
resource: {
resourceType: 'Parameters',
parameter: [
{
name: 'travelRadius',
valueDecimal: 25
}
]
}
}
]
})
).toEqual({
travelRadius: 25
});
});
it('ignores travel radius of an invalid type', () => {
expect(
parseQueryParameters({
resourceType: 'Bundle',
type: 'collection',
entry: [
{
resource: {
resourceType: 'Parameters',
parameter: [
{
name: 'travelRadius',
valueDecimal: 25
},
{
name: 'travelRadius',
valueBoolean: true
}
]
}
}
]
})
).toEqual({
travelRadius: 25
});
});

it('ignores unknown parameters', () => {
const parameters = parseQueryParameters({
resourceType: 'Bundle',
type: 'collection',
entry: [
{
resource: {
resourceType: 'Parameters',
parameter: [
{
name: 'unknown',
valueString: 'invalid'
}
]
}
}
]
});
expect(parameters).toEqual({});
});

it('ignores invalid entries', () => {
const bundle: Bundle = {
resourceType: 'Bundle',
type: 'collection',
entry: [
{
resource: undefined
}
]
};
// Force an invalid entry in
bundle.entry?.push({ invalid: true } as unknown as BundleEntry, { resource: 'invalid' } as unknown as BundleEntry);
// Passing in this case is not throwing an exception and returning nothing
expect(parseQueryParameters(bundle)).toEqual({});
});

it('ignores non-parameter resources', () => {
expect(
parseQueryParameters({
resourceType: 'Bundle',
type: 'collection',
entry: [
{
resource: {
resourceType: 'Observation',
code: {},
status: 'final'
}
}
]
})
).toEqual({});
});

it('ignores invalid parameters', () => {
// Invalid parameters object
const invalidParameters = {
resourceType: 'Parameters',
parameter: 'invalid'
} as unknown as Parameters;
// Passing in this case is not throwing an exception and returning nothing
expect(
parseQueryParameters({
resourceType: 'Bundle',
type: 'collection',
entry: [
{
resource: invalidParameters
}
]
})
).toEqual({});
});

it('ignores invalid bundles', () => {
// Passing in this case is not throwing an exception and returning nothing
expect(
parseQueryParameters({
entry: 'invalid'
} as unknown as Bundle)
).toEqual({});
});
});
4 changes: 3 additions & 1 deletion src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function restrictToHttpErrors(httpStatus: number, statusIfInvalid = 500):
* If the error code used is outside the range [400,599] it will be converted to
* 500.
*/
export default class BasicHttpError extends Error {
export class BasicHttpError extends Error {
private _httpStatus: number;
constructor(message: string, httpStatus = 500) {
super(message);
Expand All @@ -53,6 +53,8 @@ export default class BasicHttpError extends Error {
}
}

export default BasicHttpError;

/**
* Simply marks an error as an internal error: an error happened within the
* server that should be reported to the client. This restricts the range of
Expand Down
15 changes: 5 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,19 @@ export * from './tnm';
export { CodeMapper, CodeSystemEnum } from './codeMapper';
export * from './mcodeextractor';
export { Study } from './ctg-api';
export { BasicHttpError, HttpError, ServerError, ClientError } from './errors';
export { QueryParameters } from './query-parameters';
export { ClinicalTrialsGovAPI } from './clinicaltrialsgov-api';
export { createResearchStudyFromClinicalStudy } from './study-fhir-converter';
export * from './env';

// The export { v } from "mod" forms do not appear to work for types yet, so
// they have to be imported and then exported...
import BasicHttpError, { HttpError, ServerError, ClientError } from './errors';
// In order to export a default, we need to import it, so import it
import ClinicalTrialMatchingService, { ClinicalTrialMatcher, Configuration } from './server';

// Export the utility for configuring from the environment
export * from './env';

// And re-export it
export default ClinicalTrialMatchingService;
export {
ClinicalTrialMatcher,
ClinicalTrialMatchingService,
Configuration as ServiceConfiguration,
BasicHttpError,
HttpError,
ClientError,
ServerError
};
50 changes: 50 additions & 0 deletions src/query-parameters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Bundle } from 'fhir/r4';

/**
* The following parameters are defined by the PCT IG.
*/
export interface QueryParameters {
zipCode?: string;
travelRadius?: number;
phase?: string;
recruitmentStatus?: string;
}

/**
* Parses out query parameters from a patient bundle.
* @param patientBundle the patient bundle containing parameters
*/
export function parseQueryParameters(patientBundle: Bundle): QueryParameters {
// Resulting parameters
const parameters: QueryParameters = {};
if (Array.isArray(patientBundle.entry)) {
for (const entry of patientBundle.entry) {
if (!('resource' in entry)) {
// Skip bad entries
continue;
}
const resource = entry.resource;
// Pull out search parameters
if (resource?.resourceType === 'Parameters') {
if (Array.isArray(resource.parameter)) {
for (const parameter of resource.parameter) {
if (parameter.name === 'zipCode') {
parameters.zipCode = parameter.valueString;
} else if (parameter.name === 'travelRadius') {
if (typeof parameter.valueString === 'string') {
parameters.travelRadius = parseFloat(parameter.valueString);
} else if (typeof parameter.valueDecimal === 'number') {
parameters.travelRadius = parameter.valueDecimal;
}
} else if (parameter.name === 'phase') {
parameters.phase = parameter.valueString;
} else if (parameter.name === 'recruitmentStatus') {
parameters.recruitmentStatus = parameter.valueString;
}
}
}
}
}
}
return parameters;
}
5 changes: 3 additions & 2 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import * as bodyParser from 'body-parser';
import { Bundle, ResearchStudy } from 'fhir/r4';
import { isHttpError, restrictToHttpErrors } from './errors';
import { isBundle } from './fhir-type-guards';
import { QueryParameters, parseQueryParameters } from './query-parameters';
import * as http from 'http';

// SearchSet is just a bundle which is a searchset containing ResearchStudies
export interface SearchSet<T = ResearchStudy> extends Bundle<T> {
type: 'searchset';
}

export type ClinicalTrialMatcher = (patientBundle: Bundle) => Promise<SearchSet>;
export type ClinicalTrialMatcher = (patientBundle: Bundle, parameters: QueryParameters) => Promise<SearchSet>;

/**
* Server configuration.
Expand Down Expand Up @@ -210,7 +211,7 @@ export class ClinicalTrialMatchingService {
}
};
try {
this.matcher(patientBundle)
this.matcher(patientBundle, parseQueryParameters(patientBundle))
.then((result) => {
response.status(200).send(JSON.stringify(result));
})
Expand Down

0 comments on commit d5d1d12

Please sign in to comment.