From 2dcc38760ee78851e9c7f885529f425fac3742dd Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Wed, 3 Apr 2024 15:35:18 +0200 Subject: [PATCH 1/3] fix(spec): format validation errors for nested parameters Refs #9774 --- src/core/plugins/spec/selectors.js | 15 +++++-- test/unit/core/plugins/spec/selectors.js | 57 ++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/core/plugins/spec/selectors.js b/src/core/plugins/spec/selectors.js index 82607ac8f1f..8dd36dc3ce2 100644 --- a/src/core/plugins/spec/selectors.js +++ b/src/core/plugins/spec/selectors.js @@ -494,12 +494,21 @@ export const validationErrors = (state, pathMethod) => { let paramValues = state.getIn(["meta", "paths", ...pathMethod, "parameters"], fromJS([])) const result = [] + const formatErrors = (errors) => { + const stringifyMap = (e) => { + return `${e.get("propKey") || e.get("index")}: ${ + Map.isMap(e.get("error")) ? formatErrors(e.get("error")) : e.get("error") + }` + } + return List.isList(errors) + ? errors.map((e) => (Map.isMap(e) ? stringifyMap(e) : e)) + : stringifyMap(errors) + } + paramValues.forEach( (p) => { let errors = p.get("errors") if (errors && errors.count()) { - errors - .map((e) => (Map.isMap(e) ? `${e.get("propKey")}: ${e.get("error")}` : e)) - .forEach((e) => result.push(e)) + formatErrors(errors).forEach((e) => result.push(e)) } }) return result diff --git a/test/unit/core/plugins/spec/selectors.js b/test/unit/core/plugins/spec/selectors.js index 29f6ca25a34..d26a93efe5b 100644 --- a/test/unit/core/plugins/spec/selectors.js +++ b/test/unit/core/plugins/spec/selectors.js @@ -1411,6 +1411,52 @@ describe("validationErrors", function() { } } } + }, + "/nested": { + post: { + parameters: { + arrayWithObjects: { + errors: [ + { + error: "Parameter string value must be valid JSON", + index: 0 + }, + { + error: "Value must be a string", + index: 1 + } + ] + }, + objectWithArray: { + errors: [ + { + error: { + error: { + error: "Value must be a number", + propKey: "b", + }, + index: 0, + }, + propKey: "a", + } + ] + }, + objectWithoutArray: { + errors: [ + { + error: { + error: { + error: "Value must be a string", + propKey: "e", + }, + propKey: "d", + }, + propKey: "c", + } + ] + } + } + } } } } @@ -1432,4 +1478,15 @@ describe("validationErrors", function() { "name: Value must be a string" ]) }) + + it("should return formatted validation errors for nested parameters", function () { + const result = validationErrors(state, ["/nested", "post"]) + + expect(result).toEqual([ + "0: Parameter string value must be valid JSON", + "1: Value must be a string", + "a: 0: b: Value must be a number", + "c: d: e: Value must be a string" + ]) + }) }) From ae32bfe0f3567538b5b54a2b61c0a152577f2d8f Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Wed, 10 Apr 2024 09:04:12 +0200 Subject: [PATCH 2/3] fix(spec): format validation errors with parameter name and path --- src/core/plugins/spec/selectors.js | 35 ++++++++++++++++++------ test/unit/core/plugins/spec/selectors.js | 35 +++++++++++++----------- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/src/core/plugins/spec/selectors.js b/src/core/plugins/spec/selectors.js index 8dd36dc3ce2..e45c49f081e 100644 --- a/src/core/plugins/spec/selectors.js +++ b/src/core/plugins/spec/selectors.js @@ -494,21 +494,38 @@ export const validationErrors = (state, pathMethod) => { let paramValues = state.getIn(["meta", "paths", ...pathMethod, "parameters"], fromJS([])) const result = [] - const formatErrors = (errors) => { - const stringifyMap = (e) => { - return `${e.get("propKey") || e.get("index")}: ${ - Map.isMap(e.get("error")) ? formatErrors(e.get("error")) : e.get("error") - }` + const getErrorsWithPaths = (errors, path = []) => { + const getNestedErrorsWithPaths = (e, path) => { + const currPath = [...path, e.get("propKey") || e.get("index")] + return Map.isMap(e.get("error")) + ? getErrorsWithPaths(e.get("error"), currPath) + : { error: e.get("error"), path: currPath } } + return List.isList(errors) - ? errors.map((e) => (Map.isMap(e) ? stringifyMap(e) : e)) - : stringifyMap(errors) + ? errors.map((e) => (Map.isMap(e) ? getNestedErrorsWithPaths(e, path) : { error: e, path })) + : getNestedErrorsWithPaths(errors, path) + } + + const formatError = (error, path, paramName) => { + path = path.reduce((acc, curr) => { + return typeof curr === "number" + ? `${acc}[${curr}]` + : acc + ? `${acc}.${curr}` + : curr + }, "") + return `For '${paramName}'${path ? ` at path '${path}'` : ""}: ${error}.` } - paramValues.forEach( (p) => { + paramValues.forEach( (p, key) => { + const paramName = key.split(".").slice(1, -1).join(".") let errors = p.get("errors") if (errors && errors.count()) { - formatErrors(errors).forEach((e) => result.push(e)) + const errorsWithPaths= getErrorsWithPaths(errors) + errorsWithPaths.forEach(({error, path}) => { + result.push(formatError(error, path, paramName)) + }) } }) return result diff --git a/test/unit/core/plugins/spec/selectors.js b/test/unit/core/plugins/spec/selectors.js index d26a93efe5b..b49883a13e7 100644 --- a/test/unit/core/plugins/spec/selectors.js +++ b/test/unit/core/plugins/spec/selectors.js @@ -1388,7 +1388,7 @@ describe("validationErrors", function() { "/": { get: { parameters: { - id: { + "query.id.hash": { errors: [ "Value must be an integer" ] @@ -1397,7 +1397,7 @@ describe("validationErrors", function() { }, post: { parameters: { - body: { + "query.with.dot.hash": { errors: [ { error: "Value must be an integer", @@ -1415,19 +1415,22 @@ describe("validationErrors", function() { "/nested": { post: { parameters: { - arrayWithObjects: { + "query.arrayWithObjects.hash": { errors: [ { error: "Parameter string value must be valid JSON", index: 0 }, { - error: "Value must be a string", + error: { + error: "Value must be a string", + propKey: "name" + }, index: 1 } ] }, - objectWithArray: { + "query.objectWithArray.hash": { errors: [ { error: { @@ -1441,7 +1444,7 @@ describe("validationErrors", function() { } ] }, - objectWithoutArray: { + "query.objectWithoutArray.hash": { errors: [ { error: { @@ -1462,31 +1465,31 @@ describe("validationErrors", function() { } }) - it("should return validation errors without formatting them", function () { + it("should return validation errors with parameter name", function () { const result = validationErrors(state, ["/", "get"]) expect(result).toEqual([ - "Value must be an integer" + "For 'id': Value must be an integer." ]) }) - it("should return formatted validation errors", function () { + it("should return validation errors with parameter name and path", function () { const result = validationErrors(state, ["/", "post"]) expect(result).toEqual([ - "id: Value must be an integer", - "name: Value must be a string" + "For 'with.dot' at path 'id': Value must be an integer.", + "For 'with.dot' at path 'name': Value must be a string." ]) }) - it("should return formatted validation errors for nested parameters", function () { + it("should return validation errors with parameter name and path for nested parameters", function () { const result = validationErrors(state, ["/nested", "post"]) expect(result).toEqual([ - "0: Parameter string value must be valid JSON", - "1: Value must be a string", - "a: 0: b: Value must be a number", - "c: d: e: Value must be a string" + "For 'arrayWithObjects' at path '[0]': Parameter string value must be valid JSON.", + "For 'arrayWithObjects' at path '[1].name': Value must be a string.", + "For 'objectWithArray' at path 'a[0].b': Value must be a number.", + "For 'objectWithoutArray' at path 'c.d.e': Value must be a string." ]) }) }) From 1a6d81370d733957c82b365ed807838961d939ee Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Wed, 10 Apr 2024 13:01:03 +0200 Subject: [PATCH 3/3] refactor validationErrors --- src/core/plugins/spec/selectors.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/core/plugins/spec/selectors.js b/src/core/plugins/spec/selectors.js index e45c49f081e..62eddd38120 100644 --- a/src/core/plugins/spec/selectors.js +++ b/src/core/plugins/spec/selectors.js @@ -491,9 +491,11 @@ export const canExecuteScheme = ( state, path, method ) => { export const validationErrors = (state, pathMethod) => { pathMethod = pathMethod || [] - let paramValues = state.getIn(["meta", "paths", ...pathMethod, "parameters"], fromJS([])) + const paramValues = state.getIn(["meta", "paths", ...pathMethod, "parameters"], fromJS([])) const result = [] + if (paramValues.length === 0) return result + const getErrorsWithPaths = (errors, path = []) => { const getNestedErrorsWithPaths = (e, path) => { const currPath = [...path, e.get("propKey") || e.get("index")] @@ -520,9 +522,9 @@ export const validationErrors = (state, pathMethod) => { paramValues.forEach( (p, key) => { const paramName = key.split(".").slice(1, -1).join(".") - let errors = p.get("errors") + const errors = p.get("errors") if (errors && errors.count()) { - const errorsWithPaths= getErrorsWithPaths(errors) + const errorsWithPaths = getErrorsWithPaths(errors) errorsWithPaths.forEach(({error, path}) => { result.push(formatError(error, path, paramName)) })