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
This commit is contained in:
kyle
2018-05-16 22:48:44 -07:00
committed by GitHub
parent c8480a827a
commit c1007a287b
6 changed files with 209 additions and 9 deletions

View File

@@ -28,7 +28,7 @@
"lint": "eslint --cache --ext '.js,.jsx' src test",
"lint-errors": "eslint --cache --quiet --ext '.js,.jsx' src test",
"lint-fix": "eslint --cache --ext '.js,.jsx' src test --fix",
"test": "npm run lint-errors && npm run just-test-in-node",
"test": "npm run just-test-in-node && npm run lint-errors",
"test-in-node": "npm run lint-errors && npm run just-test-in-node",
"just-test": "karma start --config karma.conf.js",
"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",

View File

@@ -1,8 +1,10 @@
import React, { PureComponent, Component } from "react"
import PropTypes from "prop-types"
import { List, fromJS } from "immutable"
import cx from "classnames"
import ImPropTypes from "react-immutable-proptypes"
import DebounceInput from "react-debounce-input"
import { getSampleSchema } from "core/utils"
//import "less/json-schema-form"
const noop = ()=> {}
@@ -204,3 +206,53 @@ export class JsonSchema_boolean extends Component {
onChange={ this.onEnumChange }/>)
}
}
export class JsonSchema_object extends PureComponent {
constructor() {
super()
}
static propTypes = JsonSchemaPropShape
static defaultProps = JsonSchemaDefaultProps
componentDidMount() {
if(!this.props.value && this.props.schema) {
this.resetValueToSample()
}
}
resetValueToSample = () => {
this.onChange(getSampleSchema(this.props.schema) )
}
onChange = (value) => {
this.props.onChange(value)
}
handleOnChange = e => {
const inputValue = e.target.value
this.onChange(inputValue)
}
render() {
let {
getComponent,
value,
errors
} = this.props
const TextArea = getComponent("TextArea")
return (
<div>
<TextArea
className={cx({ invalid: errors.size })}
title={ errors.size ? errors.join(", ") : ""}
value={value}
onChange={ this.handleOnChange }/>
</div>
)
}
}

View File

@@ -503,7 +503,30 @@ export const validateParam = (param, isXml, isOAS3 = false) => {
let numberCheck = type === "number" && (value || value === 0)
let integerCheck = type === "integer" && (value || value === 0)
if ( required && !(stringCheck || arrayCheck || listCheck || fileCheck || booleanCheck || numberCheck || integerCheck) ) {
let oas3ObjectCheck = false
if(false || isOAS3 && type === "object") {
if(typeof value === "object") {
oas3ObjectCheck = true
} else if(typeof value === "string") {
try {
JSON.parse(value)
oas3ObjectCheck = true
} catch(e) {
errors.push("Parameter string value must be valid JSON")
return errors
}
}
}
const allChecks = [
stringCheck, arrayCheck, listCheck, fileCheck, booleanCheck,
numberCheck, integerCheck, oas3ObjectCheck
]
const passedAnyCheck = allChecks.some(v => !!v)
if ( required && !passedAnyCheck ) {
errors.push("Required field is not provided")
return errors
}

View File

@@ -51,7 +51,8 @@ input[type=text],
input[type=password],
input[type=search],
input[type=email],
input[type=file]
input[type=file],
textarea
{
min-width: 100px;
margin: 5px 0;

View File

@@ -1,12 +1,13 @@
/* eslint-env mocha */
import React from "react"
import { List } from "immutable"
import expect, { createSpy } from "expect"
import { Select, Input } from "components/layout-utils"
import { render } from "enzyme"
import { Select, Input, TextArea } from "components/layout-utils"
import { mount, render } from "enzyme"
import * as JsonSchemaComponents from "core/json-schema-components"
import { JsonSchemaForm } from "core/json-schema-components"
const components = {...JsonSchemaComponents, Select, Input}
const components = {...JsonSchemaComponents, Select, Input, TextArea}
const getComponentStub = (name) => {
if(components[name]) return components[name]
@@ -107,6 +108,38 @@ describe("<JsonSchemaForm/>", function(){
expect(wrapper.find("select option").first().text()).toEqual("true")
})
})
describe("objects", function() {
it("should render the correct editor for an OAS3 object parameter", function(){
let updateQueue = []
let props = {
getComponent: getComponentStub,
value: "",
onChange: (value) => {
updateQueue.push({ value })
},
keyName: "",
fn: {},
errors: List(),
schema: {
type: "object",
properties: {
id: {
type: "string",
example: "abc123"
}
}
}
}
let wrapper = mount(<JsonSchemaForm {...props}/>)
updateQueue.forEach(newProps => wrapper.setProps(newProps))
expect(wrapper.find("textarea").length).toEqual(1)
expect(wrapper.find("textarea").text()).toEqual(`{\n "id": "abc123"\n}`)
})
})
describe("unknown types", function() {
it("should render unknown types as strings", function(){

View File

@@ -350,6 +350,12 @@ describe("utils", function() {
expect( result ).toEqual( expectedError )
}
const assertValidateOas3Param = (param, expectedError) => {
// for cases where you _only_ want to try OAS3
result = validateParam( fromJS(param), false, true )
expect( result ).toEqual( expectedError )
}
it("should check the isOAS3 flag when validating parameters", function() {
// This should "skip" validation because there is no `schema` property
// and we are telling `validateParam` this is an OAS3 spec
@@ -361,6 +367,92 @@ describe("utils", function() {
expect( result ).toEqual( [] )
})
it("validates required OAS3 objects", function() {
// valid object
param = {
required: true,
schema: {
type: "object"
},
value: {
abc: 123
}
}
assertValidateOas3Param(param, [])
// valid object-as-string
param = {
required: true,
schema: {
type: "object"
},
value: JSON.stringify({
abc: 123
})
}
assertValidateOas3Param(param, [])
// invalid object-as-string
param = {
required: true,
schema: {
type: "object"
},
value: "{{}"
}
assertValidateOas3Param(param, ["Parameter string value must be valid JSON"])
// missing when required
param = {
required: true,
schema: {
type: "object"
},
}
assertValidateOas3Param(param, ["Required field is not provided"])
})
it("validates optional OAS3 objects", function() {
// valid object
param = {
schema: {
type: "object"
},
value: {
abc: 123
}
}
assertValidateOas3Param(param, [])
// valid object-as-string
param = {
schema: {
type: "object"
},
value: JSON.stringify({
abc: 123
})
}
assertValidateOas3Param(param, [])
// invalid object-as-string
param = {
schema: {
type: "object"
},
value: "{{}"
}
assertValidateOas3Param(param, ["Parameter string value must be valid JSON"])
// missing when not required
param = {
schema: {
type: "object"
},
}
assertValidateOas3Param(param, [])
})
it("validates required strings", function() {
// invalid string
param = {
@@ -962,7 +1054,7 @@ describe("utils", function() {
expect(result).toEqual(Map([[ "minimum", "b"]]))
})
})
describe("deeplyStripKey", function() {
it("should filter out a specified key", function() {
const input = {
@@ -1065,8 +1157,7 @@ describe("utils", function() {
})
it("should sanitize a `data:` url", function() {
const res = sanitizeUrl(`data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGV
sbG8iKTs8L3NjcmlwdD4=`)
const res = sanitizeUrl(`data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=`)
expect(res).toEqual("about:blank")
})