Merge branch 'master' into ft/oas3

This commit is contained in:
Kyle
2017-08-04 14:45:29 -07:00
committed by GitHub
96 changed files with 1767 additions and 1158 deletions

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@ node_modules
npm-debug.log* npm-debug.log*
.eslintcache .eslintcache
package-lock.json package-lock.json
*.iml

View File

@@ -16,7 +16,7 @@ deploy:
email: apiteam@swagger.io email: apiteam@swagger.io
skip_cleanup: true skip_cleanup: true
api_key: api_key:
secure: "IJkLaACa+rfERf1O5nwlqOyuo9sbul3FBhBt4Un9P+DvEet3AoDPV9NQVLd8SkmQYKGbGQWF4BIdjrO5nqFD6Te+JTeUX5Uo/DFS/fu9qw1xv0dQpvbJFuoYnnFlbzGTEs4CFa8lbu3ZromFHQGOQxRobjsG1Kf0dWFSSzmND3g=" secure: "YKk5L1BL4oAixvLjWp+i85fNFXK85HKOlUt6QypkZkt23My5aywuYsv5VCLjjOtuWc72zbmOzP82DTBsuRswCRViXWCiNYhl42QTdvadHu0uIlM/FL6aNlvPpzXIws4bMvz1aYOTzFTnSnNuvCTzF1daW0+2ClOo3r0nLEdDfFg="
on: on:
tags: true tags: true
repo: swagger-api/swagger-ui repo: swagger-api/swagger-ui

View File

