Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] Prevent js file use from server configuration object #213

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 87 additions & 39 deletions docs/CustomMetadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,39 +12,37 @@ Add the following code to the previously created file.

```javascript
module.exports = {
makeResource: (args, logger) => {
const identifier = {
name: "identifier",
type: 'token',
fhirtype: 'token',
xpath: 'Patient.identifier',
definition: 'http://hl7.org/fhir/SearchParameter/Patient-identifier',
description: 'A patient identifier',
}
makeResource: (args, logger) => {
const identifier = {
name: "identifier",
type: "token",
fhirtype: "token",
xpath: "Patient.identifier",
definition: "http://hl7.org/fhir/SearchParameter/Patient-identifier",
description: "A patient identifier"
};

const birthdate = {
name: "birthdate",
type: 'date',
fhirtype: 'date',
xpath: 'Patient.birthDate',
definition: 'http://hl7.org/fhir/SearchParameter/individual-birthdate',
description:
"Multiple Resources: * [Patient](patient.html): The patient's date of birth * [Person](person.html): The person's date of birth * [RelatedPerson](relatedperson.html): The Related Person's date of birth ",
}
const birthdate = {
name: "birthdate",
type: "date",
fhirtype: "date",
xpath: "Patient.birthDate",
definition: "http://hl7.org/fhir/SearchParameter/individual-birthdate",
description:
"Multiple Resources: * [Patient](patient.html): The patient's date of birth * [Person](person.html): The person's date of birth * [RelatedPerson](relatedperson.html): The Related Person's date of birth "
};

return {
type: "Patient",
profile: {
reference: "http://hl7.org/fhir/patient.html"
},
searchParam: [
identifier,
birthdate
]
};
}
return {
type: "Patient",
profile: {
reference: "http://hl7.org/fhir/patient.html"
},
searchParam: [identifier, birthdate]
};
}
};
```

With this code we say that only "identifier" and "birthdate" are implemented as search parameters for patients.
Each search parameter must be defined and added to the searchParam list.

Expand All @@ -56,17 +54,67 @@ Now, update the profiles in your `index.js` file.

```javascript
let config = {
profiles: {
patient: {
service: './patient.service.js',
versions: [
'4_0_0'
],
metadata: './patient.metadata.js'
}
}
profiles: {
patient: {
service: "./patient.service.js",
versions: ["4_0_0"],
metadata: "./patient.metadata.js"
}
}
};
```

You just have to add the metadata parameter. The value of the metadata parameter is the path to your `patient.metadata.js` file.

