fix bug about query parameter validation (#767)
* Ignore INVALID_TYPE validation in case of query parameter in string format * update revalidate logic * align change log version * put all the revalidate logic to function ReValidateIfNeed() * set timeout to single test * update revalidate logic * update revalidate logic and test files * clean old codes
This commit is contained in:
Родитель
b2c2aaad43
Коммит
3eec33d4ac
|
@ -1,6 +1,8 @@
|
|||
# Change Log - oav
|
||||
|
||||
## 04/07/2022 2.12.0
|
||||
|
||||
- ModelValidator - Ignore INVALID_TYPE validation in case of query parameter in string format
|
||||
- Traffic Validation - Support validation report generation
|
||||
|
||||
## 04/06/2022 2.11.10
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import * as lodash from "lodash";
|
||||
import {
|
||||
ChildObjectInfo,
|
||||
getInfo,
|
||||
|
@ -77,7 +78,15 @@ export class AjvSchemaValidator implements SchemaValidator {
|
|||
const result: SchemaValidateIssue[] = [];
|
||||
const isValid = validateSchema.validate.call(ctx, data);
|
||||
if (!isValid) {
|
||||
ajvErrorListToSchemaValidateIssueList(validateSchema.validate.errors!, ctx, result);
|
||||
const errors = ReValidateIfNeed(
|
||||
validateSchema.validate.errors!,
|
||||
ctx,
|
||||
data,
|
||||
validateSchema.validate
|
||||
);
|
||||
if (errors.length > 0) {
|
||||
ajvErrorListToSchemaValidateIssueList(errors, ctx, result);
|
||||
}
|
||||
validateSchema.validate.errors = null;
|
||||
}
|
||||
return result;
|
||||
|
@ -192,6 +201,76 @@ export const ajvErrorToSchemaValidateIssue = (
|
|||
return result;
|
||||
};
|
||||
|
||||
const ReValidateIfNeed = (
|
||||
originalErrors: ErrorObject[],
|
||||
ctx: SchemaValidateContext,
|
||||
data: any,
|
||||
validate: ValidateFunction
|
||||
): ErrorObject[] => {
|
||||
const result: ErrorObject[] = [];
|
||||
const newData = lodash.cloneDeep(data);
|
||||
|
||||
for (const originalError of originalErrors) {
|
||||
validate.errors = null;
|
||||
const { schema, parentSchema: parentSch, keyword, data: errorData, dataPath } = originalError;
|
||||
const parentSchema = parentSch as Schema;
|
||||
|
||||
// If the value of query parameter is in string format, we can revalidate this error
|
||||
if (
|
||||
!ctx.isResponse &&
|
||||
keyword === "type" &&
|
||||
schema === "array" &&
|
||||
typeof errorData === "string" &&
|
||||
(parentSchema as any)?.["in"] === "query"
|
||||
) {
|
||||
const arrayData = errorData.split(",").map((item) => {
|
||||
// when item is number
|
||||
const numberRegex = /^[+-]?\d+(\.\d+)?([Ee]\+?\d+)?$/g;
|
||||
if (numberRegex.test(item)) {
|
||||
return parseFloat(item);
|
||||
}
|
||||
// when item is boolean
|
||||
if (item === "true" || item === "false") {
|
||||
return item === "true";
|
||||
}
|
||||
return item;
|
||||
});
|
||||
const position = dataPath.substr(1);
|
||||
lodash.set(newData, position, arrayData);
|
||||
const isValid = validate.call(ctx, newData);
|
||||
if (!isValid) {
|
||||
// if validate.errors have new errors, add them to result
|
||||
for (const newError of validate.errors!) {
|
||||
let [includedInResult, includedInOriginalErrors] = [false, false];
|
||||
for (const resultError of result) {
|
||||
if (lodash.isEqual(newError, resultError)) {
|
||||
// error is included in result
|
||||
includedInResult = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!includedInResult) {
|
||||
for (const eachOriginalError of originalErrors) {
|
||||
if (lodash.isEqual(newError, eachOriginalError)) {
|
||||
// error is included in originalErrors
|
||||
includedInOriginalErrors = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!includedInOriginalErrors) {
|
||||
result.push(newError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
result.push(originalError);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const shouldSkipError = (error: ErrorObject, cxt: SchemaValidateContext) => {
|
||||
const { schema, parentSchema: parentSch, params, keyword, data } = error;
|
||||
const parentSchema = parentSch as Schema;
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"parameters": {
|
||||
"hello": [
|
||||
"Wake up at 3:14:16 AM",
|
||||
"World"
|
||||
]
|
||||
},
|
||||
"responses": {
|
||||
"200": {}
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"parameters": {
|
||||
"hello": "Wake up at 3:14:16 AM"
|
||||
},
|
||||
"responses": {
|
||||
"200": {}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"parameters": {
|
||||
"hello": true
|
||||
"helloArray": "true"
|
||||
},
|
||||
"responses": {
|
||||
"200": {}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"parameters": {
|
||||
"helloArray": "127.0.0.1,1.2E+20",
|
||||
"name": "hello,hello2"
|
||||
},
|
||||
"responses": {
|
||||
"200": {}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"parameters": {
|
||||
"hello": 31416
|
||||
"helloArray": "050.974520,1.2E+2"
|
||||
},
|
||||
"responses": {
|
||||
"200": {}
|
|
@ -11,154 +11,30 @@
|
|||
"consumes": [],
|
||||
"produces": [],
|
||||
"paths": {
|
||||
"/query/string/encoded": {
|
||||
"/query/string/definedAsArray/number": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Query"
|
||||
],
|
||||
"operationId": "Query_StringEncoded",
|
||||
"description": "The parameter is a query, an encoded string",
|
||||
"operationId": "Query_StringButDefinedAsArray_numberItem",
|
||||
"description": "The parameter is a query, a string but defined as an array",
|
||||
"x-ms-examples": {
|
||||
"queryStringEncoded": {
|
||||
"$ref": "./examples/string.json"
|
||||
"queryStringButDefinedAsArray": {
|
||||
"$ref": "./examples/stringWithNumber.json"
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "hello",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"description": "Encoded query string",
|
||||
"x-ms-skip-url-encoding": false
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/query/string/notEncoded": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Query"
|
||||
],
|
||||
"operationId": "Query_StringNotEncoded",
|
||||
"description": "The parameter is a query, a not encoded string",
|
||||
"x-ms-examples": {
|
||||
"queryStringNotEncoded": {
|
||||
"$ref": "./examples/string.json"
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "hello",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"description": "Not encoded query string",
|
||||
"x-ms-skip-url-encoding": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/query/bool/encoded": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Query"
|
||||
],
|
||||
"operationId": "Query_BoolEncoded",
|
||||
"description": "The parameter is a query, an encoded bool",
|
||||
"x-ms-examples": {
|
||||
"queryBoolEncoded": {
|
||||
"$ref": "./examples/bool.json"
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "hello",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean",
|
||||
"description": "Encoded query bool",
|
||||
"x-ms-skip-url-encoding": false
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/query/bool/notEncoded": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Query"
|
||||
],
|
||||
"operationId": "Query_BoolNotEncoded",
|
||||
"description": "The parameter is a query, a not encoded bool",
|
||||
"x-ms-examples": {
|
||||
"queryBoolNotEncoded": {
|
||||
"$ref": "./examples/bool.json"
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "hello",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "boolean",
|
||||
"description": "Not encoded query bool",
|
||||
"x-ms-skip-url-encoding": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/query/array/encoded": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Query"
|
||||
],
|
||||
"operationId": "Query_ArrayEncoded",
|
||||
"description": "The parameter is a query, an encoded array",
|
||||
"x-ms-examples": {
|
||||
"queryArrayEncoded": {
|
||||
"$ref": "./examples/array.json"
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "hello",
|
||||
"name": "helloArray",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "array",
|
||||
"description": "Encoded query array",
|
||||
"x-ms-skip-url-encoding": false,
|
||||
"collectionFormat": "csv",
|
||||
"description": "array items are numbers",
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"x-nullable": false,
|
||||
"x-ms-enum": {
|
||||
"name": "Hello",
|
||||
"modelAsString": true
|
||||
},
|
||||
"enum": [
|
||||
"Wake up at 3:14:16 AM",
|
||||
"World"
|
||||
]
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -169,38 +45,27 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/query/array/notEncoded": {
|
||||
"/query/string/definedAsArray/boolean": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Query"
|
||||
],
|
||||
"operationId": "Query_ArrayNotEncoded",
|
||||
"description": "The parameter is a query, a not encoded array",
|
||||
"operationId": "Query_StringButDefinedAsArray_booleanItem",
|
||||
"description": "The parameter is a query, a string but defined as an array",
|
||||
"x-ms-examples": {
|
||||
"queryArrayNotEncoded": {
|
||||
"$ref": "./examples/array.json"
|
||||
"queryStringButDefinedAsArray": {
|
||||
"$ref": "./examples/stringWithBoolean.json"
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "hello",
|
||||
"name": "helloArray",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "array",
|
||||
"description": "Encoded query array",
|
||||
"x-ms-skip-url-encoding": true,
|
||||
"collectionFormat": "csv",
|
||||
"description": "array item is boolean",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"x-nullable": false,
|
||||
"x-ms-enum": {
|
||||
"name": "Hello",
|
||||
"modelAsString": true
|
||||
},
|
||||
"enum": [
|
||||
"Wake up at 3:14:16 AM",
|
||||
"World"
|
||||
]
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -211,55 +76,44 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/query/integer/encoded": {
|
||||
"/query/string/definedAsArray/extraError": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Query"
|
||||
],
|
||||
"operationId": "Query_IntegerEncoded",
|
||||
"description": "The parameter is a query, an encoded integer",
|
||||
"operationId": "Query_StringButDefinedAsArray_extraError",
|
||||
"description": "The parameter is a query, a string but defined as an array",
|
||||
"x-ms-examples": {
|
||||
"queryIntegerEncoded": {
|
||||
"$ref": "./examples/integer.json"
|
||||
"queryStringButDefinedAsArray": {
|
||||
"$ref": "./examples/stringWithExtraError.json"
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "hello",
|
||||
"name": "helloArray",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
"description": "Encoded query integer",
|
||||
"x-ms-skip-url-encoding": false
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/query/integer/notEncoded": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Query"
|
||||
],
|
||||
"operationId": "Query_IntegerNotEncoded",
|
||||
"description": "The parameter is a query, a not encoded integer",
|
||||
"x-ms-examples": {
|
||||
"queryIntegerNotEncoded": {
|
||||
"$ref": "./examples/integer.json"
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
"type": "array",
|
||||
"description": "array items are numbers",
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"items": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "hello",
|
||||
"name": "name",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
"description": "Not encoded query integer",
|
||||
"x-ms-skip-url-encoding": true
|
||||
"type": "array",
|
||||
"description": "array items are numbers",
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"maxLength": 5
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
|
@ -461,14 +461,40 @@ describe("Model Validation", () => {
|
|||
});
|
||||
|
||||
describe("Queries - ", () => {
|
||||
it("should pass for various query parameters", async () => {
|
||||
const specPath2 = `${testPath}/modelValidation/swaggers/specification/query/test.json`;
|
||||
const result = await validate.validateExamples(specPath2, undefined, {
|
||||
consoleLogLevel: "off",
|
||||
describe("Should revalidate query parameters in string format which be defined as array", () => {
|
||||
it("array items are numbers", async () => {
|
||||
const specPath2 = `${testPath}/modelValidation/swaggers/specification/query/test.json`;
|
||||
const result = await validate.validateExamples(
|
||||
specPath2,
|
||||
"Query_StringButDefinedAsArray_numberItem",
|
||||
{ consoleLogLevel: "off" }
|
||||
);
|
||||
assert(result.length === 0, `swagger "${specPath2}" contains model validation errors.`);
|
||||
});
|
||||
it("array item is boolean", async () => {
|
||||
const specPath2 = `${testPath}/modelValidation/swaggers/specification/query/test.json`;
|
||||
const result = await validate.validateExamples(
|
||||
specPath2,
|
||||
"Query_StringButDefinedAsArray_booleanItem",
|
||||
{ consoleLogLevel: "off" }
|
||||
);
|
||||
assert(result.length === 0, `swagger "${specPath2}" contains model validation errors.`);
|
||||
});
|
||||
it("should report other error and skip INVALID_TYPE error about query parameter", async () => {
|
||||
const specPath2 = `${testPath}/modelValidation/swaggers/specification/query/test.json`;
|
||||
const result = await validate.validateExamples(
|
||||
specPath2,
|
||||
"Query_StringButDefinedAsArray_extraError",
|
||||
{ consoleLogLevel: "off" }
|
||||
);
|
||||
assert(result.length === 2);
|
||||
assert.strictEqual(result[0].code, "INVALID_TYPE");
|
||||
assert.strictEqual(result[0].message, "Expected type number but found type string");
|
||||
assert.strictEqual(result[0].schemaJsonPath, "helloArray/items/type");
|
||||
assert.strictEqual(result[1].code, "MAX_LENGTH");
|
||||
assert.strictEqual(result[1].message, "String is too long (6 chars), maximum 5");
|
||||
assert.strictEqual(result[1].schemaJsonPath, "name/items/maxLength");
|
||||
});
|
||||
// console.dir(result, { depth: null })
|
||||
assert(result.length === 0, `swagger "${specPath2}" contains model validation errors.`);
|
||||
// console.log(result)
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -771,6 +797,6 @@ describe("Model Validation", () => {
|
|||
const result = await validate.validateExamples(specPath2, undefined);
|
||||
assert.strictEqual(result.length, 1);
|
||||
assert.strictEqual(result[0].code, "XMS_EXAMPLE_NOTFOUND_ERROR");
|
||||
});
|
||||
}, 10000);
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче