Added support for validation of deepObject styled parameters

This commit is contained in:
Vishal Shingala
2021-07-15 23:25:58 +05:30
parent aeeae466e8
commit 13ebc2599e
6 changed files with 457 additions and 33 deletions

View File

@@ -2835,6 +2835,50 @@ module.exports = {
return { type, subtype };
},
/**
* Extracts all child parameters from explodable param
*
* @param {*} schema - Corresponding schema object of parent parameter to be devided into child params
* @param {*} paramKey - Parameter name of parent param object
* @param {*} metaInfo - meta information of param (i.e. required)
* @returns {Array} - Extracted child parameters
*/
extractChildParamSchema: function (schema, paramKey, metaInfo) {
let childParamSchemas = [];
_.forEach(_.get(schema, 'properties', {}), (value, key) => {
if (_.get(value, 'type') === 'object') {
childParamSchemas = _.concat(childParamSchemas, this.extractChildParamSchema(value,
`${paramKey}[${key}]`, metaInfo));
}
else {
let required = _.get(metaInfo, 'required') || false,
pathPrefix = _.get(metaInfo, 'pathPrefix');
childParamSchemas.push({
name: `${paramKey}[${key}]`,
schema: value,
required,
isResolvedParam: true,
pathPrefix
});
}
});
return childParamSchemas;
},
/**
* Tests whether given parameter is of complex array type from param key
*
* @param {*} paramKey - Parmaeter key that is to be tested
* @returns {Boolean} - result
*/
isParamComplexArray: function (paramKey) {
// this checks if parameter key numbered element (i.e. itemArray[1] is complex array param)
let regex = /\[[\d]+\]/gm;
return regex.test(paramKey);
},
/**
* Finds valid JSON media type object from content object
*
@@ -3393,22 +3437,36 @@ module.exports = {
isPropSeparable = _.includes(['form', 'deepObject'], style);
if (isPropSeparable && paramSchema.type === 'array' && explode) {
// add schema of items and instead array
resolvedSchemaParams.push(_.assign({}, param, {
schema: _.get(paramSchema, 'items'),
isResolvedParam: true
}));
/**
* avoid validation of complex array type param as OAS doesn't define serialisation
* of Array with deepObject style
*/
if (!_.includes(['array', 'object'], _.get(paramSchema, 'items.type'))) {
// add schema of corresponding items instead array
resolvedSchemaParams.push(_.assign({}, param, {
schema: _.get(paramSchema, 'items'),
isResolvedParam: true
}));
}
}
else if (isPropSeparable && paramSchema.type === 'object' && explode) {
// add schema of all properties instead entire object
_.forEach(_.get(paramSchema, 'properties', {}), (propSchema, propName) => {
resolvedSchemaParams.push({
name: propName,
schema: propSchema,
isResolvedParam: true,
pathPrefix
// resolve all child params of parent param with deepObject style
if (style === 'deepObject') {
resolvedSchemaParams = _.concat(resolvedSchemaParams, this.extractChildParamSchema(paramSchema,
param.name, { required: _.get(param, 'required'), pathPrefix }));
}
else {
// add schema of all properties instead entire object
_.forEach(_.get(paramSchema, 'properties', {}), (propSchema, propName) => {
resolvedSchemaParams.push({
name: propName,
schema: propSchema,
required: _.get(param, 'required') || false,
isResolvedParam: true,
pathPrefix
});
});
});
}
}
else {
resolvedSchemaParams.push(param);
@@ -3423,7 +3481,10 @@ module.exports = {
const schemaParam = _.find(resolvedSchemaParams, (param) => { return param.name === pQuery.key; });
if (!schemaParam) {
// no schema param found
// skip validation of complex array params
if (this.isParamComplexArray(pQuery.key)) {
return cb(null, mismatches);
}
if (options.showMissingInSchemaErrors) {
mismatches.push({
property: mismatchProperty,
@@ -3464,7 +3525,7 @@ module.exports = {
let mismatches = [],
mismatchObj;
_.each(_.filter(schemaParams, (q) => { return q.required; }), (qp) => {
_.each(_.filter(resolvedSchemaParams, (q) => { return q.required; }), (qp) => {
if (!_.find(requestQueryParams, (param) => { return param.key === qp.name; })) {
// assign parameter example(s) as schema examples;
@@ -3831,7 +3892,7 @@ module.exports = {
});
},
// Only application/json is validated for now
// Only application/json and application/x-www-form-urlencoded is validated for now
checkRequestBody: function (requestBody, transactionPathPrefix, schemaPathPrefix, schemaPath,
components, options, schemaCache, callback) {
// check for body modes
@@ -3852,7 +3913,6 @@ module.exports = {
jsonContentType = this.getJsonContentType(_.get(schemaPath, 'requestBody.content', {}));
jsonSchemaBody = _.get(schemaPath, ['requestBody', 'content', jsonContentType, 'schema']);
// only raw for now
if (requestBody && requestBody.mode === 'raw' && jsonSchemaBody) {
setTimeout(() => {
return this.checkValueAgainstSchema(mismatchProperty,
@@ -3902,21 +3962,35 @@ module.exports = {
isPropSeparable = _.includes(['form', 'deepObject'], pSerialisationInfo.style);
if (isPropSeparable && propSchema.type === 'array' && pSerialisationInfo.explode) {
// add schema of items and instead array
resolvedSchemaParams.push(_.assign({}, resolvedProp, {
schema: _.get(propSchema, 'items'),
isResolvedParam: true
}));
/**
* avoid validation of complex array type param as OAS doesn't define serialisation
* of Array with deepObject style
*/
if (!_.includes(['array', 'object'], _.get(propSchema, 'items.type'))) {
// add schema of corresponding items instead array
resolvedSchemaParams.push(_.assign({}, resolvedProp, {
schema: _.get(propSchema, 'items'),
isResolvedParam: true
}));
}
}
else if (isPropSeparable && propSchema.type === 'object' && pSerialisationInfo.explode) {
// add schema of all properties instead entire object
_.forEach(_.get(propSchema, 'properties', {}), (value, key) => {
resolvedSchemaParams.push({
name: key,
schema: value,
isResolvedParam: true
// resolve all child params of parent param with deepObject style
if (pSerialisationInfo.style === 'deepObject') {
resolvedSchemaParams = _.concat(resolvedSchemaParams, this.extractChildParamSchema(propSchema,
propName, { required: resolvedProp.required || false }));
}
else {
// add schema of all properties instead entire object
_.forEach(_.get(propSchema, 'properties', {}), (value, key) => {
resolvedSchemaParams.push({
name: key,
schema: value,
isResolvedParam: true,
required: resolvedProp.required || false
});
});
});
}
}
else {
resolvedSchemaParams.push(resolvedProp);
@@ -3931,7 +4005,10 @@ module.exports = {
const schemaParam = _.find(resolvedSchemaParams, (param) => { return param.name === uParam.key; });
if (!schemaParam) {
// no schema param found
// skip validation of complex array params
if (this.isParamComplexArray(uParam.key)) {
return cb(null, mismatches);
}
if (options.showMissingInSchemaErrors) {
mismatches.push({
property: mismatchProperty,
@@ -3995,7 +4072,7 @@ module.exports = {
});
_.each(resolvedSchemaParams, (uParam) => {
// report mismatches only for reuired properties
// report mismatches only for required properties
if (!_.find(requestBody.urlencoded, (param) => { return param.key === uParam.name; }) && uParam.required) {
mismatchObj = {
property: mismatchProperty,

View File

@@ -0,0 +1,160 @@
{
"item": [
{
"id": "9861bc73-aafe-4c44-8c91-f42e4d820ae6",
"name": "pet",
"description": {
"content": "",
"type": "text/plain"
},
"item": [
{
"id": "23d37c51-7a63-412e-b15a-10c336d49615",
"name": "Updates a pet in the store with form data",
"request": {
"name": "Updates a pet in the store with form data",
"description": {},
"url": {
"path": [
"pets"
],
"host": [
"{{baseUrl}}"
],
"query": [
{
"disabled": false,
"key": "user[id]",
"value": "notAnInteger",
"description": "(Required) info about user"
},
{
"disabled": false,
"key": "user[name]",
"value": "John Johanson",
"description": "(Required) info about user"
},
{
"disabled": false,
"key": "user[address][city]",
"value": "Delhi",
"description": "(Required) info about user"
},
{
"disabled": false,
"key": "propArrayComplex[0][prop1ArrayComp]",
"value": "notAnInteger",
"description": "(Required) deepObject with complex array structure"
},
{
"disabled": false,
"key": "propArrayComplex[0][prop2ArrayComp]",
"value": "qui anim",
"description": "(Required) deepObject with complex array structure"
},
{
"disabled": false,
"key": "propArrayComplex[1][prop1ArrayComp]",
"value": "87313126",
"description": "(Required) deepObject with complex array structure"
},
{
"disabled": false,
"key": "propArrayComplex[1][prop2ArrayComp]",
"value": "reprehenderit",
"description": "(Required) deepObject with complex array structure"
}
],
"variable": []
},
"method": "GET",
"auth": null
},
"response": [
{
"id": "76137f7a-7d78-4f8b-837f-d678124c0b8e",
"name": "Pet updated.",
"originalRequest": {
"url": {
"path": [
"pets"
],
"host": [
"{{baseUrl}}"
],
"query": [
{
"key": "user[id]",
"value": "123"
},
{
"key": "user[name]",
"value": "John Johanson"
},
{
"key": "user[address][city]",
"value": "Delhi"
},
{
"key": "user[address][country]",
"value": "India"
},
{
"key": "propArrayComplex[0][prop1ArrayComp]",
"value": "70013937"
},
{
"key": "propArrayComplex[0][prop2ArrayComp]",
"value": "pariatur sit consectetur minim"
},
{
"key": "propArrayComplex[1][prop1ArrayComp]",
"value": "-77852940"
},
{
"key": "propArrayComplex[1][prop2ArrayComp]",
"value": "amet"
}
],
"variable": []
},
"method": "GET",
"body": {}
},
"status": "OK",
"code": 200,
"header": [
{
"key": "Content-Type",
"value": "text/plain"
}
],
"body": "",
"cookie": [],
"_postman_previewlanguage": "text"
}
],
"event": []
}
],
"event": []
}
],
"event": [],
"variable": [
{
"type": "string",
"value": "http://petstore.swagger.io/v1",
"key": "baseUrl"
}
],
"info": {
"_postman_id": "c3eff23a-fbd9-40b7-9029-7e9699d9bb1b",
"name": "Swagger Petstore",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"description": {
"content": "",
"type": "text/plain"
}
}
}

View File

@@ -0,0 +1,56 @@
openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
license:
name: MIT
servers:
- url: http://petstore.swagger.io/v1
paths:
/pets:
get:
tags:
- pet
summary: Updates a pet in the store with form data
operationId: updatePetWithForm
parameters:
- name: user
in: query
description: info about user
required: true
style: deepObject
schema:
type: object
properties:
id:
type: integer
example: 123
name:
type: string
example: John Johanson
address:
type: object
properties:
city:
type: string
example: Delhi
country:
type: string
example: India
- name: propArrayComplex
in: query
description: deepObject with complex array structure
required: true
style: deepObject
schema:
type: array
items:
type: object
properties:
prop1ArrayComp:
type: integer
prop2ArrayComp:
type: string
responses:
'200':
description: Pet updated.

View File

@@ -66,6 +66,46 @@
{
"key": "propSimple",
"value": "123"
},
{
"disabled": false,
"key": "propDeepObject[id]",
"value": "123"
},
{
"disabled": false,
"key": "propDeepObject[name]",
"value": "John Johanson"
},
{
"disabled": false,
"key": "propDeepObject[address][city]",
"value": "123"
},
{
"disabled": false,
"key": "propDeepObject[address][country]",
"value": "India"
},
{
"disabled": false,
"key": "propArrayComplex[0][prop1ArrayComp]",
"value": "notAnInteger"
},
{
"disabled": false,
"key": "propArrayComplex[0][prop2ArrayComp]",
"value": "irure labore Lorem consequat l"
},
{
"disabled": false,
"key": "propArrayComplex[1][prop1ArrayComp]",
"value": "-14216671"
},
{
"disabled": false,
"key": "propArrayComplex[1][prop2ArrayComp]",
"value": "officia"
}
]
}

View File

@@ -54,6 +54,33 @@ paths:
propSimple:
type: integer
example: 123
propDeepObject:
type: object
properties:
id:
type: integer
example: 123
name:
type: string
example: John Johanson
address:
type: object
properties:
city:
type: string
example: Delhi
country:
type: string
example: India
propArrayComplex:
type: array
items:
type: object
properties:
prop1ArrayComp:
type: integer
prop2ArrayComp:
type: string
required:
- status
encoding:
@@ -63,6 +90,12 @@ paths:
propObjectNonExplodable:
style: form
explode: false
propDeepObject:
style: deepObject
explode: true
propArrayComplex:
style: deepObject
explode: true
responses:
'200':
description: Pet updated.

View File

@@ -640,6 +640,51 @@ describe('VALIDATE FUNCTION TESTS ', function () {
done();
});
});
it('Should be able to validate schema with deepObject style query params against corresponding ' +
'transactions', function (done) {
let queryParamDeepObjectSpec = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
'/queryParamDeepObjectSpec.yaml'), 'utf-8'),
queryParamDeepObjectCollection = fs.readFileSync(path.join(__dirname, VALIDATION_DATA_FOLDER_PATH +
'/queryParamDeepObjectCollection.json'), 'utf-8'),
resultObj,
historyRequest = [],
schemaPack = new Converter.SchemaPack({ type: 'string', data: queryParamDeepObjectSpec },
{ suggestAvailableFixes: true, showMissingInSchemaErrors: true });
getAllTransactions(JSON.parse(queryParamDeepObjectCollection), historyRequest);
schemaPack.validateTransaction(historyRequest, (err, result) => {
expect(err).to.be.null;
expect(result).to.be.an('object');
resultObj = result.requests[historyRequest[0].id].endpoints[0];
expect(resultObj.mismatches).to.have.lengthOf(2);
/**
* no mismatches should be found for complex array type params as validation is skipped for them,
* even though corresponding value is of incorrect type
*/
_.forEach(resultObj.mismatches, (mismatch) => {
expect(mismatch.suggestedFix.key).to.not.eql('propArrayComplex[0][prop1ArrayComp]');
});
// for deepObject param "user", child param "user[id]" is of incorrect type
expect(resultObj.mismatches[0].reasonCode).to.eql('INVALID_TYPE');
expect(resultObj.mismatches[0].transactionJsonPath).to.eql('$.request.url.query[0].value');
expect(resultObj.mismatches[0].suggestedFix.actualValue).to.eql('notAnInteger');
expect(resultObj.mismatches[0].suggestedFix.suggestedValue).to.eql(123);
// for deepObject param "user", child param "user[address][country]" is missing in transaction
expect(resultObj.mismatches[1].reasonCode).to.eql('MISSING_IN_REQUEST');
expect(resultObj.mismatches[1].suggestedFix.key).to.eql('user[address][country]');
expect(resultObj.mismatches[1].suggestedFix.actualValue).to.be.null;
expect(resultObj.mismatches[1].suggestedFix.suggestedValue).to.eql({
key: 'user[address][country]',
value: 'India'
});
done();
});
});
});
describe('getPostmanUrlSuffixSchemaScore function', function () {
@@ -668,7 +713,7 @@ describe('VALIDATE FUNCTION TESTS ', function () {
resultObj,
historyRequest = [],
schemaPack = new Converter.SchemaPack({ type: 'string', data: urlencodedBodySpec },
{ suggestAvailableFixes: true });
{ suggestAvailableFixes: true, showMissingInSchemaErrors: true });
getAllTransactions(JSON.parse(urlencodedBodyCollection), historyRequest);
@@ -676,7 +721,15 @@ describe('VALIDATE FUNCTION TESTS ', function () {
expect(err).to.be.null;
expect(result).to.be.an('object');
resultObj = result.requests[historyRequest[0].id].endpoints[0];
expect(resultObj.mismatches).to.have.lengthOf(3);
expect(resultObj.mismatches).to.have.lengthOf(4);
/**
* no mismatches should be found for complex array type params as validation is skipped for them,
* even though corresponding value is of incorrect type
*/
_.forEach(resultObj.mismatches, (mismatch) => {
expect(mismatch.suggestedFix.key).to.not.eql('propArrayComplex[0][prop1ArrayComp]');
});
// for explodable property of type object named "propObjectExplodable",
// second property named "prop2" is incorrect, while property "prop1" is correct
@@ -693,6 +746,11 @@ describe('VALIDATE FUNCTION TESTS ', function () {
expect(resultObj.mismatches[2].transactionJsonPath).to.eql('$.request.body.urlencoded[4].value');
expect(resultObj.mismatches[2].suggestedFix.actualValue).to.eql('999');
expect(resultObj.mismatches[2].suggestedFix.suggestedValue).to.eql('exampleString');
// for deepObject property named "propDeepObject" child param "propDeepObject[address][city]" is of incorrect type
expect(resultObj.mismatches[3].transactionJsonPath).to.eql('$.request.body.urlencoded[8].value');
expect(resultObj.mismatches[3].suggestedFix.actualValue).to.eql('123');
expect(resultObj.mismatches[3].suggestedFix.suggestedValue).to.eql('Delhi');
done();
});
});