Skip to content

Commit c9c3b23

Browse files
authored
feature: support for Parameter.content (#5571)
* add `getParameterSchema` OAS helper * use `Parameter.content.[firstKey].schema` as schema value when present * `newValue` -> `initialValue` * make `paramWithMeta` a const * add trailing comma to `swagger2SchemaKeys` * refactor `helpers` to a folder * deprecate `src/core/utils.js` in favor of `src/core/helpers/` * support `Parameter.content.[mediaType].schema` in validateParam * reject `null` as an OAS3 object value * expose Fetch errors in the browser console * generate ParameterRow default values based on `content` values * add tests for `getParameterSchema` * remove debugger statement * remove debugger statement * don't apply `generatedSampleValue`s to parameters with `examples` * remove extra semi * disable JSON check in parameter runtime validation * stringify JsonSchema_object textarea values * add Cypress tests * [email protected]
1 parent 24c6473 commit c9c3b23

File tree

11 files changed

+400
-52
lines changed

11 files changed

+400
-52
lines changed

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
"remarkable": "^1.7.4",
7878
"reselect": "^2.5.4",
7979
"serialize-error": "^2.1.0",
80-
"swagger-client": "^3.9.3",
80+
"swagger-client": "^3.9.4",
8181
"url-parse": "^1.4.7",
8282
"xml-but-prettier": "^1.0.1",
8383
"zenscroll": "^4.0.2"

src/core/components/parameter-row.jsx

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { Map, List } from "immutable"
33
import PropTypes from "prop-types"
44
import ImPropTypes from "react-immutable-proptypes"
55
import win from "core/window"
6-
import { getExtensions, getCommonExtensions, numberToString, stringify } from "core/utils"
6+
import { getSampleSchema, getExtensions, getCommonExtensions, numberToString, stringify } from "core/utils"
7+
import getParameterSchema from "../../helpers/get-parameter-schema.js"
78

89
export default class ParameterRow extends Component {
910
static propTypes = {
@@ -40,7 +41,7 @@ export default class ParameterRow extends Component {
4041
let enumValue
4142

4243
if(isOAS3) {
43-
let schema = parameterWithMeta.get("schema") || Map()
44+
let schema = getParameterSchema(parameterWithMeta, { isOAS3 })
4445
enumValue = schema.get("enum")
4546
} else {
4647
enumValue = parameterWithMeta ? parameterWithMeta.get("enum") : undefined
@@ -95,30 +96,68 @@ export default class ParameterRow extends Component {
9596
setDefaultValue = () => {
9697
let { specSelectors, pathMethod, rawParam, oas3Selectors } = this.props
9798

98-
let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()
99+
const paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()
100+
101+
const schema = getParameterSchema(paramWithMeta, { isOAS3: specSelectors.isOAS3() })
102+
103+
const parameterMediaType = paramWithMeta
104+
.get("content", Map())
105+
.keySeq()
106+
.first()
107+
108+
const generatedSampleValue = getSampleSchema(schema.toJS(), parameterMediaType, {
109+
includeWriteOnly: true
110+
})
99111

100112
if (!paramWithMeta || paramWithMeta.get("value") !== undefined) {
101113
return
102114
}
103115

104116
if( paramWithMeta.get("in") !== "body" ) {
105-
let newValue
117+
let initialValue
118+
119+
//// Find an initial value
106120

107121
if (specSelectors.isSwagger2()) {
108-
newValue = paramWithMeta.get("x-example")
109-
|| paramWithMeta.getIn(["default"])
122+
initialValue = paramWithMeta.get("x-example")
110123
|| paramWithMeta.getIn(["schema", "example"])
111-
|| paramWithMeta.getIn(["schema", "default"])
124+
|| schema.getIn(["default"])
112125
} else if (specSelectors.isOAS3()) {
113126
const currentExampleKey = oas3Selectors.activeExamplesMember(...pathMethod, "parameters", this.getParamKey())
114-
newValue = paramWithMeta.getIn(["examples", currentExampleKey, "value"])
127+
initialValue = paramWithMeta.getIn(["examples", currentExampleKey, "value"])
128+
|| paramWithMeta.getIn(["content", parameterMediaType, "example"])
115129
|| paramWithMeta.get("example")
116-
|| paramWithMeta.getIn(["schema", "example"])
117-
|| paramWithMeta.getIn(["schema", "default"])
130+
|| schema.get("example")
131+
|| schema.get("default")
118132
}
119-
if(newValue !== undefined) {
133+
134+
//// Process the initial value
135+
136+
if(initialValue !== undefined && !List.isList(initialValue)) {
137+
// Stringify if it isn't a List
138+
initialValue = stringify(initialValue)
139+
}
140+
141+
//// Dispatch the initial value
142+
143+
if(initialValue !== undefined) {
144+
this.onChangeWrapper(initialValue)
145+
} else if(
146+
schema.get("type") === "object"
147+
&& generatedSampleValue
148+
&& !paramWithMeta.get("examples")
149+
) {
150+
// Object parameters get special treatment.. if the user doesn't set any
151+
// default or example values, we'll provide initial values generated from
152+
// the schema.
153+
// However, if `examples` exist for the parameter, we won't do anything,
154+
// so that the appropriate `examples` logic can take over.
120155
this.onChangeWrapper(
121-
List.isList(newValue) ? newValue : stringify(newValue)
156+
List.isList(generatedSampleValue) ? (
157+
generatedSampleValue
158+
) : (
159+
stringify(generatedSampleValue)
160+
)
122161
)
123162
}
124163
}
@@ -171,7 +210,7 @@ export default class ParameterRow extends Component {
171210

172211
let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()
173212
let format = param.get("format")
174-
let schema = isOAS3 ? param.get("schema") : param
213+
let schema = getParameterSchema(param, { isOAS3 })
175214
let type = schema.get("type")
176215
let isFormData = inType === "formData"
177216
let isFormDataSupported = "FormData" in win
@@ -285,7 +324,7 @@ export default class ParameterRow extends Component {
285324
getConfigs={ getConfigs }
286325
isExecute={ isExecute }
287326
specSelectors={ specSelectors }
288-
schema={ param.get("schema") }
327+
schema={ schema }
289328
example={ bodyParam }/>
290329
: null
291330
}

src/core/json-schema-components.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { List, fromJS } from "immutable"
44
import cx from "classnames"
55
import ImPropTypes from "react-immutable-proptypes"
66
import DebounceInput from "react-debounce-input"
7+
import { stringify } from "core/utils"
78
//import "less/json-schema-form"
89

910
const noop = ()=> {}
@@ -269,7 +270,7 @@ export class JsonSchema_object extends PureComponent {
269270
<TextArea
270271
className={cx({ invalid: errors.size })}
271272
title={ errors.size ? errors.join(", ") : ""}
272-
value={value}
273+
value={stringify(value)}
273274
disabled={disabled}
274275
onChange={ this.handleOnChange }/>
275276
</div>

src/core/plugins/spec/actions.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -436,9 +436,12 @@ export const executeRequest = (req) =>
436436
specActions.setResponse(req.pathName, req.method, res)
437437
} )
438438
.catch(
439-
err => specActions.setResponse(req.pathName, req.method, {
440-
error: true, err: serializeError(err)
441-
})
439+
err => {
440+
console.error(err)
441+
specActions.setResponse(req.pathName, req.method, {
442+
error: true, err: serializeError(err)
443+
})
444+
}
442445
)
443446
}
444447

src/core/utils.js

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
/*
2+
ATTENTION! This file (but not the functions within) is deprecated.
3+
4+
You should probably add a new file to `./helpers/` instead of adding a new
5+
function here.
6+
7+
One-function-per-file is a better pattern than what we have here.
8+
9+
If you're refactoring something in here, feel free to break it out to a file
10+
in `./helpers` if you have the time.
11+
*/
12+
113
import Im from "immutable"
214
import { sanitizeUrl as braintreeSanitizeUrl } from "@braintree/sanitize-url"
315
import camelCase from "lodash/camelCase"
@@ -9,6 +21,7 @@ import eq from "lodash/eq"
921
import { memoizedSampleFromSchema, memoizedCreateXMLExample } from "core/plugins/samples/fn"
1022
import win from "./window"
1123
import cssEscape from "css.escape"
24+
import getParameterSchema from "../helpers/get-parameter-schema"
1225

1326
const DEFAULT_RESPONSE_KEY = "default"
1427

@@ -488,7 +501,7 @@ export const validateParam = (param, value, { isOAS3 = false, bypassRequiredChec
488501
let errors = []
489502
let required = param.get("required")
490503

491-
let paramDetails = isOAS3 ? param.get("schema") : param
504+
let paramDetails = getParameterSchema(param, { isOAS3 })
492505

493506
if(!paramDetails) return errors
494507

@@ -517,18 +530,23 @@ export const validateParam = (param, value, { isOAS3 = false, bypassRequiredChec
517530

518531
let oas3ObjectCheck = false
519532

520-
if(false || isOAS3 && type === "object") {
521-
if(typeof value === "object") {
533+
if(isOAS3 && type === "object") {
534+
if(typeof value === "object" && value !== null) {
522535
oas3ObjectCheck = true
523536
} else if(typeof value === "string") {
524-
try {
525-
JSON.parse(value)
526-
oas3ObjectCheck = true
527-
} catch(e) {
528-
errors.push("Parameter string value must be valid JSON")
529-
return errors
530-
}
537+
oas3ObjectCheck = true
531538
}
539+
// Disabled because `validateParam` doesn't consider the MediaType of the
540+
// `Parameter.content` hint correctly.
541+
// } else if(typeof value === "string") {
542+
// try {
543+
// JSON.parse(value)
544+
// oas3ObjectCheck = true
545+
// } catch(e) {
546+
// errors.push("Parameter string value must be valid JSON")
547+
// return errors
548+
// }
549+
// }
532550
}
533551

534552
const allChecks = [
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* @prettier
3+
*/
4+
5+
import Im from "immutable"
6+
7+
const swagger2SchemaKeys = Im.Set.of(
8+
"type",
9+
"format",
10+
"items",
11+
"default",
12+
"maximum",
13+
"exclusiveMaximum",
14+
"minimum",
15+
"exclusiveMinimum",
16+
"maxLength",
17+
"minLength",
18+
"pattern",
19+
"maxItems",
20+
"minItems",
21+
"uniqueItems",
22+
"enum",
23+
"multipleOf"
24+
)
25+
26+
/**
27+
* Get the effective schema value for a parameter, or an empty Immutable.Map if
28+
* no suitable schema can be found.
29+
*
30+
* Supports OpenAPI 3.0 `Parameter.content` priority -- since a Parameter Object
31+
* cannot have both `schema` and `content`, this function ignores `schema` when
32+
* `content` is present.
33+
*
34+
* @param {Immutable.Map} parameter The parameter to identify a schema for
35+
* @param {object} config
36+
* @param {boolean} config.isOAS3 Whether the parameter is from an OpenAPI 2.0
37+
* or OpenAPI 3.0 definition
38+
* @return {Immutable.Map} The desired schema
39+
*/
40+
export default function getParameterSchema(parameter, { isOAS3 } = {}) {
41+
// Return empty Map if `parameter` isn't a Map
42+
if (!Im.Map.isMap(parameter)) return Im.Map()
43+
44+
if (!isOAS3) {
45+
// Swagger 2.0
46+
if (parameter.get("in") === "body") {
47+
return parameter.get("schema", Im.Map())
48+
} else {
49+
return parameter.filter((v, k) => swagger2SchemaKeys.includes(k))
50+
}
51+
}
52+
53+
// If we've reached here, the parameter is OpenAPI 3.0
54+
55+
if (parameter.get("content")) {
56+
const parameterContentMediaTypes = parameter
57+
.get("content", Im.Map({}))
58+
.keySeq()
59+
60+
return parameter.getIn(
61+
["content", parameterContentMediaTypes.first(), "schema"],
62+
Im.Map()
63+
)
64+
}
65+
66+
return parameter.get("schema", Im.Map())
67+
}

0 commit comments

Comments
 (0)