@@ -22,7 +22,7 @@ The OpenAPI Specification has undergone 5 revisions since initial creation in 20 | |||
Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes | |||
------------------ | ------------ | -------------------------- | ----- | |||
3.2.0 | 2017-09-08 | 2.0, 3.0 | [tag v3.2.0](https://github.com/swagger-api/swagger-ui/tree/v3.2.0) | |||
3.2.1 | 2017-09-15 | 2.0, 3.0 | [tag v3.2.1](https://github.com/swagger-api/swagger-ui/tree/v3.2.1) | |||
3.0.21 | 2017-07-26 | 2.0 | [tag v3.0.21](https://github.com/swagger-api/swagger-ui/tree/v3.0.21) | |||
2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10) | |||
2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5) | |||
@@ -1 +1 @@ | |||
{"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;AAu7LA;;;;;;AA65DA;;;;;;;;;;;;;;;;;;;;;;;;;;AA68TA;;;;;;;;;;;;;;AAs8JA;;;;;;;;;AA69pBA;;;;;AA81QA;AAm4DA;;;;;;AAo4YA;;;;;;AA0iaA;AA4lvBA","sourceRoot":""} | |||
{"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;AAwjMA;;;;;;AA65DA;;;;;;;;;;;;;;;;;;;;;;;;;;AAs9TA;;;;;;;;;;;;;;AAs8JA;;;;;;;;;AAm/pBA;;;;;AAu5QA;;;;;AAynBA;AAi0CA;;;;;;AAq/YA;;;;;;AAojaA;AA8lvBA","sourceRoot":""} |
@@ -1 +1 @@ | |||
{"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AAokeA","sourceRoot":""} | |||
{"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AAmxeA","sourceRoot":""} |
@@ -1,6 +1,6 @@ | |||
{ | |||
"name": "swagger-ui", | |||
"version": "3.2.0", | |||
"version": "3.2.1", | |||
"main": "dist/swagger-ui.js", | |||
"repository": "git@github.com:swagger-api/swagger-ui.git", | |||
"contributors": [ | |||
@@ -41,6 +41,7 @@ | |||
"dependencies": { | |||
"base64-js": "^1.2.0", | |||
"brace": "0.7.0", | |||
"classnames": "^2.2.5", | |||
"css.escape": "1.5.1", | |||
"deep-extend": "0.4.1", | |||
"expect": "1.20.2", | |||
@@ -76,7 +77,7 @@ | |||
"scroll-to-element": "^2.0.0", | |||
"serialize-error": "2.0.0", | |||
"shallowequal": "0.2.2", | |||
"swagger-client": "3.1.0", | |||
"swagger-client": "3.1.1", | |||
"url-parse": "^1.1.8", | |||
"whatwg-fetch": "0.11.1", | |||
"worker-loader": "^0.7.1", | |||
@@ -28,6 +28,7 @@ export default class Operation extends PureComponent { | |||
authSelectors: PropTypes.object, | |||
specActions: PropTypes.object.isRequired, | |||
specSelectors: PropTypes.object.isRequired, | |||
oas3Actions: PropTypes.object.isRequired, | |||
layoutActions: PropTypes.object.isRequired, | |||
layoutSelectors: PropTypes.object.isRequired, | |||
fn: PropTypes.object.isRequired, | |||
@@ -117,7 +118,8 @@ export default class Operation extends PureComponent { | |||
specSelectors, | |||
authActions, | |||
authSelectors, | |||
getConfigs | |||
getConfigs, | |||
oas3Actions | |||
} = this.props | |||
let summary = operation.get("summary") | |||
@@ -265,6 +267,7 @@ export default class Operation extends PureComponent { | |||
getComponent={ getComponent } | |||
getConfigs={ getConfigs } | |||
specSelectors={ specSelectors } | |||
oas3Actions={oas3Actions} | |||
specActions={ specActions } | |||
produces={ produces } | |||
producesValue={ operation.get("produces_value") } | |||
@@ -9,6 +9,7 @@ export default class Operations extends React.Component { | |||
static propTypes = { | |||
specSelectors: PropTypes.object.isRequired, | |||
specActions: PropTypes.object.isRequired, | |||
oas3Actions: PropTypes.object.isRequired, | |||
getComponent: PropTypes.func.isRequired, | |||
layoutSelectors: PropTypes.object.isRequired, | |||
layoutActions: PropTypes.object.isRequired, | |||
@@ -21,6 +22,7 @@ export default class Operations extends React.Component { | |||
let { | |||
specSelectors, | |||
specActions, | |||
oas3Actions, | |||
getComponent, | |||
layoutSelectors, | |||
layoutActions, | |||
@@ -147,6 +149,8 @@ export default class Operations extends React.Component { | |||
specActions={ specActions } | |||
specSelectors={ specSelectors } | |||
oas3Actions={oas3Actions} | |||
layoutActions={ layoutActions } | |||
layoutSelectors={ layoutSelectors } | |||
@@ -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,13 +47,25 @@ 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, | |||
onContentTypeChange: PropTypes.func | |||
} | |||
static defaultProps = { | |||
response: fromJS({}), | |||
onContentTypeChange: () => {} | |||
}; | |||
_onContentTypeChange = (value) => { | |||
const { onContentTypeChange, controlsAcceptHeader } = this.props | |||
this.setState({ responseContentType: value }) | |||
onContentTypeChange({ | |||
value: value, | |||
controlsAcceptHeader | |||
}) | |||
} | |||
render() { | |||
let { | |||
code, | |||
@@ -61,7 +74,8 @@ export default class Response extends React.Component { | |||
fn, | |||
getComponent, | |||
specSelectors, | |||
contentType | |||
contentType, | |||
controlsAcceptHeader | |||
} = this.props | |||
let { inferSchema } = fn | |||
@@ -106,11 +120,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={this._onContentTypeChange} | |||
/> | |||
{ 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 { | |||
@@ -14,6 +14,7 @@ export default class Responses extends React.Component { | |||
getComponent: PropTypes.func.isRequired, | |||
specSelectors: PropTypes.object.isRequired, | |||
specActions: PropTypes.object.isRequired, | |||
oas3Actions: PropTypes.object.isRequired, | |||
pathMethod: PropTypes.array.isRequired, | |||
displayRequestDuration: PropTypes.bool.isRequired, | |||
fn: PropTypes.object.isRequired, | |||
@@ -29,8 +30,28 @@ export default class Responses extends React.Component { | |||
onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue(this.props.pathMethod, val) | |||
onResponseContentTypeChange = ({ controlsAcceptHeader, value }) => { | |||
const { oas3Actions, pathMethod } = this.props | |||
if(controlsAcceptHeader) { | |||
oas3Actions.setResponseContentType({ | |||
value, | |||
pathMethod | |||
}) | |||
} | |||
} | |||
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 +60,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"> | |||
@@ -78,7 +104,6 @@ export default class Responses extends React.Component { | |||
<tbody> | |||
{ | |||
responses.entrySeq().map( ([code, response]) => { | |||
let className = tryItOutResponse && tryItOutResponse.get("status") == code ? "response_current" : "" | |||
return ( | |||
<Response key={ code } | |||
@@ -88,6 +113,8 @@ export default class Responses extends React.Component { | |||
code={ code } | |||
response={ response } | |||
specSelectors={ specSelectors } | |||
controlsAcceptHeader={response === acceptControllingResponse} | |||
onContentTypeChange={this.onResponseContentTypeChange} | |||
contentType={ producesValue } | |||
getComponent={ getComponent }/> | |||
) | |||
@@ -4,6 +4,7 @@ | |||
export const UPDATE_SELECTED_SERVER = "oas3_set_servers" | |||
export const UPDATE_REQUEST_BODY_VALUE = "oas3_set_request_body_value" | |||
export const UPDATE_REQUEST_CONTENT_TYPE = "oas3_set_request_content_type" | |||
export const UPDATE_RESPONSE_CONTENT_TYPE = "oas3_set_response_content_type" | |||
export const UPDATE_SERVER_VARIABLE_VALUE = "oas3_set_server_variable_value" | |||
export function setSelectedServer (selectedServerUrl) { | |||
@@ -27,6 +28,13 @@ export function setRequestContentType ({ value, pathMethod }) { | |||
} | |||
} | |||
export function setResponseContentType ({ value, pathMethod }) { | |||
return { | |||
type: UPDATE_RESPONSE_CONTENT_TYPE, | |||
payload: { value, pathMethod } | |||
} | |||
} | |||
export function setServerVariableValue ({ server, key, val }) { | |||
return { | |||
type: UPDATE_SERVER_VARIABLE_VALUE, | |||
@@ -2,7 +2,8 @@ import { | |||
UPDATE_SELECTED_SERVER, | |||
UPDATE_REQUEST_BODY_VALUE, | |||
UPDATE_REQUEST_CONTENT_TYPE, | |||
UPDATE_SERVER_VARIABLE_VALUE | |||
UPDATE_SERVER_VARIABLE_VALUE, | |||
UPDATE_RESPONSE_CONTENT_TYPE | |||
} from "./actions" | |||
export default { | |||
@@ -17,6 +18,10 @@ export default { | |||
let [path, method] = pathMethod | |||
return state.setIn( [ "requestData", path, method, "requestContentType" ], value) | |||
}, | |||
[UPDATE_RESPONSE_CONTENT_TYPE]: (state, { payload: { value, pathMethod } } ) =>{ | |||
let [path, method] = pathMethod | |||
return state.setIn( [ "requestData", path, method, "responseContentType" ], value) | |||
}, | |||
[UPDATE_SERVER_VARIABLE_VALUE]: (state, { payload: { server, key, val } } ) =>{ | |||
return state.setIn( [ "serverVariableValues", server, key ], val) | |||
}, | |||
@@ -30,6 +30,11 @@ export const requestContentType = onlyOAS3((state, path, method) => { | |||
} | |||
) | |||
export const responseContentType = onlyOAS3((state, path, method) => { | |||
return state.getIn(["requestData", path, method, "responseContentType"]) || null | |||
} | |||
) | |||
export const serverVariableValue = onlyOAS3((state, server, key) => { | |||
return state.getIn(["serverVariableValues", server, key]) || null | |||
} | |||
@@ -218,6 +218,7 @@ export const executeRequest = (req) => | |||
req.server = oas3Selectors.selectedServer() | |||
req.serverVariables = oas3Selectors.serverVariables(req.server).toJS() | |||
req.requestContentType = oas3Selectors.requestContentType(pathName, method) | |||
req.responseContentType = oas3Selectors.responseContentType(pathName, method) || "*/*" | |||
const requestBody = oas3Selectors.requestBodyValue(pathName, method) | |||
if(isJSONObject(requestBody)) { | |||
@@ -652,5 +652,28 @@ export const shallowEqualKeys = (a,b, keys) => { | |||
}) | |||
} | |||
export const createDeepLinkPath = (str) => str ? str.replace(/\s/g, "_") : "" | |||
export const escapeDeepLinkPath = (str) => cssEscape( createDeepLinkPath(str) ) | |||
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 | |||
} | |||
export const createDeepLinkPath = (str) => typeof str == "string" || str instanceof String ? str.trim().replace(/\s/g, "_") : "" | |||
export const escapeDeepLinkPath = (str) => cssEscape( createDeepLinkPath(str) ) |
@@ -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, createDeepLinkPath, escapeDeepLinkPath } from "core/utils" | |||
import win from "core/window" | |||
describe("utils", function() { | |||
@@ -581,5 +581,152 @@ 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) | |||
}) | |||
}) | |||
describe("createDeepLinkPath", function() { | |||
it("creates a deep link path replacing spaces with underscores", function() { | |||
const result = createDeepLinkPath("tag id with spaces") | |||
expect(result).toEqual("tag_id_with_spaces") | |||
}) | |||
it("trims input when creating a deep link path", function() { | |||
let result = createDeepLinkPath(" spaces before and after ") | |||
expect(result).toEqual("spaces_before_and_after") | |||
result = createDeepLinkPath(" ") | |||
expect(result).toEqual("") | |||
}) | |||
it("creates a deep link path with special characters", function() { | |||
const result = createDeepLinkPath("!@#$%^&*(){}[]") | |||
expect(result).toEqual("!@#$%^&*(){}[]") | |||
}) | |||
it("returns an empty string for invalid input", function() { | |||
expect( createDeepLinkPath(null) ).toEqual("") | |||
expect( createDeepLinkPath(undefined) ).toEqual("") | |||
expect( createDeepLinkPath(1) ).toEqual("") | |||
expect( createDeepLinkPath([]) ).toEqual("") | |||
expect( createDeepLinkPath({}) ).toEqual("") | |||
}) | |||
}) | |||
describe("escapeDeepLinkPath", function() { | |||
it("creates and escapes a deep link path", function() { | |||
const result = escapeDeepLinkPath("tag id with spaces?") | |||
expect(result).toEqual("tag_id_with_spaces\\?") | |||
}) | |||
it("escapes a deep link path that starts with a number", function() { | |||
const result = escapeDeepLinkPath("123") | |||
expect(result).toEqual("\\31 23") | |||
}) | |||
it("escapes a deep link path with a class selector", function() { | |||
const result = escapeDeepLinkPath("hello.world") | |||
expect(result).toEqual("hello\\.world") | |||
}) | |||
it("escapes a deep link path with an id selector", function() { | |||
const result = escapeDeepLinkPath("hello#world") | |||
expect(result).toEqual("hello\\#world") | |||
}) | |||
}) | |||
}) |