Thats it! Now when you open [http://localhost:3000/4_0_0/metadata](http://localhost:3000/4_0_0/metadata) in your browser, you find for the patient resource only the defined search parameters.
Thats it! Now when you open [http://localhost:3000/4_0_0/metadata](http://localhost:3000/4_0_0/metadata) in your browser, you find for the patient resource only the defined search parameters.

### Object mode

In some case, you'd rather prefer to provide your own metadata object from a custom logic.
So metadata property can handle a path as previously demonstrated but also an object like below:

```javascript
let config = {
profiles: {
patient: {
service: "./patient.service.js",
versions: ["4_0_0"],
metadata: {
makeResource: (args, logger) => {
const identifier = {
name: "identifier",
type: "token",
fhirtype: "token",
xpath: "Patient.identifier",
definition:
"http://hl7.org/fhir/SearchParameter/Patient-identifier",
description: "A patient identifier"
};

const birthdate = {
name: "birthdate",
type: "date",
fhirtype: "date",
xpath: "Patient.birthDate",
definition:
"http://hl7.org/fhir/SearchParameter/individual-birthdate",
description:
"Multiple Resources: * [Patient](patient.html): The patient's date of birth * [Person](person.html): The person's date of birth * [RelatedPerson](relatedperson.html): The Related Person's date of birth "
};

return {
type: "Patient",
profile: {
reference: "http://hl7.org/fhir/patient.html"
},
searchParam: [identifier, birthdate]
};
}
}
}
}
};
```

| WARNING: Be carefull not to transform your configuration file into a mess |
| --- |
5 changes: 4 additions & 1 deletion src/server/metadata/metadata.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,11 @@ let generateCapabilityStatement = (args, config) =>
let customMakeResource = null;
server_statement.resource = active_profiles.map(profile => {
if (profile.metadata) {
customMakeResource = require(profile.metadata).makeResource;
customMakeResource =
typeof profile.metadata === 'string' ? require(profile.metadata).makeResource : profile.metadata.makeResource;
// customMakeResource = require(profile.metadata).makeResource;
} else {
// Global makeResource function if no metadata definition overload
customMakeResource = profile.service.makeResource;
}
let resource = customMakeResource
Expand Down
6 changes: 4 additions & 2 deletions src/server/metadata/metadata.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ describe('Conformance Tests', () => {
let config = Object.assign({}, test_config, { logging: { level: 'emerg' } });
server = new Server(config).setProfileRoutes().setErrorRoutes();

let keys = Object.keys(server.config.profiles);
// Exclude QuestionnaireResponse because it as a special metadata object associate and require a special mock
let keys = Object.keys(server.config.profiles).filter(k => k !== 'QuestionnaireResponse');
let { routes } = require('../route.config');

let response = await request(server.app)['get']('/3_0_1/metadata');
Expand Down Expand Up @@ -223,7 +224,8 @@ describe('Conformance Tests', () => {
let config = Object.assign({}, test_config, { logging: { level: 'emerg' } });
server = new Server(config).setProfileRoutes().setErrorRoutes();

let keys = Object.keys(server.config.profiles);
// Exclude QuestionnaireResponse because it as a special metadata object associate and require a special mock
let keys = Object.keys(server.config.profiles).filter(k => k !== 'QuestionnaireResponse');
//Patch the test config to include our custom metadata module
for (let key of keys) {
server.config.profiles[key].metadata = path.resolve(__dirname, 'metadata.test.metadata.js');
Expand Down
2 changes: 1 addition & 1 deletion src/server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class Server {

this.env = {
IS_PRODUCTION: !process.env.NODE_ENV || process.env.NODE_ENV === 'production',
USE_HTTPS: (server.ssl && server.ssl.key && server.ssl.cert) ? server.ssl : undefined,
USE_HTTPS: server.ssl && server.ssl.key && server.ssl.cert ? server.ssl : undefined,
};
// return self for chaining
return this;
Expand Down
10 changes: 6 additions & 4 deletions src/server/utils/params.utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ let getSearchParameters = (profile, version, customArgsModule, logger) => {
// If we have a custom args module, we will use this to populate the allowed
// args for this particular route instead of the default arguments included
if (customArgsModule) {
let paramsAsArray = require(String(customArgsModule)).makeResource(
Object.assign({}, { base_version: version, key: lowercaseProfileName }),
logger,
).searchParam;
const makeResourceFn =
typeof customArgsModule === 'string'
? require(String(customArgsModule)).makeResource
: customArgsModule.makeResource;
let paramsAsArray = makeResourceFn(Object.assign({}, { base_version: version, key: lowercaseProfileName }), logger)
.searchParam;
// We need to key these by name so we can remove duplicates on assign
allArguments = paramsAsArray.reduce((all, arg) => {
all[arg.name] = arg;
Expand Down
87 changes: 86 additions & 1 deletion src/server/utils/params.utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,97 @@ describe('Param Utils Tests', () => {
expect(r4Params.every(param => param.name !== undefined)).toBeTruthy();
});

test('should override the resource arguments when custom arguments provided', () => {
test('should override the resource arguments when custom arguments provided as path', () => {
let custom = path.resolve('./src/server/utils/params.utils.test.arguments.js');
let stu3Params = getSearchParameters('patient', '3_0_1', custom);

expect(Array.isArray(stu3Params)).toBeTruthy();
expect(stu3Params).toHaveLength(17);
});

test('should override the resource arguments when custom arguments provided as object', () => {
let custom = {
makeResource: (_args, _logger) => {
return {
profile: {
reference: 'https://fhir.hl7.org.uk/STU3/StructureDefinition/CareConnect-Patient-1',
},
searchInclude: [
'*',
'AllergyIntolerance:patient',
'CarePlan:patient',
'Condition:patient',
'DocumentReference:patient',
'Encounter:patient',
'Flag:patient',
'Immunization:patient',
'MedicationStatement:patient',
'Observation:patient',
'Patient:general-practitioner',
'Patient:organization',
'Procedure:patient',
],
searchParam: [
{
documentation: 'The ID of the resource',
name: '_id',
type: 'string',
},
{
documentation: 'A postalCode specified in an address',
name: 'address-postalcode',
type: 'string',
},
{
documentation: "The patient's date of birth",
name: 'birthdate',
type: 'date',
},
{
documentation: 'A value in an email contact',
name: 'email',
type: 'string',
},
{
documentation: 'A portion of the family name of the patient',
name: 'family',
type: 'string',
},
{
documentation: 'Gender of the patient',
name: 'gender',
type: 'string',
},
{
documentation: 'A portion of the given name of the patient',
name: 'given',
type: 'string',
},
{
documentation: 'A patient identifier',
name: 'identifier',
type: 'token',
},
{
documentation:
'A server defined search that may match any of the string fields in the HumanName, including family, give, prefix, suffix, suffix, and/or text',
name: 'name',
type: 'string',
},
{
documentation: 'A value in a phone contact',
name: 'bar',
type: 'string',
},
],
type: 'Patient',
};
},
};
let stu3Params = getSearchParameters('patient', '3_0_1', custom);

expect(Array.isArray(stu3Params)).toBeTruthy();
expect(stu3Params).toHaveLength(17);
});
});
});
22 changes: 21 additions & 1 deletion src/test.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,27 @@ module.exports = {
},
QuestionnaireResponse: {
service: './src/server/service.mock.js',
versions: ['3_0_1'],
versions: ['3_0_1', '4_0_0'],
metadata: {
makeResource: args => {
return {
type: args.key,
profile: {
reference: `http://example.org/fhir/${args.key}.html`,
},
searchParam: [
{
name: 'identifier',
type: 'token',
fhirtype: 'token',
xpath: 'QuestionnaireResponse.identifier',
definition: 'http://hl7.org/fhir/SearchParameter/QuestionnaireResponse-identifier',
description: 'The unique identifier for the questionnaire response',
},
],
};
},
},
},
},
};