support for defined schema dialect property on validate transaction

This commit is contained in:
Luis Tejeda
2022-02-03 12:24:50 -06:00
parent 8c2cff19e7
commit e2d324aa4a
4 changed files with 78 additions and 30 deletions

View File

@@ -30,11 +30,22 @@ function getLocalDraft(schema) {
/** /**
* Gets the correct validator according to the draft * Gets the correct validator according to the draft
* *
* @param {string} draft - the draft identifier * @param {string} draftToUse - the draft to use in validation
* @returns {string} the id * @returns {string} the draft identifier
*/ */
function getAjvValidator(draft) { function getAjvValidator(draftToUse) {
return draft === specialDraft ? validateSchemaAJVDraft04 : validateSchemaAJV; return draftToUse === specialDraft ? validateSchemaAJVDraft04 : validateSchemaAJV;
}
/**
* Defines the draft to use in validation
*
* @param {string} localDraft - the draft from the schema object
* @param {string} jsonSchemaDialect - the draft from the OAS object
* @returns {string} the draft to use
*/
function getDraftToUse(localDraft, jsonSchemaDialect) {
return localDraft ? localDraft : jsonSchemaDialect;
} }
/** /**
@@ -44,13 +55,15 @@ function getAjvValidator(draft) {
* @param {*} schema - schema to validate * @param {*} schema - schema to validate
* @param {*} valueToUse - value to validate schema against * @param {*} valueToUse - value to validate schema against
* @param {*} options - a standard list of options that's globally passed around. Check options.js for more. * @param {*} options - a standard list of options that's globally passed around. Check options.js for more.
* @param {string} jsonSchemaDialect - the defined schema in the OAS object
* @returns {*} - Found Validation Errors * @returns {*} - Found Validation Errors
*/ */
function validateSchema (schema, valueToUse, options = {}) { function validateSchema (schema, valueToUse, options = {}, jsonSchemaDialect) {
let validate, let validate,
compoundResult, compoundResult,
filteredValidationError, filteredValidationError,
draftToUse = getLocalDraft(schema); localDraft = getLocalDraft(schema),
draftToUse = getDraftToUse(localDraft, jsonSchemaDialect);
const validator = getAjvValidator(draftToUse); const validator = getAjvValidator(draftToUse);
compoundResult = validator(schema, valueToUse, draftToUse); compoundResult = validator(schema, valueToUse, draftToUse);
if (compoundResult.filteredValidationError) { if (compoundResult.filteredValidationError) {
@@ -96,5 +109,6 @@ function validateSchema (schema, valueToUse, options = {}) {
module.exports = { module.exports = {
validateSchema, validateSchema,
getLocalDraft, getLocalDraft,
getAjvValidator getAjvValidator,
getDraftToUse
}; };

View File

@@ -3037,11 +3037,12 @@ module.exports = {
* @param {Object} components - Components in the spec that the schema might refer to * @param {Object} components - Components in the spec that the schema might refer to
* @param {Object} options - Global options * @param {Object} options - Global options
* @param {Object} schemaCache object storing schemaFaker and schmeResolution caches * @param {Object} schemaCache object storing schemaFaker and schmeResolution caches
* @param {string} jsonSchemaDialect The schema dialect defined in the OAS object
* @param {Function} callback - For return * @param {Function} callback - For return
* @returns {Array} array of mismatches * @returns {Array} array of mismatches
*/ */
checkValueAgainstSchema: function (property, jsonPathPrefix, txnParamName, value, schemaPathPrefix, openApiSchemaObj, checkValueAgainstSchema: function (property, jsonPathPrefix, txnParamName, value, schemaPathPrefix, openApiSchemaObj,
parameterSourceOption, components, options, schemaCache, callback) { parameterSourceOption, components, options, schemaCache, jsonSchemaDialect, callback) {
let mismatches = [], let mismatches = [],
jsonValue, jsonValue,
@@ -3075,7 +3076,7 @@ module.exports = {
setTimeout(() => { setTimeout(() => {
this.checkValueAgainstSchema(property, jsonPathPrefix, txnParamName, value, this.checkValueAgainstSchema(property, jsonPathPrefix, txnParamName, value,
`${schemaPathPrefix}.${schema.oneOf ? 'oneOf' : 'anyOf'}[${_.findIndex(compositeSchema, elementSchema)}]`, `${schemaPathPrefix}.${schema.oneOf ? 'oneOf' : 'anyOf'}[${_.findIndex(compositeSchema, elementSchema)}]`,
elementSchema, parameterSourceOption, components, options, schemaCache, cb); elementSchema, parameterSourceOption, components, options, schemaCache, jsonSchemaDialect, cb);
}, 0); }, 0);
}, (err, results) => { }, (err, results) => {
let sortedResults; let sortedResults;
@@ -3167,7 +3168,7 @@ module.exports = {
// only do AJV if type is array or object // only do AJV if type is array or object
// simpler cases are handled by a type check // simpler cases are handled by a type check
if (isCorrectType && needJsonMatching) { if (isCorrectType && needJsonMatching) {
let filteredValidationError = validateSchema(schema, valueToUse, options); let filteredValidationError = validateSchema(schema, valueToUse, options, jsonSchemaDialect);
if (!_.isEmpty(filteredValidationError)) { if (!_.isEmpty(filteredValidationError)) {
let mismatchObj, let mismatchObj,
@@ -3312,6 +3313,7 @@ module.exports = {
* @param {*} components the components + paths from the OAS spec that need to be used to resolve $refs * @param {*} components the components + paths from the OAS spec that need to be used to resolve $refs
* @param {*} options OAS options * @param {*} options OAS options
* @param {*} schemaCache object storing schemaFaker and schmeResolution caches * @param {*} schemaCache object storing schemaFaker and schmeResolution caches
* @param {string} jsonSchemaDialect Defined schema dialect at the OAS object
* @param {*} callback Callback * @param {*} callback Callback
* @returns {array} mismatches (in the callback) * @returns {array} mismatches (in the callback)
*/ */
@@ -3322,6 +3324,7 @@ module.exports = {
components, components,
options, options,
schemaCache, schemaCache,
jsonSchemaDialect,
callback) { callback) {
// schema path should have all parameters needed // schema path should have all parameters needed
@@ -3390,7 +3393,7 @@ module.exports = {
schemaPathVar.pathPrefix + '[?(@.name==\'' + schemaPathVar.name + '\')]', schemaPathVar.pathPrefix + '[?(@.name==\'' + schemaPathVar.name + '\')]',
schemaPathVar.schema, schemaPathVar.schema,
PARAMETER_SOURCE.REQUEST, PARAMETER_SOURCE.REQUEST,
components, options, schemaCache, cb); components, options, schemaCache, jsonSchemaDialect, cb);
}, 0); }, 0);
}, (err, res) => { }, (err, res) => {
let mismatches = [], let mismatches = [],
@@ -3540,7 +3543,7 @@ module.exports = {
}, },
checkQueryParams(requestUrl, transactionPathPrefix, schemaPath, components, options, checkQueryParams(requestUrl, transactionPathPrefix, schemaPath, components, options,
schemaCache, callback) { schemaCache, jsonSchemaDialect, callback) {
let parsedUrl = require('url').parse(requestUrl), let parsedUrl = require('url').parse(requestUrl),
schemaParams = _.filter(schemaPath.parameters, (param) => { return param.in === 'query'; }), schemaParams = _.filter(schemaPath.parameters, (param) => { return param.in === 'query'; }),
requestQueryArray = [], requestQueryArray = [],
@@ -3682,7 +3685,7 @@ module.exports = {
schemaParam.pathPrefix + '[?(@.name==\'' + schemaParam.name + '\')]', schemaParam.pathPrefix + '[?(@.name==\'' + schemaParam.name + '\')]',
schemaParam.schema, schemaParam.schema,
PARAMETER_SOURCE.REQUEST, PARAMETER_SOURCE.REQUEST,
components, options, schemaCache, cb components, options, schemaCache, jsonSchemaDialect, cb
); );
}, 0); }, 0);
}, (err, res) => { }, (err, res) => {
@@ -3846,7 +3849,7 @@ module.exports = {
}, },
checkRequestHeaders: function (headers, transactionPathPrefix, schemaPathPrefix, schemaPath, checkRequestHeaders: function (headers, transactionPathPrefix, schemaPathPrefix, schemaPath,
components, options, schemaCache, callback) { components, options, schemaCache, jsonSchemaDialect, callback) {
let schemaHeaders = _.filter(schemaPath.parameters, (param) => { return param.in === 'header'; }), let schemaHeaders = _.filter(schemaPath.parameters, (param) => { return param.in === 'header'; }),
// key name of headers which are added by security schemes // key name of headers which are added by security schemes
securityHeaders = _.map(this.getSecurityParams(_.get(components, 'components'), 'header'), 'name'), securityHeaders = _.map(this.getSecurityParams(_.get(components, 'components'), 'header'), 'name'),
@@ -3904,7 +3907,7 @@ module.exports = {
schemaHeader.pathPrefix + '[?(@.name==\'' + schemaHeader.name + '\')]', schemaHeader.pathPrefix + '[?(@.name==\'' + schemaHeader.name + '\')]',
schemaHeader.schema, schemaHeader.schema,
PARAMETER_SOURCE.REQUEST, PARAMETER_SOURCE.REQUEST,
components, options, schemaCache, cb components, options, schemaCache, jsonSchemaDialect, cb
); );
}, 0); }, 0);
}, (err, res) => { }, (err, res) => {
@@ -3962,7 +3965,7 @@ module.exports = {
}, },
checkResponseHeaders: function (schemaResponse, headers, transactionPathPrefix, schemaPathPrefix, checkResponseHeaders: function (schemaResponse, headers, transactionPathPrefix, schemaPathPrefix,
components, options, schemaCache, callback) { components, options, schemaCache, jsonSchemaDialect, callback) {
// 0. Need to find relevant response from schemaPath.responses // 0. Need to find relevant response from schemaPath.responses
let schemaHeaders, let schemaHeaders,
// filter out headers which need explicit handling according to schema (other than parameters object) // filter out headers which need explicit handling according to schema (other than parameters object)
@@ -4018,7 +4021,7 @@ module.exports = {
schemaPathPrefix + '.headers[' + pHeader.key + ']', schemaPathPrefix + '.headers[' + pHeader.key + ']',
schemaHeader.schema, schemaHeader.schema,
PARAMETER_SOURCE.RESPONSE, PARAMETER_SOURCE.RESPONSE,
components, options, schemaCache, cb components, options, schemaCache, jsonSchemaDialect, cb
); );
}, 0); }, 0);
}, (err, res) => { }, (err, res) => {
@@ -4071,7 +4074,7 @@ module.exports = {
// Only application/json and application/x-www-form-urlencoded is validated for now // Only application/json and application/x-www-form-urlencoded is validated for now
checkRequestBody: function (requestBody, transactionPathPrefix, schemaPathPrefix, schemaPath, checkRequestBody: function (requestBody, transactionPathPrefix, schemaPathPrefix, schemaPath,
components, options, schemaCache, callback) { components, options, schemaCache, jsonSchemaDialect, callback) {
// check for body modes // check for body modes
let jsonSchemaBody, let jsonSchemaBody,
jsonContentType, jsonContentType,
@@ -4102,6 +4105,7 @@ module.exports = {
components, components,
_.extend({}, options, { shortValidationErrors: true }), _.extend({}, options, { shortValidationErrors: true }),
schemaCache, schemaCache,
jsonSchemaDialect,
callback callback
); );
}, 0); }, 0);
@@ -4220,7 +4224,7 @@ module.exports = {
pathPrefix + '.properties[' + schemaParam.name + ']', pathPrefix + '.properties[' + schemaParam.name + ']',
schemaParam.schema, schemaParam.schema,
PARAMETER_SOURCE.REQUEST, PARAMETER_SOURCE.REQUEST,
components, options, schemaCache, cb components, options, schemaCache, jsonSchemaDialect, cb
); );
}, 0); }, 0);
}, (err, res) => { }, (err, res) => {
@@ -4286,7 +4290,7 @@ module.exports = {
}, },
checkResponseBody: function (schemaResponse, body, transactionPathPrefix, schemaPathPrefix, checkResponseBody: function (schemaResponse, body, transactionPathPrefix, schemaPathPrefix,
components, options, schemaCache, callback) { components, options, schemaCache, jsonSchemaDialect, callback) {
let schemaContent, let schemaContent,
jsonContentType, jsonContentType,
mismatchProperty = 'RESPONSE_BODY'; mismatchProperty = 'RESPONSE_BODY';
@@ -4324,13 +4328,14 @@ module.exports = {
components, components,
_.extend({}, options, { shortValidationErrors: true }), _.extend({}, options, { shortValidationErrors: true }),
schemaCache, schemaCache,
jsonSchemaDialect,
callback callback
); );
}, 0); }, 0);
}, },
checkResponses: function (responses, transactionPathPrefix, schemaPathPrefix, schemaPath, checkResponses: function (responses, transactionPathPrefix, schemaPathPrefix, schemaPath,
components, options, schemaCache, cb) { components, options, schemaCache, jsonSchemaDialect, cb) {
// responses is an array of repsonses recd. for one Postman request // responses is an array of repsonses recd. for one Postman request
// we've already determined the schemaPath against which all responses need to be validated // we've already determined the schemaPath against which all responses need to be validated
// loop through all responses // loop through all responses
@@ -4369,13 +4374,15 @@ module.exports = {
headers: (cb) => { headers: (cb) => {
this.checkResponseHeaders(thisSchemaResponse, response.header, this.checkResponseHeaders(thisSchemaResponse, response.header,
transactionPathPrefix + '[' + response.id + '].header', transactionPathPrefix + '[' + response.id + '].header',
schemaPathPrefix + '.responses.' + responsePathPrefix, components, options, schemaCache, cb); schemaPathPrefix + '.responses.' + responsePathPrefix,
components, options, schemaCache, jsonSchemaDialect, cb);
}, },
body: (cb) => { body: (cb) => {
// assume it's JSON at this point // assume it's JSON at this point
this.checkResponseBody(thisSchemaResponse, response.body, this.checkResponseBody(thisSchemaResponse, response.body,
transactionPathPrefix + '[' + response.id + '].body', transactionPathPrefix + '[' + response.id + '].body',
schemaPathPrefix + '.responses.' + responsePathPrefix, components, options, schemaCache, cb); schemaPathPrefix + '.responses.' + responsePathPrefix,
components, options, schemaCache, jsonSchemaDialect, cb);
} }
}, (err, result) => { }, (err, result) => {
return responseCallback(null, { return responseCallback(null, {

View File

@@ -378,7 +378,8 @@ class SchemaPack {
schemaResolutionCache: this.schemaResolutionCache, schemaResolutionCache: this.schemaResolutionCache,
schemaFakerCache: this.schemaFakerCache schemaFakerCache: this.schemaFakerCache
}, },
matchedEndpoints = []; matchedEndpoints = [],
jsonSchemaDialect = schema.jsonSchemaDialect;
// Only change the stack limit if the optimizeConversion option is true // Only change the stack limit if the optimizeConversion option is true
if (options.optimizeConversion) { if (options.optimizeConversion) {
@@ -498,23 +499,23 @@ class SchemaPack {
}, },
path: function(cb) { path: function(cb) {
schemaUtils.checkPathVariables(matchedPath.pathVariables, '$.request.url.variable', matchedPath.path, schemaUtils.checkPathVariables(matchedPath.pathVariables, '$.request.url.variable', matchedPath.path,
componentsAndPaths, options, schemaCache, cb); componentsAndPaths, options, schemaCache, jsonSchemaDialect, cb);
}, },
queryparams: function(cb) { queryparams: function(cb) {
schemaUtils.checkQueryParams(requestUrl, '$.request.url.query', matchedPath.path, schemaUtils.checkQueryParams(requestUrl, '$.request.url.query', matchedPath.path,
componentsAndPaths, options, schemaCache, cb); componentsAndPaths, options, schemaCache, jsonSchemaDialect, cb);
}, },
headers: function(cb) { headers: function(cb) {
schemaUtils.checkRequestHeaders(transaction.request.header, '$.request.header', matchedPath.jsonPath, schemaUtils.checkRequestHeaders(transaction.request.header, '$.request.header', matchedPath.jsonPath,
matchedPath.path, componentsAndPaths, options, schemaCache, cb); matchedPath.path, componentsAndPaths, options, schemaCache, jsonSchemaDialect, cb);
}, },
requestBody: function(cb) { requestBody: function(cb) {
schemaUtils.checkRequestBody(transaction.request.body, '$.request.body', matchedPath.jsonPath, schemaUtils.checkRequestBody(transaction.request.body, '$.request.body', matchedPath.jsonPath,
matchedPath.path, componentsAndPaths, options, schemaCache, cb); matchedPath.path, componentsAndPaths, options, schemaCache, jsonSchemaDialect, cb);
}, },
responses: function (cb) { responses: function (cb) {
schemaUtils.checkResponses(transaction.response, '$.responses', matchedPath.jsonPath, schemaUtils.checkResponses(transaction.response, '$.responses', matchedPath.jsonPath,
matchedPath.path, componentsAndPaths, options, schemaCache, cb); matchedPath.path, componentsAndPaths, options, schemaCache, jsonSchemaDialect, cb);
} }
}, (err, result) => { }, (err, result) => {
let allMismatches = _.concat(result.metadata, result.queryparams, result.headers, result.path, let allMismatches = _.concat(result.metadata, result.queryparams, result.headers, result.path,

View File

@@ -1,4 +1,7 @@
const { getLocalDraft, getAjvValidator, validateSchema } = require('../../lib/ajValidation/ajvValidation'), const { getLocalDraft,
getAjvValidator,
validateSchema,
getDraftToUse } = require('../../lib/ajValidation/ajvValidation'),
{ validateSchemaAJVDraft04 } = require('../../lib/ajValidation/ajvValidatorDraft04'), { validateSchemaAJVDraft04 } = require('../../lib/ajValidation/ajvValidatorDraft04'),
expect = require('chai').expect; expect = require('chai').expect;
@@ -300,3 +303,26 @@ describe('validateSchema', function () {
expect(result.filteredValidationError).to.be.undefined; expect(result.filteredValidationError).to.be.undefined;
}); });
}); });
describe('getDraftToUse', function() {
it('should return the ajv draft 04 when $schema undefined and jsonSchemaDialect is the 04', function() {
let draftToUse = getDraftToUse(undefined, 'http://json-schema.org/draft-04/schema#');
expect(draftToUse).to.equal('http://json-schema.org/draft-04/schema#');
});
it('should return the ajv draft 06 when $schema is 06 and jsonSchemaDialect is the 04', function() {
let draftToUse = getDraftToUse('http://json-schema.org/draft-06/schema#',
'http://json-schema.org/draft-04/schema#');
expect(draftToUse).to.equal('http://json-schema.org/draft-06/schema#');
});
it('should return the ajv draft 06 when $schema is 06 and jsonSchemaDialect is undefined', function() {
let draftToUse = getDraftToUse('http://json-schema.org/draft-06/schema#', undefined);
expect(draftToUse).to.equal('http://json-schema.org/draft-06/schema#');
});
it('should return undefined when $schema and jsonSchemaDialect are undefined', function() {
let draftToUse = getDraftToUse(undefined, undefined);
expect(draftToUse).to.equal(undefined);
});
});