@@ -41,6 +41,7 @@ | |||
"dependencies": { | |||
"base64-js": "^1.2.0", | |||
"brace": "0.7.0", | |||
"classnames": "^2.2.5", | |||
"deep-extend": "0.4.1", | |||
"expect": "1.20.2", | |||
"getbase": "^2.8.2", | |||
@@ -1,5 +1,6 @@ | |||
import React from "react" | |||
import PropTypes from "prop-types" | |||
import cx from "classnames" | |||
import { fromJS, Seq } from "immutable" | |||
import { getSampleSchema, fromJSOrdered } from "core/utils" | |||
@@ -46,7 +47,8 @@ export default class Response extends React.Component { | |||
getComponent: PropTypes.func.isRequired, | |||
specSelectors: PropTypes.object.isRequired, | |||
fn: PropTypes.object.isRequired, | |||
contentType: PropTypes.string | |||
contentType: PropTypes.string, | |||
controlsAcceptHeader: PropTypes.bool | |||
} | |||
static defaultProps = { | |||
@@ -61,7 +63,8 @@ export default class Response extends React.Component { | |||
fn, | |||
getComponent, | |||
specSelectors, | |||
contentType | |||
contentType, | |||
controlsAcceptHeader | |||
} = this.props | |||
let { inferSchema } = fn | |||
@@ -106,11 +109,18 @@ export default class Response extends React.Component { | |||
<Markdown source={ response.get( "description" ) } /> | |||
</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 ? ( | |||
<ModelExample | |||
@@ -1,7 +1,7 @@ | |||
import React from "react" | |||
import PropTypes from "prop-types" | |||
import { fromJS } from "immutable" | |||
import { defaultStatusCode } from "core/utils" | |||
import { defaultStatusCode, getAcceptControllingResponse } from "core/utils" | |||
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) | |||
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 ) | |||
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 | |||
const isSpecOAS3 = specSelectors.isOAS3() | |||
const acceptControllingResponse = isSpecOAS3 ? | |||
getAcceptControllingResponse(responses) : null | |||
return ( | |||
<div className="responses-wrapper"> | |||
<div className="opblock-section-header"> | |||
@@ -88,6 +103,7 @@ export default class Responses extends React.Component { | |||
code={ code } | |||
response={ response } | |||
specSelectors={ specSelectors } | |||
controlsAcceptHeader={response === acceptControllingResponse} | |||
contentType={ producesValue } | |||
getComponent={ getComponent }/> | |||
) | |||
@@ -650,3 +650,26 @@ export const shallowEqualKeys = (a,b, keys) => { | |||
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 | |||
} |
@@ -775,6 +775,17 @@ | |||
.response-content-type { | |||
padding-top: 1em; | |||
&.controls-accept-header { | |||
select { | |||
border-color: green; | |||
} | |||
small { | |||
color: green; | |||
font-size: .7em; | |||
} | |||
} | |||
} | |||
@keyframes blinker | |||
@@ -1,7 +1,7 @@ | |||
/* eslint-env mocha */ | |||
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" | |||
describe("utils", function() { | |||
@@ -581,5 +581,102 @@ describe("utils", function() { | |||
const result = fromJSOrdered(param).toJS() | |||
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) | |||
}) | |||
}) | |||
}) |