Add support for requestInterceptor / responseInterceptor.
- Display mutated requests from request interceptor in curl output in UI. Put this behind showMutatedRequest flag so that the mutation can be silent. - Document requestInterceptor, responseInterceptor and showMutatedRequest in README.md - Add tests
This commit is contained in:
@@ -160,6 +160,9 @@ displayRequestDuration | Controls the display of the request duration (in millis
|
||||
maxDisplayedTags | If set, limits the number of tagged operations displayed to at most this many. The default is to show all operations.
|
||||
filter | If set, enables filtering. The top bar will show an edit box that you can use to filter the tagged operations that are shown. Can be true/false to enable or disable, or an explicit filter string in which case filtering will be enabled using that string as the filter expression. Filtering is case sensitive matching the filter expression anywhere inside the tag.
|
||||
deepLinking | If set to `true`, enables dynamic deep linking for tags and operations. [Docs](https://github.com/swagger-api/swagger-ui/blob/master/docs/deep-linking.md)
|
||||
requestInterceptor | MUST be a function. Function to intercept try-it-out requests. Accepts one argument requestInterceptor(request) and must return the potentially modified request.
|
||||
responseInterceptor | MUST be a function. Function to intercept try-it-out responses. Accepts one argument responseInterceptor(response) and must return the potentially modified response.
|
||||
showMutatedRequest | If set to `true` (the default), uses the mutated request returned from a rquestInterceptor to produce the curl command in the UI, otherwise the request before the requestInterceptor was applied is used.
|
||||
|
||||
### Plugins
|
||||
|
||||
|
||||
@@ -29,13 +29,18 @@ Duration.propTypes = {
|
||||
export default class LiveResponse extends React.Component {
|
||||
static propTypes = {
|
||||
response: PropTypes.object.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
pathMethod: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
displayRequestDuration: PropTypes.bool.isRequired
|
||||
displayRequestDuration: PropTypes.bool.isRequired,
|
||||
getConfigs: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
render() {
|
||||
const { request, response, getComponent, displayRequestDuration } = this.props
|
||||
const { response, getComponent, getConfigs, displayRequestDuration, specSelectors, pathMethod } = this.props
|
||||
const { showMutatedRequest } = getConfigs()
|
||||
|
||||
const curlRequest = showMutatedRequest ? specSelectors.mutatedRequestFor(pathMethod[0], pathMethod[1]) : specSelectors.requestFor(pathMethod[0], pathMethod[1])
|
||||
const status = response.get("status")
|
||||
const url = response.get("url")
|
||||
const headers = response.get("headers").toJS()
|
||||
@@ -55,7 +60,7 @@ export default class LiveResponse extends React.Component {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ request && <Curl request={ request }/> }
|
||||
{ curlRequest && <Curl request={ curlRequest }/> }
|
||||
<h4>Server response</h4>
|
||||
<table className="responses-table">
|
||||
<thead>
|
||||
|
||||
@@ -263,6 +263,7 @@ export default class Operation extends PureComponent {
|
||||
request={ request }
|
||||
tryItOutResponse={ response }
|
||||
getComponent={ getComponent }
|
||||
getConfigs={ getConfigs }
|
||||
specSelectors={ specSelectors }
|
||||
specActions={ specActions }
|
||||
produces={ produces }
|
||||
|
||||
@@ -16,7 +16,8 @@ export default class Responses extends React.Component {
|
||||
specActions: PropTypes.object.isRequired,
|
||||
pathMethod: PropTypes.array.isRequired,
|
||||
displayRequestDuration: PropTypes.bool.isRequired,
|
||||
fn: PropTypes.object.isRequired
|
||||
fn: PropTypes.object.isRequired,
|
||||
getConfigs: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
@@ -29,7 +30,7 @@ export default class Responses extends React.Component {
|
||||
onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue(this.props.pathMethod, val)
|
||||
|
||||
render() {
|
||||
let { responses, request, tryItOutResponse, getComponent, 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" )
|
||||
@@ -57,6 +58,9 @@ export default class Responses extends React.Component {
|
||||
<LiveResponse request={ request }
|
||||
response={ tryItOutResponse }
|
||||
getComponent={ getComponent }
|
||||
getConfigs={ getConfigs }
|
||||
specSelectors={ specSelectors }
|
||||
pathMethod={ this.props.pathMethod }
|
||||
displayRequestDuration={ displayRequestDuration } />
|
||||
<h4>Responses</h4>
|
||||
</div>
|
||||
|
||||
@@ -42,6 +42,9 @@ module.exports = function SwaggerUI(opts) {
|
||||
displayOperationId: false,
|
||||
displayRequestDuration: false,
|
||||
deepLinking: false,
|
||||
requestInterceptor: (a => a),
|
||||
responseInterceptor: (a => a),
|
||||
showMutatedRequest: true,
|
||||
|
||||
// Initial set of plugins ( TODO rename this, or refactor - we don't need presets _and_ plugins. Its just there for performance.
|
||||
// Instead, we can compile the first plugin ( it can be a collection of plugins ), then batch the rest.
|
||||
|
||||
@@ -12,6 +12,7 @@ export const UPDATE_PARAM = "spec_update_param"
|
||||
export const VALIDATE_PARAMS = "spec_validate_param"
|
||||
export const SET_RESPONSE = "spec_set_response"
|
||||
export const SET_REQUEST = "spec_set_request"
|
||||
export const SET_MUTATED_REQUEST = "spec_set_mutated_request"
|
||||
export const LOG_REQUEST = "spec_log_request"
|
||||
export const CLEAR_RESPONSE = "spec_clear_response"
|
||||
export const CLEAR_REQUEST = "spec_clear_request"
|
||||
@@ -177,6 +178,13 @@ export const setRequest = ( path, method, req ) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const setMutatedRequest = ( path, method, req ) => {
|
||||
return {
|
||||
payload: { path, method, req },
|
||||
type: SET_MUTATED_REQUEST
|
||||
}
|
||||
}
|
||||
|
||||
// This is for debugging, remove this comment if you depend on this action
|
||||
export const logRequest = (req) => {
|
||||
return {
|
||||
@@ -187,8 +195,9 @@ export const logRequest = (req) => {
|
||||
|
||||
// Actually fire the request via fn.execute
|
||||
// (For debugging) and ease of testing
|
||||
export const executeRequest = (req) => ({fn, specActions, specSelectors}) => {
|
||||
export const executeRequest = (req) => ({fn, specActions, specSelectors, getConfigs}) => {
|
||||
let { pathName, method, operation } = req
|
||||
let { requestInterceptor, responseInterceptor } = getConfigs()
|
||||
|
||||
let op = operation.toJS()
|
||||
|
||||
@@ -207,6 +216,16 @@ export const executeRequest = (req) => ({fn, specActions, specSelectors}) => {
|
||||
|
||||
specActions.setRequest(req.pathName, req.method, parsedRequest)
|
||||
|
||||
let requestInterceptorWrapper = function(r) {
|
||||
let mutatedRequest = requestInterceptor.apply(this, [r])
|
||||
let parsedMutatedRequest = Object.assign({}, mutatedRequest)
|
||||
specActions.setMutatedRequest(req.pathName, req.method, parsedMutatedRequest)
|
||||
return mutatedRequest
|
||||
}
|
||||
|
||||
req.requestInterceptor = requestInterceptorWrapper
|
||||
req.responseInterceptor = responseInterceptor
|
||||
|
||||
// track duration of request
|
||||
const startTime = Date.now()
|
||||
|
||||
|
||||
@@ -3,13 +3,14 @@ import { fromJSOrdered, validateParam } from "core/utils"
|
||||
import win from "../../window"
|
||||
|
||||
import {
|
||||
UPDATE_SPEC,
|
||||
UPDATE_SPEC,
|
||||
UPDATE_URL,
|
||||
UPDATE_JSON,
|
||||
UPDATE_PARAM,
|
||||
VALIDATE_PARAMS,
|
||||
SET_RESPONSE,
|
||||
SET_REQUEST,
|
||||
SET_MUTATED_REQUEST,
|
||||
UPDATE_RESOLVED,
|
||||
UPDATE_OPERATION_VALUE,
|
||||
CLEAR_RESPONSE,
|
||||
@@ -101,6 +102,10 @@ export default {
|
||||
return state.setIn( [ "requests", path, method ], fromJSOrdered(req))
|
||||
},
|
||||
|
||||
[SET_MUTATED_REQUEST]: (state, { payload: { req, path, method } } ) =>{
|
||||
return state.setIn( [ "mutatedRequests", path, method ], fromJSOrdered(req))
|
||||
},
|
||||
|
||||
[UPDATE_OPERATION_VALUE]: (state, { payload: { path, value, key } }) => {
|
||||
let operationPath = ["resolved", "paths", ...path]
|
||||
if(!state.getIn(operationPath)) {
|
||||
|
||||
@@ -237,6 +237,11 @@ export const requests = createSelector(
|
||||
state => state.get( "requests", Map() )
|
||||
)
|
||||
|
||||
export const mutatedRequests = createSelector(
|
||||
state,
|
||||
state => state.get( "mutatedRequests", Map() )
|
||||
)
|
||||
|
||||
export const responseFor = (state, path, method) => {
|
||||
return responses(state).getIn([path, method], null)
|
||||
}
|
||||
@@ -245,6 +250,10 @@ export const requestFor = (state, path, method) => {
|
||||
return requests(state).getIn([path, method], null)
|
||||
}
|
||||
|
||||
export const mutatedRequestFor = (state, path, method) => {
|
||||
return mutatedRequests(state).getIn([path, method], null)
|
||||
}
|
||||
|
||||
export const allowTryItOutFor = () => {
|
||||
// This is just a hook for now.
|
||||
return true
|
||||
|
||||
85
test/components/live-response.js
Normal file
85
test/components/live-response.js
Normal file
@@ -0,0 +1,85 @@
|
||||
/* eslint-env mocha */
|
||||
import React from "react"
|
||||
import { fromJSOrdered } from "core/utils"
|
||||
import expect, { createSpy } from "expect"
|
||||
import { shallow } from "enzyme"
|
||||
import Curl from "components/curl"
|
||||
import LiveResponse from "components/live-response"
|
||||
import ResponseBody from "components/response-body"
|
||||
|
||||
describe("<LiveResponse/>", function(){
|
||||
let request = fromJSOrdered({
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
accept: "application/xml"
|
||||
},
|
||||
url: "http://petstore.swagger.io/v2/pet/1"
|
||||
})
|
||||
|
||||
let mutatedRequest = fromJSOrdered({
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
accept: "application/xml",
|
||||
mutated: "header"
|
||||
},
|
||||
url: "http://petstore.swagger.io/v2/pet/1"
|
||||
})
|
||||
|
||||
let requests = {
|
||||
request: request,
|
||||
mutatedRequest: mutatedRequest
|
||||
}
|
||||
|
||||
const tests = [
|
||||
{ showMutatedRequest: true, expected: { request: "mutatedRequest", requestForCalls: 0, mutatedRequestForCalls: 1 } },
|
||||
{ showMutatedRequest: false, expected: { request: "request", requestForCalls: 1, mutatedRequestForCalls: 0 } }
|
||||
]
|
||||
|
||||
tests.forEach(function(test) {
|
||||
it("passes " + test.expected.request + " to Curl when showMutatedRequest = " + test.showMutatedRequest, function() {
|
||||
|
||||
// Given
|
||||
|
||||
let response = fromJSOrdered({
|
||||
status: 200,
|
||||
url: "http://petstore.swagger.io/v2/pet/1",
|
||||
headers: {},
|
||||
text: "<response/>",
|
||||
})
|
||||
|
||||
let mutatedRequestForSpy = createSpy().andReturn(mutatedRequest)
|
||||
let requestForSpy = createSpy().andReturn(request)
|
||||
|
||||
let components = {
|
||||
curl: Curl,
|
||||
responseBody: ResponseBody
|
||||
}
|
||||
|
||||
let props = {
|
||||
response: response,
|
||||
specSelectors: {
|
||||
mutatedRequestFor: mutatedRequestForSpy,
|
||||
requestFor: requestForSpy,
|
||||
},
|
||||
pathMethod: [ "/one", "get" ],
|
||||
getComponent: (c) => {
|
||||
return components[c]
|
||||
},
|
||||
displayRequestDuration: true,
|
||||
getConfigs: () => ({ showMutatedRequest: test.showMutatedRequest })
|
||||
}
|
||||
|
||||
// When
|
||||
let wrapper = shallow(<LiveResponse {...props}/>)
|
||||
|
||||
// Then
|
||||
expect(mutatedRequestForSpy.calls.length).toEqual(test.expected.mutatedRequestForCalls)
|
||||
expect(requestForSpy.calls.length).toEqual(test.expected.requestForCalls)
|
||||
|
||||
const curl = wrapper.find(Curl)
|
||||
expect(curl.length).toEqual(1)
|
||||
expect(curl.props().request).toBe(requests[test.expected.request])
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -93,6 +93,54 @@ describe("spec plugin - actions", function(){
|
||||
})
|
||||
})
|
||||
|
||||
it("should pass requestInterceptor/responseInterceptor to fn.execute", function(){
|
||||
// Given
|
||||
let configs = {
|
||||
requestInterceptor: createSpy(),
|
||||
responseInterceptor: createSpy()
|
||||
}
|
||||
const system = {
|
||||
fn: {
|
||||
buildRequest: createSpy(),
|
||||
execute: createSpy().andReturn(Promise.resolve())
|
||||
},
|
||||
specActions: {
|
||||
executeRequest: createSpy(),
|
||||
setMutatedRequest: createSpy(),
|
||||
setRequest: createSpy()
|
||||
},
|
||||
specSelectors: {
|
||||
spec: () => fromJS({}),
|
||||
parameterValues: () => fromJS({}),
|
||||
contentTypeValues: () => fromJS({}),
|
||||
url: () => fromJS({})
|
||||
},
|
||||
getConfigs: () => configs
|
||||
}
|
||||
// When
|
||||
let executeFn = executeRequest({
|
||||
pathName: "/one",
|
||||
method: "GET",
|
||||
operation: fromJS({operationId: "getOne"})
|
||||
})
|
||||
let res = executeFn(system)
|
||||
|
||||
// Then
|
||||
expect(system.fn.execute.calls.length).toEqual(1)
|
||||
expect(system.fn.execute.calls[0].arguments[0]).toIncludeKey("requestInterceptor")
|
||||
expect(system.fn.execute.calls[0].arguments[0]).toInclude({
|
||||
responseInterceptor: configs.responseInterceptor
|
||||
})
|
||||
expect(system.specActions.setMutatedRequest.calls.length).toEqual(0)
|
||||
expect(system.specActions.setRequest.calls.length).toEqual(1)
|
||||
|
||||
|
||||
let wrappedRequestInterceptor = system.fn.execute.calls[0].arguments[0].requestInterceptor
|
||||
wrappedRequestInterceptor(system.fn.execute.calls[0].arguments[0])
|
||||
expect(configs.requestInterceptor.calls.length).toEqual(1)
|
||||
expect(system.specActions.setMutatedRequest.calls.length).toEqual(1)
|
||||
expect(system.specActions.setRequest.calls.length).toEqual(1)
|
||||
})
|
||||
})
|
||||
|
||||
xit("should call specActions.setResponse, when fn.execute resolves", function(){
|
||||
|
||||
Reference in New Issue
Block a user