Skip to content

Commit c1007a2

Browse files
authored
feature: OAS3 object parameter support (#4563)
* render suitable interface for `type: object` parameters * validate OAS3 object parameters correctly * display parameter validation errors * remove irrelevant css classes * rm comment * fix failing tests * add validateParam tests * add enzyme tests for object parameter rendering * run actual tests first
1 parent c8480a8 commit c1007a2

File tree

6 files changed

+209
-9
lines changed

6 files changed

+209
-9
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"lint": "eslint --cache --ext '.js,.jsx' src test",
2929
"lint-errors": "eslint --cache --quiet --ext '.js,.jsx' src test",
3030
"lint-fix": "eslint --cache --ext '.js,.jsx' src test --fix",
31-
"test": "npm run lint-errors && npm run just-test-in-node",
31+
"test": "npm run just-test-in-node && npm run lint-errors",
3232
"test-in-node": "npm run lint-errors && npm run just-test-in-node",
3333
"just-test": "karma start --config karma.conf.js",
3434
"just-test-in-node": "mocha --require test/setup.js --recursive --compilers js:babel-core/register test/core test/components test/bugs test/swagger-ui-dist-package test/xss",

src/core/json-schema-components.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import React, { PureComponent, Component } from "react"
22
import PropTypes from "prop-types"
33
import { List, fromJS } from "immutable"
4+
import cx from "classnames"
45
import ImPropTypes from "react-immutable-proptypes"
56
import DebounceInput from "react-debounce-input"
7+
import { getSampleSchema } from "core/utils"
68
//import "less/json-schema-form"
79

810
const noop = ()=> {}
@@ -204,3 +206,53 @@ export class JsonSchema_boolean extends Component {
204206
onChange={ this.onEnumChange }/>)
205207
}
206208
}
209+
210+
export class JsonSchema_object extends PureComponent {
211+
constructor() {
212+
super()
213+
}
214+
215+
static propTypes = JsonSchemaPropShape
216+
static defaultProps = JsonSchemaDefaultProps
217+
218+
componentDidMount() {
219+
if(!this.props.value && this.props.schema) {
220+
this.resetValueToSample()
221+
}
222+
}
223+
224+
resetValueToSample = () => {
225+
this.onChange(getSampleSchema(this.props.schema) )
226+
}
227+
228+
onChange = (value) => {
229+
this.props.onChange(value)
230+
}
231+
232+
handleOnChange = e => {
233+
const inputValue = e.target.value
234+
235+
this.onChange(inputValue)
236+
}
237+
238+
render() {
239+
let {
240+
getComponent,
241+
value,
242+
errors
243+
} = this.props
244+
245+
const TextArea = getComponent("TextArea")
246+
247+
return (
248+
<div>
249+
<TextArea
250+
className={cx({ invalid: errors.size })}
251+
title={ errors.size ? errors.join(", ") : ""}
252+
value={value}
253+
onChange={ this.handleOnChange }/>
254+
</div>
255+
)
256+
257+
}
258+
}

