Skip to content

Commit 75a0e5d

Browse files
authored
fix(validateParam): validate JSON parameter values + support Parameter.content (swagger-api#5657)
* improve(getParameterSchema): ParameterSchemaDescriptor pattern * chore: update usage of `getParameterSchema` * consider `Parameter.content` media type when validating JSON values
1 parent 71a17f8 commit 75a0e5d

File tree

5 files changed

+190
-51
lines changed

5 files changed

+190
-51
lines changed

src/core/components/parameter-row.jsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export default class ParameterRow extends Component {
4141
let enumValue
4242

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

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

101-
const schema = getParameterSchema(paramWithMeta, { isOAS3: specSelectors.isOAS3() })
101+
const { schema } = getParameterSchema(paramWithMeta, { isOAS3: specSelectors.isOAS3() })
102102

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

212+
let { schema } = getParameterSchema(param, { isOAS3 })
212213
let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()
214+
213215
let format = param.get("format")
214-
let schema = getParameterSchema(param, { isOAS3 })
215216
let type = schema.get("type")
216217
let isFormData = inType === "formData"
217218
let isFormDataSupported = "FormData" in win

src/core/utils.js

+20-15
Original file line numberDiff line numberDiff line change
@@ -501,12 +501,14 @@ export const validatePattern = (val, rxPattern) => {
501501
export const validateParam = (param, value, { isOAS3 = false, bypassRequiredCheck = false } = {}) => {
502502

503503
let errors = []
504-
let required = param.get("required")
505504

506-
let paramDetails = getParameterSchema(param, { isOAS3 })
505+
let paramRequired = param.get("required")
506+
507+
let { schema: paramDetails, parameterContentMediaType } = getParameterSchema(param, { isOAS3 })
507508

508509
if(!paramDetails) return errors
509510

511+
let required = paramDetails.get("required")
510512
let maximum = paramDetails.get("maximum")
511513
let minimum = paramDetails.get("minimum")
512514
let type = paramDetails.get("type")
@@ -520,7 +522,7 @@ export const validateParam = (param, value, { isOAS3 = false, bypassRequiredChec
520522
then we should do our validation routine.
521523
Only bother validating the parameter if the type was specified.
522524
*/
523-
if ( type && (required || value) ) {
525+
if ( type && (paramRequired || required || value) ) {
524526
// These checks should evaluate to true if there is a parameter
525527
let stringCheck = type === "string" && value
526528
let arrayCheck = type === "array" && Array.isArray(value) && value.length
@@ -533,29 +535,32 @@ export const validateParam = (param, value, { isOAS3 = false, bypassRequiredChec
533535
let objectCheck = type === "object" && typeof value === "object" && value !== null
534536
let objectStringCheck = type === "object" && typeof value === "string" && value
535537

536-
// if(type === "object" && typeof value === "string") {
537-
// // Disabled because `validateParam` doesn't consider the MediaType of the
538-
// // `Parameter.content` hint correctly.
539-
// try {
540-
// JSON.parse(value)
541-
// } catch(e) {
542-
// errors.push("Parameter string value must be valid JSON")
543-
// return errors
544-
// }
545-
// }
546-
547538
const allChecks = [
548539
stringCheck, arrayCheck, arrayListCheck, arrayStringCheck, fileCheck,
549540
booleanCheck, numberCheck, integerCheck, objectCheck, objectStringCheck,
550541
]
551542

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

554-
if (required && !passedAnyCheck && !bypassRequiredCheck ) {
545+
if ((paramRequired || required) && !passedAnyCheck && !bypassRequiredCheck ) {
555546
errors.push("Required field is not provided")
556547
return errors
557548
}
558549

550+
if (
551+
type === "object" &&
552+
typeof value === "string" &&
553+
(parameterContentMediaType === null ||
554+
parameterContentMediaType === "application/json")
555+
) {
556+
try {
557+
JSON.parse(value)
558+
} catch (e) {
559+
errors.push("Parameter string value must be valid JSON")
560+
return errors
561+
}
562+
}
563+
559564
if (pattern) {
560565
let err = validatePattern(value, pattern)
561566
if (err) errors.push(err)

src/helpers/get-parameter-schema.js

+34-9
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ const swagger2SchemaKeys = Im.Set.of(
2323
"multipleOf"
2424
)
2525

26+
/**
27+
* @typedef {Object} ParameterSchemaDescriptor
28+
* @property {Immutable.Map} schema - the parameter schema
29+
* @property {string|null} parameterContentMediaType - the effective media type, for `content`-based OpenAPI 3.0 Parameters, or `null` otherwise
30+
*/
31+
2632
/**
2733
* Get the effective schema value for a parameter, or an empty Immutable.Map if
2834
* no suitable schema can be found.
@@ -35,18 +41,29 @@ const swagger2SchemaKeys = Im.Set.of(
3541
* @param {object} config
3642
* @param {boolean} config.isOAS3 Whether the parameter is from an OpenAPI 2.0
3743
* or OpenAPI 3.0 definition
38-
* @return {Immutable.Map} The desired schema
44+
* @return {ParameterSchemaDescriptor} Information about the parameter schema
3945
*/
4046
export default function getParameterSchema(parameter, { isOAS3 } = {}) {
4147
// Return empty Map if `parameter` isn't a Map
42-
if (!Im.Map.isMap(parameter)) return Im.Map()
48+
if (!Im.Map.isMap(parameter)) {
49+
return {
50+
schema: Im.Map(),
51+
parameterContentMediaType: null,
52+
}
53+
}
4354

4455
if (!isOAS3) {
4556
// Swagger 2.0
4657
if (parameter.get("in") === "body") {
47-
return parameter.get("schema", Im.Map())
58+
return {
59+
schema: parameter.get("schema", Im.Map()),
60+
parameterContentMediaType: null,
61+
}
4862
} else {
49-
return parameter.filter((v, k) => swagger2SchemaKeys.includes(k))
63+
return {
64+
schema: parameter.filter((v, k) => swagger2SchemaKeys.includes(k)),
65+
parameterContentMediaType: null,
66+
}
5067
}
5168
}
5269

@@ -57,11 +74,19 @@ export default function getParameterSchema(parameter, { isOAS3 } = {}) {
5774
.get("content", Im.Map({}))
5875
.keySeq()
5976

60-
return parameter.getIn(
61-
["content", parameterContentMediaTypes.first(), "schema"],
62-
Im.Map()
63-
)
77+
const parameterContentMediaType = parameterContentMediaTypes.first()
78+
79+
return {
80+
schema: parameter.getIn(
81+
["content", parameterContentMediaType, "schema"],
82+
Im.Map()
83+
),
84+
parameterContentMediaType,
85+
}
6486
}
6587

66-
return parameter.get("schema", Im.Map())
88+
return {
89+
schema: parameter.get("schema", Im.Map()),
90+
parameterContentMediaType: null,
91+
}
6792
}

test/mocha/core/helpers/get-parameter-schema.js

+13-7
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,22 @@
33
*/
44

55
import expect from "expect"
6-
import Im, { fromJS } from "immutable"
6+
import { fromJS } from "immutable"
77
import getParameterSchema from "../../../../src/helpers/get-parameter-schema"
88

99
describe("getParameterSchema", () => {
1010
it("should return an empty Map when given no parameters", () => {
1111
const result = getParameterSchema()
1212

13-
expect(result).toEqual(fromJS({}))
13+
expect(result.schema.toJS()).toEqual({})
14+
expect(result.parameterContentMediaType).toEqual(null)
1415
})
1516

1617
it("should return an empty Map when given an empty Map", () => {
1718
const result = getParameterSchema(fromJS({}))
1819

19-
expect(result).toEqual(fromJS({}))
20+
expect(result.schema.toJS()).toEqual({})
21+
expect(result.parameterContentMediaType).toEqual(null)
2022
})
2123

2224
it("should return a schema for a Swagger 2.0 query parameter", () => {
@@ -34,12 +36,13 @@ describe("getParameterSchema", () => {
3436
})
3537
)
3638

37-
expect(result.toJS()).toEqual({
39+
expect(result.schema.toJS()).toEqual({
3840
type: "array",
3941
items: {
4042
type: "string",
4143
},
4244
})
45+
expect(result.parameterContentMediaType).toEqual(null)
4346
})
4447

4548
it("should return a schema for a Swagger 2.0 body parameter", () => {
@@ -58,12 +61,13 @@ describe("getParameterSchema", () => {
5861
})
5962
)
6063

61-
expect(result.toJS()).toEqual({
64+
expect(result.schema.toJS()).toEqual({
6265
type: "array",
6366
items: {
6467
type: "string",
6568
},
6669
})
70+
expect(result.parameterContentMediaType).toEqual(null)
6771
})
6872

6973
it("should return a schema for an OpenAPI 3.0 query parameter", () => {
@@ -87,12 +91,13 @@ describe("getParameterSchema", () => {
8791
}
8892
)
8993

90-
expect(result.toJS()).toEqual({
94+
expect(result.schema.toJS()).toEqual({
9195
type: "array",
9296
items: {
9397
type: "string",
9498
},
9599
})
100+
expect(result.parameterContentMediaType).toEqual(null)
96101
})
97102

98103
it("should return a schema for an OpenAPI 3.0 query parameter with `content`", () => {
@@ -126,7 +131,7 @@ describe("getParameterSchema", () => {
126131
}
127132
)
128133

129-
expect(result.toJS()).toEqual({
134+
expect(result.schema.toJS()).toEqual({
130135
type: "object",
131136
required: ["lat", "long"],
132137
properties: {
@@ -138,5 +143,6 @@ describe("getParameterSchema", () => {
138143
},
139144
},
140145
})
146+
expect(result.parameterContentMediaType).toEqual(`application/json`)
141147
})
142148
})

0 commit comments

Comments
 (0)