Browse Source

OAS3 Accept header control: Component-side

bubble
Kyle Shockey 7 years ago
parent
commit
26edaa5f0b
6 changed files with 169 additions and 11 deletions
  1. +1
    -0
      package.json
  2. +17
    -7
      src/core/components/response.jsx
  3. +18
    -2
      src/core/components/responses.jsx
  4. +23
    -0
      src/core/utils.js
  5. +11
    -0
      src/style/_layout.scss
  6. +99
    -2
      test/core/utils.js

+ 1
- 0
package.json View File

@@ -41,6 +41,7 @@
"dependencies": { "dependencies": {
"base64-js": "^1.2.0", "base64-js": "^1.2.0",
"brace": "0.7.0", "brace": "0.7.0",
"classnames": "^2.2.5",
"deep-extend": "0.4.1", "deep-extend": "0.4.1",
"expect": "1.20.2", "expect": "1.20.2",
"getbase": "^2.8.2", "getbase": "^2.8.2",


+ 17
- 7
src/core/components/response.jsx View File

@@ -1,5 +1,6 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import cx from "classnames"
import { fromJS, Seq } from "immutable" import { fromJS, Seq } from "immutable"
import { getSampleSchema, fromJSOrdered } from "core/utils" import { getSampleSchema, fromJSOrdered } from "core/utils"


@@ -46,7 +47,8 @@ export default class Response extends React.Component {
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired, fn: PropTypes.object.isRequired,
contentType: PropTypes.string
contentType: PropTypes.string,
controlsAcceptHeader: PropTypes.bool
} }


static defaultProps = { static defaultProps = {
@@ -61,7 +63,8 @@ export default class Response extends React.Component {
fn, fn,
getComponent, getComponent,
specSelectors, specSelectors,
contentType
contentType,
controlsAcceptHeader
} = this.props } = this.props


let { inferSchema } = fn let { inferSchema } = fn
@@ -106,11 +109,18 @@ export default class Response extends React.Component {
<Markdown source={ response.get( "description" ) } /> <Markdown source={ response.get( "description" ) } />
</div> </div>


{ isOAS3 ? <ContentType
value={this.state.responseContentType}
contentTypes={ response.get("content") ? response.get("content").keySeq() : Seq() }
onChange={(val) => this.setState({ responseContentType: val })}
className="response-content-type" /> : null }
{ isOAS3 ?
<div className={cx("response-content-type", {
"controls-accept-header": controlsAcceptHeader
})}>
<ContentType
value={this.state.responseContentType}
contentTypes={ response.get("content") ? response.get("content").keySeq() : Seq() }
onChange={(val) => this.setState({ responseContentType: val })}
/>
{ controlsAcceptHeader ? <small>Controls <code>Accept</code> header.</small> : null }
</div>
: null }


{ example ? ( { example ? (
<ModelExample <ModelExample


+ 18
- 2
src/core/components/responses.jsx View File

@@ -1,7 +1,7 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { fromJS } from "immutable" import { fromJS } from "immutable"
import { defaultStatusCode } from "core/utils"
import { defaultStatusCode, getAcceptControllingResponse } from "core/utils"


export default class Responses extends React.Component { export default class Responses extends React.Component {


@@ -30,7 +30,17 @@ export default class Responses extends React.Component {
onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue(this.props.pathMethod, val) onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue(this.props.pathMethod, val)


render() { render() {
let { responses, request, tryItOutResponse, getComponent, getConfigs, specSelectors, fn, producesValue, displayRequestDuration } = this.props
let {
responses,
request,
tryItOutResponse,
getComponent,
getConfigs,
specSelectors,
fn,
producesValue,
displayRequestDuration
} = this.props
let defaultCode = defaultStatusCode( responses ) let defaultCode = defaultStatusCode( responses )


const ContentType = getComponent( "contentType" ) const ContentType = getComponent( "contentType" )
@@ -39,6 +49,11 @@ export default class Responses extends React.Component {


let produces = this.props.produces && this.props.produces.size ? this.props.produces : Responses.defaultProps.produces let produces = this.props.produces && this.props.produces.size ? this.props.produces : Responses.defaultProps.produces


const isSpecOAS3 = specSelectors.isOAS3()

const acceptControllingResponse = isSpecOAS3 ?
getAcceptControllingResponse(responses) : null

return ( return (
<div className="responses-wrapper"> <div className="responses-wrapper">
<div className="opblock-section-header"> <div className="opblock-section-header">
@@ -88,6 +103,7 @@ export default class Responses extends React.Component {
code={ code } code={ code }
response={ response } response={ response }
specSelectors={ specSelectors } specSelectors={ specSelectors }
controlsAcceptHeader={response === acceptControllingResponse}
contentType={ producesValue } contentType={ producesValue }
getComponent={ getComponent }/> getComponent={ getComponent }/>
) )


+ 23
- 0
src/core/utils.js View File

@@ -650,3 +650,26 @@ export const shallowEqualKeys = (a,b, keys) => {
return eq(a[key], b[key]) return eq(a[key], b[key])
}) })
} }

export function getAcceptControllingResponse(responses) {
if(!Im.OrderedMap.isOrderedMap(responses)) {
// wrong type!
return null
}

if(!responses.size) {
// responses is empty
return null
}

const suitable2xxResponse = responses.find((res, k) => {
return k.startsWith("2") && Object.keys(res.get("content") || {}).length > 0
})

// try to find a suitable `default` responses
const defaultResponse = responses.get("default") || Im.OrderedMap()
const defaultResponseMediaTypes = (defaultResponse.get("content") || Im.OrderedMap()).keySeq().toJS()
const suitableDefaultResponse = defaultResponseMediaTypes.length ? defaultResponse : null

return suitable2xxResponse || suitableDefaultResponse
}

+ 11
- 0
src/style/_layout.scss View File

@@ -775,6 +775,17 @@


.response-content-type { .response-content-type {
padding-top: 1em; padding-top: 1em;

&.controls-accept-header {
select {
border-color: green;
}

small {
color: green;
font-size: .7em;
}
}
} }


@keyframes blinker @keyframes blinker


+ 99
- 2
test/core/utils.js View File

@@ -1,7 +1,7 @@
/* eslint-env mocha */ /* eslint-env mocha */
import expect from "expect" import expect from "expect"
import { fromJS } from "immutable"
import { mapToList, validateNumber, validateInteger, validateParam, validateFile, fromJSOrdered } from "core/utils"
import { fromJS, OrderedMap } from "immutable"
import { mapToList, validateNumber, validateInteger, validateParam, validateFile, fromJSOrdered, getAcceptControllingResponse } from "core/utils"
import win from "core/window" import win from "core/window"


describe("utils", function() { describe("utils", function() {
@@ -581,5 +581,102 @@ describe("utils", function() {
const result = fromJSOrdered(param).toJS() const result = fromJSOrdered(param).toJS()
expect( result ).toEqual( [1, 1, 2, 3, 5, 8] ) expect( result ).toEqual( [1, 1, 2, 3, 5, 8] )
}) })
})

describe.only("getAcceptControllingResponse", () => {
it("should return the first 2xx response with a media type", () => {
const responses = fromJSOrdered({
"200": {
content: {
"application/json": {
schema: {
type: "object"
}
}
}
},
"201": {
content: {
"application/json": {
schema: {
type: "object"
}
}
}
}
})

expect(getAcceptControllingResponse(responses)).toEqual(responses.get("200"))
}) })
it("should skip 2xx responses without defined media types", () => {
const responses = fromJSOrdered({
"200": {
content: {
"application/json": {
schema: {
type: "object"
}
}
}
},
"201": {
content: {
"application/json": {
schema: {
type: "object"
}
}
}
}
})

expect(getAcceptControllingResponse(responses)).toEqual(responses.get("201"))
})
it("should default to the `default` response if it has defined media types", () => {
const responses = fromJSOrdered({
"200": {
description: "quite empty"
},
"201": {
description: "quite empty"
},
default: {
content: {
"application/json": {
schema: {
type: "object"
}
}
}
}
})

expect(getAcceptControllingResponse(responses)).toEqual(responses.get("default"))
})
it("should return null if there are no suitable controlling responses", () => {
const responses = fromJSOrdered({
"200": {
description: "quite empty"
},
"201": {
description: "quite empty"
},
"default": {
description: "also empty.."
}
})

expect(getAcceptControllingResponse(responses)).toBe(null)
})
it("should return null if an empty OrderedMap is passed", () => {
const responses = fromJSOrdered()

expect(getAcceptControllingResponse(responses)).toBe(null)
})
it("should return null if anything except an OrderedMap is passed", () => {
const responses = {}

expect(getAcceptControllingResponse(responses)).toBe(null)
})
})
}) })

Loading…
Cancel
Save