Просмотр исходного кода

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
bubble
kyle 5 лет назад
committed by GitHub
Родитель
Сommit
75a0e5d5dc
Не найден GPG ключ соответствующий данной подписи Идентификатор GPG ключа: 4AEE18F83AFDEB23
5 измененных файлов: 190 добавлений и 51 удалений
  1. +4
    -3
      src/core/components/parameter-row.jsx
  2. +20
    -15
      src/core/utils.js
  3. +34
    -9
      src/helpers/get-parameter-schema.js
  4. +13
    -7
      test/mocha/core/helpers/get-parameter-schema.js
  5. +119
    -17
      test/mocha/core/utils.js

+ 4
- 3
src/core/components/parameter-row.jsx Просмотреть файл

@@ -41,7 +41,7 @@ export default class ParameterRow extends Component {
let enumValue

if(isOAS3) {
let schema = getParameterSchema(parameterWithMeta, { isOAS3 })
let { schema } = getParameterSchema(parameterWithMeta, { isOAS3 })
enumValue = schema.get("enum")
} else {
enumValue = parameterWithMeta ? parameterWithMeta.get("enum") : undefined
@@ -98,7 +98,7 @@ export default class ParameterRow extends Component {

const paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()

const schema = getParameterSchema(paramWithMeta, { isOAS3: specSelectors.isOAS3() })
const { schema } = getParameterSchema(paramWithMeta, { isOAS3: specSelectors.isOAS3() })

const parameterMediaType = paramWithMeta
.get("content", Map())
@@ -209,9 +209,10 @@ export default class ParameterRow extends Component {
const ExamplesSelectValueRetainer = getComponent("ExamplesSelectValueRetainer")
const Example = getComponent("Example")

let { schema } = getParameterSchema(param, { isOAS3 })
let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()

let format = param.get("format")
let schema = getParameterSchema(param, { isOAS3 })
let type = schema.get("type")
let isFormData = inType === "formData"
let isFormDataSupported = "FormData" in win


+ 20
- 15
src/core/utils.js Просмотреть файл

@@ -501,12 +501,14 @@ export const validatePattern = (val, rxPattern) => {
export const validateParam = (param, value, { isOAS3 = false, bypassRequiredCheck = false } = {}) => {
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

let required = paramDetails.get("required")
let maximum = paramDetails.get("maximum")
let minimum = paramDetails.get("minimum")
let type = paramDetails.get("type")
@@ -520,7 +522,7 @@ export const validateParam = (param, value, { isOAS3 = false, bypassRequiredChec
then we should do our validation routine.
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
let stringCheck = type === "string" && value
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 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 = [
stringCheck, arrayCheck, arrayListCheck, arrayStringCheck, fileCheck,
booleanCheck, numberCheck, integerCheck, objectCheck, objectStringCheck,
@@ -551,11 +542,25 @@ export const validateParam = (param, value, { isOAS3 = false, bypassRequiredChec

const passedAnyCheck = allChecks.some(v => !!v)

if (required && !passedAnyCheck && !bypassRequiredCheck ) {
if ((paramRequired || required) && !passedAnyCheck && !bypassRequiredCheck ) {
errors.push("Required field is not provided")
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) {
let err = validatePattern(value, pattern)
if (err) errors.push(err)


+ 34
- 9
src/helpers/get-parameter-schema.js Просмотреть файл

@@ -23,6 +23,12 @@ const swagger2SchemaKeys = Im.Set.of(
"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
* no suitable schema can be found.
@@ -35,18 +41,29 @@ const swagger2SchemaKeys = Im.Set.of(
* @param {object} config
* @param {boolean} config.isOAS3 Whether the parameter is from an OpenAPI 2.0
* or OpenAPI 3.0 definition
* @return {Immutable.Map} The desired schema
* @return {ParameterSchemaDescriptor} Information about the parameter schema
*/
export default function getParameterSchema(parameter, { isOAS3 } = {}) {
// 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) {
// Swagger 2.0
if (parameter.get("in") === "body") {
return parameter.get("schema", Im.Map())
return {
schema: parameter.get("schema", Im.Map()),
parameterContentMediaType: null,
}
} 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({}))
.keySeq()

return parameter.getIn(
["content", parameterContentMediaTypes.first(), "schema"],
Im.Map()
)
const parameterContentMediaType = parameterContentMediaTypes.first()

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,
}
}

+ 13
- 7
test/mocha/core/helpers/get-parameter-schema.js Просмотреть файл

@@ -3,20 +3,22 @@
*/

import expect from "expect"
import Im, { fromJS } from "immutable"
import { fromJS } from "immutable"
import getParameterSchema from "../../../../src/helpers/get-parameter-schema"

describe("getParameterSchema", () => {
it("should return an empty Map when given no parameters", () => {
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", () => {
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", () => {
@@ -34,12 +36,13 @@ describe("getParameterSchema", () => {
})
)

expect(result.toJS()).toEqual({
expect(result.schema.toJS()).toEqual({
type: "array",
items: {
type: "string",
},
})
expect(result.parameterContentMediaType).toEqual(null)
})

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",
items: {
type: "string",
},
})
expect(result.parameterContentMediaType).toEqual(null)
})

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",
items: {
type: "string",
},
})
expect(result.parameterContentMediaType).toEqual(null)
})

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",
required: ["lat", "long"],
properties: {
@@ -138,5 +143,6 @@ describe("getParameterSchema", () => {
},
},
})
expect(result.parameterContentMediaType).toEqual(`application/json`)
})
})

+ 119
- 17
test/mocha/core/utils.js Просмотреть файл

@@ -414,15 +414,15 @@ describe("utils", function() {
})
assertValidateOas3Param(param, value, [])
// // invalid object-as-string
// param = {
// required: true,
// schema: {
// type: "object"
// }
// }
// value = "{{}"
// assertValidateOas3Param(param, value, ["Parameter string value must be valid JSON"])
// invalid object-as-string
param = {
required: true,
schema: {
type: "object"
}
}
value = "{{}"
assertValidateOas3Param(param, value, ["Parameter string value must be valid JSON"])
// missing when required
param = {
@@ -458,14 +458,14 @@ describe("utils", function() {
})
assertValidateOas3Param(param, value, [])
// // invalid object-as-string
// param = {
// schema: {
// type: "object"
// }
// }
// value = "{{}"
// assertValidateOas3Param(param, value, ["Parameter string value must be valid JSON"])
// invalid object-as-string
param = {
schema: {
type: "object"
}
}
value = "{{}"
assertValidateOas3Param(param, value, ["Parameter string value must be valid JSON"])
// missing when not required
param = {
@@ -505,6 +505,108 @@ describe("utils", function() {
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() {
// invalid string with max length
param = {


Загрузка…
Отмена
Сохранить