diff --git a/src/core/components/execute.jsx b/src/core/components/execute.jsx index e6b3e1b0bed..eeb74ae9c00 100644 --- a/src/core/components/execute.jsx +++ b/src/core/components/execute.jsx @@ -25,7 +25,8 @@ export default class Execute extends Component { let { path, method, specSelectors, oas3Selectors, oas3Actions } = this.props let validationErrors = { missingBodyValue: false, - missingRequiredKeys: [] + missingRequiredKeys: [], + valueErrors: [] } // context: reset errors, then (re)validate oas3Actions.clearRequestBodyValidateError({ path, method }) @@ -33,6 +34,7 @@ export default class Execute extends Component { let oas3RequestBodyValue = oas3Selectors.requestBodyValue(path, method) let oas3ValidateBeforeExecuteSuccess = oas3Selectors.validateBeforeExecute([path, method]) let oas3RequestContentType = oas3Selectors.requestContentType(path, method) + let oas3RequestBody = specSelectors.getOAS3RequestBody([path, method]) if (!oas3ValidateBeforeExecuteSuccess) { validationErrors.missingBodyValue = true @@ -47,12 +49,21 @@ export default class Execute extends Component { oas3RequestContentType, oas3RequestBodyValue }) - if (!missingRequiredKeys || missingRequiredKeys.length < 1) { + let valueErrors = oas3Selectors.validateValues({ + oas3RequestBody, + oas3RequestContentType, + oas3RequestBodyValue + }) + if ((!missingRequiredKeys || missingRequiredKeys.length < 1) + && (!valueErrors || valueErrors.length < 1)) { return true } missingRequiredKeys.forEach((missingKey) => { validationErrors.missingRequiredKeys.push(missingKey) }) + valueErrors.forEach((error) => { + validationErrors.valueErrors.push(error) + }) oas3Actions.setRequestBodyValidateError({ path, method, validationErrors }) return false } diff --git a/src/core/components/operation.jsx b/src/core/components/operation.jsx index 9e6046addb6..dce79c088b0 100644 --- a/src/core/components/operation.jsx +++ b/src/core/components/operation.jsx @@ -115,7 +115,10 @@ export default class Operation extends PureComponent { let onChangeKey = [ path, method ] // Used to add values to _this_ operation ( indexed by path and method ) - const validationErrors = specSelectors.validationErrors([path, method]) + const oas3ValidationErrors = specSelectors.isOAS3() + ? oas3Selectors.validationErrors(path, method) + : [] + const validationErrors = specSelectors.validationErrors([path, method]).concat(oas3ValidationErrors) return (
diff --git a/src/core/plugins/oas3/reducers.js b/src/core/plugins/oas3/reducers.js index 9efdb267726..7ff4db056c1 100644 --- a/src/core/plugins/oas3/reducers.js +++ b/src/core/plugins/oas3/reducers.js @@ -81,6 +81,10 @@ export default { }, missingKeyValues) }) } + if (validationErrors.valueErrors && validationErrors.valueErrors.length > 0) { + // context: is application/json, with list of errors + return state.setIn(["requestData", path, method, "errors"], fromJS(validationErrors.valueErrors)) + } console.warn("unexpected result: SET_REQUEST_BODY_VALIDATE_ERROR") return state }, diff --git a/src/core/plugins/oas3/selectors.js b/src/core/plugins/oas3/selectors.js index d902a2db698..52996b6c6e7 100644 --- a/src/core/plugins/oas3/selectors.js +++ b/src/core/plugins/oas3/selectors.js @@ -5,7 +5,7 @@ import { OrderedMap, Map, List } from "immutable" import constant from "lodash/constant" import { getDefaultRequestBodyValue } from "./components/request-body" -import { stringify } from "core/utils" +import { stringify, validateParam } from "core/utils" // Helpers @@ -165,6 +165,19 @@ export const requestBodyErrors = onlyOAS3((state, path, method) => { return state.getIn(["requestData", path, method, "errors"]) || null }) +export const validationErrors = onlyOAS3((state, path, method) => { + const errors = state.getIn(["requestData", path, method, "errors"]) || null + const result = [] + + if (errors && errors.count()) { + errors + .map((e) => (Map.isMap(e) ? `${e.get("propKey")}: ${e.get("error")}` : e)) + .forEach((e) => result.push(e)) + } + + return result +}) + export const activeExamplesMember = onlyOAS3( (state, path, method, type, name) => { return ( @@ -295,6 +308,23 @@ export const validateShallowRequired = ( return missingRequiredKeys } +export const validateValues = ( + state, + { + oas3RequestBody, + oas3RequestContentType, + oas3RequestBodyValue + } +) => { + if (oas3RequestContentType !== "application/json") { + return [] + } + return validateParam(oas3RequestBody, oas3RequestBodyValue, { + bypassRequiredCheck: false, + isOAS3: true, + }) +} + export const validOperationMethods = constant([ "get", "put", diff --git a/src/core/plugins/spec/selectors.js b/src/core/plugins/spec/selectors.js index 82607ac8f1f..a9f196febfb 100644 --- a/src/core/plugins/spec/selectors.js +++ b/src/core/plugins/spec/selectors.js @@ -509,6 +509,10 @@ export const validateBeforeExecute = (state, pathMethod) => { return validationErrors(state, pathMethod).length === 0 } +export const getOAS3RequestBody = (state, pathMethod) => { + return state.getIn(["resolvedSubtrees", "paths", ...pathMethod, "requestBody"], fromJS([])) +} + export const getOAS3RequiredRequestBodyContentType = (state, pathMethod) => { let requiredObj = { requestBody: false, diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 9ff5770ee2e..0b9a6b11240 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -9,7 +9,7 @@ If you're refactoring something in here, feel free to break it out to a file in `./helpers` if you have the time. */ -import Im, { fromJS, Set } from "immutable" +import Im, { fromJS, Set, List } from "immutable" import { sanitizeUrl as braintreeSanitizeUrl } from "@braintree/sanitize-url" import camelCase from "lodash/camelCase" import upperFirst from "lodash/upperFirst" @@ -486,7 +486,7 @@ function validateValueBySchema(value, schema, requiredByParam, bypassRequiredChe return errors } } - if(schema && schema.has("required") && isFunc(requiredBySchema.isList) && requiredBySchema.isList()) { + if(schema && requiredBySchema && List.isList(requiredBySchema)) { requiredBySchema.forEach(key => { if(objectVal[key] === undefined) { errors.push({ propKey: key, error: "Required property not found" }) diff --git a/test/unit/core/plugins/oas3/reducers.js b/test/unit/core/plugins/oas3/reducers.js index 22371eb8943..cd3e5624316 100644 --- a/test/unit/core/plugins/oas3/reducers.js +++ b/test/unit/core/plugins/oas3/reducers.js @@ -314,6 +314,142 @@ describe("oas3 plugin - reducer", function () { }) }) + describe("valueErrors exists with length, e.g. application/json", () => { + it("should set errors", () => { + const state = fromJS({ + requestData: { + "/pet": { + post: { + bodyValue: { + id: { + value: "10", + }, + name: { + value: "", + }, + }, + requestContentType: "application/json" + } + } + } + }) + + const result = setRequestBodyValidateError(state, { + payload: { + path: "/pet", + method: "post", + validationErrors: { + missingBodyValue: null, + valueErrors: [ + { + propKey: "name", + error: "Required property not found" + } + ] + }, + } + }) + + const expectedResult = { + requestData: { + "/pet": { + post: { + bodyValue: { + id: { + value: "10", + }, + name: { + value: "", + }, + }, + requestContentType: "application/json", + errors: [ + { + propKey: "name", + error: "Required property not found" + } + ] + } + } + } + } + + expect(result.toJS()).toEqual(expectedResult) + }) + + it("should overwrite errors", () => { + const state = fromJS({ + requestData: { + "/pet": { + post: { + bodyValue: { + id: { + value: "10", + }, + name: { + value: "", + }, + }, + requestContentType: "application/json", + errors: [ + { + propKey: "id", + error: "some fake error" + }, + { + propKey: "name", + error: "some fake error" + } + ] + } + } + } + }) + + const result = setRequestBodyValidateError(state, { + payload: { + path: "/pet", + method: "post", + validationErrors: { + missingBodyValue: null, + valueErrors: [ + { + propKey: "name", + error: "Required property not found" + } + ] + }, + } + }) + + const expectedResult = { + requestData: { + "/pet": { + post: { + bodyValue: { + id: { + value: "10", + }, + name: { + value: "", + }, + }, + requestContentType: "application/json", + errors: [ + { + propKey: "name", + error: "Required property not found" + } + ] + } + } + } + } + + expect(result.toJS()).toEqual(expectedResult) + }) + }) + describe("other unexpected payload, e.g. no missingBodyValue or missingRequiredKeys", () => { it("should not throw error if receiving unexpected validationError format. return state unchanged", () => { const state = fromJS({