diff --git a/src/core/plugins/spec/selectors.js b/src/core/plugins/spec/selectors.js index 82607ac8f1f..62eddd38120 100644 --- a/src/core/plugins/spec/selectors.js +++ b/src/core/plugins/spec/selectors.js @@ -491,15 +491,43 @@ 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 = [] - paramValues.forEach( (p) => { - let errors = p.get("errors") + if (paramValues.length === 0) return result + + 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) ? 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, key) => { + const paramName = key.split(".").slice(1, -1).join(".") + const 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)) + 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 29f6ca25a34..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", @@ -1411,25 +1411,85 @@ describe("validationErrors", function() { } } } + }, + "/nested": { + post: { + parameters: { + "query.arrayWithObjects.hash": { + errors: [ + { + error: "Parameter string value must be valid JSON", + index: 0 + }, + { + error: { + error: "Value must be a string", + propKey: "name" + }, + index: 1 + } + ] + }, + "query.objectWithArray.hash": { + errors: [ + { + error: { + error: { + error: "Value must be a number", + propKey: "b", + }, + index: 0, + }, + propKey: "a", + } + ] + }, + "query.objectWithoutArray.hash": { + errors: [ + { + error: { + error: { + error: "Value must be a string", + propKey: "e", + }, + propKey: "d", + }, + propKey: "c", + } + ] + } + } + } } } } }) - 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 validation errors with parameter name and path for nested parameters", function () { + const result = validationErrors(state, ["/nested", "post"]) + + expect(result).toEqual([ + "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." ]) }) })