Skip to content

Commit

Permalink
fix(oas3): request body validation
Browse files Browse the repository at this point in the history
  • Loading branch information
glowcloud committed Mar 14, 2024
1 parent b5ce300 commit 8fc64e7
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 6 deletions.
15 changes: 13 additions & 2 deletions src/core/components/execute.jsx
Expand Up @@ -25,14 +25,16 @@ 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 })
let oas3RequiredRequestBodyContentType = specSelectors.getOAS3RequiredRequestBodyContentType([path, method])
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
Expand All @@ -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
}
Expand Down
5 changes: 4 additions & 1 deletion src/core/components/operation.jsx
Expand Up @@ -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 (
<div className={deprecated ? "opblock opblock-deprecated" : isShown ? `opblock opblock-${method} is-open` : `opblock opblock-${method}`} id={escapeDeepLinkPath(isShownKey.join("-"))} >
Expand Down
4 changes: 4 additions & 0 deletions src/core/plugins/oas3/reducers.js
Expand Up @@ -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
},
Expand Down
32 changes: 31 additions & 1 deletion src/core/plugins/oas3/selectors.js
Expand Up @@ -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

Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions src/core/plugins/spec/selectors.js
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions src/core/utils/index.js
Expand Up @@ -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"
Expand Down Expand Up @@ -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" })
Expand Down
136 changes: 136 additions & 0 deletions test/unit/core/plugins/oas3/reducers.js
Expand Up @@ -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({
Expand Down

0 comments on commit 8fc64e7

Please sign in to comment.