@@ -18,16 +18,17 @@ This repo publishes to two different NPM packages:
For the older version of swagger-ui, refer to the [*2.x branch*](https://github.com/swagger-api/swagger-ui/tree/2.x). For the older version of swagger-ui, refer to the [*2.x branch*](https://github.com/swagger-api/swagger-ui/tree/2.x).
## Compatibility ## Compatibility
The OpenAPI Specification has undergone 4 revisions since initial creation in 2010. Compatibility between swagger-ui and the OpenAPI Specification is as follows: The OpenAPI Specification has undergone 5 revisions since initial creation in 2010. Compatibility between swagger-ui and the OpenAPI Specification is as follows:
Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes | Status Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes
------------------ | ------------ | -------------------------- | ----- | ------ ------------------ | ------------ | -------------------------- | -----
3.0.17 | 2017-06-23 | 2.0 | [tag v3.0.17](https://github.com/swagger-api/swagger-ui/tree/v3.0.17) | 3.1.2 | 2017-07-31 | 2.0, 3.0 | [tag v3.1.2](https://github.com/swagger-api/swagger-ui/tree/v3.1.2)
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) | 3.0.21 | 2017-07-26 | 2.0 | [tag v3.0.21](https://github.com/swagger-api/swagger-ui/tree/v3.0.21)
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) | 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.0.24 | 2014-09-12 | 1.1, 1.2 | [tag v2.0.24](https://github.com/swagger-api/swagger-ui/tree/v2.0.24) | 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.0.13 | 2013-03-08 | 1.1, 1.2 | [tag v1.0.13](https://github.com/swagger-api/swagger-ui/tree/v1.0.13) | 2.0.24 | 2014-09-12 | 1.1, 1.2 | [tag v2.0.24](https://github.com/swagger-api/swagger-ui/tree/v2.0.24)
1.0.1 | 2011-10-11 | 1.0, 1.1 | [tag v1.0.1](https://github.com/swagger-api/swagger-ui/tree/v1.0.1) | 1.0.13 | 2013-03-08 | 1.1, 1.2 | [tag v1.0.13](https://github.com/swagger-api/swagger-ui/tree/v1.0.13)
1.0.1 | 2011-10-11 | 1.0, 1.1 | [tag v1.0.1](https://github.com/swagger-api/swagger-ui/tree/v1.0.1)
### How to run ### How to run
@@ -67,7 +68,6 @@ To help with the migration, here are the currently known issues with 3.X. This l
- Only part of the [parameters](#parameters) previously supported are available. - Only part of the [parameters](#parameters) previously supported are available.
- The JSON Form Editor is not implemented. - The JSON Form Editor is not implemented.
- Shebang URL support for operations is missing.
- Support for `collectionFormat` is partial. - Support for `collectionFormat` is partial.
- l10n (translations) is not implemented. - l10n (translations) is not implemented.
- Relative path support for external files is not implemented. - Relative path support for external files is not implemented.
@@ -97,7 +97,8 @@ To use swagger-ui's bundles, you should take a look at the [source of swagger-ui
#### OAuth2 configuration #### OAuth2 configuration
You can configure OAuth2 authorization by calling `initOAuth` method with passed configs under the instance of `SwaggerUIBundle` You can configure OAuth2 authorization by calling `initOAuth` method with passed configs under the instance of `SwaggerUIBundle`
default `client_id` and `client_secret`, `realm`, an application name `appName`, `scopeSeparator`, `additionalQueryStringParams`. default `client_id` and `client_secret`, `realm`, an application name `appName`, `scopeSeparator`, `additionalQueryStringParams`,
`useBasicAuthenticationWithAccessCodeGrant`.
Config Name | Description Config Name | Description
--- | --- --- | ---
@@ -107,6 +108,7 @@ realm | realm query parameter (for oauth1) added to `authorizationUrl` and `toke
appName | application name, displayed in authorization popup. MUST be a string appName | application name, displayed in authorization popup. MUST be a string
scopeSeparator | scope separator for passing scopes, encoded before calling, default value is a space (encoded value `%20`). MUST be a string scopeSeparator | scope separator for passing scopes, encoded before calling, default value is a space (encoded value `%20`). MUST be a string
additionalQueryStringParams | Additional query parameters added to `authorizationUrl` and `tokenUrl`. MUST be an object additionalQueryStringParams | Additional query parameters added to `authorizationUrl` and `tokenUrl`. MUST be an object
useBasicAuthenticationWithAccessCodeGrant | Only activated for the `accessCode` flow. During the `authorization_code` request to the `tokenUrl`, pass the [Client Password](https://tools.ietf.org/html/rfc6749#section-2.3.1) using the HTTP Basic Authentication scheme (`Authorization` header with `Basic base64encoded[client_id:client_secret]`). The default is `false`
``` ```
const ui = SwaggerUIBundle({...}) const ui = SwaggerUIBundle({...})
@@ -136,7 +138,9 @@ urls.primaryName | When using `urls`, you can use this subparameter. If the valu
spec | A JSON object describing the OpenAPI Specification. When used, the `url` parameter will not be parsed. This is useful for testing manually-generated specifications without hosting them. spec | A JSON object describing the OpenAPI Specification. When used, the `url` parameter will not be parsed. This is useful for testing manually-generated specifications without hosting them.
validatorUrl | By default, Swagger-UI attempts to validate specs against swagger.io's online validator. You can use this parameter to set a different validator URL, for example for locally deployed validators ([Validator Badge](https://github.com/swagger-api/validator-badge)). Setting it to `null` will disable validation. validatorUrl | By default, Swagger-UI attempts to validate specs against swagger.io's online validator. You can use this parameter to set a different validator URL, for example for locally deployed validators ([Validator Badge](https://github.com/swagger-api/validator-badge)). Setting it to `null` will disable validation.
dom_id | The id of a dom element inside which SwaggerUi will put the user interface for swagger. dom_id | The id of a dom element inside which SwaggerUi will put the user interface for swagger.
domNode | The HTML DOM element inside which SwaggerUi will put the user interface for swagger. Overrides `dom_id`.
oauth2RedirectUrl | OAuth redirect URL oauth2RedirectUrl | OAuth redirect URL
tagsSorter | Apply a sort to the tag list of each API. It can be 'alpha' (sort by paths alphanumerically) or a function (see [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) to learn how to write a sort function). Two tag name strings are passed to the sorter for each pass. Default is the order determined by Swagger-UI.
operationsSorter | Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically), 'method' (sort by HTTP method) or a function (see Array.prototype.sort() to know how sort function works). Default is the order returned by the server unchanged. operationsSorter | Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically), 'method' (sort by HTTP method) or a function (see Array.prototype.sort() to know how sort function works). Default is the order returned by the server unchanged.
configUrl | Configs URL configUrl | Configs URL
parameterMacro | MUST be a function. Function to set default value to parameters. Accepts two arguments parameterMacro(operation, parameter). Operation and parameter are objects passed for context, both remain immutable parameterMacro | MUST be a function. Function to set default value to parameters. Accepts two arguments parameterMacro(operation, parameter). Operation and parameter are objects passed for context, both remain immutable
@@ -144,6 +148,9 @@ modelPropertyMacro | MUST be a function. Function to set default values to each
docExpansion | Controls the default expansion setting for the operations and tags. It can be 'list' (expands only the tags), 'full' (expands the tags and operations) or 'none' (expands nothing). The default is 'list'. docExpansion | Controls the default expansion setting for the operations and tags. It can be 'list' (expands only the tags), 'full' (expands the tags and operations) or 'none' (expands nothing). The default is 'list'.
displayOperationId | Controls the display of operationId in operations list. The default is `false`. displayOperationId | Controls the display of operationId in operations list. The default is `false`.
displayRequestDuration | Controls the display of the request duration (in milliseconds) for `Try it out` requests. The default is `false`. 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 ### Plugins
@@ -233,6 +240,10 @@ Access-Control-Allow-Headers: Content-Type, api_key, Authorization
Only headers with these names will be allowed to be sent by Swagger-UI. 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 ## License
Copyright 2017 SmartBear Software Copyright 2017 SmartBear Software

1
dist/index.html vendored
View File

@@ -76,6 +76,7 @@ window.onload = function() {
const ui = SwaggerUIBundle({ const ui = SwaggerUIBundle({
url: "http://petstore.swagger.io/v2/swagger.json", url: "http://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui', dom_id: '#swagger-ui',
deepLinking: true,
presets: [ presets: [
SwaggerUIBundle.presets.apis, SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset SwaggerUIStandalonePreset

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;AAsyKA;;;;;;AA0sEA;;;;;;;;;;;;;;;;;;;;;;;;;;AAgmUA;;;;;;;;;;;;;;AA8+JA;;;;;;;;;AAoipBA;;;;;AAwvPA;AAm4DA;;;;;;AA4iXA;;;;;;AAkiaA;AAs/uBA","sourceRoot":""} {"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;AAoyKA;;;;;;AAy+EA;;;;;;;;;;;;;;;;;;;;;;;;;;AAw1TA;;;;;;;;;;;;;;AAs8JA;;;;;;;;;AAy6oBA;;;;;AAqqQA;AAm4DA;;;;;;AAo4YA;;;;;;AA8jaA;AAumvBA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"swagger-ui-standalone-preset.js","sources":["webpack:///swagger-ui-standalone-preset.js"],"mappings":"AAAA;;;;;AA02CA;;;;;;AAyuFA","sourceRoot":""} {"version":3,"file":"swagger-ui-standalone-preset.js","sources":["webpack:///swagger-ui-standalone-preset.js"],"mappings":"AAAA;;;;;AA00CA;;;;;;AAmlFA","sourceRoot":""}

5
dist/swagger-ui.css vendored

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"swagger-ui.css","sources":[],"mappings":";;;","sourceRoot":""} {"version":3,"file":"swagger-ui.css","sources":[],"mappings":"","sourceRoot":""}

4
dist/swagger-ui.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AAmjcA","sourceRoot":""} {"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AAyvcA","sourceRoot":""}

36
docs/deep-linking.md Normal file
View File

@@ -0,0 +1,36 @@
# Deep linking
Swagger-UI allows you to deeply link into tags and operations within a spec. When Swagger-UI is provided a URL fragment at runtime, it will automatically expand and scroll to a specified tag or operation.
## Usage
👉🏼 Add `deepLinking: true` to your Swagger-UI configuration to enable this functionality.
When you expand a tag or operation, Swagger-UI will automatically update its URL fragment with a deep link to the item.
Conversely, when you collapse a tag or operation, Swagger-UI will clear the URL fragment.
You can also right-click a tag name or operation path in order to copy a link to that tag or operation.
#### Fragment format
The fragment is formatted in one of two ways:
- `#/{tagName}`, to trigger the focus of a specific tag
- `#/{tagName}/{operationId}`, to trigger the focus of a specific operation within a tag
`operationId` is the explicit operationId provided in the spec, if one exists.
Otherwise, Swagger-UI generates an implicit operationId by combining the operation's path and method, and escaping non-alphanumeric characters.
## FAQ
> I'm using Swagger-UI in an application that needs control of the URL fragment. How do I disable deep-linking?
This functionality is disabled by default, but you can pass `deepLinking: false` into Swagger-UI as a configuration item to be sure.
> Can I link to multiple tags or operations?
No, this is not supported.
> Can I collapse everything except the operation or tag I'm linking to?
Sure - use `docExpansion: none` to collapse all tags and operations. Your deep link will take precedence over the setting, so only the tag or operation you've specified will be expanded.

View File

@@ -1,11 +1,12 @@
var path = require('path') var path = require("path")
var webpack = require('webpack') var webpack = require("webpack")
var ExtractTextPlugin = require('extract-text-webpack-plugin') var ExtractTextPlugin = require("extract-text-webpack-plugin")
var deepExtend = require('deep-extend') var deepExtend = require("deep-extend")
const {gitDescribeSync} = require('git-describe'); const {gitDescribeSync} = require("git-describe")
const os = require("os")
var pkg = require('./package.json') var pkg = require("./package.json")
let gitInfo let gitInfo
@@ -13,7 +14,7 @@ try {
gitInfo = gitDescribeSync(__dirname) gitInfo = gitDescribeSync(__dirname)
} catch(e) { } catch(e) {
gitInfo = { gitInfo = {
hash: 'noGit', hash: "noGit",
dirty: false dirty: false
} }
} }
@@ -21,21 +22,21 @@ try {
var commonRules = [ var commonRules = [
{ test: /\.(js(x)?)(\?.*)?$/, { test: /\.(js(x)?)(\?.*)?$/,
use: [{ use: [{
loader: 'babel-loader', loader: "babel-loader",
options: { options: {
retainLines: true retainLines: true
} }
}], }],
include: [ path.join(__dirname, 'src') ] include: [ path.join(__dirname, "src") ]
}, },
{ test: /\.(txt|yaml)(\?.*)?$/, { test: /\.(txt|yaml)(\?.*)?$/,
loader: 'raw-loader' }, loader: "raw-loader" },
{ test: /\.(png|jpg|jpeg|gif|svg)(\?.*)?$/, { test: /\.(png|jpg|jpeg|gif|svg)(\?.*)?$/,
loader: 'url-loader?limit=10000' }, loader: "url-loader?limit=10000" },
{ test: /\.(woff|woff2)(\?.*)?$/, { test: /\.(woff|woff2)(\?.*)?$/,
loader: 'url-loader?limit=100000' }, loader: "url-loader?limit=100000" },
{ test: /\.(ttf|eot)(\?.*)?$/, { test: /\.(ttf|eot)(\?.*)?$/,
loader: 'file-loader' } loader: "file-loader" }
] ]
module.exports = function(rules, options) { module.exports = function(rules, options) {
@@ -54,13 +55,12 @@ module.exports = function(rules, options) {
if( specialOptions.separateStylesheets ) { if( specialOptions.separateStylesheets ) {
plugins.push(new ExtractTextPlugin({ plugins.push(new ExtractTextPlugin({
filename: '[name].css' + (specialOptions.longTermCaching ? '?[contenthash]' : ''), filename: "[name].css" + (specialOptions.longTermCaching ? "?[contenthash]" : ""),
allChunks: true allChunks: true
})) }))
} }
if( specialOptions.minimize ) { if( specialOptions.minimize ) {
plugins.push( plugins.push(
new webpack.optimize.UglifyJsPlugin({ new webpack.optimize.UglifyJsPlugin({
sourceMap: true, sourceMap: true,
@@ -78,15 +78,16 @@ module.exports = function(rules, options) {
plugins.push( plugins.push(
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': { "process.env": {
NODE_ENV: specialOptions.minimize ? JSON.stringify('production') : null, NODE_ENV: specialOptions.minimize ? JSON.stringify("production") : null,
WEBPACK_INLINE_STYLES: !Boolean(specialOptions.separateStylesheets) WEBPACK_INLINE_STYLES: !specialOptions.separateStylesheets
}, },
'buildInfo': JSON.stringify({ "buildInfo": JSON.stringify({
PACKAGE_VERSION: (pkg.version), PACKAGE_VERSION: (pkg.version),
GIT_COMMIT: gitInfo.hash, GIT_COMMIT: gitInfo.hash,
GIT_DIRTY: gitInfo.dirty GIT_DIRTY: gitInfo.dirty,
HOSTNAME: os.hostname(),
BUILD_TIME: new Date().toUTCString()
}) })
})) }))
@@ -96,17 +97,17 @@ module.exports = function(rules, options) {
entry: {}, entry: {},
output: { output: {
path: path.join(__dirname, 'dist'), path: path.join(__dirname, "dist"),
publicPath: '/', publicPath: "/",
filename: '[name].js', filename: "[name].js",
chunkFilename: '[name].js' chunkFilename: "[name].js"
}, },
target: 'web', target: "web",
// yaml-js has a reference to `fs`, this is a workaround // yaml-js has a reference to `fs`, this is a workaround
node: { node: {
fs: 'empty' fs: "empty"
}, },
module: { module: {
@@ -114,17 +115,17 @@ module.exports = function(rules, options) {
}, },
resolveLoader: { resolveLoader: {
modules: [path.join(__dirname, 'node_modules')], modules: [path.join(__dirname, "node_modules")],
}, },
externals: { externals: {
'buffertools': true // json-react-schema/deeper depends on buffertools, which fails. "buffertools": true // json-react-schema/deeper depends on buffertools, which fails.
}, },
resolve: { resolve: {
modules: [ modules: [
path.join(__dirname, './src'), path.join(__dirname, "./src"),
'node_modules' "node_modules"
], ],
extensions: [".web.js", ".js", ".jsx", ".json", ".less"], extensions: [".web.js", ".js", ".jsx", ".json", ".less"],
alias: { alias: {
@@ -132,7 +133,7 @@ module.exports = function(rules, options) {
} }
}, },
devtool: specialOptions.sourcemaps ? 'cheap-module-source-map' : null, devtool: specialOptions.sourcemaps ? "cheap-module-source-map" : null,
plugins, plugins,

View File

@@ -1,6 +1,6 @@
{ {
"name": "swagger-ui", "name": "swagger-ui",
"version": "3.0.17", "version": "3.1.2",
"main": "dist/swagger-ui.js", "main": "dist/swagger-ui.js",
"repository": "git@github.com:swagger-api/swagger-ui.git", "repository": "git@github.com:swagger-api/swagger-ui.git",
"contributors": [ "contributors": [
@@ -43,7 +43,6 @@
"ieee754": "^1.1.8", "ieee754": "^1.1.8",
"immutable": "^3.x.x", "immutable": "^3.x.x",
"js-yaml": "^3.5.5", "js-yaml": "^3.5.5",
"less": "2.7.1",
"lodash": "4.17.2", "lodash": "4.17.2",
"matcher": "^0.1.2", "matcher": "^0.1.2",
"memoizee": "0.4.1", "memoizee": "0.4.1",
@@ -69,9 +68,10 @@
"redux-logger": "*", "redux-logger": "*",
"reselect": "2.5.3", "reselect": "2.5.3",
"sanitize-html": "^1.14.1", "sanitize-html": "^1.14.1",
"scroll-to-element": "^2.0.0",
"serialize-error": "2.0.0", "serialize-error": "2.0.0",
"shallowequal": "0.2.2", "shallowequal": "0.2.2",
"swagger-client": "3.0.16", "swagger-client": "3.0.19",
"url-parse": "^1.1.8", "url-parse": "^1.1.8",
"whatwg-fetch": "0.11.1", "whatwg-fetch": "0.11.1",
"worker-loader": "^0.7.1", "worker-loader": "^0.7.1",
@@ -109,7 +109,6 @@
"karma-sourcemap-loader": "^0.3.7", "karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "2.0.3", "karma-webpack": "2.0.3",
"less": "2.7.2", "less": "2.7.2",
"less-loader": "4.0.4",
"license-checker": "^11.0.0", "license-checker": "^11.0.0",
"mocha": "^3.4.2", "mocha": "^3.4.2",
"node-sass": "^4.5.0", "node-sass": "^4.5.0",

View File

@@ -1 +1,5 @@
module.exports = {}; module.exports = {
plugins: [
require("autoprefixer")
]
}

View File

@@ -0,0 +1,47 @@
import React, { Component } from "react"
import PropTypes from "prop-types"
const propStyle = { color: "#999", fontStyle: "italic" }
export default class ArrayModel extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
required: PropTypes.bool,
expandDepth: PropTypes.number,
depth: PropTypes.number
}
render(){
let { getComponent, required, schema, depth, expandDepth, name } = this.props
let items = schema.get("items")
let title = schema.get("title") || name
let properties = schema.filter( ( v, key) => ["type", "items", "$$ref"].indexOf(key) === -1 )
const ModelCollapse = getComponent("ModelCollapse")
const Model = getComponent("Model")
const titleEl = title &&
<span className="model-title">
<span className="model-title__text">{ title }</span>
</span>
return <span className="model">
<ModelCollapse title={titleEl} collapsed={ depth > expandDepth } collapsedContent="[...]">
[
<span><Model { ...this.props } schema={ items } required={ false }/></span>
]
{
properties.size ? <span>
{ properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={propStyle}>
<br />{ `${key}:`}{ String(v) }</span>)
}<br /></span>
: null
}
</ModelCollapse>
{ required && <span style={{ color: "red" }}>*</span>}
</span>
}
}

View File

@@ -0,0 +1,19 @@
import React from "react"
import ImPropTypes from "react-immutable-proptypes"
const EnumModel = ({ value, getComponent }) => {
let ModelCollapse = getComponent("ModelCollapse")
let collapsedContent = <span>Array [ { value.count() } ]</span>
return <span className="prop-enum">
Enum:<br />
<ModelCollapse collapsedContent={ collapsedContent }>
[ { value.join(", ") } ]
</ModelCollapse>
</span>
}
EnumModel.propTypes = {
value: ImPropTypes.iterable,
getComponent: ImPropTypes.func
}
export default EnumModel

View File

@@ -15,7 +15,7 @@ class Path extends React.Component {
return ( return (
<pre className="base-url"> <pre className="base-url">
[ Base url: {host}{basePath}] [ Base URL: {host}{basePath} ]
</pre> </pre>
) )
} }

View File

@@ -129,7 +129,8 @@ export class Select extends React.Component {
value: PropTypes.any, value: PropTypes.any,
onChange: PropTypes.func, onChange: PropTypes.func,
multiple: PropTypes.bool, multiple: PropTypes.bool,
allowEmptyValue: PropTypes.bool allowEmptyValue: PropTypes.bool,
className: PropTypes.string
} }
static defaultProps = { static defaultProps = {
@@ -142,7 +143,7 @@ export class Select extends React.Component {
let value let value
if (props.value !== undefined) { if (props.value) {
value = props.value value = props.value
} else { } else {
value = props.multiple ? [""] : "" 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 let value = this.state.value.toJS ? this.state.value.toJS() : this.state.value
return ( return (
<select multiple={ multiple } value={ value } onChange={ this.onChange } > <select className={this.props.className} multiple={ multiple } value={ value } onChange={ this.onChange } >
{ allowEmptyValue ? <option value="">--</option> : null } { allowEmptyValue ? <option value="">--</option> : null }
{ {
allowedValues.map(function (item, key) { allowedValues.map(function (item, key) {

View File

@@ -13,8 +13,13 @@ export default class BaseLayout extends React.Component {
getComponent: PropTypes.func.isRequired getComponent: PropTypes.func.isRequired
} }
onFilterChange =(e) => {
let {target: {value}} = e
this.props.layoutActions.updateFilter(value)
}
render() { render() {
let { specSelectors, specActions, getComponent } = this.props let { specSelectors, specActions, getComponent, layoutSelectors } = this.props
let info = specSelectors.info() let info = specSelectors.info()
let url = specSelectors.url() let url = specSelectors.url()
@@ -26,11 +31,20 @@ export default class BaseLayout extends React.Component {
let Info = getComponent("info") let Info = getComponent("info")
let Operations = getComponent("operations", true) let Operations = getComponent("operations", true)
let Models = getComponent("models", true) let Models = getComponent("Models", true)
let AuthorizeBtn = getComponent("authorizeBtn", true) let AuthorizeBtn = getComponent("authorizeBtn", true)
let Row = getComponent("Row") let Row = getComponent("Row")
let Col = getComponent("Col") let Col = getComponent("Col")
let Errors = getComponent("errors", true) let Errors = getComponent("errors", true)
let isLoading = specSelectors.loadingStatus() === "loading"
let isFailed = specSelectors.loadingStatus() === "failed"
let filter = layoutSelectors.currentFilter()
let inputStyle = {}
if(isFailed) inputStyle.color = "red"
if(isLoading) inputStyle.color = "#aaa"
const Schemes = getComponent("schemes") const Schemes = getComponent("schemes")
const isSpecEmpty = !specSelectors.specStr() const isSpecEmpty = !specSelectors.specStr()
@@ -57,6 +71,7 @@ export default class BaseLayout extends React.Component {
{ schemes && schemes.size ? ( { schemes && schemes.size ? (
<Schemes schemes={ schemes } specActions={ specActions } /> <Schemes schemes={ schemes } specActions={ specActions } />
) : null } ) : null }
{ securityDefinitions ? ( { securityDefinitions ? (
<AuthorizeBtn /> <AuthorizeBtn />
) : null } ) : null }
@@ -64,6 +79,15 @@ export default class BaseLayout extends React.Component {
</div> </div>
) : null } ) : null }
{
filter === null || filter === false ? null :
<div className="filter-container">
<Col className="filter wrapper" mobile={12}>
<input className="operation-filter-input" placeholder="Filter by tag" type="text" onChange={this.onFilterChange} value={filter === true || filter === "true" ? "" : filter} disabled={isLoading} style={inputStyle} />
</Col>
</div>
}
<Row> <Row>
<Col mobile={12} desktop={12} > <Col mobile={12} desktop={12} >
<Operations/> <Operations/>

View File

@@ -0,0 +1,47 @@
import React, { Component } from "react"
import PropTypes from "prop-types"
export default class ModelCollapse extends Component {
static propTypes = {
collapsedContent: PropTypes.any,
collapsed: PropTypes.bool,
children: PropTypes.any,
title: PropTypes.element
}
static defaultProps = {
collapsedContent: "{...}",
collapsed: true,
title: null
}
constructor(props, context) {
super(props, context)
let { collapsed, collapsedContent } = this.props
this.state = {
collapsed: collapsed !== undefined ? collapsed : ModelCollapse.defaultProps.collapsed,
collapsedContent: collapsedContent || ModelCollapse.defaultProps.collapsedContent
}
}
toggleCollapsed=()=>{
this.setState({
collapsed: !this.state.collapsed
})
}
render () {
const {title} = this.props
return (
<span>
{ title && <span onClick={this.toggleCollapsed} style={{ "cursor": "pointer" }}>{title}</span> }
<span onClick={ this.toggleCollapsed } style={{ "cursor": "pointer" }}>
<span className={ "model-toggle" + ( this.state.collapsed ? " collapsed" : "" ) }></span>
</span>
{ this.state.collapsed ? this.state.collapsedContent : this.props.children }
</span>
)
}
}

View File

@@ -28,7 +28,7 @@ export default class ModelExample extends React.Component {
render() { render() {
let { getComponent, specSelectors, schema, example, isExecute } = this.props let { getComponent, specSelectors, schema, example, isExecute } = this.props
const Model = getComponent("model") const ModelWrapper = getComponent("ModelWrapper")
return <div> return <div>
<ul className="tab"> <ul className="tab">
@@ -44,7 +44,7 @@ export default class ModelExample extends React.Component {
(isExecute || this.state.activeTab === "example") && example (isExecute || this.state.activeTab === "example") && example
} }
{ {
!isExecute && this.state.activeTab === "model" && <Model schema={ schema } !isExecute && this.state.activeTab === "model" && <ModelWrapper schema={ schema }
getComponent={ getComponent } getComponent={ getComponent }
specSelectors={ specSelectors } specSelectors={ specSelectors }
expandDepth={ 1 } /> expandDepth={ 1 } />

View File

@@ -0,0 +1,23 @@
import React, { Component, } from "react"
import PropTypes from "prop-types"
export default class ModelComponent extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
name: PropTypes.string,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
expandDepth: PropTypes.number
}
render(){
let { getComponent } = this.props
const Model = getComponent("Model")
return <div className="model-box">
<Model { ...this.props } depth={ 1 } expandDepth={ this.props.expandDepth || 0 }/>
</div>
}
}

View File

@@ -1,275 +1,7 @@
import React, { Component } from "react" import React, { Component } from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { List } from "immutable"
const braceOpen = "{"
const braceClose = "}"
const propStyle = { color: "#999", fontStyle: "italic" } export default class Model extends Component {
const EnumModel = ({ value }) => {
let collapsedContent = <span>Array [ { value.count() } ]</span>
return <span className="prop-enum">
Enum:<br />
<Collapse collapsedContent={ collapsedContent }>
[ { value.join(", ") } ]
</Collapse>
</span>
}
EnumModel.propTypes = {
value: ImPropTypes.iterable
}
class ObjectModel extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
isRef: PropTypes.bool,
expandDepth: PropTypes.number,
depth: PropTypes.number,
deprecated: PropTypes.boolean
}
render(){
let { schema, name, isRef, getComponent, depth, deprecated, ...props } = this.props
let { expandDepth, specSelectors } = this.props
const JumpToPath = getComponent("JumpToPath", true)
let description = schema.get("description")
let nullable = schema.get("nullable")
let properties = schema.get("properties")
let additionalProperties = schema.get("additionalProperties")
let title = schema.get("title") || name
let required = schema.get("required")
let anyOf = specSelectors.isOAS3() ? schema.get("anyOf") : null
let oneOf = specSelectors.isOAS3() ? schema.get("oneOf") : null
let not = specSelectors.isOAS3() ? schema.get("not") : null
const Markdown = getComponent("Markdown")
const JumpToPathSection = ({ name }) => <span className="model-jump-to-path"><JumpToPath path={`definitions.${name}`} /></span>
let collapsedContent = (<span>
<span>{ braceOpen }</span>...<span>{ braceClose }</span>
{
isRef ? <JumpToPathSection name={ name }/> : ""
}
</span>)
return <span className={`model ${deprecated ? "deprecated" : ""}`}>
{
title && <span className="model-title">
{ isRef && schema.get("$$ref") && <span className="model-hint">{ schema.get("$$ref") }</span> }
<span className="model-title__text">{ title }</span>
</span>
}
<Collapse collapsed={ depth > expandDepth } collapsedContent={ collapsedContent }>
<span className="brace-open object">{ braceOpen }</span>
{
!isRef ? null : <JumpToPathSection name={ name }/>
}
<span className="inner-object">
{
<table className="model" style={{ marginLeft: "2em" }}><tbody>
{
!description ? null : <tr style={{ color: "#999", fontStyle: "italic" }}>
<td>description:</td>
<td>
<Markdown source={ description } />
</td>
</tr>
}
{
(!specSelectors.isOAS3() || !nullable) ? null : <tr style={{ color: "#999", fontStyle: "italic" }}>
<td>nullable</td>
<td>{ nullable }</td>
</tr>
}
{
!(properties && properties.size) ? null : properties.entrySeq().map(
([key, value]) => {
let isRequired = List.isList(required) && required.contains(key)
let propertyStyle = { verticalAlign: "top", paddingRight: "0.2em" }
if ( isRequired ) {
propertyStyle.fontWeight = "bold"
}
return (<tr key={key} className={`${value.get("deprecated") ? "deprecated" : ""}`}>
<td style={ propertyStyle }>{ key }:</td>
<td style={{ verticalAlign: "top" }}>
<Model key={ `object-${name}-${key}_${value}` } { ...props }
required={ isRequired }
getComponent={ getComponent }
schema={ value }
depth={ depth + 1 } />
</td>
</tr>)
}).toArray()
}
{
!additionalProperties || !additionalProperties.size ? null
: <tr>
<td>{ "< * >:" }</td>
<td>
<Model { ...props } required={ false }
getComponent={ getComponent }
schema={ additionalProperties }
depth={ depth + 1 } />
</td>
</tr>
}
{
!anyOf ? null
: <tr>
<td>{ "anyOf ->" }</td>
<td>
{anyOf.map(schema => {
return <div><Model { ...props } required={ false }
getComponent={ getComponent }
schema={ schema }
depth={ depth + 1 } /></div>
})}
</td>
</tr>
}
{
!oneOf ? null
: <tr>
<td>{ "oneOf ->" }</td>
<td>
{oneOf.map(schema => {
return <div><Model { ...props } required={ false }
getComponent={ getComponent }
schema={ schema }
depth={ depth + 1 } /></div>
})}
</td>
</tr>
}
{
!not ? null
: <tr>
<td>{ "not ->" }</td>
<td>
{not.map(schema => {
return <div><Model { ...props } required={ false }
getComponent={ getComponent }
schema={ schema }
depth={ depth + 1 } /></div>
})}
</td>
</tr>
}
</tbody></table>
}
</span>
<span className="brace-close">{ braceClose }</span>
</Collapse>
</span>
}
}
class Primitive extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
name: PropTypes.string,
getComponent: PropTypes.func.isRequired,
required: PropTypes.bool,
deprecated: PropTypes.bool
}
render(){
let { schema, getComponent, name, required, deprecated } = this.props
if(!schema || !schema.get) {
// don't render if schema isn't correctly formed
return <div></div>
}
let type = schema.get("type")
let format = schema.get("format")
let xml = schema.get("xml")
let enumArray = schema.get("enum")
let title = schema.get("title") || name
let description = schema.get("description")
let properties = schema.filter( ( v, key) => ["enum", "type", "format", "description", "$$ref"].indexOf(key) === -1 )
let style = required ? { fontWeight: "bold" } : {}
const Markdown = getComponent("Markdown")
return <span className={`model ${deprecated ? "deprecated" : ""}`}>
{
title && <span className="model-title" style={{ marginRight: "2em" }}>
<span className="model-title__text">{ title }</span>
</span>
}
<span className="prop-type" style={ style }>{ type }</span> { required && <span style={{ color: "red" }}>*</span>}
{ format && <span className="prop-format">(${format})</span>}
{
properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }>
<br />{ key }: { String(v) }</span>)
: null
}
{
!description ? null :
<Markdown source={ description } />
}
{
xml && xml.size ? (<span><br /><span style={ propStyle }>xml:</span>
{
xml.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }><br/>&nbsp;&nbsp;&nbsp;{key}: { String(v) }</span>).toArray()
}
</span>): null
}
{
enumArray && <EnumModel value={ enumArray } />
}
</span>
}
}
class ArrayModel extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
required: PropTypes.bool,
expandDepth: PropTypes.number,
depth: PropTypes.number
}
render(){
let { required, schema, depth, name, expandDepth } = this.props
let items = schema.get("items")
let title = schema.get("title") || name
let properties = schema.filter( ( v, key) => ["type", "items", "$$ref"].indexOf(key) === -1 )
return <span className="model">
{
title && <span className="model-title">
<span className="model-title__text">{ title }</span>
</span>
}
<Collapse collapsed={ depth > expandDepth } collapsedContent="[...]">
[
<span><Model { ...this.props } name="" schema={ items } required={ false }/></span>
]
{
properties.size ? <span>
{ properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={propStyle}>
<br />{ `${key}:`}{ String(v) }</span>)
}<br /></span>
: null
}
</Collapse>
{ required && <span style={{ color: "red" }}>*</span>}
</span>
}
}
export class Model extends Component {
static propTypes = { static propTypes = {
schema: PropTypes.object.isRequired, schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
@@ -285,6 +17,9 @@ export class Model extends Component {
if ( ref.indexOf("#/definitions/") !== -1 ) { if ( ref.indexOf("#/definitions/") !== -1 ) {
return ref.replace(/^.*#\/definitions\//, "") return ref.replace(/^.*#\/definitions\//, "")
} }
if ( ref.indexOf("#/components/schemas/") !== -1 ) {
return ref.replace("#/components/schemas/", "")
}
} }
getRefSchema =( model )=> { getRefSchema =( model )=> {
@@ -295,6 +30,9 @@ export class Model extends Component {
render () { render () {
let { getComponent, specSelectors, schema, required, name, isRef } = this.props let { getComponent, specSelectors, schema, required, name, isRef } = this.props
let ObjectModel = getComponent("ObjectModel")
let ArrayModel = getComponent("ArrayModel")
let PrimitiveModel = getComponent("PrimitiveModel")
let $$ref = schema && schema.get("$$ref") let $$ref = schema && schema.get("$$ref")
let modelName = $$ref && this.getModelName( $$ref ) let modelName = $$ref && this.getModelName( $$ref )
let modelSchema, type let modelSchema, type
@@ -332,69 +70,12 @@ export class Model extends Component {
case "integer": case "integer":
case "boolean": case "boolean":
default: default:
return <Primitive return <PrimitiveModel
{ ...this.props } { ...this.props }
getComponent={ getComponent } getComponent={ getComponent }
schema={ modelSchema } schema={ modelSchema }
name={ name || modelName } name={ name || modelName }
deprecated={deprecated} deprecated={deprecated}
required={ required }/> required={ required }/> }
}
}
}
export default class ModelComponent extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
name: PropTypes.string,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
expandDepth: PropTypes.number
}
render(){
return <div className="model-box">
<Model { ...this.props } depth={ 1 } expandDepth={ this.props.expandDepth || 0 }/>
</div>
}
}
class Collapse extends Component {
static propTypes = {
collapsedContent: PropTypes.any,
collapsed: PropTypes.bool,
children: PropTypes.any
}
static defaultProps = {
collapsedContent: "{...}",
collapsed: true,
}
constructor(props, context) {
super(props, context)
let { collapsed, collapsedContent } = this.props
this.state = {
collapsed: collapsed !== undefined ? collapsed : Collapse.defaultProps.collapsed,
collapsedContent: collapsedContent || Collapse.defaultProps.collapsedContent
}
}
toggleCollapsed=()=>{
this.setState({
collapsed: !this.state.collapsed
})
}
render () {
return (<span>
<span onClick={ this.toggleCollapsed } style={{ "cursor": "pointer" }}>
<span className={ "model-toggle" + ( this.state.collapsed ? " collapsed" : "" ) }></span>
</span>
{ this.state.collapsed ? this.state.collapsedContent : this.props.children }
</span>)
} }
} }

View File

@@ -16,7 +16,7 @@ export default class Models extends Component {
let { docExpansion } = getConfigs() let { docExpansion } = getConfigs()
let showModels = layoutSelectors.isShown("models", docExpansion === "full" || docExpansion === "list" ) let showModels = layoutSelectors.isShown("models", docExpansion === "full" || docExpansion === "list" )
const Model = getComponent("model") const ModelWrapper = getComponent("ModelWrapper")
const Collapse = getComponent("Collapse") const Collapse = getComponent("Collapse")
if (!definitions.size) return null if (!definitions.size) return null
@@ -28,11 +28,11 @@ export default class Models extends Component {
<use xlinkHref={showModels ? "#large-arrow-down" : "#large-arrow"} /> <use xlinkHref={showModels ? "#large-arrow-down" : "#large-arrow"} />
</svg> </svg>
</h4> </h4>
<Collapse isOpened={showModels} animated> <Collapse isOpened={showModels}>
{ {
definitions.entrySeq().map( ( [ name, model ])=>{ definitions.entrySeq().map( ( [ name, model ])=>{
return <div className="model-container" key={ `models-section-${name}` }> return <div className="model-container" key={ `models-section-${name}` }>
<Model name={ name } <ModelWrapper name={ name }
schema={ model } schema={ model }
isRef={ true } isRef={ true }
getComponent={ getComponent } getComponent={ getComponent }

View File

@@ -0,0 +1,155 @@
import React, { Component, } from "react"
import PropTypes from "prop-types"
import { List } from "immutable"
const braceOpen = "{"
const braceClose = "}"
export default class ObjectModel extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
isRef: PropTypes.bool,
expandDepth: PropTypes.number,
depth: PropTypes.number
}
render(){
let { schema, name, isRef, getComponent, depth, ...props } = this.props
let { expandDepth, specSelectors } = this.props
let { isOAS3 } = specSelectors
let description = schema.get("description")
let properties = schema.get("properties")
let additionalProperties = schema.get("additionalProperties")
let title = schema.get("title") || name
let requiredProperties = schema.get("required")
const JumpToPath = getComponent("JumpToPath", true)
const Markdown = getComponent("Markdown")
const Model = getComponent("Model")
const ModelCollapse = getComponent("ModelCollapse")
const JumpToPathSection = ({ name }) => {
const path = isOAS3 && isOAS3() ? `components.schemas.${name}` : `definitions.${name}`
return <span className="model-jump-to-path"><JumpToPath path={path} /></span>
}
const collapsedContent = (<span>
<span>{ braceOpen }</span>...<span>{ braceClose }</span>
{
isRef ? <JumpToPathSection name={ name }/> : ""
}
</span>)
const anyOf = specSelectors.isOAS3() ? schema.get("anyOf") : null
const oneOf = specSelectors.isOAS3() ? schema.get("oneOf") : null
const not = specSelectors.isOAS3() ? schema.get("not") : null
const titleEl = title && <span className="model-title">
{ isRef && schema.get("$$ref") && <span className="model-hint">{ schema.get("$$ref") }</span> }
<span className="model-title__text">{ title }</span>
</span>
return <span className="model">
<ModelCollapse title={titleEl} collapsed={ depth > expandDepth } collapsedContent={ collapsedContent }>
<span className="brace-open object">{ braceOpen }</span>
{
!isRef ? null : <JumpToPathSection name={ name }/>
}
<span className="inner-object">
{
<table className="model" style={{ marginLeft: "2em" }}><tbody>
{
!description ? null : <tr style={{ color: "#999", fontStyle: "italic" }}>
<td>description:</td>
<td>
<Markdown source={ description } />
</td>
</tr>
}
{
!(properties && properties.size) ? null : properties.entrySeq().map(
([key, value]) => {
let isRequired = List.isList(requiredProperties) && requiredProperties.contains(key)
let propertyStyle = { verticalAlign: "top", paddingRight: "0.2em" }
if ( isRequired ) {
propertyStyle.fontWeight = "bold"
}
return (<tr key={key}>
<td style={ propertyStyle }>
{ key }{ isRequired && <span style={{ color: "red" }}>*</span> }
</td>
<td style={{ verticalAlign: "top" }}>
<Model key={ `object-${name}-${key}_${value}` } { ...props }
required={ isRequired }
getComponent={ getComponent }
schema={ value }
depth={ depth + 1 } />
</td>
</tr>)
}).toArray()
}
{
!additionalProperties || !additionalProperties.size ? null
: <tr>
<td>{ "< * >:" }</td>
<td>
<Model { ...props } required={ false }
getComponent={ getComponent }
schema={ additionalProperties }
depth={ depth + 1 } />
</td>
</tr>
}
{
!anyOf ? null
: <tr>
<td>{ "anyOf ->" }</td>
<td>
{anyOf.map((schema, k) => {
return <div key={k}><Model { ...props } required={ false }
getComponent={ getComponent }
schema={ schema }
depth={ depth + 1 } /></div>
})}
</td>
</tr>
}
{
!oneOf ? null
: <tr>
<td>{ "oneOf ->" }</td>
<td>
{oneOf.map((schema, k) => {
return <div key={k}><Model { ...props } required={ false }
getComponent={ getComponent }
schema={ schema }
depth={ depth + 1 } /></div>
})}
</td>
</tr>
}
{
!not ? null
: <tr>
<td>{ "not ->" }</td>
<td>
{not.map((schema, k) => {
return <div key={k}><Model { ...props } required={ false }
getComponent={ getComponent }
schema={ schema }
depth={ depth + 1 } /></div>
})}
</td>
</tr>
}
</tbody></table>
}
</span>
<span className="brace-close">{ braceClose }</span>
</ModelCollapse>
</span>
}
}

View File

@@ -116,7 +116,8 @@ export default class Operation extends PureComponent {
specActions, specActions,
specSelectors, specSelectors,
authActions, authActions,
authSelectors authSelectors,
getConfigs
} = this.props } = this.props
let summary = operation.get("summary") let summary = operation.get("summary")
@@ -141,6 +142,10 @@ export default class Operation extends PureComponent {
const Markdown = getComponent( "Markdown" ) const Markdown = getComponent( "Markdown" )
const Schemes = getComponent( "schemes" ) const Schemes = getComponent( "schemes" )
const { deepLinking } = getConfigs()
const isDeepLinkingEnabled = deepLinking && deepLinking !== "false"
// Merge in Live Response // Merge in Live Response
if(response && response.size > 0) { if(response && response.size > 0) {
let notDocumented = !responses.get(String(response.get("status"))) let notDocumented = !responses.get(String(response.get("status")))
@@ -152,11 +157,16 @@ export default class Operation extends PureComponent {
let onChangeKey = [ path, method ] // Used to add values to _this_ operation ( indexed by path and method ) let onChangeKey = [ path, method ] // Used to add values to _this_ operation ( indexed by path and method )
return ( return (
<div className={deprecated ? "opblock opblock-deprecated" : shown ? `opblock opblock-${method} is-open` : `opblock opblock-${method}`} id={isShownKey} > <div className={deprecated ? "opblock opblock-deprecated" : shown ? `opblock opblock-${method} is-open` : `opblock opblock-${method}`} id={isShownKey.join("-")} >
<div className={`opblock-summary opblock-summary-${method}`} onClick={this.toggleShown} > <div className={`opblock-summary opblock-summary-${method}`} onClick={this.toggleShown} >
<span className="opblock-summary-method">{method.toUpperCase()}</span> <span className="opblock-summary-method">{method.toUpperCase()}</span>
<span className={ deprecated ? "opblock-summary-path__deprecated" : "opblock-summary-path" } > <span className={ deprecated ? "opblock-summary-path__deprecated" : "opblock-summary-path" } >
<a
className="nostyle"
onClick={(e) => e.preventDefault()}
href={ isDeepLinkingEnabled ? `#/${isShownKey[1]}/${isShownKey[2]}` : ""} >
<span>{path}</span> <span>{path}</span>
</a>
<JumpToPath path={jumpToKey} /> <JumpToPath path={jumpToKey} />
</span> </span>
@@ -191,7 +201,9 @@ export default class Operation extends PureComponent {
<div className="opblock-external-docs-wrapper"> <div className="opblock-external-docs-wrapper">
<h4 className="opblock-title_normal">Find more details</h4> <h4 className="opblock-title_normal">Find more details</h4>
<div className="opblock-external-docs"> <div className="opblock-external-docs">
<span className="opblock-external-docs__description">{ externalDocs.get("description") }</span> <span className="opblock-external-docs__description">
<Markdown source={ externalDocs.get("description") } />
</span>
<a className="opblock-external-docs__link" href={ externalDocs.get("url") }>{ externalDocs.get("url") }</a> <a className="opblock-external-docs__link" href={ externalDocs.get("url") }>{ externalDocs.get("url") }</a>
</div> </div>
</div> : null </div> : null

View File

@@ -1,5 +1,8 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { helpers } from "swagger-client"
const { opId } = helpers
export default class Operations extends React.Component { export default class Operations extends React.Component {
@@ -33,7 +36,29 @@ export default class Operations extends React.Component {
const Collapse = getComponent("Collapse") const Collapse = getComponent("Collapse")
let showSummary = layoutSelectors.showSummary() let showSummary = layoutSelectors.showSummary()
let { docExpansion, displayOperationId, displayRequestDuration } = getConfigs() let {
docExpansion,
displayOperationId,
displayRequestDuration,
maxDisplayedTags,
deepLinking
} = getConfigs()
const isDeepLinkingEnabled = deepLinking && deepLinking !== "false"
let filter = layoutSelectors.currentFilter()
if (filter) {
if (filter !== true) {
taggedOps = taggedOps.filter((tagObj, tag) => {
return tag.indexOf(filter) !== -1
})
}
}
if (maxDisplayedTags && !isNaN(maxDisplayedTags) && maxDisplayedTags >= 0) {
taggedOps = taggedOps.slice(0, maxDisplayedTags)
}
return ( return (
<div> <div>
@@ -41,6 +66,8 @@ export default class Operations extends React.Component {
taggedOps.map( (tagObj, tag) => { taggedOps.map( (tagObj, tag) => {
let operations = tagObj.get("operations") let operations = tagObj.get("operations")
let tagDescription = tagObj.getIn(["tagDetails", "description"], null) let tagDescription = tagObj.getIn(["tagDetails", "description"], null)
let tagExternalDocsDescription = tagObj.getIn(["tagDetails", "externalDocs", "description"])
let tagExternalDocsUrl = tagObj.getIn(["tagDetails", "externalDocs", "url"])
let isShownKey = ["operations-tag", tag] let isShownKey = ["operations-tag", tag]
let showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list") let showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list")
@@ -48,14 +75,38 @@ export default class Operations extends React.Component {
return ( return (
<div className={showTag ? "opblock-tag-section is-open" : "opblock-tag-section"} key={"operation-" + tag}> <div className={showTag ? "opblock-tag-section is-open" : "opblock-tag-section"} key={"operation-" + tag}>
<h4 onClick={() => layoutActions.show(isShownKey, !showTag)} className={!tagDescription ? "opblock-tag no-desc" : "opblock-tag" }> <h4
onClick={() => layoutActions.show(isShownKey, !showTag)}
className={!tagDescription ? "opblock-tag no-desc" : "opblock-tag" }
id={isShownKey.join("-")}>
<a
className="nostyle"
onClick={(e) => e.preventDefault()}
href={ isDeepLinkingEnabled ? `#/${tag}` : ""}>
<span>{tag}</span> <span>{tag}</span>
</a>
{ !tagDescription ? null : { !tagDescription ? null :
<small> <small>
{ tagDescription } { tagDescription }
</small> </small>
} }
<div>
{ !tagExternalDocsDescription ? null :
<small>
{ tagExternalDocsDescription }
{ tagExternalDocsUrl ? ": " : null }
{ tagExternalDocsUrl ?
<a
href={tagExternalDocsUrl}
onClick={(e) => e.stopPropagation()}
target={"_blank"}
>{tagExternalDocsUrl}</a> : null
}
</small>
}
</div>
<button className="expand-operation" title="Expand operation" onClick={() => layoutActions.show(isShownKey, !showTag)}> <button className="expand-operation" title="Expand operation" onClick={() => layoutActions.show(isShownKey, !showTag)}>
<svg className="arrow" width="20" height="20"> <svg className="arrow" width="20" height="20">
<use xlinkHref={showTag ? "#large-arrow-down" : "#large-arrow"} /> <use xlinkHref={showTag ? "#large-arrow-down" : "#large-arrow"} />
@@ -67,11 +118,14 @@ export default class Operations extends React.Component {
{ {
operations.map( op => { operations.map( op => {
const isShownKey = ["operations", op.get("id"), tag]
const path = op.get("path", "") const path = op.get("path", "")
const method = op.get("method", "") const method = op.get("method", "")
const jumpToKey = `paths.${path}.${method}` const jumpToKey = `paths.${path}.${method}`
const operationId =
op.getIn(["operation", "operationId"]) || op.getIn(["operation", "__originalOperationId"]) || opId(op.get("operation"), path, method) || op.get("id")
const isShownKey = ["operations", tag, operationId]
const allowTryItOut = specSelectors.allowTryItOutFor(op.get("path"), op.get("method")) const allowTryItOut = specSelectors.allowTryItOutFor(op.get("path"), op.get("method"))
const response = specSelectors.responseFor(op.get("path"), op.get("method")) const response = specSelectors.responseFor(op.get("path"), op.get("method"))
const request = specSelectors.requestFor(op.get("path"), op.get("method")) const request = specSelectors.requestFor(op.get("path"), op.get("method"))

View File

@@ -49,10 +49,11 @@ export default class ParamBody extends PureComponent {
let { specSelectors, pathMethod, param, isExecute, consumesValue="" } = props let { specSelectors, pathMethod, param, isExecute, consumesValue="" } = props
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name")) : {} let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name")) : {}
let isXml = /xml/i.test(consumesValue) let isXml = /xml/i.test(consumesValue)
let isJson = /json/i.test(consumesValue)
let paramValue = isXml ? parameter.get("value_xml") : parameter.get("value") let paramValue = isXml ? parameter.get("value_xml") : parameter.get("value")
if ( paramValue !== undefined ) { if ( paramValue !== undefined ) {
let val = !paramValue && !isXml ? "{}" : paramValue let val = !paramValue && isJson ? "{}" : paramValue
this.setState({ value: val }) this.setState({ value: val })
this.onChange(val, {isXml: isXml, isEditBox: isExecute}) this.onChange(val, {isXml: isXml, isEditBox: isExecute})
} else { } else {
@@ -79,8 +80,11 @@ export default class ParamBody extends PureComponent {
_onChange = (val, isXml) => { (this.props.onChange || NOOP)(this.props.param, val, isXml) } _onChange = (val, isXml) => { (this.props.onChange || NOOP)(this.props.param, val, isXml) }
handleOnChange = e => { handleOnChange = e => {
let {consumesValue} = this.props const {consumesValue} = this.props
this.onChange(e.target.value.trim(), {isXml: /xml/i.test(consumesValue)}) const isJson = /json/i.test(consumesValue)
const isXml = /xml/i.test(consumesValue)
const inputValue = isJson ? e.target.value.trim() : e.target.value
this.onChange(inputValue, {isXml})
} }
toggleIsEditBox = () => this.setState( state => ({isEditBox: !state.isEditBox})) toggleIsEditBox = () => this.setState( state => ({isEditBox: !state.isEditBox}))

View File

@@ -94,7 +94,7 @@ export default class ParameterRow extends Component {
{ param.get("name") } { param.get("name") }
{ !required ? null : <span style={{color: "red"}}>&nbsp;*</span> } { !required ? null : <span style={{color: "red"}}>&nbsp;*</span> }
</div> </div>
<div className="parаmeter__type">{ param.get("type") } { itemType && `[${itemType}]` }</div> <div className="parameter__type">{ param.get("type") } { itemType && `[${itemType}]` }</div>
<div className="parameter__in">({ param.get("in") })</div> <div className="parameter__in">({ param.get("in") })</div>
</td> </td>

View File

@@ -72,7 +72,9 @@ export default class Parameters extends Component {
return ( return (
<div className="opblock-section"> <div className="opblock-section">
<div className="opblock-section-header"> <div className="opblock-section-header">
<div className="tab-header">
<h4 className="opblock-title">Parameters</h4> <h4 className="opblock-title">Parameters</h4>
</div>
{ allowTryItOut ? ( { allowTryItOut ? (
<TryItOutButton enabled={ tryItOutEnabled } onCancelClick={ onCancelClick } onTryoutClick={ onTryoutClick } /> <TryItOutButton enabled={ tryItOutEnabled } onCancelClick={ onCancelClick } onTryoutClick={ onTryoutClick } />
) : null } ) : null }

View File

@@ -0,0 +1,55 @@
import React, { Component } from "react"
import PropTypes from "prop-types"
const propStyle = { color: "#999", fontStyle: "italic" }
export default class Primitive extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
required: PropTypes.bool
}
render(){
let { schema, getComponent, required } = this.props
if(!schema || !schema.get) {
// don't render if schema isn't correctly formed
return <div></div>
}
let type = schema.get("type")
let format = schema.get("format")
let xml = schema.get("xml")
let enumArray = schema.get("enum")
let description = schema.get("description")
let properties = schema.filter( ( v, key) => ["enum", "type", "format", "description", "$$ref"].indexOf(key) === -1 )
let style = required ? { fontWeight: "bold" } : {}
const Markdown = getComponent("Markdown")
const EnumModel = getComponent("EnumModel")
return <span className="prop">
<span className="prop-type" style={ style }>{ type }</span> { required && <span style={{ color: "red" }}>*</span>}
{ format && <span className="prop-format">(${format})</span>}
{
properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }>
<br />{ key }: { String(v) }</span>)
: null
}
{
!description ? null :
<Markdown source={ description } />
}
{
xml && xml.size ? (<span><br /><span style={ propStyle }>xml:</span>
{
xml.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }><br/>&nbsp;&nbsp;&nbsp;{key}: { String(v) }</span>).toArray()
}
</span>): null
}
{
enumArray && <EnumModel value={ enumArray } getComponent={ getComponent } />
}
</span>
}
}

View File

@@ -11,10 +11,12 @@ function Markdown({ source }) {
return null return null
} }
return <Remarkable return <div className="markdown">
options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}} <Remarkable
options={{html: true, typographer: true, breaks: true, linkify: true, linkTarget: "_blank"}}
source={sanitized} source={sanitized}
></Remarkable> ></Remarkable>
</div>
} }
Markdown.propTypes = { Markdown.propTypes = {

View File

@@ -40,7 +40,7 @@ export default class ResponseBody extends React.Component {
// Image // Image
} else if (/^image\//i.test(contentType)) { } else if (/^image\//i.test(contentType)) {
bodyEl = <img src={ url } /> bodyEl = <img style={{ maxWidth: "100%" }} src={ window.URL.createObjectURL(content) } />
// Audio // Audio
} else if (/^audio\//i.test(contentType)) { } else if (/^audio\//i.test(contentType)) {

View File

@@ -79,14 +79,15 @@ export default class Response extends React.Component {
const ContentType = getComponent("contentType") const ContentType = getComponent("contentType")
var sampleResponse var sampleResponse
var schema
if(isOAS3()) { if(isOAS3()) {
let oas3SchemaForContentType = response.getIn(["content", this.state.responseContentType, "schema"]) let oas3SchemaForContentType = response.getIn(["content", this.state.responseContentType, "schema"])
sampleResponse = oas3SchemaForContentType ? getSampleSchema(oas3SchemaForContentType.toJS(), this.state.responseContentType, { includeReadOnly: true }) : null sampleResponse = oas3SchemaForContentType ? getSampleSchema(oas3SchemaForContentType.toJS(), this.state.responseContentType, { includeReadOnly: true }) : null
var schema = oas3SchemaForContentType ? inferSchema(oas3SchemaForContentType.toJS()) : null schema = oas3SchemaForContentType ? inferSchema(oas3SchemaForContentType.toJS()) : null
} else { } else {
schema = inferSchema(response.toJS())
sampleResponse = schema ? getSampleSchema(schema, contentType, { includeReadOnly: true }) : null sampleResponse = schema ? getSampleSchema(schema, contentType, { includeReadOnly: true }) : null
var schema = inferSchema(response.toJS())
} }
let example = getExampleComponent( sampleResponse, examples, HighlightCode ) let example = getExampleComponent( sampleResponse, examples, HighlightCode )

View File

@@ -19,8 +19,9 @@ export default class Schemes extends React.Component {
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if ( this.props.operationScheme && !nextProps.schemes.has(this.props.operationScheme) ) { if ( !this.props.operationScheme || !nextProps.schemes.has(this.props.operationScheme) ) {
//fire 'change' event if our selected scheme is no longer an option // 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()) this.setScheme(nextProps.schemes.first())
} }
} }

View File

@@ -1,5 +1,12 @@
import React from "react" import React from "react"
import PropTypes from "prop-types"
export default ({ version }) => { const VersionStamp = ({ version }) => {
return <small><pre className="version"> { version } </pre></small> return <small><pre className="version"> { version } </pre></small>
} }
VersionStamp.propTypes = {
version: PropTypes.string.isRequired
}
export default VersionStamp

View File

@@ -4,37 +4,44 @@ import System from "core/system"
import win from "core/window" import win from "core/window"
import ApisPreset from "core/presets/apis" import ApisPreset from "core/presets/apis"
import * as AllPlugins from "core/plugins/all" import * as AllPlugins from "core/plugins/all"
import { parseSeach, filterConfigs } from "core/utils" import { parseSearch } 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" , "displayRequestDuration"]
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
const { GIT_DIRTY, GIT_COMMIT, PACKAGE_VERSION } = buildInfo const { GIT_DIRTY, GIT_COMMIT, PACKAGE_VERSION, HOSTNAME, BUILD_TIME } = buildInfo
module.exports = function SwaggerUI(opts) { module.exports = function SwaggerUI(opts) {
win.versions = win.versions || {} win.versions = win.versions || {}
win.versions.swaggerUi = `${PACKAGE_VERSION}/${GIT_COMMIT || "unknown"}${GIT_DIRTY ? "-dirty" : ""}` win.versions.swaggerUi = {
version: PACKAGE_VERSION,
gitRevision: GIT_COMMIT,
gitDirty: GIT_DIRTY,
buildTimestamp: BUILD_TIME,
machine: HOSTNAME
}
const defaults = { const defaults = {
// Some general settings, that we floated to the top // Some general settings, that we floated to the top
dom_id: null, dom_id: null,
domNode: null,
spec: {}, spec: {},
url: "", url: "",
urls: null, urls: null,
layout: "BaseLayout", layout: "BaseLayout",
docExpansion: "list", docExpansion: "list",
maxDisplayedTags: null,
filter: null,
validatorUrl: "https://online.swagger.io/validator", validatorUrl: "https://online.swagger.io/validator",
configs: {}, configs: {},
custom: {}, custom: {},
displayOperationId: false, displayOperationId: false,
displayRequestDuration: false, displayRequestDuration: false,
deepLinking: false,
// Initial set of plugins ( TODO rename this, or refactor - we don't need presets _and_ plugins. Its just there for performance. // 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. // Instead, we can compile the first plugin ( it can be a collection of plugins ), then batch the rest.
presets: [ presets: [
ApisPreset
], ],
// Plugins; ( loaded after presets ) // Plugins; ( loaded after presets )
@@ -50,7 +57,9 @@ module.exports = function SwaggerUI(opts) {
store: { }, store: { },
} }
const constructorConfig = deepExtend({}, defaults, opts) let queryConfig = parseSearch()
const constructorConfig = deepExtend({}, defaults, opts, queryConfig)
const storeConfigs = deepExtend({}, constructorConfig.store, { const storeConfigs = deepExtend({}, constructorConfig.store, {
system: { system: {
@@ -59,7 +68,8 @@ module.exports = function SwaggerUI(opts) {
plugins: constructorConfig.presets, plugins: constructorConfig.presets,
state: { state: {
layout: { layout: {
layout: constructorConfig.layout layout: constructorConfig.layout,
filter: constructorConfig.filter
}, },
spec: { spec: {
spec: "", spec: "",
@@ -80,7 +90,6 @@ module.exports = function SwaggerUI(opts) {
store.register([constructorConfig.plugins, inlinePlugin]) store.register([constructorConfig.plugins, inlinePlugin])
var system = store.getSystem() var system = store.getSystem()
let queryConfig = parseSeach()
system.initOAuth = system.authActions.configureAuth system.initOAuth = system.authActions.configureAuth
@@ -91,7 +100,13 @@ module.exports = function SwaggerUI(opts) {
let localConfig = system.specSelectors.getLocalConfig ? system.specSelectors.getLocalConfig() : {} let localConfig = system.specSelectors.getLocalConfig ? system.specSelectors.getLocalConfig() : {}
let mergedConfig = deepExtend({}, localConfig, constructorConfig, fetchedConfig || {}, queryConfig) let mergedConfig = deepExtend({}, localConfig, constructorConfig, fetchedConfig || {}, queryConfig)
store.setConfigs(filterConfigs(mergedConfig, CONFIGS))
// deep extend mangles domNode, we need to set it manually
if(opts.domNode) {
mergedConfig.domNode = opts.domNode
}
store.setConfigs(mergedConfig)
if (fetchedConfig !== null) { if (fetchedConfig !== null) {
if (!queryConfig.url && typeof mergedConfig.spec === "object" && Object.keys(mergedConfig.spec).length) { if (!queryConfig.url && typeof mergedConfig.spec === "object" && Object.keys(mergedConfig.spec).length) {
@@ -104,10 +119,13 @@ module.exports = function SwaggerUI(opts) {
} }
} }
if(mergedConfig.dom_id) { if(mergedConfig.domNode) {
system.render(mergedConfig.dom_id, "App") system.render(mergedConfig.domNode, "App")
} else if(mergedConfig.dom_id) {
let domNode = document.querySelector(mergedConfig.dom_id)
system.render(domNode, "App")
} else { } else {
console.error("Skipped rendering: no `dom_id` was specified") console.error("Skipped rendering: no `dom_id` or `domNode` was specified")
} }
return system return system

View File

@@ -57,7 +57,8 @@ export class JsonSchema_string extends Component {
if ( enumValue ) { if ( enumValue ) {
const Select = getComponent("Select") const Select = getComponent("Select")
return (<Select allowedValues={ enumValue } return (<Select className={ errors.length ? "invalid" : ""}
allowedValues={ enumValue }
value={ value } value={ value }
allowEmptyValue={ !required } allowEmptyValue={ !required }
onChange={ this.onEnumChange }/>) onChange={ this.onEnumChange }/>)
@@ -121,6 +122,7 @@ export class JsonSchema_array extends PureComponent {
render() { render() {
let { getComponent, required, schema, fn } = this.props let { getComponent, required, schema, fn } = this.props
let errors = schema.errors || []
let itemSchema = fn.inferSchema(schema.items) let itemSchema = fn.inferSchema(schema.items)
const JsonSchemaForm = getComponent("JsonSchemaForm") const JsonSchemaForm = getComponent("JsonSchemaForm")
@@ -131,19 +133,17 @@ export class JsonSchema_array extends PureComponent {
if ( enumValue ) { if ( enumValue ) {
const Select = getComponent("Select") const Select = getComponent("Select")
return (<Select multiple={ true } return (<Select className={ errors.length ? "invalid" : ""}
multiple={ true }
value={ value } value={ value }
allowedValues={ enumValue } allowedValues={ enumValue }
allowEmptyValue={ !required } allowEmptyValue={ !required }
onChange={ this.onEnumChange }/>) onChange={ this.onEnumChange }/>)
} }
let errors = schema.errors || []
return ( return (
<div> <div>
{ !value || value.count() < 1 ? { !value || value.count() < 1 ? null :
(errors.length ? <span style={{ color: "red", fortWeight: "bold" }}>{ errors[0] }</span> : null) :
value.map( (item,i) => { value.map( (item,i) => {
let schema = Object.assign({}, itemSchema) let schema = Object.assign({}, itemSchema)
if ( errors.length ) { if ( errors.length ) {
@@ -153,12 +153,12 @@ export class JsonSchema_array extends PureComponent {
return ( return (
<div key={i} className="json-schema-form-item"> <div key={i} className="json-schema-form-item">
<JsonSchemaForm fn={fn} getComponent={getComponent} value={item} onChange={(val) => this.onItemChange(val, i)} schema={schema} /> <JsonSchemaForm fn={fn} getComponent={getComponent} value={item} onChange={(val) => this.onItemChange(val, i)} schema={schema} />
<Button className="json-schema-form-item-remove" onClick={()=> this.removeItem(i)} > - </Button> <Button className="btn btn-sm json-schema-form-item-remove" onClick={()=> this.removeItem(i)} > - </Button>
</div> </div>
) )
}).toArray() }).toArray()
} }
<Button className="json-schema-form-item-add" onClick={this.addItem}> Add item </Button> <Button className={`btn btn-sm json-schema-form-item-add ${errors.length ? "invalid" : null}`} onClick={this.addItem}> Add item </Button>
</div> </div>
) )
} }
@@ -170,12 +170,14 @@ export class JsonSchema_boolean extends Component {
onEnumChange = (val) => this.props.onChange(val) onEnumChange = (val) => this.props.onChange(val)
render() { render() {
let { getComponent, required, value } = this.props let { getComponent, value, schema } = this.props
let errors = schema.errors || []
const Select = getComponent("Select") const Select = getComponent("Select")
return (<Select value={ String(value) } return (<Select className={ errors.length ? "invalid" : ""}
value={ String(value) }
allowedValues={ fromJS(["true", "false"]) } allowedValues={ fromJS(["true", "false"]) }
allowEmptyValue={ !required } allowEmptyValue={ true }
onChange={ this.onEnumChange }/>) onChange={ this.onEnumChange }/>)
} }
} }

View File

@@ -68,11 +68,21 @@ export default function authorize ( { auth, authActions, errActions, configs, au
// pass action authorizeOauth2 and authentication data through window // pass action authorizeOauth2 and authentication data through window
// to authorize with oauth2 // to authorize with oauth2
let callback
if (flow === "implicit") {
callback = authActions.preAuthorizeImplicit
} else if (authConfigs.useBasicAuthenticationWithAccessCodeGrant) {
callback = authActions.authorizeAccessCodeWithBasicAuthentication
} else {
callback = authActions.authorizeAccessCodeWithFormParams
}
win.swaggerUIRedirectOauth2 = { win.swaggerUIRedirectOauth2 = {
auth: auth, auth: auth,
state: state, state: state,
redirectUrl: redirectUrl, redirectUrl: redirectUrl,
callback: flow === "implicit" ? authActions.preAuthorizeImplicit : authActions.authorizeAccessCode, callback: callback,
errCb: errActions.newAuthErr errCb: errActions.newAuthErr
} }

View File

@@ -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
}

View File

@@ -73,7 +73,7 @@ export const authorizePassword = ( auth ) => ( { authActions } ) => {
let { schema, name, username, password, passwordType, clientId, clientSecret } = auth let { schema, name, username, password, passwordType, clientId, clientSecret } = auth
let form = { let form = {
grant_type: "password", grant_type: "password",
scopes: encodeURIComponent(auth.scopes.join(scopeSeparator)) scope: encodeURIComponent(auth.scopes.join(scopeSeparator))
} }
let query = {} let query = {}
let headers = {} let headers = {}
@@ -111,7 +111,7 @@ export const authorizeApplication = ( auth ) => ( { authActions } ) => {
return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth, headers }) return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth, headers })
} }
export const authorizeAccessCode = ( { auth, redirectUrl } ) => ( { authActions } ) => { export const authorizeAccessCodeWithFormParams = ( { auth, redirectUrl } ) => ( { authActions } ) => {
let { schema, name, clientId, clientSecret } = auth let { schema, name, clientId, clientSecret } = auth
let form = { let form = {
grant_type: "authorization_code", grant_type: "authorization_code",
@@ -124,6 +124,21 @@ export const authorizeAccessCode = ( { auth, redirectUrl } ) => ( { authActions
return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth}) return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth})
} }
export const authorizeAccessCodeWithBasicAuthentication = ( { auth, redirectUrl } ) => ( { authActions } ) => {
let { schema, name, clientId, clientSecret } = auth
let headers = {
Authorization: "Basic " + btoa(clientId + ":" + clientSecret)
}
let form = {
grant_type: "authorization_code",
code: auth.code,
client_id: clientId,
redirect_uri: redirectUrl
}
return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth, headers})
}
export const authorizeRequest = ( data ) => ( { fn, authActions, errActions, authSelectors } ) => { export const authorizeRequest = ( data ) => ( { fn, authActions, errActions, authSelectors } ) => {
let { body, query={}, headers={}, name, url, auth } = data let { body, query={}, headers={}, name, url, auth } = data
let { additionalQueryStringParams } = authSelectors.getConfigs() || {} let { additionalQueryStringParams } = authSelectors.getConfigs() || {}
@@ -135,7 +150,6 @@ export const authorizeRequest = ( data ) => ( { fn, authActions, errActions, aut
let _headers = Object.assign({ let _headers = Object.assign({
"Accept":"application/json, text/plain, */*", "Accept":"application/json, text/plain, */*",
"Access-Control-Allow-Origin": "*",
"Content-Type": "application/x-www-form-urlencoded" "Content-Type": "application/x-www-form-urlencoded"
}, headers) }, headers)

View File

@@ -0,0 +1 @@
See `docs/deep-linking.md`.

View File

@@ -0,0 +1,7 @@
export const setHash = (value) => {
if(value) {
return history.pushState(null, null, `#${value}`)
} else {
return window.location.hash = ""
}
}

View File

@@ -0,0 +1,18 @@
// import reducers from "./reducers"
// import * as actions from "./actions"
// import * as selectors from "./selectors"
import * as specWrapActions from "./spec-wrap-actions"
import * as layoutWrapActions from "./layout-wrap-actions"
export default function() {
return {
statePlugins: {
spec: {
wrapActions: specWrapActions
},
layout: {
wrapActions: layoutWrapActions
}
}
}
}

View File

@@ -0,0 +1,36 @@
import { setHash } from "./helpers"
export const show = (ori, { getConfigs }) => (...args) => {
ori(...args)
const isDeepLinkingEnabled = getConfigs().deepLinking
if(!isDeepLinkingEnabled || isDeepLinkingEnabled === "false") {
return
}
try {
let [thing, shown] = args
let [type] = thing
if(type === "operations-tag" || type === "operations") {
if(!shown) {
return setHash("/")
}
if(type === "operations") {
let [, tag, operationId] = thing
setHash(`/${tag}/${operationId}`)
}
if(type === "operations-tag") {
let [, tag] = thing
setHash(`/${tag}`)
}
}
} catch(e) {
// This functionality is not mission critical, so if something goes wrong
// we'll just move on
console.error(e)
}
}

View File

@@ -0,0 +1,51 @@
import scrollTo from "scroll-to-element"
const SCROLL_OFFSET = -5
let hasHashBeenParsed = false
export const updateResolved = (ori, { layoutActions, getConfigs }) => (...args) => {
ori(...args)
const isDeepLinkingEnabled = getConfigs().deepLinking
if(!isDeepLinkingEnabled || isDeepLinkingEnabled === "false") {
return
}
if(window.location.hash && !hasHashBeenParsed ) {
let hash = window.location.hash.slice(1) // # is first character
if(hash[0] === "!") {
// Parse UI 2.x shebangs
hash = hash.slice(1)
}
if(hash[0] === "/") {
// "/pet/addPet" => "pet/addPet"
// makes the split result cleaner
// also handles forgotten leading slash
hash = hash.slice(1)
}
let [tag, operationId] = hash.split("/")
if(tag && operationId) {
// Pre-expand and scroll to the operation
layoutActions.show(["operations-tag", tag], true)
layoutActions.show(["operations", tag, operationId], true)
scrollTo(`#operations-${tag}-${operationId}`, {
offset: SCROLL_OFFSET
})
} else if(tag) {
// Pre-expand and scroll to the tag
layoutActions.show(["operations-tag", tag], true)
scrollTo(`#operations-tag-${tag}`, {
offset: SCROLL_OFFSET
})
}
}
hasHashBeenParsed = true
}

View File

@@ -1,6 +1,7 @@
import { normalizeArray } from "core/utils" import { normalizeArray } from "core/utils"
export const UPDATE_LAYOUT = "layout_update_layout" export const UPDATE_LAYOUT = "layout_update_layout"
export const UPDATE_FILTER = "layout_update_filter"
export const UPDATE_MODE = "layout_update_mode" export const UPDATE_MODE = "layout_update_mode"
export const SHOW = "layout_show" export const SHOW = "layout_show"
@@ -13,6 +14,13 @@ export function updateLayout(layout) {
} }
} }
export function updateFilter(filter) {
return {
type: UPDATE_FILTER,
payload: filter
}
}
export function show(thing, shown=true) { export function show(thing, shown=true) {
thing = normalizeArray(thing) thing = normalizeArray(thing)
return { return {

View File

@@ -1,5 +1,6 @@
import { import {
UPDATE_LAYOUT, UPDATE_LAYOUT,
UPDATE_FILTER,
UPDATE_MODE, UPDATE_MODE,
SHOW SHOW
} from "./actions" } from "./actions"
@@ -8,6 +9,8 @@ export default {
[UPDATE_LAYOUT]: (state, action) => state.set("layout", action.payload), [UPDATE_LAYOUT]: (state, action) => state.set("layout", action.payload),
[UPDATE_FILTER]: (state, action) => state.set("filter", action.payload),
[SHOW]: (state, action) => { [SHOW]: (state, action) => {
let thing = action.payload.thing let thing = action.payload.thing
let shown = action.payload.shown let shown = action.payload.shown

View File

@@ -5,6 +5,8 @@ const state = state => state
export const current = state => state.get("layout") export const current = state => state.get("layout")
export const currentFilter = state => state.get("filter")
export const isShown = (state, thing, def) => { export const isShown = (state, thing, def) => {
thing = normalizeArray(thing) thing = normalizeArray(thing)
return Boolean(state.getIn(["shown", ...thing], def)) return Boolean(state.getIn(["shown", ...thing], def))

View File

@@ -1,8 +1,9 @@
import React from "react" import React from "react"
import PropTypes from "prop-types"
export default (props) => { const Callbacks = (props) => {
let { callbacks, getComponent } = props let { callbacks, getComponent } = props
const Markdown = getComponent("Markdown") // const Markdown = getComponent("Markdown")
const Operation = getComponent("operation", true) const Operation = getComponent("operation", true)
if(!callbacks) { if(!callbacks) {
@@ -10,15 +11,16 @@ export default (props) => {
} }
let callbackElements = callbacks.map((callback, callbackName) => { let callbackElements = callbacks.map((callback, callbackName) => {
return <div> return <div key={callbackName}>
<h2>{callbackName}</h2> <h2>{callbackName}</h2>
{ callback.map((pathItem, pathItemName) => { { callback.map((pathItem, pathItemName) => {
return <div> return <div key={pathItemName}>
{ pathItem.map((operation, method) => { { pathItem.map((operation, method) => {
return <Operation return <Operation
operation={operation} operation={operation}
key={method} key={method}
method={method} method={method}
isShownKey={["callbacks", operation.get("id"), callbackName]}
path={pathItemName} path={pathItemName}
allowTryItOut={false} allowTryItOut={false}
{...props}></Operation> {...props}></Operation>
@@ -37,3 +39,11 @@ export default (props) => {
{callbackElements} {callbackElements}
</div> </div>
} }
Callbacks.propTypes = {
getComponent: PropTypes.func.isRequired,
callbacks: PropTypes.array.isRequired
}
export default Callbacks

View File

@@ -1,6 +1,8 @@
import React, { Component } from "react" import React, { Component } from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
export default class OperationLink extends Component { class OperationLink extends Component {
render() { render() {
const { link, name } = this.props const { link, name } = this.props
@@ -26,3 +28,10 @@ function padString(n, string) {
.map((line, i) => i > 0 ? Array(n + 1).join(" ") + line : line) .map((line, i) => i > 0 ? Array(n + 1).join(" ") + line : line)
.join("\n") .join("\n")
} }
OperationLink.propTypes = {
link: ImPropTypes.orderedMap.isRequired,
name: PropTypes.String
}
export default OperationLink

View File

@@ -1,9 +1,11 @@
import React from "react" import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { OrderedMap } from "immutable" import { OrderedMap } from "immutable"
import { getSampleSchema } from "core/utils" import { getSampleSchema } from "core/utils"
export default ({ requestBody, getComponent, specSelectors, contentType }) => { const RequestBody = ({ requestBody, getComponent, specSelectors, contentType }) => {
const Markdown = getComponent("Markdown") const Markdown = getComponent("Markdown")
const ModelExample = getComponent("modelExample") const ModelExample = getComponent("modelExample")
const HighlightCode = getComponent("highlightCode") const HighlightCode = getComponent("highlightCode")
@@ -14,7 +16,7 @@ export default ({ requestBody, getComponent, specSelectors, contentType }) => {
const mediaTypeValue = requestBodyContent.get(contentType) const mediaTypeValue = requestBodyContent.get(contentType)
const sampleSchema = getSampleSchema(mediaTypeValue.get("schema")) const sampleSchema = getSampleSchema(mediaTypeValue.get("schema").toJS(), contentType)
return <div> return <div>
{ requestBodyDescription && { requestBodyDescription &&
@@ -29,3 +31,12 @@ export default ({ requestBody, getComponent, specSelectors, contentType }) => {
/> />
</div> </div>
} }
RequestBody.propTypes = {
requestBody: ImPropTypes.orderedMap.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
contentType: PropTypes.string.isRequired
}
export default RequestBody

View File

@@ -3,11 +3,13 @@ import parameters from "./parameters"
import VersionStamp from "./version-stamp" import VersionStamp from "./version-stamp"
import OnlineValidatorBadge from "./online-validator-badge" import OnlineValidatorBadge from "./online-validator-badge"
import Model from "./model" import Model from "./model"
import TryItOutButton from "./try-it-out-button"
export default { export default {
Markdown, Markdown,
parameters, parameters,
VersionStamp, VersionStamp,
model: Model, model: Model,
onlineValidatorBadge: OnlineValidatorBadge onlineValidatorBadge: OnlineValidatorBadge,
TryItOutButton
} }

View File

@@ -1,4 +1,5 @@
import React, { Component, PropTypes } from "react" import React, { Component } from "react"
import PropTypes from "prop-types"
import { OAS3ComponentWrapFactory } from "../helpers" import { OAS3ComponentWrapFactory } from "../helpers"
import { Model } from "core/components/model" import { Model } from "core/components/model"

View File

@@ -1,4 +1,3 @@
import React from "react"
import { OAS3ComponentWrapFactory } from "../helpers" import { OAS3ComponentWrapFactory } from "../helpers"
// We're disabling the Online Validator Badge until the online validator // We're disabling the Online Validator Badge until the online validator

View File

@@ -1,12 +1,9 @@
import React, { Component, PropTypes } from "react" import React, { Component } from "react"
import PropTypes from "prop-types"
import Im, { Map } from "immutable" import Im, { Map } from "immutable"
import ImPropTypes from "react-immutable-proptypes" import ImPropTypes from "react-immutable-proptypes"
import { OAS3ComponentWrapFactory } from "../helpers" import { OAS3ComponentWrapFactory } from "../helpers"
const mapRequestBody = (iterable, fn) => iterable.entries().filter(Im.Map.isMap).map((val) => {
return fn(val.get(0), val.get(1))
})
// More readable, just iterate over maps, only // More readable, just iterate over maps, only
const eachMap = (iterable, fn) => iterable.valueSeq().filter(Im.Map.isMap).map(fn) const eachMap = (iterable, fn) => iterable.valueSeq().filter(Im.Map.isMap).map(fn)
@@ -159,7 +156,7 @@ class Parameters extends Component {
isOAS3() && requestBody && this.state.parametersVisible && isOAS3() && requestBody && this.state.parametersVisible &&
<div className="opblock-section"> <div className="opblock-section">
<div className="opblock-section-header"> <div className="opblock-section-header">
<h4 className="opblock-title">Request body</h4> <h4 className={`opblock-title parameter__name ${requestBody.get("required") && "required"}`}>Request body</h4>
<label> <label>
<ContentType <ContentType
value={this.state.requestBodyContentType} value={this.state.requestBodyContentType}

View File

@@ -0,0 +1,5 @@
import { OAS3ComponentWrapFactory } from "../helpers"
export default OAS3ComponentWrapFactory(() => {
return null
})

View File

@@ -1,6 +1,6 @@
import { createSelector } from "reselect" import { createSelector } from "reselect"
import { sorters } from "core/utils" import { sorters } from "core/utils"
import { fromJS, Set, Map, List } from "immutable" import { fromJS, Set, Map, OrderedMap, List } from "immutable"
const DEFAULT_TAG = "default" const DEFAULT_TAG = "default"
@@ -49,8 +49,10 @@ export const spec = state => {
export const isOAS3 = createSelector( export const isOAS3 = createSelector(
// isOAS3 is stubbed out here to work around an issue with injecting more selectors // isOAS3 is stubbed out here to work around an issue with injecting more selectors
// in the OAS3 plugin, and to ensure that the function is always available. // in the OAS3 plugin, and to ensure that the function is always available.
// It's not perfect, but our hybrid (core+plugin code) implementation for OAS3
// needs this. //KS
spec, spec,
spec => false () => false
) )
export const info = createSelector( export const info = createSelector(
@@ -194,25 +196,35 @@ export const tagDetails = (state, tag) => {
export const operationsWithTags = createSelector( export const operationsWithTags = createSelector(
operationsWithRootInherited, operationsWithRootInherited,
operations => { tags,
(operations, tags) => {
return operations.reduce( (taggedMap, op) => { return operations.reduce( (taggedMap, op) => {
let tags = Set(op.getIn(["operation","tags"])) let tags = Set(op.getIn(["operation","tags"]))
if(tags.count() < 1) if(tags.count() < 1)
return taggedMap.update(DEFAULT_TAG, List(), ar => ar.push(op)) return taggedMap.update(DEFAULT_TAG, List(), ar => ar.push(op))
return tags.reduce( (res, tag) => res.update(tag, List(), (ar) => ar.push(op)), taggedMap ) return tags.reduce( (res, tag) => res.update(tag, List(), (ar) => ar.push(op)), taggedMap )
}, Map()) }, tags.reduce( (taggedMap, tag) => {
return taggedMap.set(tag.get("name"), List())
} , OrderedMap()))
} }
) )
export const taggedOperations = (state) => ({ getConfigs }) => { export const taggedOperations = (state) => ({ getConfigs }) => {
let { operationsSorter }= getConfigs() let { tagsSorter, operationsSorter } = getConfigs()
return operationsWithTags(state)
.sortBy(
(val, key) => key, // get the name of the tag to be passed to the sorter
(tagA, tagB) => {
let sortFn = (typeof tagsSorter === "function" ? tagsSorter : sorters.tagsSorter[ tagsSorter ])
return (!sortFn ? null : sortFn(tagA, tagB))
}
)
.map((ops, tag) => {
let sortFn = (typeof operationsSorter === "function" ? operationsSorter : sorters.operationsSorter[ operationsSorter ])
let operations = (!sortFn ? ops : ops.sort(sortFn))
return operationsWithTags(state).map((ops, tag) => { return Map({ tagDetails: tagDetails(state, tag), operations: operations })
let sortFn = typeof operationsSorter === "function" ? operationsSorter })
: sorters.operationsSorter[operationsSorter]
let operations = !sortFn ? ops : ops.sort(sortFn)
return Map({tagDetails: tagDetails(state, tag), operations: operations})})
} }
export const responses = createSelector( export const responses = createSelector(
@@ -281,12 +293,13 @@ export function parametersIncludeType(parameters, typeValue="") {
export function contentTypeValues(state, pathMethod) { export function contentTypeValues(state, pathMethod) {
let op = spec(state).getIn(["paths", ...pathMethod], fromJS({})) let op = spec(state).getIn(["paths", ...pathMethod], fromJS({}))
const parameters = op.get("parameters") || new List() const parameters = op.get("parameters") || new List()
const requestContentType = (
parametersIncludeType(parameters, "file") ? "multipart/form-data"
: parametersIncludeIn(parameters, "formData") ? "application/x-www-form-urlencoded"
: op.get("consumes_value")
)
const requestContentType = (
op.get("consumes_value") ? op.get("consumes_value")
: parametersIncludeType(parameters, "file") ? "multipart/form-data"
: parametersIncludeType(parameters, "formData") ? "application/x-www-form-urlencoded"
: undefined
)
return fromJS({ return fromJS({
requestContentType, requestContentType,

View File

@@ -1,7 +1,6 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import SplitPane from "react-split-pane" import SplitPane from "react-split-pane"
import "./split-pane-mode.less"
const MODE_KEY = ["split-pane-mode"] const MODE_KEY = ["split-pane-mode"]
const MODE_LEFT = "left" const MODE_LEFT = "left"

View File

@@ -1,5 +0,0 @@
.swagger-ui {
.Resizer.vertical.disabled {
display: none;
}
}

View File

@@ -1,8 +1,7 @@
import { shallowEqualKeys } from "core/utils" import { shallowEqualKeys } from "core/utils"
import { transformPathToArray } from "core/path-translator"
export default function() { export default function() {
return { return {
fn: { shallowEqualKeys, transformPathToArray } fn: { shallowEqualKeys }
} }
} }

View File

@@ -58,8 +58,7 @@ export const makeMappedContainer = (getSystem, getStore, memGetComponent, getCom
} }
export const render = (getSystem, getStore, getComponent, getComponents, dom) => { export const render = (getSystem, getStore, getComponent, getComponents, domNode) => {
let domNode = document.querySelector(dom)
let App = (getComponent(getSystem, getStore, getComponents, "App", "root")) let App = (getComponent(getSystem, getStore, getComponents, "App", "root"))
ReactDOM.render(( <App/> ), domNode) ReactDOM.render(( <App/> ), domNode)
} }

View File

@@ -10,6 +10,7 @@ import auth from "core/plugins/auth"
import util from "core/plugins/util" import util from "core/plugins/util"
import SplitPaneModePlugin from "core/plugins/split-pane-mode" import SplitPaneModePlugin from "core/plugins/split-pane-mode"
import downloadUrlPlugin from "core/plugins/download-url" import downloadUrlPlugin from "core/plugins/download-url"
import deepLinkingPlugin from "core/plugins/deep-linking"
import App from "core/components/app" import App from "core/components/app"
import AuthorizationPopup from "core/components/auth/authorization-popup" import AuthorizationPopup from "core/components/auth/authorization-popup"
@@ -41,9 +42,15 @@ import Footer from "core/components/footer"
import ParamBody from "core/components/param-body" import ParamBody from "core/components/param-body"
import Curl from "core/components/curl" import Curl from "core/components/curl"
import Schemes from "core/components/schemes" import Schemes from "core/components/schemes"
import ModelCollapse from "core/components/model-collapse"
import ModelExample from "core/components/model-example" import ModelExample from "core/components/model-example"
import ModelWrapper from "core/components/model-wrapper"
import Model from "core/components/model" import Model from "core/components/model"
import Models from "core/components/models" import Models from "core/components/models"
import EnumModel from "core/components/enum-model"
import ObjectModel from "core/components/object-model"
import ArrayModel from "core/components/array-model"
import PrimitiveModel from "core/components/primitive-model"
import TryItOutButton from "core/components/try-it-out-button" import TryItOutButton from "core/components/try-it-out-button"
import VersionStamp from "core/components/version-stamp" import VersionStamp from "core/components/version-stamp"
@@ -89,8 +96,14 @@ export default function() {
curl: Curl, curl: Curl,
schemes: Schemes, schemes: Schemes,
modelExample: ModelExample, modelExample: ModelExample,
model: Model, ModelWrapper,
models: Models, ModelCollapse,
Model,
Models,
EnumModel,
ObjectModel,
ArrayModel,
PrimitiveModel,
TryItOutButton, TryItOutButton,
Markdown, Markdown,
BaseLayout, BaseLayout,
@@ -121,6 +134,7 @@ export default function() {
auth, auth,
ast, ast,
SplitPaneModePlugin, SplitPaneModePlugin,
downloadUrlPlugin downloadUrlPlugin,
deepLinkingPlugin
] ]
} }

View File

@@ -41,7 +41,7 @@ export function fromJSOrdered (js) {
return !isObject(js) ? js : return !isObject(js) ? js :
Array.isArray(js) ? Array.isArray(js) ?
Im.Seq(js).map(fromJSOrdered).toList() : Im.Seq(js).map(fromJSOrdered).toList() :
Im.Seq(js).map(fromJSOrdered).toOrderedMap() Im.OrderedMap(js).map(fromJSOrdered)
} }
export function bindToState(obj, state) { export function bindToState(obj, state) {
@@ -462,6 +462,24 @@ export const validateInteger = ( val ) => {
} }
} }
export const validateFile = ( val ) => {
if ( val && !(val instanceof win.File) ) {
return "Value must be a file"
}
}
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 // validation of parameters before execute
export const validateParam = (param, isXml) => { export const validateParam = (param, isXml) => {
let errors = [] let errors = []
@@ -469,15 +487,35 @@ export const validateParam = (param, isXml) => {
let required = param.get("required") let required = param.get("required")
let type = param.get("type") let type = param.get("type")
let stringCheck = type === "string" && !value /*
let arrayCheck = type === "array" && Array.isArray(value) && !value.length If the parameter is required OR the parameter has a value (meaning optional, but filled in)
let listCheck = type === "array" && Im.List.isList(value) && !value.count() then we should do our validation routine.
if ( required && (stringCheck || arrayCheck || listCheck) ) { Only bother validating the parameter if the type was specified.
*/
if ( type && (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 || booleanCheck || numberCheck || integerCheck) ) {
errors.push("Required field is not provided") errors.push("Required field is not provided")
return errors return errors
} }
if ( type === "number" ) { 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) let err = validateNumber(value)
if (!err) return errors if (!err) return errors
errors.push(err) errors.push(err)
@@ -499,13 +537,19 @@ export const validateParam = (param, isXml) => {
err = validateNumber(item) err = validateNumber(item)
} else if (itemType === "integer") { } else if (itemType === "integer") {
err = validateInteger(item) err = validateInteger(item)
} else if (itemType === "string") {
err = validateString(item)
} }
if ( err ) { if ( err ) {
errors.push({ index: index, error: err}) errors.push({ index: index, error: err})
} }
}) })
} else if ( type === "file" ) {
let err = validateFile(value)
if (!err) return errors
errors.push(err)
}
} }
return errors return errors
@@ -531,7 +575,7 @@ export const getSampleSchema = (schema, contentType="", config={}) => {
return JSON.stringify(memoizedSampleFromSchema(schema, config), null, 2) return JSON.stringify(memoizedSampleFromSchema(schema, config), null, 2)
} }
export const parseSeach = () => { export const parseSearch = () => {
let map = {} let map = {}
let search = window.location.search let search = window.location.search
@@ -563,6 +607,9 @@ export const sorters = {
operationsSorter: { operationsSorter: {
alpha: (a, b) => a.get("path").localeCompare(b.get("path")), alpha: (a, b) => a.get("path").localeCompare(b.get("path")),
method: (a, b) => a.get("method").localeCompare(b.get("method")) method: (a, b) => a.get("method").localeCompare(b.get("method"))
},
tagsSorter: {
alpha: (a, b) => a.localeCompare(b)
} }
} }
@@ -578,18 +625,6 @@ export const buildFormData = (data) => {
return formArr.join("&") return formArr.join("&")
} }
export const filterConfigs = (configs, allowed) => {
let i, filteredConfigs = {}
for (i in configs) {
if (allowed.indexOf(i) !== -1) {
filteredConfigs[i] = configs[i]
}
}
return filteredConfigs
}
// Is this really required as a helper? Perhaps. TODO: expose the system of presets.apis in docs, so we know what is supported // Is this really required as a helper? Perhaps. TODO: expose the system of presets.apis in docs, so we know what is supported
export const shallowEqualKeys = (a,b, keys) => { export const shallowEqualKeys = (a,b, keys) => {
return !!find(keys, (key) => { return !!find(keys, (key) => {

View File

@@ -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. 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` Here is an example of each `type`

View File

@@ -6,6 +6,10 @@ import Logo from "./logo_small.png"
export default class Topbar extends React.Component { export default class Topbar extends React.Component {
static propTypes = {
layoutActions: PropTypes.object.isRequired
}
constructor(props, context) { constructor(props, context) {
super(props, context) super(props, context)
this.state = { url: props.specSelectors.url(), selectedIndex: 0 } this.state = { url: props.specSelectors.url(), selectedIndex: 0 }
@@ -80,6 +84,11 @@ export default class Topbar extends React.Component {
} }
} }
onFilterChange =(e) => {
let {target: {value}} = e
this.props.layoutActions.updateFilter(value)
}
render() { render() {
let { getComponent, specSelectors, getConfigs } = this.props let { getComponent, specSelectors, getConfigs } = this.props
const Button = getComponent("Button") const Button = getComponent("Button")
@@ -121,7 +130,7 @@ export default class Topbar extends React.Component {
<div className="wrapper"> <div className="wrapper">
<div className="topbar-wrapper"> <div className="topbar-wrapper">
<Link href="#" title="Swagger UX"> <Link href="#" title="Swagger UX">
<img height="30" width="30" src={ Logo } alt="Swagger UX"/> <img height="30" width="30" src={ Logo } alt="Swagger UI"/>
<span>swagger</span> <span>swagger</span>
</Link> </Link>
<form className="download-url-wrapper" onSubmit={formOnSubmit}> <form className="download-url-wrapper" onSubmit={formOnSubmit}>

View File

@@ -1,52 +0,0 @@
.swagger-ui {
.topbar {
background-color: #89bf04;
}
.topbar-wrapper {
padding: 0.7em;
}
.topbar-logo__img {
float: left;
}
.topbar-logo__title {
display: inline-block;
color: #fff;
font-size: 1.5em;
font-weight: bold;
margin: 0.15em 0 0 0.5em;
}
.download-url-wrapper {
text-align: right;
float: right;
}
.topbar .download-url__text {
width: 28em;
height: 2em;
margin-right: 0.5em;
}
.download-url__btn {
background-color: #547f00;
border-color: #547f00;
text-decoration: none;
font-weight: bold;
padding: 0.2em 0.3em;
color: white;
border-radius: 0.1em;
&:hover {
&:extend(.download-url__btn);
}
}
.center-700 {
display: block;
margin: 0 auto;
width: 45em;
}
}

View File

@@ -1,6 +1,4 @@
import StandaloneLayout from "./layout" import StandaloneLayout from "./layout"
import "../style/main.scss"
import TopbarPlugin from "plugins/topbar" import TopbarPlugin from "plugins/topbar"
import ConfigsPlugin from "plugins/configs" import ConfigsPlugin from "plugins/configs"

View File

@@ -14,6 +14,12 @@
@include text_headline(); @include text_headline();
&.btn-sm
{
font-size: 12px;
padding: 4px 23px;
}
&[disabled] &[disabled]
{ {
cursor: not-allowed; cursor: not-allowed;
@@ -165,6 +171,10 @@
button button
{ {
cursor: pointer; cursor: pointer;
outline: none; outline: none;
&.invalid
{
@include invalidFormElement();
}
} }

View File

@@ -21,6 +21,10 @@ select
background: #f7f7f7; background: #f7f7f7;
} }
&.invalid {
@include invalidFormElement();
}
} }
.opblock-body select .opblock-body select
@@ -42,7 +46,8 @@ label
input[type=text], input[type=text],
input[type=password], input[type=password],
input[type=search], input[type=search],
input[type=email] input[type=email],
input[type=file]
{ {
min-width: 100px; min-width: 100px;
margin: 5px 0; margin: 5px 0;
@@ -54,10 +59,7 @@ input[type=email]
&.invalid &.invalid
{ {
animation: shake .4s 1; @include invalidFormElement();
border-color: $_color-delete;
background: lighten($_color-delete, 35%);
} }
} }

View File

@@ -45,6 +45,7 @@ body
.opblock-tag .opblock-tag
{ {
display: flex; display: flex;
align-items: center;
padding: 10px 20px 10px 10px; padding: 10px 20px 10px 10px;
@@ -53,8 +54,6 @@ body
border-bottom: 1px solid rgba(#3b4151, .3); border-bottom: 1px solid rgba(#3b4151, .3);
align-items: center;
&:hover &:hover
{ {
background: rgba(#000,.02); background: rgba(#000,.02);
@@ -111,14 +110,15 @@ body
font-size: 14px; font-size: 14px;
font-weight: normal; font-weight: normal;
flex: 1;
padding: 0 10px; padding: 0 10px;
flex: 1;
@include text_body(); @include text_body();
} }
} }
.parаmeter__type .parameter__type
{ {
font-size: 12px; font-size: 12px;
@@ -139,6 +139,8 @@ body
transition: all .5s; transition: all .5s;
} }
.opblock .opblock
{ {
margin: 0 0 15px 0; margin: 0 0 15px 0;
@@ -204,25 +206,26 @@ body
.opblock-section-header .opblock-section-header
{ {
display: flex; display: flex;
align-items: center;
padding: 8px 20px; padding: 8px 20px;
min-height: 50px;
background: rgba(#fff,.8); background: rgba(#fff,.8);
box-shadow: 0 1px 2px rgba(#000,.1); box-shadow: 0 1px 2px rgba(#000,.1);
align-items: center;
label label
{ {
font-size: 12px; font-size: 12px;
font-weight: bold; font-weight: bold;
display: flex; display: flex;
align-items: center;
margin: 0; margin: 0;
margin-left: auto; margin-left: auto;
align-items: center;
@include text_headline(); @include text_headline();
span span
@@ -235,6 +238,8 @@ body
{ {
font-size: 14px; font-size: 14px;
flex: 1;
margin: 0; margin: 0;
@include text_headline(); @include text_headline();
@@ -265,11 +270,11 @@ body
font-size: 16px; font-size: 16px;
display: flex; display: flex;
align-items: center;
padding: 0 10px; padding: 0 10px;
@include text_code(); @include text_code();
align-items: center;
.view-line-link .view-line-link
{ {
@@ -308,18 +313,18 @@ body
font-size: 13px; font-size: 13px;
flex: 1; flex: 1;
@include text_body(); @include text_body();
} }
.opblock-summary .opblock-summary
{ {
display: flex; display: flex;
align-items: center;
padding: 5px; padding: 5px;
cursor: pointer; cursor: pointer;
align-items: center;
} }
&.opblock-post &.opblock-post
@@ -375,6 +380,18 @@ body
} }
} }
.filter
{
.operation-filter-input
{
width: 100%;
margin: 20px 0;
padding: 10px 10px;
border: 2px solid #d8dde7;
}
}
.tab .tab
{ {
@@ -426,6 +443,7 @@ body
} }
.opblock-description-wrapper, .opblock-description-wrapper,
.opblock-external-docs-wrapper,
.opblock-title_normal .opblock-title_normal
{ {
font-size: 12px; font-size: 12px;
@@ -454,6 +472,12 @@ body
} }
} }
.opblock-external-docs-wrapper {
h4 {
padding-left: 0px;
}
}
.execute-wrapper .execute-wrapper
{ {
padding: 20px; padding: 20px;
@@ -537,6 +561,15 @@ body
{ {
margin: 0; margin: 0;
} }
a
{
@include text_code(#89bf04);
text-decoration: underline;
&:hover {
color: #81b10c;
}
}
} }
} }
@@ -548,13 +581,11 @@ body
margin: 0; margin: 0;
padding: 10px; padding: 10px;
white-space: pre-wrap;
word-wrap: break-word; word-wrap: break-word;
word-break: break-all; word-break: break-all;
word-break: break-word; word-break: break-word;
hyphens: auto; hyphens: auto;
white-space: pre-wrap;
border-radius: 4px; border-radius: 4px;
background: #41444e; background: #41444e;
@@ -583,7 +614,6 @@ body
.schemes .schemes
{ {
display: flex; display: flex;
align-items: center; align-items: center;
> label > label
@@ -686,3 +716,25 @@ body
opacity: 0; opacity: 0;
} }
} }
section
{
h3
{
@include text_headline();
}
}
a.nostyle {
text-decoration: inherit;
color: inherit;
cursor: auto;
display: inline;
&:visited {
text-decoration: inherit;
color: inherit;
cursor: auto;
}
}

View File

@@ -166,3 +166,9 @@ $browser-context: 16;
@warn 'Breakpoint mixin supports: tablet, mobile, desktop'; @warn 'Breakpoint mixin supports: tablet, mobile, desktop';
} }
} }
@mixin invalidFormElement() {
animation: shake .4s 1;
border-color: $_color-delete;
background: lighten($_color-delete, 35%);
}

View File

@@ -86,6 +86,10 @@
border-radius: 4px; border-radius: 4px;
background: rgba(#000,.7); background: rgba(#000,.7);
} }
p {
margin: 0 0 1em 0;
}
} }

View File

@@ -0,0 +1,3 @@
.Resizer.vertical.disabled {
display: none;
}

View File

@@ -97,6 +97,10 @@ table
width: 100%; width: 100%;
max-width: 340px; max-width: 340px;
} }
select {
border-width: 1px;
}
} }
.parameter__name .parameter__name

View File

@@ -43,7 +43,6 @@
margin: 0; margin: 0;
border: 2px solid #547f00; border: 2px solid #547f00;
border-radius: 4px 0 0 4px;
outline: none; outline: none;
} }

View File

@@ -14,4 +14,5 @@
@import 'information'; @import 'information';
@import 'authorize'; @import 'authorize';
@import 'errors'; @import 'errors';
@import 'split-pane-mode';
} }

View File

@@ -1,3 +1,17 @@
try {
module.exports.SwaggerUIBundle = require("./swagger-ui-bundle.js") module.exports.SwaggerUIBundle = require("./swagger-ui-bundle.js")
module.exports.SwaggerUIStandalonePreset = require("./swagger-ui-standalone-preset.js") module.exports.SwaggerUIStandalonePreset = require("./swagger-ui-standalone-preset.js")
} catch(e) {
// swallow the error if there's a problem loading the assets.
// allows this module to support providing the assets for browserish contexts,
// without exploding in a Node context.
//
// see https://github.com/swagger-api/swagger-ui/issues/3291#issuecomment-311195388
// for more information.
}
// `absolutePath` and `getAbsoluteFSPath` are both here because at one point,
// we documented having one and actually implemented the other.
// They were both retained so we don't break anyone's code.
module.exports.absolutePath = require("./absolute-path.js") module.exports.absolutePath = require("./absolute-path.js")
module.exports.getAbsoluteFSPath = require("./absolute-path.js")

View File

@@ -1,2 +1,5 @@
env: env:
mocha: true mocha: true
rules:
"react/prop-types": 1 # bah humbug
"no-unused-vars": 1 # unused vars in tests can be useful for indicating a full signature

View File

@@ -0,0 +1,41 @@
/* eslint-env mocha */
import React from "react"
import expect, { createSpy } from "expect"
import { shallow } from "enzyme"
import { fromJS } from "immutable"
import Schemes from "components/schemes"
describe("<Schemes/>", function(){
it("calls props.specActions.setScheme() when no operationScheme is selected", function(){
// Given
let props = {
specActions: {
setScheme: createSpy()
},
schemes: fromJS([
"http",
"https"
]),
operationScheme: undefined,
path: "/test",
method: "get"
}
// When
let wrapper = shallow(<Schemes {...props}/>)
// Then operationScheme should default to first scheme in options list
expect(props.specActions.setScheme).toHaveBeenCalledWith("http", "/test" , "get")
// When the operationScheme is no longer in the list of options
props.schemes = fromJS([
"https"
])
wrapper.setProps(props)
// Then operationScheme should default to first scheme in options list
expect(props.specActions.setScheme).toHaveBeenCalledWith("https", "/test", "get")
})
})

View File

@@ -1,183 +0,0 @@
/* eslint-env mocha */
import expect from "expect"
import { transformPathToArray } from "core/path-translator"
describe("validation plugin - path translator", function(){
describe("string paths", function(){
it("should translate a simple string path to an array", function(){
// Given
let jsSpec = {
one: {
a: "a thing",
b: "another thing",
c: "one more thing"
},
two: 2
}
let path = "instance.one.a"
// Then
expect(transformPathToArray(path, jsSpec)).toEqual(["one", "a"])
})
it("should translate an ambiguous string path to an array", function(){
// Since JSONSchema uses periods to mark different properties,
// a key with a period in it is ambiguous, because it can mean at least two things.
// In our case, the path can mean:
// ["google", "com", "a"] or ["google.com", "a"]
// Given
let jsSpec = {
"google.com": {
a: "a thing",
b: "another thing",
c: "one more thing"
},
"gmail.com": {
d: "more stuff",
e: "even more stuff"
}
}
let path = "instance.google.com.a"
// Then
expect(transformPathToArray(path, jsSpec)).toEqual(["google.com", "a"])
})
it("should translate an doubly ambiguous string path to an array", function(){
// Since JSONSchema uses periods to mark different properties,
// a key with two periods in it (like "www.google.com") is doubly ambiguous,
// because it can mean at least three things.
// Given
let jsSpec = {
"www.google.com": {
a: "a thing",
b: "another thing",
c: "one more thing"
},
"gmail.com": {
d: "more stuff",
e: "even more stuff"
}
}
let path = "instance.www.google.com.a"
// Then
expect(transformPathToArray(path, jsSpec)).toEqual(["www.google.com", "a"])
})
it("should return null for an invalid path", function(){
// Given
let jsSpec = {
"google.com": {
a: "a thing",
b: "another thing",
c: "one more thing"
},
"gmail.com": {
d: "more stuff",
e: "even more stuff"
}
}
let path = "instance.google.net.a"
// Then
expect(transformPathToArray(path, jsSpec)).toEqual(null)
})
it("should return inline array indices in their own value", function(){
// "a[1]" => ["a", "1"]
// Given
let jsSpec = {
"google.com": {
a: [
"hello",
"here is the target"
],
b: "another thing",
c: "one more thing"
},
"gmail.com": {
d: "more stuff",
e: "even more stuff"
}
}
let path = "instance.google.com.a[1]"
// Then
expect(transformPathToArray(path, jsSpec)).toEqual(["google.com", "a", "1"])
})
it("should return the correct path when the last part is ambiguous", function(){
// Given
let jsSpec = {
"google.com": {
a: [
"hello",
{
"gmail.com": 1234
}
],
b: "another thing",
c: "one more thing"
},
"gmail.com": {
d: "more stuff",
e: "even more stuff"
}
}
let path = "instance.google.com.a[1].gmail.com"
// Then
expect(transformPathToArray(path, jsSpec)).toEqual(["google.com", "a", "1", "gmail.com"])
})
it("should return the correct path when the last part is doubly ambiguous", function(){
// Given
let jsSpec = {
"google.com": {
a: [
"hello",
{
"www.gmail.com": 1234
}
],
b: "another thing",
c: "one more thing"
},
"gmail.com": {
d: "more stuff",
e: "even more stuff"
}
}
let path = "instance.google.com.a[1].www.gmail.com"
// Then
expect(transformPathToArray(path, jsSpec)).toEqual(["google.com", "a", "1", "www.gmail.com"])
})
})
})

View File

@@ -52,7 +52,6 @@ describe("spec plugin - selectors", function(){
}) })
describe("contentTypeValues", function(){ describe("contentTypeValues", function(){
it("should return { requestContentType, responseContentType } from an operation", function(){ it("should return { requestContentType, responseContentType } from an operation", function(){
// Given // Given
let state = fromJS({ let state = fromJS({
@@ -77,6 +76,73 @@ describe("spec plugin - selectors", function(){
}) })
}) })
it("should prioritize consumes value first from an operation", function(){
// Given
let state = fromJS({
resolved: {
paths: {
"/one": {
get: {
"consumes_value": "one",
"parameters": [{
"type": "file"
}],
}
}
}
}
})
// When
let contentTypes = contentTypeValues(state, [ "/one", "get" ])
// Then
expect(contentTypes.toJS().requestContentType).toEqual("one")
})
it("should fallback to multipart/form-data if there is no consumes value but there is a file parameter", function(){
// Given
let state = fromJS({
resolved: {
paths: {
"/one": {
get: {
"parameters": [{
"type": "file"
}],
}
}
}
}
})
// When
let contentTypes = contentTypeValues(state, [ "/one", "get" ])
// Then
expect(contentTypes.toJS().requestContentType).toEqual("multipart/form-data")
})
it("should fallback to application/x-www-form-urlencoded if there is no consumes value, no file parameter, but there is a formData parameter", function(){
// Given
let state = fromJS({
resolved: {
paths: {
"/one": {
get: {
"parameters": [{
"type": "formData"
}],
}
}
}
}
})
// When
let contentTypes = contentTypeValues(state, [ "/one", "get" ])
// Then
expect(contentTypes.toJS().requestContentType).toEqual("application/x-www-form-urlencoded")
})
it("should be ok, if no operation found", function(){ it("should be ok, if no operation found", function(){
// Given // Given
let state = fromJS({ }) let state = fromJS({ })

View File

@@ -326,7 +326,7 @@ describe("bound system", function(){
}) })
describe('wrapSelectors', () => { describe("wrapSelectors", () => {
it("should wrap a selector and provide a reference to the original", function(){ it("should wrap a selector and provide a reference to the original", function(){
// Given // Given
@@ -427,7 +427,7 @@ describe("bound system", function(){
wrapSelectors: { wrapSelectors: {
wow: (ori, system) => (dogeState) => { wow: (ori, system) => (dogeState) => {
// Then // Then
expect(dogeState.toJS().abc).toEqual('123') expect(dogeState.toJS().abc).toEqual("123")
done() done()
return ori() + " wrapper" return ori() + " wrapper"
} }

View File

@@ -1,7 +1,8 @@
/* eslint-env mocha */ /* eslint-env mocha */
import expect from "expect" import expect from "expect"
import { fromJS } from "immutable" import { fromJS } from "immutable"
import { mapToList, validateNumber, validateInteger, validateParam } from "core/utils" import { mapToList, validateNumber, validateInteger, validateParam, validateFile, fromJSOrdered } from "core/utils"
import win from "core/window"
describe("utils", function() { describe("utils", function() {
@@ -157,11 +158,36 @@ describe("utils", function(){
}) })
}) })
describe("validateFile", function() {
let errorMessage = "Value must be a file"
it("validates against objects which are instances of 'File'", function() {
let fileObj = new win.File([], "Test File")
expect(validateFile(fileObj)).toBeFalsy()
expect(validateFile(null)).toBeFalsy()
expect(validateFile(undefined)).toBeFalsy()
expect(validateFile(1)).toEqual(errorMessage)
expect(validateFile("string")).toEqual(errorMessage)
})
})
describe("validateParam", function() { describe("validateParam", function() {
let param = null let param = null
let result = null let result = null
it("skips validation when `type` is not specified", function() {
// invalid type
param = fromJS({
required: false,
type: undefined,
value: ""
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
it("validates required strings", function() { it("validates required strings", function() {
// invalid string
param = fromJS({ param = fromJS({
required: true, required: true,
type: "string", type: "string",
@@ -169,9 +195,88 @@ describe("utils", function(){
}) })
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] ) expect( result ).toEqual( ["Required field is not provided"] )
// valid string
param = fromJS({
required: true,
type: "string",
value: "test string"
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
it("validates optional strings", function() {
// valid (empty) string
param = fromJS({
required: false,
type: "string",
value: ""
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
// valid string
param = fromJS({
required: false,
type: "string",
value: "test"
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
it("validates required files", function() {
// invalid file
param = fromJS({
required: true,
type: "file",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
// valid file
param = fromJS({
required: true,
type: "file",
value: new win.File()
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
it("validates optional files", function() {
// invalid file
param = fromJS({
required: false,
type: "file",
value: "not a file"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Value must be a file"] )
// valid (empty) file
param = fromJS({
required: false,
type: "file",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
// valid file
param = fromJS({
required: false,
type: "file",
value: new win.File()
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}) })
it("validates required arrays", function() { it("validates required arrays", function() {
// invalid (empty) array
param = fromJS({ param = fromJS({
required: true, required: true,
type: "array", type: "array",
@@ -180,37 +285,51 @@ describe("utils", function(){
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] ) expect( result ).toEqual( ["Required field is not provided"] )
// invalid (not an array)
param = fromJS({ param = fromJS({
required: true, required: true,
type: "array", type: "array",
value: [] value: undefined
}) })
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] ) expect( result ).toEqual( ["Required field is not provided"] )
})
it("validates numbers", function() { // invalid array, items do not match correct type
param = fromJS({ param = fromJS({
required: false, required: true,
type: "number", type: "array",
value: "test" value: [1],
items: {
type: "string"
}
}) })
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( ["Value must be a number"] ) expect( result ).toEqual( [{index: 0, error: "Value must be a string"}] )
})
it("validates integers", function() { // valid array, with no 'type' for items
param = fromJS({ param = fromJS({
required: false, required: true,
type: "integer", type: "array",
value: "test" value: ["1"]
}) })
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( ["Value must be an integer"] ) expect( result ).toEqual( [] )
// valid array, items match type
param = fromJS({
required: true,
type: "array",
value: ["1"],
items: {
type: "string"
}
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}) })
it("validates arrays", function() { it("validates optional arrays", function() {
// empty array // valid, empty array
param = fromJS({ param = fromJS({
required: false, required: false,
type: "array", type: "array",
@@ -219,7 +338,7 @@ describe("utils", function(){
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( [] ) expect( result ).toEqual( [] )
// numbers // invalid, items do not match correct type
param = fromJS({ param = fromJS({
required: false, required: false,
type: "array", type: "array",
@@ -231,17 +350,236 @@ describe("utils", function(){
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( [{index: 0, error: "Value must be a number"}] ) expect( result ).toEqual( [{index: 0, error: "Value must be a number"}] )
// integers // valid
param = fromJS({ param = fromJS({
required: false, required: false,
type: "array", type: "array",
value: ["not", "numbers"], value: ["test"],
items: { items: {
type: "integer" type: "string"
} }
}) })
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( [{index: 0, error: "Value must be an integer"}, {index: 1, error: "Value must be an integer"}] ) expect( result ).toEqual( [] )
})
it("validates required booleans", function() {
// invalid boolean value
param = fromJS({
required: true,
type: "boolean",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
// invalid boolean value (not a boolean)
param = fromJS({
required: true,
type: "boolean",
value: "test string"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
// valid boolean value
param = fromJS({
required: true,
type: "boolean",
value: "true"
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
// valid boolean value
param = fromJS({
required: true,
type: "boolean",
value: false
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
it("validates optional booleans", function() {
// valid (empty) boolean value
param = fromJS({
required: false,
type: "boolean",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
// invalid boolean value (not a boolean)
param = fromJS({
required: false,
type: "boolean",
value: "test string"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Value must be a boolean"] )
// valid boolean value
param = fromJS({
required: false,
type: "boolean",
value: "true"
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
// valid boolean value
param = fromJS({
required: false,
type: "boolean",
value: false
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
it("validates required numbers", function() {
// invalid number, string instead of a number
param = fromJS({
required: true,
type: "number",
value: "test"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
// invalid number, undefined value
param = fromJS({
required: true,
type: "number",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
// valid number
param = fromJS({
required: true,
type: "number",
value: 10
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
it("validates optional numbers", function() {
// invalid number, string instead of a number
param = fromJS({
required: false,
type: "number",
value: "test"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Value must be a number"] )
// valid (empty) number
param = fromJS({
required: false,
type: "number",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
// valid number
param = fromJS({
required: false,
type: "number",
value: 10
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
it("validates required integers", function() {
// invalid integer, string instead of an integer
param = fromJS({
required: true,
type: "integer",
value: "test"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
// invalid integer, undefined value
param = fromJS({
required: true,
type: "integer",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
// valid integer
param = fromJS({
required: true,
type: "integer",
value: 10
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
it("validates optional integers", function() {
// invalid integer, string instead of an integer
param = fromJS({
required: false,
type: "integer",
value: "test"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Value must be an integer"] )
// valid (empty) integer
param = fromJS({
required: false,
type: "integer",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
// integers
param = fromJS({
required: false,
type: "integer",
value: 10
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
})
describe("fromJSOrdered", () => {
it("should create an OrderedMap from an object", () => {
const param = {
value: "test"
}
const result = fromJSOrdered(param).toJS()
expect( result ).toEqual( { value: "test" } )
})
it("should not use an object's length property for Map size", () => {
const param = {
length: 5
}
const result = fromJSOrdered(param).toJS()
expect( result ).toEqual( { length: 5 } )
})
it("should create an OrderedMap from an array", () => {
const param = [1, 1, 2, 3, 5, 8]
const result = fromJSOrdered(param).toJS()
expect( result ).toEqual( [1, 1, 2, 3, 5, 8] )
}) })
}) })
}) })

View File

@@ -1,64 +1,32 @@
var path = require('path') const path = require("path")
var rules = [ const styleRules = require("./webpack.dist-style.config.js")
let rules = [
{ test: /\.(worker\.js)(\?.*)?$/, { test: /\.(worker\.js)(\?.*)?$/,
use: [ use: [
{ {
loader: 'worker-loader', loader: "worker-loader",
options: { options: {
inline: true, inline: true,
name: '[name].js' name: "[name].js"
} }
}, },
{ loader: 'babel-loader' } { loader: "babel-loader" }
]
},
{ test: /\.(css)(\?.*)?$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
},
{ test: /\.(scss)(\?.*)?$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: { sourceMap: true }
},
{ loader: 'sass-loader',
options: {
outputStyle: 'expanded',
sourceMap: true,
sourceMapContents: 'true'
}
}
]
},
{ test: /\.(less)(\?.*)?$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
},
'less-loader'
] ]
} }
] ]
module.exports = require('./make-webpack-config.js')(rules, { module.exports = require("./make-webpack-config.js")(rules, {
_special: { _special: {
separateStylesheets: false, separateStylesheets: true,
minimize: true, minimize: true,
sourcemaps: true, sourcemaps: true,
}, },
entry: { entry: {
'swagger-ui-bundle': [ "swagger-ui-bundle": [
'./src/polyfills', "./src/polyfills",
'./src/core/index.js' "./src/core/index.js"
] ]
}, },

View File

@@ -1,65 +1,32 @@
var path = require('path') const path = require("path")
const styleRules = require("./webpack.dist-style.config.js")
var rules = [ let rules = [
{ test: /\.(worker\.js)(\?.*)?$/, { test: /\.(worker\.js)(\?.*)?$/,
use: [ use: [
{ {
loader: 'worker-loader', loader: "worker-loader",
options: { options: {
inline: true, inline: true,
name: '[name].js' name: "[name].js"
} }
}, },
{ loader: 'babel-loader' } { loader: "babel-loader" }
]
},
{ test: /\.(css)(\?.*)?$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
},
{ test: /\.(scss)(\?.*)?$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: { sourceMap: true }
},
{ loader: 'sass-loader',
options: {
outputStyle: 'expanded',
sourceMap: true,
sourceMapContents: 'true'
}
}
]
},
{ test: /\.(less)(\?.*)?$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
},
'less-loader'
] ]
} }
] ]
module.exports = require('./make-webpack-config.js')(rules, { module.exports = require("./make-webpack-config.js")(rules, {
_special: { _special: {
separateStylesheets: false, separateStylesheets: true,
minimize: true, minimize: true,
sourcemaps: true, sourcemaps: true,
}, },
entry: { entry: {
'swagger-ui-standalone-preset': [ "swagger-ui-standalone-preset": [
'./src/polyfills', "./src/polyfills",
'./src/standalone/index.js' "./src/standalone/index.js"
] ]
}, },

View File

@@ -1,66 +1,25 @@
var path = require('path') const path = require("path")
var fs = require('fs') const fs = require("fs")
const nodeModules = fs.readdirSync("node_modules").filter(function(x) { return x !== ".bin" }) const nodeModules = fs.readdirSync("node_modules").filter(function(x) { return x !== ".bin" })
var ExtractTextPlugin = require('extract-text-webpack-plugin') const styleRules = require("./webpack.dist-style.config.js")
var rules = [ let rules = [
{ test: /\.(worker\.js)(\?.*)?$/, { test: /\.(worker\.js)(\?.*)?$/,
use: [ use: [
{ {
loader: 'worker-loader', loader: "worker-loader",
options: { options: {
inline: true, inline: true,
name: '[name].js' name: "[name].js"
} }
}, },
{ loader: 'babel-loader' } { loader: "babel-loader" }
] ]
},
{ test: /\.(css)(\?.*)?$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
'css-loader',
'postcss-loader'
]
})
},
{ test: /\.(scss)(\?.*)?$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: { minimize: true }
},
{
loader: 'postcss-loader',
options: { sourceMap: true }
},
{ loader: 'sass-loader',
options: {
outputStyle: 'expanded',
sourceMap: true,
sourceMapContents: 'true'
}
}
]
})
},
{ test: /\.(less)(\?.*)?$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader',
{
loader: 'postcss-loader',
},
'less-loader'
]
})
} }
] ]
rules = rules.concat(styleRules)
module.exports = require('./make-webpack-config.js')(rules, { module.exports = require("./make-webpack-config.js")(rules, {
_special: { _special: {
separateStylesheets: true, separateStylesheets: true,
minimize: true, minimize: true,

View File

@@ -1,56 +1,46 @@
var path = require('path') const path = require("path")
var rules = [ const rules = [
{ test: /\.(worker\.js)(\?.*)?$/, { test: /\.(worker\.js)(\?.*)?$/,
use: [ use: [
{ {
loader: 'worker-loader', loader: "worker-loader",
options: { options: {
inline: true inline: true
} }
}, },
{ loader: 'babel-loader' } { loader: "babel-loader" }
] ]
}, },
{ test: /\.(jsx)(\?.*)?$/, { test: /\.(jsx)(\?.*)?$/,
use: [ use: [
{ loader: 'react-hot-loader' }, { loader: "react-hot-loader" },
{ loader: 'babel-loader' } { loader: "babel-loader" }
] ]
}, },
{ test: /\.(css)(\?.*)?$/, { test: /\.(css)(\?.*)?$/,
use: [ use: [
'style-loader', "style-loader",
'css-loader', "css-loader",
'postcss-loader' "postcss-loader"
] ]
}, },
{ test: /\.(scss)(\?.*)?$/, { test: /\.(scss)(\?.*)?$/,
use: [ use: [
'style-loader', "style-loader",
'css-loader', "css-loader",
{ {
loader: 'postcss-loader', loader: "postcss-loader",
options: { sourceMap: true } options: { sourceMap: true }
}, },
{ loader: 'sass-loader', { loader: "sass-loader",
options: { options: {
outputStyle: 'expanded', outputStyle: "expanded",
sourceMap: true, sourceMap: true,
sourceMapContents: 'true' sourceMapContents: "true"
} }
} }
] ]
},
{ test: /\.(less)(\?.*)?$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
},
'less-loader'
]
} }
] ]
@@ -60,25 +50,26 @@ module.exports = require("./make-webpack-config")(rules, {
}, },
devtool: "eval", devtool: "eval",
entry: { entry: {
'swagger-ui-bundle': [ "swagger-ui-bundle": [
'./src/polyfills', "./src/polyfills",
'./src/core/index.js' "./src/core/index.js"
], ],
'swagger-ui-standalone-preset': [ "swagger-ui-standalone-preset": [
'./src/polyfills', "./src/style/main.scss",
'./src/standalone/index.js', "./src/polyfills",
"./src/standalone/index.js",
] ]
}, },
output: { output: {
pathinfo: true, pathinfo: true,
filename: '[name].js', filename: "[name].js",
library: "[name]", library: "[name]",
libraryTarget: "umd", libraryTarget: "umd",
chunkFilename: "[id].js" chunkFilename: "[id].js"
}, },
devServer: { devServer: {
port: 3200, port: 3200,
contentBase: path.join(__dirname, 'dev-helpers'), contentBase: path.join(__dirname, "dev-helpers"),
publicPath: "/", publicPath: "/",
noInfo: true, noInfo: true,
hot: true, hot: true,

View File

@@ -1,3 +1,3 @@
var config = require("./webpack-dist.config.js") const config = require("./webpack-dist.config.js")
module.exports = config module.exports = config

View File

@@ -1,8 +1,7 @@
const webpack = require('webpack') const path = require("path")
const path = require('path') const deepMerge = require("deepmerge")
const deepMerge = require('deepmerge') const webpackConfig = require("./webpack-dist-bundle.config.js")
const webpackConfig = require('./webpack-dist-bundle.config.js') const DEPS_CHECK_DIR = require("./package.json").config.deps_check_dir
const DEPS_CHECK_DIR = require('./package.json').config.deps_check_dir
module.exports = deepMerge( module.exports = deepMerge(
webpackConfig, { webpackConfig, {

View File

@@ -1,3 +1,3 @@
module.exports = require("./make-webpack-config")({ module.exports = require("./make-webpack-config")({
}); })

View File

@@ -0,0 +1,34 @@
const ExtractTextPlugin = require("extract-text-webpack-plugin")
module.exports = [{
test: /\.(css)(\?.*)?$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [
"css-loader",
"postcss-loader"
]
})
},
{ test: /\.(scss)(\?.*)?$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [
{
loader: "css-loader",
options: { minimize: true }
},
{
loader: "postcss-loader",
options: { sourceMap: true }
},
{ loader: "sass-loader",
options: {
outputStyle: "expanded",
sourceMap: true,
sourceMapContents: "true"
}
}
]
})
}]