diff --git a/README.md b/README.md
index 0144744e..dac53d35 100644
--- a/README.md
+++ b/README.md
@@ -146,6 +146,7 @@ displayOperationId | Controls the display of operationId in operations list. The
displayRequestDuration | Controls the display of the request duration (in milliseconds) for `Try it out` requests. The default is `false`.
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)
### Plugins
@@ -235,6 +236,10 @@ Access-Control-Allow-Headers: Content-Type, api_key, Authorization
Only headers with these names will be allowed to be sent by Swagger-UI.
+## Security contact
+
+Please disclose any security-related issues or vulnerabilities by emailing [security@swagger.io](mailto:security@swagger.io), instead of using the public issue tracker.
+
## License
Copyright 2017 SmartBear Software
diff --git a/dist/index.html b/dist/index.html
index f6aaf9e4..52590951 100644
--- a/dist/index.html
+++ b/dist/index.html
@@ -76,6 +76,7 @@ window.onload = function() {
const ui = SwaggerUIBundle({
url: "http://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
+ deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
diff --git a/make-webpack-config.js b/make-webpack-config.js
index 2a6ed131..2eeb9bf9 100644
--- a/make-webpack-config.js
+++ b/make-webpack-config.js
@@ -61,7 +61,6 @@ module.exports = function(rules, options) {
}
if( specialOptions.minimize ) {
-
plugins.push(
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
diff --git a/package.json b/package.json
index 4b754ce7..3e0465d7 100644
--- a/package.json
+++ b/package.json
@@ -109,7 +109,6 @@
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "2.0.3",
"less": "2.7.2",
- "less-loader": "4.0.4",
"license-checker": "^11.0.0",
"mocha": "^3.4.2",
"node-sass": "^4.5.0",
diff --git a/src/core/components/info.jsx b/src/core/components/info.jsx
index 6ea1ed48..5e2b368a 100644
--- a/src/core/components/info.jsx
+++ b/src/core/components/info.jsx
@@ -15,7 +15,7 @@ class Path extends React.Component {
return (
- [ Base url: {host}{basePath}]
+ [ Base URL: {host}{basePath} ]
)
}
diff --git a/src/core/components/layout-utils.jsx b/src/core/components/layout-utils.jsx
index 00a4a09d..f80e0c90 100644
--- a/src/core/components/layout-utils.jsx
+++ b/src/core/components/layout-utils.jsx
@@ -129,7 +129,8 @@ export class Select extends React.Component {
value: PropTypes.any,
onChange: PropTypes.func,
multiple: PropTypes.bool,
- allowEmptyValue: PropTypes.bool
+ allowEmptyValue: PropTypes.bool,
+ className: PropTypes.string
}
static defaultProps = {
@@ -142,7 +143,7 @@ export class Select extends React.Component {
let value
- if (props.value !== undefined) {
+ if (props.value) {
value = props.value
} else {
value = props.multiple ? [""] : ""
@@ -178,7 +179,7 @@ export class Select extends React.Component {
let value = this.state.value.toJS ? this.state.value.toJS() : this.state.value
return (
-
+
{ allowEmptyValue ? -- : null }
{
allowedValues.map(function (item, key) {
diff --git a/src/core/components/models.jsx b/src/core/components/models.jsx
index c61ae0c9..86c3256d 100644
--- a/src/core/components/models.jsx
+++ b/src/core/components/models.jsx
@@ -24,8 +24,8 @@ export default class Models extends Component {
return
layoutActions.show("models", !showModels)}>
Models
-
-
+
+
diff --git a/src/core/components/schemes.jsx b/src/core/components/schemes.jsx
index 8be4180a..f9fe8f81 100644
--- a/src/core/components/schemes.jsx
+++ b/src/core/components/schemes.jsx
@@ -19,8 +19,9 @@ export default class Schemes extends React.Component {
}
componentWillReceiveProps(nextProps) {
- if ( this.props.operationScheme && !nextProps.schemes.has(this.props.operationScheme) ) {
- //fire 'change' event if our selected scheme is no longer an option
+ if ( !this.props.operationScheme || !nextProps.schemes.has(this.props.operationScheme) ) {
+ // if we don't have a selected operationScheme or if our selected scheme is no longer an option,
+ // then fire 'change' event and select the first scheme in the list of options
this.setScheme(nextProps.schemes.first())
}
}
diff --git a/src/core/json-schema-components.js b/src/core/json-schema-components.js
index bf4ae514..338bd6e3 100644
--- a/src/core/json-schema-components.js
+++ b/src/core/json-schema-components.js
@@ -57,7 +57,8 @@ export class JsonSchema_string extends Component {
if ( enumValue ) {
const Select = getComponent("Select")
- return ( )
@@ -121,6 +122,7 @@ export class JsonSchema_array extends PureComponent {
render() {
let { getComponent, required, schema, fn } = this.props
+ let errors = schema.errors || []
let itemSchema = fn.inferSchema(schema.items)
const JsonSchemaForm = getComponent("JsonSchemaForm")
@@ -131,19 +133,17 @@ export class JsonSchema_array extends PureComponent {
if ( enumValue ) {
const Select = getComponent("Select")
- return ( )
}
- let errors = schema.errors || []
-
return (
- { !value || value.count() < 1 ?
- (errors.length ?
{ errors[0] } : null) :
+ { !value || value.count() < 1 ? null :
value.map( (item,i) => {
let schema = Object.assign({}, itemSchema)
if ( errors.length ) {
@@ -153,12 +153,12 @@ export class JsonSchema_array extends PureComponent {
return (
this.onItemChange(val, i)} schema={schema} />
- this.removeItem(i)} > -
+ this.removeItem(i)} > -
)
}).toArray()
}
-
Add item
+
Add item
)
}
@@ -170,12 +170,14 @@ export class JsonSchema_boolean extends Component {
onEnumChange = (val) => this.props.onChange(val)
render() {
- let { getComponent, required, value } = this.props
+ let { getComponent, value, schema } = this.props
+ let errors = schema.errors || []
const Select = getComponent("Select")
- return ( )
}
}
diff --git a/src/core/path-translator.js b/src/core/path-translator.js
deleted file mode 100644
index 4d1ed27b..00000000
--- a/src/core/path-translator.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import get from "lodash/get"
-
-export function transformPathToArray(property, jsSpec) {
- if(property.slice(0,9) === "instance.") {
- var str = property.slice(9)
- } else { // eslint-disable-next-line no-redeclare
- var str = property
- }
-
- var pathArr = []
-
- str
- .split(".")
- .map(item => {
- // "key[0]" becomes ["key", "0"]
- if(item.includes("[")) {
- let index = parseInt(item.match(/\[(.*)\]/)[1])
- let keyName = item.slice(0, item.indexOf("["))
- return [keyName, index.toString()]
- } else {
- return item
- }
- })
- .reduce(function(a, b) {
- // flatten!
- return a.concat(b)
- }, [])
- .concat([""]) // add an empty item into the array, so we don't get stuck with something in our buffer below
- .reduce((buffer, curr) => {
- let obj = pathArr.length ? get(jsSpec, pathArr) : jsSpec
-
- if(get(obj, makeAccessArray(buffer, curr))) {
- if(buffer.length) {
- pathArr.push(buffer)
- }
- if(curr.length) {
- pathArr.push(curr)
- }
- return ""
- } else {
- // attach key to buffer
- return `${buffer}${buffer.length ? "." : ""}${curr}`
- }
- }, "")
-
- if(typeof get(jsSpec, pathArr) !== "undefined") {
- return pathArr
- } else {
- // if our path is not correct (there is no value at the path),
- // return null
- return null
- }
-}
-
-function makeAccessArray(buffer, curr) {
- let arr = []
-
- if(buffer.length) {
- arr.push(buffer)
- }
-
- if(curr.length) {
- arr.push(curr)
- }
-
- return arr
-}
diff --git a/src/core/plugins/auth/actions.js b/src/core/plugins/auth/actions.js
index 221d80a8..772e858c 100644
--- a/src/core/plugins/auth/actions.js
+++ b/src/core/plugins/auth/actions.js
@@ -73,7 +73,7 @@ export const authorizePassword = ( auth ) => ( { authActions } ) => {
let { schema, name, username, password, passwordType, clientId, clientSecret } = auth
let form = {
grant_type: "password",
- scopes: encodeURIComponent(auth.scopes.join(scopeSeparator))
+ scope: encodeURIComponent(auth.scopes.join(scopeSeparator))
}
let query = {}
let headers = {}
diff --git a/src/core/plugins/split-pane-mode/components/split-pane-mode.jsx b/src/core/plugins/split-pane-mode/components/split-pane-mode.jsx
index 73f2acbc..c1a8fa9c 100644
--- a/src/core/plugins/split-pane-mode/components/split-pane-mode.jsx
+++ b/src/core/plugins/split-pane-mode/components/split-pane-mode.jsx
@@ -1,7 +1,6 @@
import React from "react"
import PropTypes from "prop-types"
import SplitPane from "react-split-pane"
-import "./split-pane-mode.less"
const MODE_KEY = ["split-pane-mode"]
const MODE_LEFT = "left"
diff --git a/src/core/plugins/split-pane-mode/components/split-pane-mode.less b/src/core/plugins/split-pane-mode/components/split-pane-mode.less
deleted file mode 100644
index d5437b37..00000000
--- a/src/core/plugins/split-pane-mode/components/split-pane-mode.less
+++ /dev/null
@@ -1,5 +0,0 @@
-.swagger-ui {
- .Resizer.vertical.disabled {
- display: none;
- }
-}
diff --git a/src/core/plugins/util/index.js b/src/core/plugins/util/index.js
index 57e163f4..033288a6 100644
--- a/src/core/plugins/util/index.js
+++ b/src/core/plugins/util/index.js
@@ -1,8 +1,7 @@
import { shallowEqualKeys } from "core/utils"
-import { transformPathToArray } from "core/path-translator"
export default function() {
return {
- fn: { shallowEqualKeys, transformPathToArray }
+ fn: { shallowEqualKeys }
}
}
diff --git a/src/core/utils.js b/src/core/utils.js
index 95771fb4..7cc5beda 100644
--- a/src/core/utils.js
+++ b/src/core/utils.js
@@ -41,7 +41,7 @@ export function fromJSOrdered (js) {
return !isObject(js) ? js :
Array.isArray(js) ?
Im.Seq(js).map(fromJSOrdered).toList() :
- Im.Seq(js).map(fromJSOrdered).toOrderedMap()
+ Im.OrderedMap(js).map(fromJSOrdered)
}
export function bindToState(obj, state) {
@@ -468,6 +468,18 @@ export const validateFile = ( val ) => {
}
}
+export const validateBoolean = ( val ) => {
+ if ( !(val === "true" || val === "false" || val === true || val === false) ) {
+ return "Value must be a boolean"
+ }
+}
+
+export const validateString = ( val ) => {
+ if ( val && typeof val !== "string" ) {
+ return "Value must be a string"
+ }
+}
+
// validation of parameters before execute
export const validateParam = (param, isXml) => {
let errors = []
@@ -475,52 +487,66 @@ export const validateParam = (param, isXml) => {
let required = param.get("required")
let type = param.get("type")
- let stringCheck = type === "string" && !value
- let arrayCheck = type === "array" && Array.isArray(value) && !value.length
- let listCheck = type === "array" && Im.List.isList(value) && !value.count()
- let fileCheck = type === "file" && !(value instanceof win.File)
+ // If the parameter is required OR the parameter has a value (meaning optional, but filled in)
+ // then we should do our validation routine
+ if ( required || value ) {
+ // These checks should evaluate to true if the parameter's value is valid
+ let stringCheck = type === "string" && value && !validateString(value)
+ let arrayCheck = type === "array" && Array.isArray(value) && value.length
+ let listCheck = type === "array" && Im.List.isList(value) && value.count()
+ let fileCheck = type === "file" && value instanceof win.File
+ let booleanCheck = type === "boolean" && !validateBoolean(value)
+ let numberCheck = type === "number" && !validateNumber(value) // validateNumber returns undefined if the value is a number
+ let integerCheck = type === "integer" && !validateInteger(value) // validateInteger returns undefined if the value is an integer
- if ( required && (stringCheck || arrayCheck || listCheck || fileCheck) ) {
- errors.push("Required field is not provided")
- return errors
- }
+ if ( required && !(stringCheck || arrayCheck || listCheck || fileCheck || booleanCheck || numberCheck || integerCheck) ) {
+ errors.push("Required field is not provided")
+ return errors
+ }
- if ( value === null || value === undefined ) {
- return errors
- }
+ if ( type === "string" ) {
+ let err = validateString(value)
+ if (!err) return errors
+ errors.push(err)
+ } else if ( type === "boolean" ) {
+ let err = validateBoolean(value)
+ if (!err) return errors
+ errors.push(err)
+ } else if ( type === "number" ) {
+ let err = validateNumber(value)
+ if (!err) return errors
+ errors.push(err)
+ } else if ( type === "integer" ) {
+ let err = validateInteger(value)
+ if (!err) return errors
+ errors.push(err)
+ } else if ( type === "array" ) {
+ let itemType
- if ( type === "number" ) {
- let err = validateNumber(value)
- if (!err) return errors
- errors.push(err)
- } else if ( type === "integer" ) {
- let err = validateInteger(value)
- if (!err) return errors
- errors.push(err)
- } else if ( type === "array" ) {
- let itemType
+ if ( !value.count() ) { return errors }
- if ( !value.count() ) { return errors }
+ itemType = param.getIn(["items", "type"])
- itemType = param.getIn(["items", "type"])
+ value.forEach((item, index) => {
+ let err
- value.forEach((item, index) => {
- let err
+ if (itemType === "number") {
+ err = validateNumber(item)
+ } else if (itemType === "integer") {
+ err = validateInteger(item)
+ } else if (itemType === "string") {
+ err = validateString(item)
+ }
- if (itemType === "number") {
- err = validateNumber(item)
- } else if (itemType === "integer") {
- err = validateInteger(item)
- }
-
- if ( err ) {
- errors.push({ index: index, error: err})
- }
- })
- } else if ( type === "file" ) {
- let err = validateFile(value)
- if (!err) return errors
- errors.push(err)
+ if ( err ) {
+ errors.push({ index: index, error: err})
+ }
+ })
+ } else if ( type === "file" ) {
+ let err = validateFile(value)
+ if (!err) return errors
+ errors.push(err)
+ }
}
return errors
diff --git a/src/plugins/add-plugin.md b/src/plugins/add-plugin.md
index 7a77cb30..2e2d52e1 100644
--- a/src/plugins/add-plugin.md
+++ b/src/plugins/add-plugin.md
@@ -20,7 +20,7 @@ SwaggerUI({
})
```
-Or if you're updating the core plugins.. you'll add it to [src/js/bootstrap-plugin](https://github.com/SmartBear/swagger-ux/blob/master/src/js/bootstrap-plugin.js)
+Or if you're updating the core plugins.. you'll add it to the base preset: [src/core/presets/base.js](https://github.com/swagger-api/swagger-ui/blob/master/src/core/presets/base.js)
Each Plugin is a function that returns an object. That object will get merged with the `system` and later bound to the state.
Here is an example of each `type`
diff --git a/src/plugins/topbar/topbar.jsx b/src/plugins/topbar/topbar.jsx
index fd3654bb..ec9db730 100644
--- a/src/plugins/topbar/topbar.jsx
+++ b/src/plugins/topbar/topbar.jsx
@@ -130,7 +130,7 @@ export default class Topbar extends React.Component {
-
+
swagger