Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: added response validator class * fix: aligned remaining security validator syntax to the other classes * feat: added ResponseValidator to the Endpoint class * docs: updated readme with new response validator param * chore: dropped support for node 14 (EOL) in favor of import assertion of json
- Loading branch information
1 parent
091db91
commit 2533fca
Showing
18 changed files
with
1,471 additions
and
263 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
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
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,112 @@ | ||
import debug from 'debug'; | ||
import inspector from 'schema-inspector'; | ||
import _ from 'lodash'; | ||
import ResponseError from './errors/ResponseError.js'; | ||
import { convertOpenAPIToSchemaInspector, parseContentTypesPayloads } from './helpers.js'; | ||
import openApiResponsesSchema from './openapi-validators/responses.json' assert { type: "json" }; | ||
|
||
/** | ||
* Response Validation class (should be initiated once per endpoint upon its setup) | ||
* @module ResponseValidator | ||
*/ | ||
export default class ResponseValidator { | ||
/** | ||
* ResponseValidator | ||
* @property {Object[]} responseDefinition - openapi responses definition | ||
* @property {boolean} shouldEnforce - whether to throw or silently fail on invalid response | ||
* @see {@link https://swagger.io/docs/specification/describing-responses/} - for responseDefinition | ||
*/ | ||
constructor(responseDefinition, shouldEnforce) { | ||
this.debug = debug('openapi:response'); | ||
/** | ||
* OpenAPI definition for responses | ||
* @type object | ||
* @see {@link https://swagger.io/docs/specification/describing-responses/} - for responseDefinition | ||
*/ | ||
this.definition = responseDefinition; | ||
|
||
/** | ||
* whether to throw or silently fail on invalid response | ||
* @type {boolean} | ||
*/ | ||
this.shouldEnforce = shouldEnforce; | ||
|
||
this.debug('set response definition', JSON.stringify(this.definition)); | ||
|
||
this.validateOpenAPISchema(); | ||
this.setupTestSchema(); | ||
} | ||
|
||
/** | ||
* Validate the openapi schema | ||
* @private | ||
*/ | ||
validateOpenAPISchema() { | ||
const validationSchema = { | ||
...openApiResponsesSchema, | ||
exec(schema, value) { | ||
const statusCodes = Object.keys(value); | ||
const invalidStatusCodes = statusCodes.filter((statusCode) => !/\d/.test(statusCode) && statusCode !== 'default'); | ||
if (invalidStatusCodes.length > 0) { | ||
this.report(`invalid status codes: ${invalidStatusCodes.join(', ')}`, 'INVALID_STATUS_CODES'); | ||
} | ||
}, | ||
}; | ||
|
||
const result = inspector.validate(validationSchema, this.definition); | ||
if (!result.valid) { | ||
throw new ResponseError('invalid openapi schema provided', result.error); | ||
} | ||
} | ||
|
||
/** | ||
* Convert openapi input to schema-inspector format | ||
* @private | ||
*/ | ||
setupTestSchema() { | ||
const testProperties = _.reduce(this.definition, (all, { content: contentTypes }, statusCode) => { | ||
const statusContentTypes = _.reduce(contentTypes, (allContentTypes, contentTypeDef, contentType) => { | ||
const data = { ...contentTypeDef.schema }; | ||
convertOpenAPIToSchemaInspector(data); | ||
|
||
return { | ||
...allContentTypes, | ||
[contentType]: data, | ||
}; | ||
}, {}); | ||
|
||
return { | ||
...all, | ||
[statusCode]: parseContentTypesPayloads(statusContentTypes), | ||
}; | ||
}, {}); | ||
|
||
this.testSchema = { | ||
type: 'object', | ||
strict: true, | ||
error: 'status code is illegal', | ||
properties: testProperties, | ||
}; | ||
} | ||
|
||
/** | ||
* test an outgoing response against the current instance of ResponseValidator | ||
* @param {string} statusCode response status code | ||
* @param {string} contentType response content type | ||
* @param {object} body response body | ||
* @return {ResponseError|boolean} | ||
*/ | ||
test(statusCode, contentType, body) { | ||
const result = inspector.validate(this.testSchema, { [statusCode]: { [contentType]: body } }); | ||
|
||
if (!result.valid) { | ||
const error = new ResponseError('invalid response sent from endpoint controller', result.error); | ||
if (this.shouldEnforce) { | ||
throw error; | ||
} | ||
return error; | ||
} | ||
|
||
return true; | ||
} | ||
} |
Oops, something went wrong.