fix(validateParam): validate JSON parameter values + support Parameter.content (#5657)
* improve(getParameterSchema): ParameterSchemaDescriptor pattern * chore: update usage of `getParameterSchema` * consider `Parameter.content` media type when validating JSON values
This commit is contained in:
@@ -41,7 +41,7 @@ export default class ParameterRow extends Component {
|
|||||||
let enumValue
|
let enumValue
|
||||||
|
|
||||||
if(isOAS3) {
|
if(isOAS3) {
|
||||||
let schema = getParameterSchema(parameterWithMeta, { isOAS3 })
|
let { schema } = getParameterSchema(parameterWithMeta, { isOAS3 })
|
||||||
enumValue = schema.get("enum")
|
enumValue = schema.get("enum")
|
||||||
} else {
|
} else {
|
||||||
enumValue = parameterWithMeta ? parameterWithMeta.get("enum") : undefined
|
enumValue = parameterWithMeta ? parameterWithMeta.get("enum") : undefined
|
||||||
@@ -98,7 +98,7 @@ export default class ParameterRow extends Component {
|
|||||||
|
|
||||||
const paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()
|
const paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()
|
||||||
|
|
||||||
const schema = getParameterSchema(paramWithMeta, { isOAS3: specSelectors.isOAS3() })
|
const { schema } = getParameterSchema(paramWithMeta, { isOAS3: specSelectors.isOAS3() })
|
||||||
|
|
||||||
const parameterMediaType = paramWithMeta
|
const parameterMediaType = paramWithMeta
|
||||||
.get("content", Map())
|
.get("content", Map())
|
||||||
@@ -209,9 +209,10 @@ export default class ParameterRow extends Component {
|
|||||||
const ExamplesSelectValueRetainer = getComponent("ExamplesSelectValueRetainer")
|
const ExamplesSelectValueRetainer = getComponent("ExamplesSelectValueRetainer")
|
||||||
const Example = getComponent("Example")
|
const Example = getComponent("Example")
|
||||||
|
|
||||||
|
let { schema } = getParameterSchema(param, { isOAS3 })
|
||||||
let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()
|
let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()
|
||||||
|
|
||||||
let format = param.get("format")
|
let format = param.get("format")
|
||||||
let schema = getParameterSchema(param, { isOAS3 })
|
|
||||||
let type = schema.get("type")
|
let type = schema.get("type")
|
||||||
let isFormData = inType === "formData"
|
let isFormData = inType === "formData"
|
||||||
let isFormDataSupported = "FormData" in win
|
let isFormDataSupported = "FormData" in win
|
||||||
|
|||||||
@@ -501,12 +501,14 @@ export const validatePattern = (val, rxPattern) => {
|
|||||||
export const validateParam = (param, value, { isOAS3 = false, bypassRequiredCheck = false } = {}) => {
|
export const validateParam = (param, value, { isOAS3 = false, bypassRequiredCheck = false } = {}) => {
|
||||||
|
|
||||||
let errors = []
|
let errors = []
|
||||||
let required = param.get("required")
|
|
||||||
|
|
||||||
let paramDetails = getParameterSchema(param, { isOAS3 })
|
let paramRequired = param.get("required")
|
||||||
|
|
||||||
|
let { schema: paramDetails, parameterContentMediaType } = getParameterSchema(param, { isOAS3 })
|
||||||
|
|
||||||
if(!paramDetails) return errors
|
if(!paramDetails) return errors
|
||||||
|
|
||||||
|
let required = paramDetails.get("required")
|
||||||
let maximum = paramDetails.get("maximum")
|
let maximum = paramDetails.get("maximum")
|
||||||
let minimum = paramDetails.get("minimum")
|
let minimum = paramDetails.get("minimum")
|
||||||
let type = paramDetails.get("type")
|
let type = paramDetails.get("type")
|
||||||
@@ -520,7 +522,7 @@ export const validateParam = (param, value, { isOAS3 = false, bypassRequiredChec
|
|||||||
then we should do our validation routine.
|
then we should do our validation routine.
|
||||||
Only bother validating the parameter if the type was specified.
|
Only bother validating the parameter if the type was specified.
|
||||||
*/
|
*/
|
||||||
if ( type && (required || value) ) {
|
if ( type && (paramRequired || required || value) ) {
|
||||||
// These checks should evaluate to true if there is a parameter
|
// These checks should evaluate to true if there is a parameter
|
||||||
let stringCheck = type === "string" && value
|
let stringCheck = type === "string" && value
|
||||||
let arrayCheck = type === "array" && Array.isArray(value) && value.length
|
let arrayCheck = type === "array" && Array.isArray(value) && value.length
|
||||||
@@ -533,17 +535,6 @@ export const validateParam = (param, value, { isOAS3 = false, bypassRequiredChec
|
|||||||
let objectCheck = type === "object" && typeof value === "object" && value !== null
|
let objectCheck = type === "object" && typeof value === "object" && value !== null
|
||||||
let objectStringCheck = type === "object" && typeof value === "string" && value
|
let objectStringCheck = type === "object" && typeof value === "string" && value
|
||||||
|
|
||||||
// if(type === "object" && typeof value === "string") {
|
|
||||||
// // Disabled because `validateParam` doesn't consider the MediaType of the
|
|
||||||
// // `Parameter.content` hint correctly.
|
|
||||||
// try {
|
|
||||||
// JSON.parse(value)
|
|
||||||
// } catch(e) {
|
|
||||||
// errors.push("Parameter string value must be valid JSON")
|
|
||||||
// return errors
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
const allChecks = [
|
const allChecks = [
|
||||||
stringCheck, arrayCheck, arrayListCheck, arrayStringCheck, fileCheck,
|
stringCheck, arrayCheck, arrayListCheck, arrayStringCheck, fileCheck,
|
||||||
booleanCheck, numberCheck, integerCheck, objectCheck, objectStringCheck,
|
booleanCheck, numberCheck, integerCheck, objectCheck, objectStringCheck,
|
||||||
@@ -551,11 +542,25 @@ export const validateParam = (param, value, { isOAS3 = false, bypassRequiredChec
|
|||||||
|
|
||||||
const passedAnyCheck = allChecks.some(v => !!v)
|
const passedAnyCheck = allChecks.some(v => !!v)
|
||||||
|
|
||||||
if (required && !passedAnyCheck && !bypassRequiredCheck ) {
|
if ((paramRequired || required) && !passedAnyCheck && !bypassRequiredCheck ) {
|
||||||
errors.push("Required field is not provided")
|
errors.push("Required field is not provided")
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
type === "object" &&
|
||||||
|
typeof value === "string" &&
|
||||||
|
(parameterContentMediaType === null ||
|
||||||
|
parameterContentMediaType === "application/json")
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
JSON.parse(value)
|
||||||
|
} catch (e) {
|
||||||
|
errors.push("Parameter string value must be valid JSON")
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (pattern) {
|
if (pattern) {
|
||||||
let err = validatePattern(value, pattern)
|
let err = validatePattern(value, pattern)
|
||||||
if (err) errors.push(err)
|
if (err) errors.push(err)
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ const swagger2SchemaKeys = Im.Set.of(
|
|||||||
"multipleOf"
|
"multipleOf"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} ParameterSchemaDescriptor
|
||||||
|
* @property {Immutable.Map} schema - the parameter schema
|
||||||
|
* @property {string|null} parameterContentMediaType - the effective media type, for `content`-based OpenAPI 3.0 Parameters, or `null` otherwise
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the effective schema value for a parameter, or an empty Immutable.Map if
|
* Get the effective schema value for a parameter, or an empty Immutable.Map if
|
||||||
* no suitable schema can be found.
|
* no suitable schema can be found.
|
||||||
@@ -35,18 +41,29 @@ const swagger2SchemaKeys = Im.Set.of(
|
|||||||
* @param {object} config
|
* @param {object} config
|
||||||
* @param {boolean} config.isOAS3 Whether the parameter is from an OpenAPI 2.0
|
* @param {boolean} config.isOAS3 Whether the parameter is from an OpenAPI 2.0
|
||||||
* or OpenAPI 3.0 definition
|
* or OpenAPI 3.0 definition
|
||||||
* @return {Immutable.Map} The desired schema
|
* @return {ParameterSchemaDescriptor} Information about the parameter schema
|
||||||
*/
|
*/
|
||||||
export default function getParameterSchema(parameter, { isOAS3 } = {}) {
|
export default function getParameterSchema(parameter, { isOAS3 } = {}) {
|
||||||
// Return empty Map if `parameter` isn't a Map
|
// Return empty Map if `parameter` isn't a Map
|
||||||
if (!Im.Map.isMap(parameter)) return Im.Map()
|
if (!Im.Map.isMap(parameter)) {
|
||||||
|
return {
|
||||||
|
schema: Im.Map(),
|
||||||
|
parameterContentMediaType: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!isOAS3) {
|
if (!isOAS3) {
|
||||||
// Swagger 2.0
|
// Swagger 2.0
|
||||||
if (parameter.get("in") === "body") {
|
if (parameter.get("in") === "body") {
|
||||||
return parameter.get("schema", Im.Map())
|
return {
|
||||||
|
schema: parameter.get("schema", Im.Map()),
|
||||||
|
parameterContentMediaType: null,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return parameter.filter((v, k) => swagger2SchemaKeys.includes(k))
|
return {
|
||||||
|
schema: parameter.filter((v, k) => swagger2SchemaKeys.includes(k)),
|
||||||
|
parameterContentMediaType: null,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,11 +74,19 @@ export default function getParameterSchema(parameter, { isOAS3 } = {}) {
|
|||||||
.get("content", Im.Map({}))
|
.get("content", Im.Map({}))
|
||||||
.keySeq()
|
.keySeq()
|
||||||
|
|
||||||
return parameter.getIn(
|
const parameterContentMediaType = parameterContentMediaTypes.first()
|
||||||
["content", parameterContentMediaTypes.first(), "schema"],
|
|
||||||
Im.Map()
|
return {
|
||||||
)
|
schema: parameter.getIn(
|
||||||
|
["content", parameterContentMediaType, "schema"],
|
||||||
|
Im.Map()
|
||||||
|
),
|
||||||
|
parameterContentMediaType,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return parameter.get("schema", Im.Map())
|
return {
|
||||||
|
schema: parameter.get("schema", Im.Map()),
|
||||||
|
parameterContentMediaType: null,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,20 +3,22 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import expect from "expect"
|
import expect from "expect"
|
||||||
import Im, { fromJS } from "immutable"
|
import { fromJS } from "immutable"
|
||||||
import getParameterSchema from "../../../../src/helpers/get-parameter-schema"
|
import getParameterSchema from "../../../../src/helpers/get-parameter-schema"
|
||||||
|
|
||||||
describe("getParameterSchema", () => {
|
describe("getParameterSchema", () => {
|
||||||
it("should return an empty Map when given no parameters", () => {
|
it("should return an empty Map when given no parameters", () => {
|
||||||
const result = getParameterSchema()
|
const result = getParameterSchema()
|
||||||
|
|
||||||
expect(result).toEqual(fromJS({}))
|
expect(result.schema.toJS()).toEqual({})
|
||||||
|
expect(result.parameterContentMediaType).toEqual(null)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should return an empty Map when given an empty Map", () => {
|
it("should return an empty Map when given an empty Map", () => {
|
||||||
const result = getParameterSchema(fromJS({}))
|
const result = getParameterSchema(fromJS({}))
|
||||||
|
|
||||||
expect(result).toEqual(fromJS({}))
|
expect(result.schema.toJS()).toEqual({})
|
||||||
|
expect(result.parameterContentMediaType).toEqual(null)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should return a schema for a Swagger 2.0 query parameter", () => {
|
it("should return a schema for a Swagger 2.0 query parameter", () => {
|
||||||
@@ -34,12 +36,13 @@ describe("getParameterSchema", () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(result.toJS()).toEqual({
|
expect(result.schema.toJS()).toEqual({
|
||||||
type: "array",
|
type: "array",
|
||||||
items: {
|
items: {
|
||||||
type: "string",
|
type: "string",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
expect(result.parameterContentMediaType).toEqual(null)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should return a schema for a Swagger 2.0 body parameter", () => {
|
it("should return a schema for a Swagger 2.0 body parameter", () => {
|
||||||
@@ -58,12 +61,13 @@ describe("getParameterSchema", () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(result.toJS()).toEqual({
|
expect(result.schema.toJS()).toEqual({
|
||||||
type: "array",
|
type: "array",
|
||||||
items: {
|
items: {
|
||||||
type: "string",
|
type: "string",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
expect(result.parameterContentMediaType).toEqual(null)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should return a schema for an OpenAPI 3.0 query parameter", () => {
|
it("should return a schema for an OpenAPI 3.0 query parameter", () => {
|
||||||
@@ -87,12 +91,13 @@ describe("getParameterSchema", () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(result.toJS()).toEqual({
|
expect(result.schema.toJS()).toEqual({
|
||||||
type: "array",
|
type: "array",
|
||||||
items: {
|
items: {
|
||||||
type: "string",
|
type: "string",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
expect(result.parameterContentMediaType).toEqual(null)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should return a schema for an OpenAPI 3.0 query parameter with `content`", () => {
|
it("should return a schema for an OpenAPI 3.0 query parameter with `content`", () => {
|
||||||
@@ -126,7 +131,7 @@ describe("getParameterSchema", () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(result.toJS()).toEqual({
|
expect(result.schema.toJS()).toEqual({
|
||||||
type: "object",
|
type: "object",
|
||||||
required: ["lat", "long"],
|
required: ["lat", "long"],
|
||||||
properties: {
|
properties: {
|
||||||
@@ -138,5 +143,6 @@ describe("getParameterSchema", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
expect(result.parameterContentMediaType).toEqual(`application/json`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -414,15 +414,15 @@ describe("utils", function() {
|
|||||||
})
|
})
|
||||||
assertValidateOas3Param(param, value, [])
|
assertValidateOas3Param(param, value, [])
|
||||||
|
|
||||||
// // invalid object-as-string
|
// invalid object-as-string
|
||||||
// param = {
|
param = {
|
||||||
// required: true,
|
required: true,
|
||||||
// schema: {
|
schema: {
|
||||||
// type: "object"
|
type: "object"
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// value = "{{}"
|
value = "{{}"
|
||||||
// assertValidateOas3Param(param, value, ["Parameter string value must be valid JSON"])
|
assertValidateOas3Param(param, value, ["Parameter string value must be valid JSON"])
|
||||||
|
|
||||||
// missing when required
|
// missing when required
|
||||||
param = {
|
param = {
|
||||||
@@ -458,14 +458,14 @@ describe("utils", function() {
|
|||||||
})
|
})
|
||||||
assertValidateOas3Param(param, value, [])
|
assertValidateOas3Param(param, value, [])
|
||||||
|
|
||||||
// // invalid object-as-string
|
// invalid object-as-string
|
||||||
// param = {
|
param = {
|
||||||
// schema: {
|
schema: {
|
||||||
// type: "object"
|
type: "object"
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// value = "{{}"
|
value = "{{}"
|
||||||
// assertValidateOas3Param(param, value, ["Parameter string value must be valid JSON"])
|
assertValidateOas3Param(param, value, ["Parameter string value must be valid JSON"])
|
||||||
|
|
||||||
// missing when not required
|
// missing when not required
|
||||||
param = {
|
param = {
|
||||||
@@ -505,6 +505,108 @@ describe("utils", function() {
|
|||||||
assertValidateParam(param, value, [])
|
assertValidateParam(param, value, [])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("handles OAS3 `Parameter.content`", function() {
|
||||||
|
// invalid string
|
||||||
|
param = {
|
||||||
|
content: {
|
||||||
|
"text/plain": {
|
||||||
|
schema: {
|
||||||
|
required: true,
|
||||||
|
type: "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value = ""
|
||||||
|
assertValidateOas3Param(param, value, ["Required field is not provided"])
|
||||||
|
|
||||||
|
// valid string
|
||||||
|
param = {
|
||||||
|
content: {
|
||||||
|
"text/plain": {
|
||||||
|
schema: {
|
||||||
|
required: true,
|
||||||
|
type: "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value = "test string"
|
||||||
|
assertValidateOas3Param(param, value, [])
|
||||||
|
|
||||||
|
|
||||||
|
// invalid (empty) JSON string
|
||||||
|
param = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: {
|
||||||
|
required: true,
|
||||||
|
type: "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value = ""
|
||||||
|
assertValidateOas3Param(param, value, ["Required field is not provided"])
|
||||||
|
|
||||||
|
// invalid (malformed) JSON string
|
||||||
|
param = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: {
|
||||||
|
required: true,
|
||||||
|
type: "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value = "{{}"
|
||||||
|
assertValidateOas3Param(param, value, ["Parameter string value must be valid JSON"])
|
||||||
|
|
||||||
|
|
||||||
|
// valid (empty object) JSON string
|
||||||
|
param = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: {
|
||||||
|
required: true,
|
||||||
|
type: "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value = "{}"
|
||||||
|
assertValidateOas3Param(param, value, [])
|
||||||
|
|
||||||
|
// valid (empty object) JSON object
|
||||||
|
param = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: {
|
||||||
|
required: true,
|
||||||
|
type: "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value = {}
|
||||||
|
assertValidateOas3Param(param, value, [])
|
||||||
|
|
||||||
|
// should skip JSON validation for non-JSON media types
|
||||||
|
param = {
|
||||||
|
content: {
|
||||||
|
"application/definitely-not-json": {
|
||||||
|
schema: {
|
||||||
|
required: true,
|
||||||
|
type: "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value = "{{}"
|
||||||
|
assertValidateOas3Param(param, value, [])
|
||||||
|
})
|
||||||
|
|
||||||
it("validates required strings with min and max length", function() {
|
it("validates required strings with min and max length", function() {
|
||||||
// invalid string with max length
|
// invalid string with max length
|
||||||
param = {
|
param = {
|
||||||
|
|||||||
Reference in New Issue
Block a user