diff --git a/package.json b/package.json index 709752ff..77f14ab7 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/core/components/response.jsx b/src/core/components/response.jsx index 02662f90..b2507a2e 100644 --- a/src/core/components/response.jsx +++ b/src/core/components/response.jsx @@ -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 { - { isOAS3 ? this.setState({ responseContentType: val })} - className="response-content-type" /> : null } + { isOAS3 ? +
+ this.setState({ responseContentType: val })} + /> + { controlsAcceptHeader ? Controls Accept header. : null } +
+ : null } { example ? ( 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 (
@@ -88,6 +103,7 @@ export default class Responses extends React.Component { code={ code } response={ response } specSelectors={ specSelectors } + controlsAcceptHeader={response === acceptControllingResponse} contentType={ producesValue } getComponent={ getComponent }/> ) diff --git a/src/core/utils.js b/src/core/utils.js index 743c4749..99cb9525 100644 --- a/src/core/utils.js +++ b/src/core/utils.js @@ -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 +} diff --git a/src/style/_layout.scss b/src/style/_layout.scss index edb21360..d070c96c 100644 --- a/src/style/_layout.scss +++ b/src/style/_layout.scss @@ -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 diff --git a/test/core/utils.js b/test/core/utils.js index 55dfeace..58e85241 100644 --- a/test/core/utils.js +++ b/test/core/utils.js @@ -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) + }) + }) })