feat(RequestBody): validation support for required fields (#6223)

fixes #5181

* application/json
* application/xml
* application/x-www-form-urlencoded

* Set requestBodyValue values to be an immutable Map, as "value". Previously stored as a normal String.
* This enables adding "errors" to the Map, for validation use

* note: getOAS3RequiredRequestBodyContentType requires state.spec,
* which is not available to state.oas3
This commit is contained in:
Tim Lai
2020-07-16 17:53:28 -07:00
committed by GitHub
parent b68942c043
commit 2fd1e4037c
15 changed files with 1029 additions and 26 deletions

View File

@@ -1,7 +1,6 @@
import { OrderedMap, Map } from "immutable"
import { isOAS3 as isOAS3Helper } from "./helpers"
// Helpers
function onlyOAS3(selector) {
@@ -15,6 +14,35 @@ function onlyOAS3(selector) {
}
}
function validateRequestBodyIsRequired(selector) {
return (...args) => (system) => {
const specJson = system.getSystem().specSelectors.specJson()
const argsList = [...args]
// expect argsList[0] = state
let pathMethod = argsList[1] || []
let isOas3RequestBodyRequired = specJson.getIn(["paths", ...pathMethod, "requestBody", "required"])
if (isOas3RequestBodyRequired) {
return selector(...args)
} else {
// validation pass b/c not required
return true
}
}
}
const validateRequestBodyValueExists = (state, pathMethod) => {
pathMethod = pathMethod || []
let oas3RequestBodyValue = state.getIn(["requestData", ...pathMethod, "bodyValue"])
// context: bodyValue can be a String, or a Map
if (!oas3RequestBodyValue) {
return false
}
// validation pass if String is not empty, or if Map exists
return true
}
export const selectedServer = onlyOAS3((state, namespace) => {
const path = namespace ? [namespace, "selectedServer"] : ["selectedServer"]
return state.getIn(path) || ""
@@ -31,6 +59,11 @@ export const requestBodyInclusionSetting = onlyOAS3((state, path, method) => {
}
)
export const requestBodyErrors = onlyOAS3((state, path, method) => {
return state.getIn(["requestData", path, method, "errors"]) || null
}
)
export const activeExamplesMember = onlyOAS3((state, path, method, type, name) => {
return state.getIn(["examples", path, method, type, name, "activeExample"]) || null
}
@@ -116,3 +149,34 @@ export const serverEffectiveValue = onlyOAS3((state, locationData) => {
return str
}
)
export const validateBeforeExecute = validateRequestBodyIsRequired(
(state, pathMethod) => validateRequestBodyValueExists(state, pathMethod)
)
export const validateShallowRequired = ( state, {oas3RequiredRequestBodyContentType, oas3RequestBodyValue} ) => {
let missingRequiredKeys = []
// context: json => String; urlencoded => Map
if (!Map.isMap(oas3RequestBodyValue)) {
return missingRequiredKeys
}
let requiredKeys = []
// We intentionally cycle through list of contentTypes for defined requiredKeys
// instead of assuming first contentType will accurately list all expected requiredKeys
// Alternatively, we could try retrieving the contentType first, and match exactly. This would be a more accurate representation of definition
Object.keys(oas3RequiredRequestBodyContentType.requestContentType).forEach((contentType) => {
let contentTypeVal = oas3RequiredRequestBodyContentType.requestContentType[contentType]
contentTypeVal.forEach((requiredKey) => {
if (requiredKeys.indexOf(requiredKey) < 0 ) {
requiredKeys.push(requiredKey)
}
})
})
requiredKeys.forEach((key) => {
let requiredKeyValue = oas3RequestBodyValue.getIn([key, "value"])
if (!requiredKeyValue) {
missingRequiredKeys.push(key)
}
})
return missingRequiredKeys
}