src/core/utils.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,30 @@ export const validateParam = (param, isXml, isOAS3 = false) => {
503503
let numberCheck = type === "number" && (value || value === 0)
504504
let integerCheck = type === "integer" && (value || value === 0)
505505

506-
if ( required && !(stringCheck || arrayCheck || listCheck || fileCheck || booleanCheck || numberCheck || integerCheck) ) {
506+
let oas3ObjectCheck = false
507+
508+
if(false || isOAS3 && type === "object") {
509+
if(typeof value === "object") {
510+
oas3ObjectCheck = true
511+
} else if(typeof value === "string") {
512+
try {
513+
JSON.parse(value)
514+
oas3ObjectCheck = true
515+
} catch(e) {
516+
errors.push("Parameter string value must be valid JSON")
517+
return errors
518+
}
519+
}
520+
}
521+
522+
const allChecks = [
523+
stringCheck, arrayCheck, listCheck, fileCheck, booleanCheck,
524+
numberCheck, integerCheck, oas3ObjectCheck
525+
]
526+
527+
const passedAnyCheck = allChecks.some(v => !!v)
528+
529+
if ( required && !passedAnyCheck ) {
507530
errors.push("Required field is not provided")
508531
return errors
509532
}

src/style/_form.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ input[type=text],
5151
input[type=password],
5252
input[type=search],
5353
input[type=email],
54-
input[type=file]
54+
input[type=file],
55+
textarea
5556
{
5657
min-width: 100px;
5758
margin: 5px 0;

test/components/json-schema-form.js

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
/* eslint-env mocha */
22
import React from "react"
3+
import { List } from "immutable"
34
import expect, { createSpy } from "expect"
4-
import { Select, Input } from "components/layout-utils"
5-
import { render } from "enzyme"
5+
import { Select, Input, TextArea } from "components/layout-utils"
6+
import { mount, render } from "enzyme"
67
import * as JsonSchemaComponents from "core/json-schema-components"
78
import { JsonSchemaForm } from "core/json-schema-components"
89

9-
const components = {...JsonSchemaComponents, Select, Input}
10+
const components = {...JsonSchemaComponents, Select, Input, TextArea}
1011

1112
const getComponentStub = (name) => {
1213
if(components[name]) return components[name]
@@ -107,6 +108,38 @@ describe("<JsonSchemaForm/>", function(){
107108
expect(wrapper.find("select option").first().text()).toEqual("true")
108109
})
109110
})
111+
describe("objects", function() {
112+
it("should render the correct editor for an OAS3 object parameter", function(){
113+
let updateQueue = []
114+
115+
let props = {
116+
getComponent: getComponentStub,
117+
value: "",
118+
onChange: (value) => {
119+
updateQueue.push({ value })
120+
},
121+
keyName: "",
122+
fn: {},
123+
errors: List(),
124+
schema: {
125+
type: "object",
126+
properties: {
127+
id: {
128+
type: "string",
129+
example: "abc123"
130+
}
131+
}
132+
}
133+
}
134+
135+
let wrapper = mount(<JsonSchemaForm {...props}/>)
136+
137+
updateQueue.forEach(newProps => wrapper.setProps(newProps))
138+
139+
expect(wrapper.find("textarea").length).toEqual(1)
140+
expect(wrapper.find("textarea").text()).toEqual(`{\n "id": "abc123"\n}`)
141+
})
142+
})
110143
describe("unknown types", function() {
111144
it("should render unknown types as strings", function(){
112145

test/core/utils.js

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,12 @@ describe("utils", function() {
350350
expect( result ).toEqual( expectedError )
351351
}
352352

353+
const assertValidateOas3Param = (param, expectedError) => {
354+
// for cases where you _only_ want to try OAS3
355+
result = validateParam( fromJS(param), false, true )
356+
expect( result ).toEqual( expectedError )
357+
}
358+
353359
it("should check the isOAS3 flag when validating parameters", function() {
354360
// This should "skip" validation because there is no `schema` property
355361
// and we are telling `validateParam` this is an OAS3 spec
@@ -361,6 +367,92 @@ describe("utils", function() {
361367
expect( result ).toEqual( [] )
362368
})
363369

370+
it("validates required OAS3 objects", function() {
371+
// valid object
372+
param = {
373+
required: true,
374+
schema: {
375+
type: "object"
376+
},
377+
value: {
378+
abc: 123
379+
}
380+
}
381+
assertValidateOas3Param(param, [])
382+
383+
// valid object-as-string
384+
param = {
385+
required: true,
386+
schema: {
387+
type: "object"
388+
},
389+
value: JSON.stringify({
390+
abc: 123
391+
})
392+
}
393+
assertValidateOas3Param(param, [])
394+
395+
// invalid object-as-string
396+
param = {
397+
required: true,
398+
schema: {
399+
type: "object"
400+
},
401+
value: "{{}"
402+
}
403+
assertValidateOas3Param(param, ["Parameter string value must be valid JSON"])
404+
405+
// missing when required
406+
param = {
407+
required: true,
408+
schema: {
409+
type: "object"
410+
},
411+
}
412+
assertValidateOas3Param(param, ["Required field is not provided"])
413+
})
414+
415+
it("validates optional OAS3 objects", function() {
416+
// valid object
417+
param = {
418+
schema: {
419+
type: "object"
420+
},
421+
value: {
422+
abc: 123
423+
}
424+
}
425+
assertValidateOas3Param(param, [])
426+
427+
// valid object-as-string
428+
param = {
429+
schema: {
430+
type: "object"
431+
},
432+
value: JSON.stringify({
433+
abc: 123
434+
})
435+
}
436+
assertValidateOas3Param(param, [])
437+
438+
// invalid object-as-string
439+
param = {
440+
schema: {
441+
type: "object"
442+
},
443+
value: "{{}"
444+
}
445+
assertValidateOas3Param(param, ["Parameter string value must be valid JSON"])
446+
447+
// missing when not required
448+
param = {
449+
schema: {
450+
type: "object"
451+
},
452+
}
453+
assertValidateOas3Param(param, [])
454+
})
455+
364456
it("validates required strings", function() {
365457
// invalid string
366458
param = {
@@ -962,7 +1054,7 @@ describe("utils", function() {
9621054
expect(result).toEqual(Map([[ "minimum", "b"]]))
9631055
})
9641056
})
965-
1057+
9661058
describe("deeplyStripKey", function() {
9671059
it("should filter out a specified key", function() {
9681060
const input = {
@@ -1065,8 +1157,7 @@ describe("utils", function() {
10651157
})
10661158

10671159
it("should sanitize a `data:` url", function() {
1068-
const res = sanitizeUrl(`data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGV
1069-
sbG8iKTs8L3NjcmlwdD4=`)
1160+
const res = sanitizeUrl(`data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=`)
10701161

10711162
expect(res).toEqual("about:blank")
10721163
})

0 commit comments

Comments
 (0)