@@ -88,6 +88,7 @@ export default class Operations extends React.Component {
allowTryItOut={allowTryItOut}
displayOperationId={displayOperationId}
+ displayRequestDuration={displayRequestDuration}
specActions={ specActions }
specSelectors={ specSelectors }
diff --git a/src/core/components/response-body.jsx b/src/core/components/response-body.jsx
index b933eb70..df3bd061 100644
--- a/src/core/components/response-body.jsx
+++ b/src/core/components/response-body.jsx
@@ -7,7 +7,7 @@ export default class ResponseBody extends React.Component {
static propTypes = {
content: PropTypes.any.isRequired,
- contentType: PropTypes.string.isRequired,
+ contentType: PropTypes.string,
getComponent: PropTypes.func.isRequired,
headers: PropTypes.object,
url: PropTypes.string
diff --git a/src/core/components/responses.jsx b/src/core/components/responses.jsx
index f32e7a1f..b0dfc4f7 100644
--- a/src/core/components/responses.jsx
+++ b/src/core/components/responses.jsx
@@ -15,19 +15,21 @@ export default class Responses extends React.Component {
specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
pathMethod: PropTypes.array.isRequired,
+ displayRequestDuration: PropTypes.bool.isRequired,
fn: PropTypes.object.isRequired
}
static defaultProps = {
request: null,
tryItOutResponse: null,
- produces: fromJS(["application/json"])
+ produces: fromJS(["application/json"]),
+ displayRequestDuration: false
}
onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue(this.props.pathMethod, val)
render() {
- let { responses, request, tryItOutResponse, getComponent, specSelectors, fn, producesValue } = this.props
+ let { responses, request, tryItOutResponse, getComponent, specSelectors, fn, producesValue, displayRequestDuration } = this.props
let defaultCode = defaultStatusCode( responses )
const ContentType = getComponent( "contentType" )
@@ -54,7 +56,8 @@ export default class Responses extends React.Component {
:
+ getComponent={ getComponent }
+ displayRequestDuration={ displayRequestDuration } />
Responses
diff --git a/src/core/components/schemes.jsx b/src/core/components/schemes.jsx
index 4742e691..8be4180a 100644
--- a/src/core/components/schemes.jsx
+++ b/src/core/components/schemes.jsx
@@ -7,7 +7,8 @@ export default class Schemes extends React.Component {
specActions: PropTypes.object.isRequired,
schemes: PropTypes.object.isRequired,
path: PropTypes.string,
- method: PropTypes.string
+ method: PropTypes.string,
+ operationScheme: PropTypes.string
}
componentWillMount() {
@@ -17,11 +18,18 @@ export default class Schemes extends React.Component {
this.setScheme(schemes.first())
}
+ componentWillReceiveProps(nextProps) {
+ if ( this.props.operationScheme && !nextProps.schemes.has(this.props.operationScheme) ) {
+ //fire 'change' event if our selected scheme is no longer an option
+ this.setScheme(nextProps.schemes.first())
+ }
+ }
+
onChange =( e ) => {
this.setScheme( e.target.value )
}
- setScheme =( value ) => {
+ setScheme = ( value ) => {
let { path, method, specActions } = this.props
specActions.setScheme( value, path, method )
diff --git a/src/core/index.js b/src/core/index.js
index 107001cb..80335da5 100644
--- a/src/core/index.js
+++ b/src/core/index.js
@@ -8,7 +8,7 @@ import { parseSeach, filterConfigs } from "core/utils"
const CONFIGS = [ "url", "urls", "urls.primaryName", "spec", "validatorUrl", "onComplete", "onFailure", "authorizations", "docExpansion",
"apisSorter", "operationsSorter", "supportedSubmitMethods", "dom_id", "defaultModelRendering", "oauth2RedirectUrl",
- "showRequestHeaders", "custom", "modelPropertyMacro", "parameterMacro", "displayOperationId" ]
+ "showRequestHeaders", "custom", "modelPropertyMacro", "parameterMacro", "displayOperationId" , "displayRequestDuration"]
// eslint-disable-next-line no-undef
const { GIT_DIRTY, GIT_COMMIT, PACKAGE_VERSION } = buildInfo
@@ -30,6 +30,7 @@ module.exports = function SwaggerUI(opts) {
configs: {},
custom: {},
displayOperationId: false,
+ displayRequestDuration: false,
// 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.
diff --git a/src/core/plugins/spec/actions.js b/src/core/plugins/spec/actions.js
index d531630a..31671b87 100644
--- a/src/core/plugins/spec/actions.js
+++ b/src/core/plugins/spec/actions.js
@@ -92,28 +92,28 @@ export const resolveSpec = (json, url) => ({specActions, specSelectors, errActio
let specStr = specSelectors.specStr()
return resolve({fetch, spec: json, baseDoc: url, modelPropertyMacro, parameterMacro })
- .then( ({spec, errors}) => {
- errActions.clear({
- type: "thrown"
- })
+ .then( ({spec, errors}) => {
+ errActions.clear({
+ type: "thrown"
+ })
- if(errors.length > 0) {
- let preparedErrors = errors
- .map(err => {
- console.error(err)
- err.line = err.fullPath ? getLineNumberForPath(specStr, err.fullPath) : null
- err.path = err.fullPath ? err.fullPath.join(".") : null
- err.level = "error"
- err.type = "thrown"
- err.source = "resolver"
- Object.defineProperty(err, "message", { enumerable: true, value: err.message })
- return err
- })
- errActions.newThrownErrBatch(preparedErrors)
- }
+ if(errors.length > 0) {
+ let preparedErrors = errors
+ .map(err => {
+ console.error(err)
+ err.line = err.fullPath ? getLineNumberForPath(specStr, err.fullPath) : null
+ err.path = err.fullPath ? err.fullPath.join(".") : null
+ err.level = "error"
+ err.type = "thrown"
+ err.source = "resolver"
+ Object.defineProperty(err, "message", { enumerable: true, value: err.message })
+ return err
+ })
+ errActions.newThrownErrBatch(preparedErrors)
+ }
- return specActions.updateResolved(spec)
- })
+ return specActions.updateResolved(spec)
+ })
}
export const formatIntoYaml = () => ({specActions, specSelectors}) => {
@@ -207,8 +207,14 @@ export const executeRequest = (req) => ({fn, specActions, specSelectors}) => {
specActions.setRequest(req.pathName, req.method, parsedRequest)
+ // track duration of request
+ const startTime = Date.now()
+
return fn.execute(req)
- .then( res => specActions.setResponse(req.pathName, req.method, res))
+ .then( res => {
+ res.duration = Date.now() - startTime
+ specActions.setResponse(req.pathName, req.method, res)
+ } )
.catch( err => specActions.setResponse(req.pathName, req.method, { error: true, err: serializeError(err) } ) )
}
diff --git a/src/core/plugins/spec/reducers.js b/src/core/plugins/spec/reducers.js
index 6bc51d53..d9670d7b 100644
--- a/src/core/plugins/spec/reducers.js
+++ b/src/core/plugins/spec/reducers.js
@@ -75,7 +75,12 @@ export default {
[SET_RESPONSE]: (state, { payload: { res, path, method } } ) =>{
let result
if ( res.error ) {
- result = Object.assign({error: true}, res.err)
+ result = Object.assign({
+ error: true,
+ name: res.err.name,
+ message: res.err.message,
+ statusCode: res.err.statusCode
+ }, res.err.response)
} else {
result = res
}
@@ -86,7 +91,7 @@ export default {
let newState = state.setIn( [ "responses", path, method ], fromJSOrdered(result) )
// ImmutableJS messes up Blob. Needs to reset its value.
- if (res.data instanceof win.Blob) {
+ if (win.Blob && res.data instanceof win.Blob) {
newState = newState.setIn( [ "responses", path, method, "text" ], res.data)
}
return newState
diff --git a/src/core/utils.js b/src/core/utils.js
index 2131e8d2..caaa88cc 100644
--- a/src/core/utils.js
+++ b/src/core/utils.js
@@ -450,15 +450,15 @@ export const propChecker = (props, nextProps, objectList=[], ignoreList=[]) => {
|| objectList.some( objectPropName => !eq(props[objectPropName], nextProps[objectPropName])))
}
-const validateNumber = ( val ) => {
- if ( !/^-?\d+(.?\d+)?$/.test(val)) {
+export const validateNumber = ( val ) => {
+ if ( !/^-?\d+(\.?\d+)?$/.test(val)) {
return "Value must be a number"
}
}
-const validateInteger = ( val ) => {
+export const validateInteger = ( val ) => {
if ( !/^-?\d+$/.test(val)) {
- return "Value must be integer"
+ return "Value must be an integer"
}
}
@@ -469,13 +469,14 @@ export const validateParam = (param, isXml) => {
let required = param.get("required")
let type = param.get("type")
- if ( required && (!value || (type==="array" && Array.isArray(value) && !value.length ))) {
+ let stringCheck = type === "string" && !value
+ let arrayCheck = type === "array" && Array.isArray(value) && !value.length
+ let listCheck = type === "array" && Im.List.isList(value) && !value.count()
+ if ( required && (stringCheck || arrayCheck || listCheck) ) {
errors.push("Required field is not provided")
return errors
}
- if ( !value ) return errors
-
if ( type === "number" ) {
let err = validateNumber(value)
if (!err) return errors
diff --git a/test/core/plugins/spec-reducer.js b/test/core/plugins/spec-reducer.js
index 32113543..09e73b7c 100644
--- a/test/core/plugins/spec-reducer.js
+++ b/test/core/plugins/spec-reducer.js
@@ -69,4 +69,56 @@ describe("spec plugin - reducer", function(){
expect(result.toJS()).toEqual(state.toJS())
})
})
+
+ describe("set response value", function() {
+ it("should combine the response and error objects", () => {
+ const setResponse = reducer["spec_set_response"]
+
+ const path = "/pet/post"
+ const method = "POST"
+
+ const state = fromJS({})
+ const result = setResponse(state, {
+ payload: {
+ path: path,
+ method: method,
+ res: {
+ error: true,
+ err: {
+ message: "Not Found",
+ name: "Error",
+ response: {
+ data: "response data",
+ headers: {
+ key: "value"
+ },
+ ok: false,
+ status: 404,
+ statusText: "Not Found"
+ },
+ status: 404,
+ statusCode: 404
+ }
+ }
+ }
+ })
+
+ let expectedResult = {
+ error: true,
+ message: "Not Found",
+ name: "Error",
+ data: "response data",
+ headers: {
+ key: "value"
+ },
+ ok: false,
+ status: 404,
+ statusCode: 404,
+ statusText: "Not Found"
+ }
+
+ const response = result.getIn(["responses", path, method]).toJS()
+ expect(response).toEqual(expectedResult)
+ })
+ })
})
diff --git a/test/core/utils.js b/test/core/utils.js
index 636c5f00..baf7dbf7 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 } from "core/utils"
+import { mapToList, validateNumber, validateInteger, validateParam } from "core/utils"
describe("utils", function(){
@@ -67,9 +67,181 @@ describe("utils", function(){
// Then
expect(aList.toJS()).toEqual([])
-
})
})
+ describe("validateNumber", function() {
+ let errorMessage = "Value must be a number"
+
+ it("doesn't return for whole numbers", function() {
+ expect(validateNumber(0)).toBeFalsy()
+ expect(validateNumber(1)).toBeFalsy()
+ expect(validateNumber(20)).toBeFalsy()
+ expect(validateNumber(5000000)).toBeFalsy()
+ expect(validateNumber("1")).toBeFalsy()
+ expect(validateNumber("2")).toBeFalsy()
+ expect(validateNumber(-1)).toBeFalsy()
+ expect(validateNumber(-20)).toBeFalsy()
+ expect(validateNumber(-5000000)).toBeFalsy()
+ })
+
+ it("doesn't return for negative numbers", function() {
+ expect(validateNumber(-1)).toBeFalsy()
+ expect(validateNumber(-20)).toBeFalsy()
+ expect(validateNumber(-5000000)).toBeFalsy()
+ })
+
+ it("doesn't return for decimal numbers", function() {
+ expect(validateNumber(1.1)).toBeFalsy()
+ expect(validateNumber(2.5)).toBeFalsy()
+ expect(validateNumber(-30.99)).toBeFalsy()
+ })
+
+ it("returns a message for strings", function() {
+ expect(validateNumber("")).toEqual(errorMessage)
+ expect(validateNumber(" ")).toEqual(errorMessage)
+ expect(validateNumber("test")).toEqual(errorMessage)
+ })
+
+ it("returns a message for invalid input", function() {
+ expect(validateNumber(undefined)).toEqual(errorMessage)
+ expect(validateNumber(null)).toEqual(errorMessage)
+ expect(validateNumber({})).toEqual(errorMessage)
+ expect(validateNumber([])).toEqual(errorMessage)
+ expect(validateNumber(true)).toEqual(errorMessage)
+ expect(validateNumber(false)).toEqual(errorMessage)
+ })
+ })
+
+ describe("validateInteger", function() {
+ let errorMessage = "Value must be an integer"
+
+ it("doesn't return for positive integers", function() {
+ expect(validateInteger(0)).toBeFalsy()
+ expect(validateInteger(1)).toBeFalsy()
+ expect(validateInteger(20)).toBeFalsy()
+ expect(validateInteger(5000000)).toBeFalsy()
+ expect(validateInteger("1")).toBeFalsy()
+ expect(validateInteger("2")).toBeFalsy()
+ expect(validateInteger(-1)).toBeFalsy()
+ expect(validateInteger(-20)).toBeFalsy()
+ expect(validateInteger(-5000000)).toBeFalsy()
+ })
+
+ it("doesn't return for negative integers", function() {
+ expect(validateInteger(-1)).toBeFalsy()
+ expect(validateInteger(-20)).toBeFalsy()
+ expect(validateInteger(-5000000)).toBeFalsy()
+ })
+
+ it("returns a message for decimal values", function() {
+ expect(validateInteger(1.1)).toEqual(errorMessage)
+ expect(validateInteger(2.5)).toEqual(errorMessage)
+ expect(validateInteger(-30.99)).toEqual(errorMessage)
+ })
+
+ it("returns a message for strings", function() {
+ expect(validateInteger("")).toEqual(errorMessage)
+ expect(validateInteger(" ")).toEqual(errorMessage)
+ expect(validateInteger("test")).toEqual(errorMessage)
+ })
+
+ it("returns a message for invalid input", function() {
+ expect(validateInteger(undefined)).toEqual(errorMessage)
+ expect(validateInteger(null)).toEqual(errorMessage)
+ expect(validateInteger({})).toEqual(errorMessage)
+ expect(validateInteger([])).toEqual(errorMessage)
+ expect(validateInteger(true)).toEqual(errorMessage)
+ expect(validateInteger(false)).toEqual(errorMessage)
+ })
+ })
+
+ describe("validateParam", function() {
+ let param = null
+ let result = null
+
+ it("validates required strings", function() {
+ param = fromJS({
+ required: true,
+ type: "string",
+ value: ""
+ })
+ result = validateParam( param, false )
+ expect( result ).toEqual( ["Required field is not provided"] )
+ })
+
+ it("validates required arrays", function() {
+ param = fromJS({
+ required: true,
+ type: "array",
+ value: []
+ })
+ result = validateParam( param, false )
+ expect( result ).toEqual( ["Required field is not provided"] )
+
+ param = fromJS({
+ required: true,
+ type: "array",
+ value: []
+ })
+ result = validateParam( param, false )
+ expect( result ).toEqual( ["Required field is not provided"] )
+ })
+
+ it("validates numbers", function() {
+ param = fromJS({
+ required: false,
+ type: "number",
+ value: "test"
+ })
+ result = validateParam( param, false )
+ expect( result ).toEqual( ["Value must be a number"] )
+ })
+
+ it("validates integers", function() {
+ param = fromJS({
+ required: false,
+ type: "integer",
+ value: "test"
+ })
+ result = validateParam( param, false )
+ expect( result ).toEqual( ["Value must be an integer"] )
+ })
+
+ it("validates arrays", function() {
+ // empty array
+ param = fromJS({
+ required: false,
+ type: "array",
+ value: []
+ })
+ result = validateParam( param, false )
+ expect( result ).toEqual( [] )
+
+ // numbers
+ param = fromJS({
+ required: false,
+ type: "array",
+ value: ["number"],
+ items: {
+ type: "number"
+ }
+ })
+ result = validateParam( param, false )
+ expect( result ).toEqual( [{index: 0, error: "Value must be a number"}] )
+
+ // integers
+ param = fromJS({
+ required: false,
+ type: "array",
+ value: ["not", "numbers"],
+ items: {
+ type: "integer"
+ }
+ })
+ result = validateParam( param, false )
+ expect( result ).toEqual( [{index: 0, error: "Value must be an integer"}, {index: 1, error: "Value must be an integer"}] )
+ })
+ })
})