Skip to content

Commit 4d409c7

Browse files
author
Matthew Gramigna
committed
Add more granular resource-level validation and logging
1 parent c9ec376 commit 4d409c7

File tree

4 files changed

+57
-8
lines changed

4 files changed

+57
-8
lines changed

src/client/BaseClient.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,25 @@ class BaseClient {
9696
}
9797
}, Promise.resolve(contextBundle));
9898

99+
// Report detailed validation errors
99100
if (!isValidFHIR(contextBundle)) {
100-
logger.warn(`Extracted bundle is not valid FHIR, the following resources failed validation: ${invalidResourcesFromBundle(contextBundle).join(',')}`);
101+
const invalidResources = invalidResourcesFromBundle(contextBundle);
102+
const baseWarningMessage = 'Extracted bundle is not valid FHIR, the following resources failed validation: ';
103+
104+
const warnMessages = [];
105+
const debugMessages = [];
106+
invalidResources.forEach(({ failureId, errors }) => {
107+
warnMessages.push(`${failureId} at ${errors.map((e) => e.dataPath).join(',')}`);
108+
109+
errors.forEach((e) => {
110+
debugMessages.push(`${failureId} at ${e.dataPath} - ${e.message}`);
111+
});
112+
});
113+
114+
logger.warn(`${baseWarningMessage}${warnMessages.join(', ')}`);
115+
debugMessages.forEach((m) => {
116+
logger.debug(m);
117+
});
101118
}
102119

103120
return { bundle: contextBundle, extractionErrors };

src/helpers/fhirUtils.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const logger = require('./logger');
77

88
const ajv = new Ajv({ logger: false });
99
ajv.addMetaSchema(metaSchema);
10-
const validator = ajv.addSchema(schema, 'FHIR');
10+
const validate = ajv.compile(schema);
1111

1212
// Unit codes and display values fo Vital Signs values
1313
// Code mapping is based on http://hl7.org/fhir/R4/observation-vitalsigns.html
@@ -164,17 +164,29 @@ const logOperationOutcomeInfo = (operationOutcome) => {
164164
};
165165

166166
function isValidFHIR(resource) {
167-
return validator.validate('FHIR', resource);
167+
return validate(resource);
168168
}
169+
170+
function errorFilter(error) {
171+
return error.message !== 'should NOT have additional properties' && error.keyword !== 'oneOf' && error.keyword !== 'const';
172+
}
173+
169174
function invalidResourcesFromBundle(bundle) {
170175
// Bundle is assumed to have all resources in bundle.entry[x].resource
171176
const failingResources = [];
172177
bundle.entry.forEach((e) => {
173178
const { resource } = e;
174179
const { id, resourceType } = resource;
175-
if (!validator.validate('FHIR', resource)) {
180+
181+
// Validate only this resource to get more scoped errors
182+
const subSchema = schema;
183+
subSchema.oneOf = [{ $ref: `#/definitions/${resourceType}` }];
184+
185+
const validateResource = ajv.compile(subSchema);
186+
187+
if (!validateResource(resource)) {
176188
const failureId = `${resourceType}-${id}`;
177-
failingResources.push(failureId);
189+
failingResources.push({ failureId, errors: validateResource.errors.filter(errorFilter) });
178190
}
179191
});
180192
return failingResources;

test/client/BaseClient.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ describe('BaseClient', () => {
7676
});
7777
it('should iterate over all extractors gets, aggregating resultant entries in a bundle', async () => {
7878
const extractorClassesWithEntryGets = [
79-
class Ext1 { get() { return { entry: [{ resource: 'here' }] }; }},
80-
class Ext2 { get() { return { entry: [{ resource: 'alsoHere' }] }; }},
79+
class Ext1 { get() { return { entry: [{ resource: { resourceType: 'Patient' } }] }; }},
80+
class Ext2 { get() { return { entry: [{ resource: { resourceType: 'Patient' } }] }; }},
8181
class Ext3 { get() { throw new Error('Error'); }},
8282
];
8383
engine.registerExtractors(...extractorClassesWithEntryGets);

test/helpers/fhirUtils.test.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,26 @@ describe('invalidResourcesFromBundle', () => {
160160
invalidBundle.entry[0].resource = invalidResource;
161161
// This is dependent on implementation details intrinsic to invalidResourcesFromBundle
162162
const invalidResourceId = `${invalidResource.resourceType}-${invalidResource.id}`;
163-
expect(invalidResourcesFromBundle(invalidBundle)).toEqual([invalidResourceId]);
163+
expect(invalidResourcesFromBundle(invalidBundle)).toEqual([
164+
{
165+
failureId: invalidResourceId,
166+
errors: [
167+
{
168+
keyword: 'enum',
169+
dataPath: '.gender',
170+
schemaPath: '#/properties/gender/enum',
171+
params: {
172+
allowedValues: [
173+
'male',
174+
'female',
175+
'other',
176+
'unknown',
177+
],
178+
},
179+
message: 'should be equal to one of the allowed values',
180+
},
181+
],
182+
},
183+
]);
164184
});
165185
});

0 commit comments

Comments
 (0)