/** * @prettier */ import { OrderedMap, Map, List } from "immutable" import escapeRegExp from "lodash/escapeRegExp" import constant from "lodash/constant" import { getDefaultRequestBodyValue } from "./components/request-body" import { stringify } from "core/utils" // Helpers const onlyOAS3 = (selector) => (state, ...args) => (system) => { if (system.getSystem().specSelectors.isOAS3()) { const selectedValue = selector(state, ...args) return typeof selectedValue === "function" ? selectedValue(system) : selectedValue } else { return null } } 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) || "" }) export const requestBodyValue = onlyOAS3((state, path, method) => { return state.getIn(["requestData", path, method, "bodyValue"]) || null }) export const shouldRetainRequestBodyValue = onlyOAS3((state, path, method) => { return state.getIn(["requestData", path, method, "retainBodyValue"]) || false }) export const selectDefaultRequestBodyValue = (state, path, method) => (system) => { const { oas3Selectors, specSelectors, fn } = system.getSystem() if (specSelectors.isOAS3()) { const currentMediaType = oas3Selectors.requestContentType(path, method) if (currentMediaType) { return getDefaultRequestBodyValue( specSelectors.specResolvedSubtree([ "paths", path, method, "requestBody", ]), currentMediaType, oas3Selectors.activeExamplesMember( path, method, "requestBody", "requestBody" ), fn ) } } return null } export const hasUserEditedBody = onlyOAS3((state, path, method) => (system) => { const { oas3Selectors, specSelectors, fn } = system let userHasEditedBody = false const currentMediaType = oas3Selectors.requestContentType(path, method) let userEditedRequestBody = oas3Selectors.requestBodyValue(path, method) const requestBody = specSelectors.specResolvedSubtree([ "paths", path, method, "requestBody", ]) /** * The only request body that can currently be edited is for Path Items that are direct values of OpenAPI.paths. * Path Item contained within the Callback Object or OpenAPI.webhooks (OpenAPI 3.1.0) have `Try it out` * disabled and thus body cannot be edited. */ if (!requestBody) { return false } if (Map.isMap(userEditedRequestBody)) { // context is not application/json media-type userEditedRequestBody = stringify( userEditedRequestBody .mapEntries((kv) => Map.isMap(kv[1]) ? [kv[0], kv[1].get("value")] : kv ) .toJS() ) } if (List.isList(userEditedRequestBody)) { userEditedRequestBody = stringify(userEditedRequestBody) } if (currentMediaType) { const currentMediaTypeDefaultBodyValue = getDefaultRequestBodyValue( requestBody, currentMediaType, oas3Selectors.activeExamplesMember( path, method, "requestBody", "requestBody" ), fn ) userHasEditedBody = !!userEditedRequestBody && userEditedRequestBody !== currentMediaTypeDefaultBodyValue } return userHasEditedBody }) export const requestBodyInclusionSetting = onlyOAS3((state, path, method) => { return state.getIn(["requestData", path, method, "bodyInclusion"]) || Map() }) 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 ) } ) export const requestContentType = onlyOAS3((state, path, method) => { return ( state.getIn(["requestData", path, method, "requestContentType"]) || null ) }) export const responseContentType = onlyOAS3((state, path, method) => { return ( state.getIn(["requestData", path, method, "responseContentType"]) || null ) }) export const serverVariableValue = onlyOAS3((state, locationData, key) => { let path // locationData may take one of two forms, for backwards compatibility // Object: ({server, namespace?}) or String:(server) if (typeof locationData !== "string") { const { server, namespace } = locationData if (namespace) { path = [namespace, "serverVariableValues", server, key] } else { path = ["serverVariableValues", server, key] } } else { const server = locationData path = ["serverVariableValues", server, key] } return state.getIn(path) || null }) export const serverVariables = onlyOAS3((state, locationData) => { let path // locationData may take one of two forms, for backwards compatibility // Object: ({server, namespace?}) or String:(server) if (typeof locationData !== "string") { const { server, namespace } = locationData if (namespace) { path = [namespace, "serverVariableValues", server] } else { path = ["serverVariableValues", server] } } else { const server = locationData path = ["serverVariableValues", server] } return state.getIn(path) || OrderedMap() }) export const serverEffectiveValue = onlyOAS3((state, locationData) => { var varValues, serverValue // locationData may take one of two forms, for backwards compatibility // Object: ({server, namespace?}) or String:(server) if (typeof locationData !== "string") { const { server, namespace } = locationData serverValue = server if (namespace) { varValues = state.getIn([namespace, "serverVariableValues", serverValue]) } else { varValues = state.getIn(["serverVariableValues", serverValue]) } } else { serverValue = locationData varValues = state.getIn(["serverVariableValues", serverValue]) } varValues = varValues || OrderedMap() let str = serverValue varValues.map((val, key) => { str = str.replace(new RegExp(`{${escapeRegExp(key)}}`, "g"), val) }) return str }) export const validateBeforeExecute = validateRequestBodyIsRequired( (state, pathMethod) => validateRequestBodyValueExists(state, pathMethod) ) export const validateShallowRequired = ( state, { oas3RequiredRequestBodyContentType, oas3RequestContentType, oas3RequestBodyValue, } ) => { let missingRequiredKeys = [] // context: json => String; urlencoded, form-data => Map if (!Map.isMap(oas3RequestBodyValue)) { return missingRequiredKeys } let requiredKeys = [] // Cycle through list of possible contentTypes for matching contentType and defined requiredKeys Object.keys(oas3RequiredRequestBodyContentType.requestContentType).forEach( (contentType) => { if (contentType === oas3RequestContentType) { 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 } export const validOperationMethods = constant([ "get", "put", "post", "delete", "options", "head", "patch", "trace", ])