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:
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(){
|
||||
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user