Merge branch 'master' of github.com:swagger-api/swagger-ui into ft/performance

This commit is contained in:
Owen Conti
2017-10-22 12:37:10 -06:00
72 changed files with 1515 additions and 366 deletions

View File

@@ -16,7 +16,8 @@
"extends": ["eslint:recommended", "plugin:react/recommended"], "extends": ["eslint:recommended", "plugin:react/recommended"],
"plugins": [ "plugins": [
"react" "react",
"mocha"
], ],
"rules": { "rules": {
@@ -32,6 +33,7 @@
"comma-dangle": 0, "comma-dangle": 0,
"no-console": ["error", { allow: ["warn", "error"] }], "no-console": ["error", { allow: ["warn", "error"] }],
"react/jsx-no-bind": 1, "react/jsx-no-bind": 1,
"react/display-name": 0 "react/display-name": 0,
"mocha/no-exclusive-tests": "error"
} }
} }

View File

@@ -6,14 +6,14 @@
**This is the new version of swagger-ui, 3.x. Want to learn more? Check out our [FAQ](http://swagger.io/new-ui-faq/).** **This is the new version of swagger-ui, 3.x. Want to learn more? Check out our [FAQ](http://swagger.io/new-ui-faq/).**
**👉🏼 Want to score an easy open-source contribution?** Check out our [Good first contribution](https://github.com/swagger-api/swagger-ui/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+first+contribution%22) label. **👉🏼 Want to score an easy open-source contribution?** Check out our [Good first issue](https://github.com/swagger-api/swagger-ui/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+first+issue%22) label.
As a brand new version, written from the ground up, there are some known issues and unimplemented features. Check out the [Known Issues](#known-issues) section for more details. As a brand new version, written from the ground up, there are some known issues and unimplemented features. Check out the [Known Issues](#known-issues) section for more details.
This repo publishes to two different NPM packages: This repository publishes to two different NPM modules:
* [swagger-ui](https://www.npmjs.com/package/swagger-ui) is intended for use as a node module. * [swagger-ui](https://www.npmjs.com/package/swagger-ui) is a traditional npm module intended for use in JavaScript web application projects that are capable of resolving dependencies (via Webpack, Browserify, etc).
* [swagger-ui-dist](https://www.npmjs.com/package/swagger-ui-dist) comes pre-bundled with all dependencies and can be incorporated directly in a webapp. * [swagger-ui-dist](https://www.npmjs.com/package/swagger-ui-dist) is a dependency-free module that includes everything you need to serve Swagger-UI in a server-side project, or a web project that can't resolve npm module dependencies.
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).
@@ -22,7 +22,7 @@ The OpenAPI Specification has undergone 5 revisions since initial creation in 20
Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes
------------------ | ------------ | -------------------------- | ----- ------------------ | ------------ | -------------------------- | -----
3.2.0 | 2017-09-08 | 2.0, 3.0 | [tag v3.2.0](https://github.com/swagger-api/swagger-ui/tree/v3.2.0) 3.4.0 | 2017-10-20 | 2.0, 3.0 | [tag v3.4.0](https://github.com/swagger-api/swagger-ui/tree/v3.4.0)
3.0.21 | 2017-07-26 | 2.0 | [tag v3.0.21](https://github.com/swagger-api/swagger-ui/tree/v3.0.21) 3.0.21 | 2017-07-26 | 2.0 | [tag v3.0.21](https://github.com/swagger-api/swagger-ui/tree/v3.0.21)
2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10) 2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10)
2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5) 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)
@@ -119,7 +119,7 @@ scopeSeparator | scope separator for passing scopes, encoded before calling, def
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` 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`
``` ```javascript
const ui = SwaggerUIBundle({...}) const ui = SwaggerUIBundle({...})
// Method can be called in any place after calling constructor SwaggerUIBundle // Method can be called in any place after calling constructor SwaggerUIBundle
@@ -151,6 +151,8 @@ domNode | The HTML DOM element inside which SwaggerUi will put the user interfac
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. 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.
defaultModelRendering | Controls how models are shown when the API is first rendered. (The user can always switch the rendering for a given model by clicking the 'Model' and 'Example Value' links.) It can be set to 'model' or 'example', and the default is 'example'.
defaultModelExpandDepth | The default expansion depth for models. The default value is 1.
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
modelPropertyMacro | MUST be a function. Function to set default values to each property in model. Accepts one argument modelPropertyMacro(property), property is immutable modelPropertyMacro | MUST be a function. Function to set default values to each property in model. Accepts one argument modelPropertyMacro(property), property is immutable
@@ -169,7 +171,7 @@ showMutatedRequest | If set to `true` (the default), uses the mutated request re
#### Topbar plugin #### Topbar plugin
Topbar plugin enables top bar with input for spec path and explore button or a dropdown if `urls` is used. By default the plugin is enabled, and to disable it you need to remove Topbar plugin from presets in `src/standalone/index.js`: Topbar plugin enables top bar with input for spec path and explore button or a dropdown if `urls` is used. By default the plugin is enabled, and to disable it you need to remove Topbar plugin from presets in `src/standalone/index.js`:
``` ```javascript
let preset = [ let preset = [
// TopbarPlugin, // TopbarPlugin,
ConfigsPlugin, ConfigsPlugin,

View File

@@ -3,6 +3,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Swagger UI</title> <title>Swagger UI</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" > <link rel="stylesheet" type="text/css" href="./swagger-ui.css" >

View File

@@ -27,7 +27,10 @@
isValid = qp.state === sentState isValid = qp.state === sentState
if (oauth2.auth.schema.get("flow") === "accessCode" && !oauth2.auth.code) { if ((
oauth2.auth.schema.get("flow") === "accessCode"||
oauth2.auth.schema.get("flow") === "authorizationCode"
) && !oauth2.auth.code) {
if (!isValid) { if (!isValid) {
oauth2.errCb({ oauth2.errCb({
authId: oauth2.auth.name, authId: oauth2.auth.name,

View File

@@ -27,7 +27,10 @@
isValid = qp.state === sentState isValid = qp.state === sentState
if (oauth2.auth.schema.get("flow") === "accessCode" && !oauth2.auth.code) { if ((
oauth2.auth.schema.get("flow") === "accessCode"||
oauth2.auth.schema.get("flow") === "authorizationCode"
) && !oauth2.auth.code) {
if (!isValid) { if (!isValid) {
oauth2.errCb({ oauth2.errCb({
authId: oauth2.auth.name, authId: oauth2.auth.name,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
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,"sources":[],"names":[],"mappings":"","file":"swagger-ui.css","sourceRoot":""}

4
dist/swagger-ui.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -137,7 +137,7 @@ module.exports = function(rules, options) {
} }
}, },
devtool: specialOptions.sourcemaps ? "cheap-module-source-map" : null, devtool: specialOptions.sourcemaps ? "nosource-source-map" : null,
plugins, plugins,

View File

@@ -1,6 +1,6 @@
{ {
"name": "swagger-ui", "name": "swagger-ui",
"version": "3.2.0", "version": "3.4.0",
"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": [
@@ -32,7 +32,7 @@
"test": "npm run lint-errors && npm run just-test-in-node", "test": "npm run lint-errors && npm run just-test-in-node",
"test-in-node": "npm run lint-errors && npm run just-test-in-node", "test-in-node": "npm run lint-errors && npm run just-test-in-node",
"just-test": "karma start --config karma.conf.js", "just-test": "karma start --config karma.conf.js",
"just-test-in-node": "mocha --recursive --compilers js:babel-core/register test/core test/components test/bugs test/swagger-ui-dist-package", "just-test-in-node": "mocha --recursive --compilers js:babel-core/register test/core test/components test/bugs test/swagger-ui-dist-package test/xss",
"test-e2e": "sleep 3 && nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json", "test-e2e": "sleep 3 && nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json",
"e2e-initial-render": "nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json --group initial-render", "e2e-initial-render": "nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json --group initial-render",
"mock-api": "json-server --watch test/e2e/db.json --port 3204", "mock-api": "json-server --watch test/e2e/db.json --port 3204",
@@ -42,6 +42,7 @@
"base64-js": "^1.2.0", "base64-js": "^1.2.0",
"brace": "0.7.0", "brace": "0.7.0",
"classnames": "^2.2.5", "classnames": "^2.2.5",
"commonmark": "^0.28.1",
"css.escape": "1.5.1", "css.escape": "1.5.1",
"deep-extend": "0.4.1", "deep-extend": "0.4.1",
"expect": "1.20.2", "expect": "1.20.2",
@@ -67,17 +68,17 @@
"react-motion": "0.4.4", "react-motion": "0.4.4",
"react-object-inspector": "0.2.1", "react-object-inspector": "0.2.1",
"react-redux": "^4.x.x", "react-redux": "^4.x.x",
"react-remarkable": "1.1.1",
"react-split-pane": "0.1.57", "react-split-pane": "0.1.57",
"redux": "^3.x.x", "redux": "^3.x.x",
"redux-immutable": "3.0.8", "redux-immutable": "3.0.8",
"redux-logger": "*", "redux-logger": "*",
"remarkable": "^1.7.1",
"reselect": "2.5.3", "reselect": "2.5.3",
"sanitize-html": "^1.14.1", "sanitize-html": "^1.14.1",
"scroll-to-element": "^2.0.0", "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.1.0", "swagger-client": "^3.3.0",
"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",
@@ -104,6 +105,7 @@
"enzyme": "^2.7.1", "enzyme": "^2.7.1",
"eslint": "^4.1.1", "eslint": "^4.1.1",
"eslint-plugin-import": "^2.6.0", "eslint-plugin-import": "^2.6.0",
"eslint-plugin-mocha": "^4.11.0",
"eslint-plugin-react": "^7.1.0", "eslint-plugin-react": "^7.1.0",
"extract-text-webpack-plugin": "^2.1.2", "extract-text-webpack-plugin": "^2.1.2",
"file-loader": "0.11.2", "file-loader": "0.11.2",

View File

@@ -16,10 +16,12 @@ export default class ArrayModel extends Component {
render(){ render(){
let { getComponent, schema, depth, expandDepth, name } = this.props let { getComponent, schema, depth, expandDepth, name } = this.props
let description = schema.get("description")
let items = schema.get("items") let items = schema.get("items")
let title = schema.get("title") || name let title = schema.get("title") || name
let properties = schema.filter( ( v, key) => ["type", "items", "$$ref"].indexOf(key) === -1 ) let properties = schema.filter( ( v, key) => ["type", "items", "description", "$$ref"].indexOf(key) === -1 )
const Markdown = getComponent("Markdown")
const ModelCollapse = getComponent("ModelCollapse") const ModelCollapse = getComponent("ModelCollapse")
const Model = getComponent("Model") const Model = getComponent("Model")
@@ -36,15 +38,17 @@ export default class ArrayModel extends Component {
return <span className="model"> return <span className="model">
<ModelCollapse title={titleEl} collapsed={ depth > expandDepth } collapsedContent="[...]"> <ModelCollapse title={titleEl} collapsed={ depth > expandDepth } collapsedContent="[...]">
[ [
<span><Model { ...this.props } name={null} schema={ items } required={ false } depth={ depth + 1 } /></span>
]
{ {
properties.size ? <span> properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }>
{ properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={propStyle}> <br />{ key }: { String(v) }</span>)
<br />{ `${key}:`}{ String(v) }</span>)
}<br /></span>
: null : null
} }
{
!description ? null :
<Markdown source={ description } />
}
<span><Model { ...this.props } name={null} schema={ items } required={ false } depth={ depth + 1 } /></span>
]
</ModelCollapse> </ModelCollapse>
</span> </span>
} }

View File

@@ -51,14 +51,15 @@ export default class ApiKeyAuth extends React.Component {
return ( return (
<div> <div>
<h4>Api key authorization<JumpToPath path={[ "securityDefinitions", name ]} /></h4> <h4>
<code>{ name || schema.get("name") }</code>&nbsp;
(apiKey)
<JumpToPath path={[ "securityDefinitions", name ]} />
</h4>
{ value && <h6>Authorized</h6>} { value && <h6>Authorized</h6>}
<Row> <Row>
<Markdown source={ schema.get("description") } /> <Markdown source={ schema.get("description") } />
</Row> </Row>
<Row>
<p>Name: <code>{ schema.get("name") }</code></p>
</Row>
<Row> <Row>
<p>In: <code>{ schema.get("in") }</code></p> <p>In: <code>{ schema.get("in") }</code></p>
</Row> </Row>

View File

@@ -0,0 +1,62 @@
import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
export default class Auths extends React.Component {
static propTypes = {
schema: ImPropTypes.orderedMap.isRequired,
name: PropTypes.string.isRequired,
onAuthChange: PropTypes.func.isRequired,
authorized: ImPropTypes.orderedMap.isRequired
}
render() {
let {
schema,
name,
getComponent,
onAuthChange,
authorized,
errSelectors
} = this.props
const ApiKeyAuth = getComponent("apiKeyAuth")
const BasicAuth = getComponent("basicAuth")
let authEl
const type = schema.get("type")
switch(type) {
case "apiKey": authEl = <ApiKeyAuth key={ name }
schema={ schema }
name={ name }
errSelectors={ errSelectors }
authorized={ authorized }
getComponent={ getComponent }
onChange={ onAuthChange } />
break
case "basic": authEl = <BasicAuth key={ name }
schema={ schema }
name={ name }
errSelectors={ errSelectors }
authorized={ authorized }
getComponent={ getComponent }
onChange={ onAuthChange } />
break
default: authEl = <div key={ name }>Unknown security definition type { type }</div>
}
return (<div key={`${name}-jump`}>
{ authEl }
</div>)
}
static propTypes = {
errSelectors: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
authSelectors: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
definitions: ImPropTypes.iterable.isRequired
}
}

View File

@@ -27,7 +27,6 @@ export default class Auths extends React.Component {
e.preventDefault() e.preventDefault()
let { authActions } = this.props let { authActions } = this.props
authActions.authorize(this.state) authActions.authorize(this.state)
} }
@@ -44,8 +43,7 @@ export default class Auths extends React.Component {
render() { render() {
let { definitions, getComponent, authSelectors, errSelectors } = this.props let { definitions, getComponent, authSelectors, errSelectors } = this.props
const ApiKeyAuth = getComponent("apiKeyAuth") const AuthItem = getComponent("AuthItem")
const BasicAuth = getComponent("basicAuth")
const Oauth2 = getComponent("oauth2", true) const Oauth2 = getComponent("oauth2", true)
const Button = getComponent("Button") const Button = getComponent("Button")
@@ -64,33 +62,15 @@ export default class Auths extends React.Component {
!!nonOauthDefinitions.size && <form onSubmit={ this.submitAuth }> !!nonOauthDefinitions.size && <form onSubmit={ this.submitAuth }>
{ {
nonOauthDefinitions.map( (schema, name) => { nonOauthDefinitions.map( (schema, name) => {
let type = schema.get("type") return <AuthItem
let authEl key={name}
switch(type) {
case "apiKey": authEl = <ApiKeyAuth key={ name }
schema={schema} schema={schema}
name={name} name={name}
errSelectors={ errSelectors }
authorized={ authorized }
getComponent={getComponent} getComponent={getComponent}
onChange={ this.onAuthChange } /> onAuthChange={this.onAuthChange}
break
case "basic": authEl = <BasicAuth key={ name }
schema={ schema }
name={ name }
errSelectors={ errSelectors }
authorized={authorized} authorized={authorized}
getComponent={ getComponent } errSelectors={errSelectors}
onChange={ this.onAuthChange } /> />
break
default: authEl = <div key={ name }>Unknown security definition type { type }</div>
}
return (<div key={`${name}-jump`}>
{ authEl }
</div>)
}).toArray() }).toArray()
} }
<div className="auth-btn-wrapper"> <div className="auth-btn-wrapper">

View File

@@ -2,11 +2,6 @@ import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import oauth2Authorize from "core/oauth2-authorize" import oauth2Authorize from "core/oauth2-authorize"
const IMPLICIT = "implicit"
const ACCESS_CODE = "accessCode"
const PASSWORD = "password"
const APPLICATION = "application"
export default class Oauth2 extends React.Component { export default class Oauth2 extends React.Component {
static propTypes = { static propTypes = {
name: PropTypes.string, name: PropTypes.string,
@@ -16,6 +11,7 @@ export default class Oauth2 extends React.Component {
authSelectors: PropTypes.object.isRequired, authSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired, authActions: PropTypes.object.isRequired,
errSelectors: PropTypes.object.isRequired, errSelectors: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
errActions: PropTypes.object.isRequired, errActions: PropTypes.object.isRequired,
getConfigs: PropTypes.any getConfigs: PropTypes.any
} }
@@ -83,7 +79,9 @@ export default class Oauth2 extends React.Component {
} }
render() { render() {
let { schema, getComponent, authSelectors, errSelectors, name } = this.props let {
schema, getComponent, authSelectors, errSelectors, name, specSelectors
} = this.props
const Input = getComponent("Input") const Input = getComponent("Input")
const Row = getComponent("Row") const Row = getComponent("Row")
const Col = getComponent("Col") const Col = getComponent("Col")
@@ -92,6 +90,14 @@ export default class Oauth2 extends React.Component {
const JumpToPath = getComponent("JumpToPath", true) const JumpToPath = getComponent("JumpToPath", true)
const Markdown = getComponent( "Markdown" ) const Markdown = getComponent( "Markdown" )
const { isOAS3 } = specSelectors
// Auth type consts
const IMPLICIT = "implicit"
const PASSWORD = "password"
const ACCESS_CODE = isOAS3() ? "authorizationCode" : "accessCode"
const APPLICATION = isOAS3() ? "clientCredentials" : "application"
let flow = schema.get("flow") let flow = schema.get("flow")
let scopes = schema.get("allowedScopes") || schema.get("scopes") let scopes = schema.get("allowedScopes") || schema.get("scopes")
let authorizedAuth = authSelectors.authorized().get(name) let authorizedAuth = authSelectors.authorized().get(name)
@@ -102,7 +108,7 @@ export default class Oauth2 extends React.Component {
return ( return (
<div> <div>
<h4>OAuth2.0 <JumpToPath path={[ "securityDefinitions", name ]} /></h4> <h4>{name} (OAuth2, { schema.get("flow") }) <JumpToPath path={[ "securityDefinitions", name ]} /></h4>
{ !this.state.appName ? null : <h5>Application: { this.state.appName } </h5> } { !this.state.appName ? null : <h5>Application: { this.state.appName } </h5> }
{ description && <Markdown source={ schema.get("description") } /> } { description && <Markdown source={ schema.get("description") } /> }

View File

@@ -61,6 +61,13 @@ export default class LiveResponse extends React.Component {
return ( return (
<div> <div>
{ curlRequest && <Curl request={ curlRequest }/> } { curlRequest && <Curl request={ curlRequest }/> }
{ url && <div>
<h4>Request URL</h4>
<div className="request-url">
<pre>{url}</pre>
</div>
</div>
}
<h4>Server response</h4> <h4>Server response</h4>
<table className="responses-table"> <table className="responses-table">
<thead> <thead>

View File

@@ -7,14 +7,19 @@ export default class ModelExample extends React.Component {
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
schema: PropTypes.object.isRequired, schema: PropTypes.object.isRequired,
example: PropTypes.any.isRequired, example: PropTypes.any.isRequired,
isExecute: PropTypes.bool isExecute: PropTypes.bool,
getConfigs: PropTypes.func.isRequired
} }
constructor(props, context) { constructor(props, context) {
super(props, context) super(props, context)
let { getConfigs } = this.props
let { defaultModelRendering } = getConfigs()
if (defaultModelRendering !== "example" && defaultModelRendering !== "model") {
defaultModelRendering = "example"
}
this.state = { this.state = {
activeTab: "example" activeTab: defaultModelRendering
} }
} }
@@ -27,7 +32,8 @@ export default class ModelExample extends React.Component {
} }
render() { render() {
let { getComponent, specSelectors, schema, example, isExecute } = this.props let { getComponent, specSelectors, schema, example, isExecute, getConfigs } = this.props
let { defaultModelExpandDepth } = getConfigs()
const ModelWrapper = getComponent("ModelWrapper") const ModelWrapper = getComponent("ModelWrapper")
return <div> return <div>
@@ -47,7 +53,7 @@ export default class ModelExample extends React.Component {
!isExecute && this.state.activeTab === "model" && <ModelWrapper schema={ schema } !isExecute && this.state.activeTab === "model" && <ModelWrapper schema={ schema }
getComponent={ getComponent } getComponent={ getComponent }
specSelectors={ specSelectors } specSelectors={ specSelectors }
expandDepth={ 1 } /> expandDepth={ defaultModelExpandDepth } />
} }

View File

@@ -13,7 +13,7 @@ export default class Models extends Component {
render(){ render(){
let { specSelectors, getComponent, layoutSelectors, layoutActions, getConfigs } = this.props let { specSelectors, getComponent, layoutSelectors, layoutActions, getConfigs } = this.props
let definitions = specSelectors.definitions() let definitions = specSelectors.definitions()
let { docExpansion } = getConfigs() let { docExpansion, defaultModelExpandDepth } = getConfigs()
let showModels = layoutSelectors.isShown("models", docExpansion === "full" || docExpansion === "list" ) let showModels = layoutSelectors.isShown("models", docExpansion === "full" || docExpansion === "list" )
const ModelWrapper = getComponent("ModelWrapper") const ModelWrapper = getComponent("ModelWrapper")
@@ -33,6 +33,7 @@ export default class Models extends Component {
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}` }>
<ModelWrapper name={ name } <ModelWrapper name={ name }
expandDepth={ defaultModelExpandDepth }
schema={ model } schema={ model }
getComponent={ getComponent } getComponent={ getComponent }
specSelectors={ specSelectors }/> specSelectors={ specSelectors }/>

View File

@@ -149,7 +149,7 @@ export default class Operation extends PureComponent {
const isDeepLinkingEnabled = deepLinking && deepLinking !== "false" const isDeepLinkingEnabled = deepLinking && deepLinking !== "false"
// Merge in Live Response // Merge in Live Response
if(response && response.size > 0) { if(responses && response && response.size > 0) {
let notDocumented = !responses.get(String(response.get("status"))) let notDocumented = !responses.get(String(response.get("status")))
response = response.set("notDocumented", notDocumented) response = response.set("notDocumented", notDocumented)
} }
@@ -224,6 +224,7 @@ export default class Operation extends PureComponent {
specActions={ specActions } specActions={ specActions }
specSelectors={ specSelectors } specSelectors={ specSelectors }
pathMethod={ [path, method] } pathMethod={ [path, method] }
getConfigs={ getConfigs }
/> />
{!tryItOutEnabled || !allowTryItOut ? null : schemes && schemes.size ? <div className="opblock-schemes"> {!tryItOutEnabled || !allowTryItOut ? null : schemes && schemes.size ? <div className="opblock-schemes">

View File

@@ -36,6 +36,7 @@ export default class Operations extends React.Component {
const Operation = getComponent("operation") const Operation = getComponent("operation")
const Collapse = getComponent("Collapse") const Collapse = getComponent("Collapse")
const Markdown = getComponent("Markdown")
let showSummary = layoutSelectors.showSummary() let showSummary = layoutSelectors.showSummary()
let { let {
@@ -89,7 +90,7 @@ export default class Operations extends React.Component {
</a> </a>
{ !tagDescription ? null : { !tagDescription ? null :
<small> <small>
{ tagDescription } <Markdown source={tagDescription} />
</small> </small>
} }

View File

@@ -47,7 +47,7 @@ export default class ParamBody extends PureComponent {
updateValues = (props) => { updateValues = (props) => {
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"), param.get("in")) : {}
let isXml = /xml/i.test(consumesValue) let isXml = /xml/i.test(consumesValue)
let isJson = /json/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")
@@ -107,7 +107,7 @@ export default class ParamBody extends PureComponent {
const HighlightCode = getComponent("highlightCode") const HighlightCode = getComponent("highlightCode")
const ContentType = getComponent("contentType") const ContentType = getComponent("contentType")
// for domains where specSelectors not passed // for domains where specSelectors not passed
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name")) : param let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name"), param.get("in")) : param
let errors = parameter.get("errors", List()) let errors = parameter.get("errors", List())
let consumesValue = specSelectors.contentTypeValues(pathMethod).get("requestContentType") let consumesValue = specSelectors.contentTypeValues(pathMethod).get("requestContentType")
let consumes = this.props.consumes && this.props.consumes.size ? this.props.consumes : ParamBody.defaultProp.consumes let consumes = this.props.consumes && this.props.consumes.size ? this.props.consumes : ParamBody.defaultProp.consumes

View File

@@ -11,7 +11,8 @@ export default class ParameterRow extends Component {
isExecute: PropTypes.bool, isExecute: PropTypes.bool,
onChangeConsumes: PropTypes.func.isRequired, onChangeConsumes: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
pathMethod: PropTypes.array.isRequired pathMethod: PropTypes.array.isRequired,
getConfigs: PropTypes.func.isRequired
} }
constructor(props, context) { constructor(props, context) {
@@ -19,7 +20,7 @@ export default class ParameterRow extends Component {
let { specSelectors, pathMethod, param } = props let { specSelectors, pathMethod, param } = props
let defaultValue = param.get("default") let defaultValue = param.get("default")
let parameter = specSelectors.getParameter(pathMethod, param.get("name")) let parameter = specSelectors.getParameter(pathMethod, param.get("name"), param.get("in"))
let value = parameter ? parameter.get("value") : "" let value = parameter ? parameter.get("value") : ""
if ( defaultValue !== undefined && value === undefined ) { if ( defaultValue !== undefined && value === undefined ) {
this.onChangeWrapper(defaultValue) this.onChangeWrapper(defaultValue)
@@ -30,7 +31,7 @@ export default class ParameterRow extends Component {
let { specSelectors, pathMethod, param } = props let { specSelectors, pathMethod, param } = props
let example = param.get("example") let example = param.get("example")
let defaultValue = param.get("default") let defaultValue = param.get("default")
let parameter = specSelectors.getParameter(pathMethod, param.get("name")) let parameter = specSelectors.getParameter(pathMethod, param.get("name"), param.get("in"))
let paramValue = parameter ? parameter.get("value") : undefined let paramValue = parameter ? parameter.get("value") : undefined
let enumValue = parameter ? parameter.get("enum") : undefined let enumValue = parameter ? parameter.get("enum") : undefined
let value let value
@@ -56,7 +57,7 @@ export default class ParameterRow extends Component {
} }
render() { render() {
let {param, onChange, getComponent, isExecute, fn, onChangeConsumes, specSelectors, pathMethod} = this.props let {param, onChange, getComponent, getConfigs, isExecute, fn, onChangeConsumes, specSelectors, pathMethod} = this.props
let { isOAS3 } = specSelectors let { isOAS3 } = specSelectors
@@ -86,7 +87,7 @@ export default class ParameterRow extends Component {
let isFormDataSupported = "FormData" in win let isFormDataSupported = "FormData" in win
let required = param.get("required") let required = param.get("required")
let itemType = param.getIn(isOAS3 && isOAS3() ? ["schema", "items", "type"] : ["items", "type"]) let itemType = param.getIn(isOAS3 && isOAS3() ? ["schema", "items", "type"] : ["items", "type"])
let parameter = specSelectors.getParameter(pathMethod, param.get("name")) let parameter = specSelectors.getParameter(pathMethod, param.get("name"), param.get("in"))
let value = parameter ? parameter.get("value") : "" let value = parameter ? parameter.get("value") : ""
return ( return (
@@ -114,12 +115,13 @@ export default class ParameterRow extends Component {
required={ required } required={ required }
description={param.get("description") ? `${param.get("name")} - ${param.get("description")}` : `${param.get("name")}`} description={param.get("description") ? `${param.get("name")} - ${param.get("description")}` : `${param.get("name")}`}
onChange={ this.onChangeWrapper } onChange={ this.onChangeWrapper }
schema={ param }/> schema={ isOAS3 && isOAS3() ? param.get("schema") : param }/>
} }
{ {
bodyParam && schema ? <ModelExample getComponent={ getComponent } bodyParam && schema ? <ModelExample getComponent={ getComponent }
getConfigs={ getConfigs }
isExecute={ isExecute } isExecute={ isExecute }
specSelectors={ specSelectors } specSelectors={ specSelectors }
schema={ schema } schema={ schema }

View File

@@ -19,7 +19,8 @@ export default class Parameters extends Component {
onTryoutClick: PropTypes.func, onTryoutClick: PropTypes.func,
onCancelClick: PropTypes.func, onCancelClick: PropTypes.func,
onChangeKey: PropTypes.array, onChangeKey: PropTypes.array,
pathMethod: PropTypes.array.isRequired pathMethod: PropTypes.array.isRequired,
getConfigs: PropTypes.func.isRequired
} }
@@ -37,7 +38,7 @@ export default class Parameters extends Component {
onChangeKey, onChangeKey,
} = this.props } = this.props
changeParam( onChangeKey, param.get("name"), value, isXml) changeParam( onChangeKey, param.get("name"), param.get("in"), value, isXml)
} }
onChangeConsumesWrapper = ( val ) => { onChangeConsumesWrapper = ( val ) => {
@@ -60,6 +61,7 @@ export default class Parameters extends Component {
fn, fn,
getComponent, getComponent,
getConfigs,
specSelectors, specSelectors,
pathMethod pathMethod
} = this.props } = this.props
@@ -93,8 +95,9 @@ export default class Parameters extends Component {
eachMap(parameters, (parameter) => ( eachMap(parameters, (parameter) => (
<ParameterRow fn={ fn } <ParameterRow fn={ fn }
getComponent={ getComponent } getComponent={ getComponent }
getConfigs={ getConfigs }
param={ parameter } param={ parameter }
key={ parameter.get( "name" ) } key={ `${parameter.get( "in" )}.${parameter.get("name")}` }
onChange={ this.onChange } onChange={ this.onChange }
onChangeConsumes={this.onChangeConsumesWrapper} onChangeConsumes={this.onChangeConsumesWrapper}
specSelectors={ specSelectors } specSelectors={ specSelectors }

View File

@@ -23,6 +23,7 @@ export default class Primitive extends Component {
let format = schema.get("format") let format = schema.get("format")
let xml = schema.get("xml") let xml = schema.get("xml")
let enumArray = schema.get("enum") let enumArray = schema.get("enum")
let title = schema.get("title") || name
let description = schema.get("description") let description = schema.get("description")
let properties = schema.filter( ( v, key) => ["enum", "type", "format", "description", "$$ref"].indexOf(key) === -1 ) let properties = schema.filter( ( v, key) => ["enum", "type", "format", "description", "$$ref"].indexOf(key) === -1 )
const Markdown = getComponent("Markdown") const Markdown = getComponent("Markdown")
@@ -30,7 +31,7 @@ export default class Primitive extends Component {
return <span className="model"> return <span className="model">
<span className="prop"> <span className="prop">
{ name && <span className={`${depth === 1 && "model-title"} prop-name`}>{ name }</span> } { name && <span className={`${depth === 1 && "model-title"} prop-name`}>{ title }</span> }
<span className="prop-type">{ type }</span> <span className="prop-type">{ type }</span>
{ format && <span className="prop-format">(${format})</span>} { format && <span className="prop-format">(${format})</span>}
{ {

View File

@@ -1,22 +1,25 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import Remarkable from "react-remarkable" import Remarkable from "remarkable"
import sanitize from "sanitize-html" import sanitize from "sanitize-html"
function Markdown({ source }) { function Markdown({ source }) {
const sanitized = sanitizer(source) const html = new Remarkable({
html: true,
typographer: true,
breaks: true,
linkify: true,
linkTarget: "_blank"
}).render(source)
const sanitized = sanitizer(html)
// sometimes the sanitizer returns "undefined" as a string if ( !source || !html || !sanitized ) {
if(!source || !sanitized || sanitized === "undefined") {
return null return null
} }
return <div className="markdown"> return (
<Remarkable <div className="markdown" dangerouslySetInnerHTML={{ __html: sanitized }}></div>
options={{html: true, typographer: true, breaks: true, linkify: true, linkTarget: "_blank"}} )
source={sanitized}
></Remarkable>
</div>
} }
Markdown.propTypes = { Markdown.propTypes = {
@@ -26,9 +29,13 @@ Markdown.propTypes = {
export default Markdown export default Markdown
const sanitizeOptions = { const sanitizeOptions = {
allowedTags: sanitize.defaults.allowedTags.concat([ "h1", "h2", "img" ]),
allowedAttributes: {
...sanitize.defaults.allowedAttributes,
"img": sanitize.defaults.allowedAttributes.img.concat(["title"])
},
textFilter: function(text) { textFilter: function(text) {
return text return text.replace(/&quot;/g, "\"")
.replace(/&quot;/g, "\"")
} }
} }

View File

@@ -49,10 +49,10 @@ export default class ResponseBody extends React.Component {
// Download // Download
} else if ( } else if (
/^application\/octet-stream/i.test(contentType) || /^application\/octet-stream/i.test(contentType) ||
headers["Content-Disposition"] && (/attachment/i).test(headers["Content-Disposition"]) || (headers["Content-Disposition"] && (/attachment/i).test(headers["Content-Disposition"])) ||
headers["content-disposition"] && (/attachment/i).test(headers["content-disposition"]) || (headers["content-disposition"] && (/attachment/i).test(headers["content-disposition"])) ||
headers["Content-Description"] && (/File Transfer/i).test(headers["Content-Description"]) || (headers["Content-Description"] && (/File Transfer/i).test(headers["Content-Description"])) ||
headers["content-description"] && (/File Transfer/i).test(headers["content-description"])) { (headers["content-description"] && (/File Transfer/i).test(headers["content-description"]))) {
let contentLength = headers["content-length"] || headers["Content-Length"] let contentLength = headers["content-length"] || headers["Content-Length"]
if ( !(+contentLength) ) return null if ( !(+contentLength) ) return null
@@ -83,8 +83,12 @@ export default class ResponseBody extends React.Component {
// Anything else (CORS) // Anything else (CORS)
} else if (typeof content === "string") { } else if (typeof content === "string") {
bodyEl = <HighlightCode value={ content } /> bodyEl = <HighlightCode value={ content } />
} else { } else if ( content.size > 0 ) {
// We don't know the contentType, but there was some content returned
bodyEl = <div>Unknown response type</div> bodyEl = <div>Unknown response type</div>
} else {
// We don't know the contentType and there was no content returned
bodyEl = null
} }
return ( !bodyEl ? null : <div> return ( !bodyEl ? null : <div>

View File

@@ -45,6 +45,7 @@ export default class Response extends React.Component {
response: PropTypes.object, response: PropTypes.object,
className: PropTypes.string, className: PropTypes.string,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired, fn: PropTypes.object.isRequired,
contentType: PropTypes.string, contentType: PropTypes.string,
@@ -73,6 +74,7 @@ export default class Response extends React.Component {
className, className,
fn, fn,
getComponent, getComponent,
getConfigs,
specSelectors, specSelectors,
contentType, contentType,
controlsAcceptHeader controlsAcceptHeader
@@ -107,6 +109,14 @@ export default class Response extends React.Component {
includeWriteOnly: true // writeOnly has no filtering effect in swagger 2.0 includeWriteOnly: true // writeOnly has no filtering effect in swagger 2.0
}) : null }) : null
} }
if(examples) {
examples = examples.map(example => {
// Remove unwanted properties from examples
return example.set ? example.set("$$ref", undefined) : example
})
}
let example = getExampleComponent( sampleResponse, examples, HighlightCode ) let example = getExampleComponent( sampleResponse, examples, HighlightCode )
return ( return (
@@ -136,6 +146,7 @@ export default class Response extends React.Component {
{ example ? ( { example ? (
<ModelExample <ModelExample
getComponent={ getComponent } getComponent={ getComponent }
getConfigs={ getConfigs }
specSelectors={ specSelectors } specSelectors={ specSelectors }
schema={ fromJSOrdered(schema) } schema={ fromJSOrdered(schema) }
example={ example }/> example={ example }/>

View File

@@ -12,13 +12,13 @@ export default class Responses extends React.Component {
produces: PropTypes.object, produces: PropTypes.object,
producesValue: PropTypes.any, producesValue: PropTypes.any,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired, specActions: PropTypes.object.isRequired,
oas3Actions: PropTypes.object.isRequired, oas3Actions: PropTypes.object.isRequired,
pathMethod: PropTypes.array.isRequired, pathMethod: PropTypes.array.isRequired,
displayRequestDuration: PropTypes.bool.isRequired, displayRequestDuration: PropTypes.bool.isRequired,
fn: PropTypes.object.isRequired, fn: PropTypes.object.isRequired
getConfigs: PropTypes.func.isRequired
} }
static defaultProps = { static defaultProps = {
@@ -116,6 +116,7 @@ export default class Responses extends React.Component {
controlsAcceptHeader={response === acceptControllingResponse} controlsAcceptHeader={response === acceptControllingResponse}
onContentTypeChange={this.onResponseContentTypeChange} onContentTypeChange={this.onResponseContentTypeChange}
contentType={ producesValue } contentType={ producesValue }
getConfigs={ getConfigs }
getComponent={ getComponent }/> getComponent={ getComponent }/>
) )
}).toArray() }).toArray()

View File

@@ -45,6 +45,8 @@ module.exports = function SwaggerUI(opts) {
requestInterceptor: (a => a), requestInterceptor: (a => a),
responseInterceptor: (a => a), responseInterceptor: (a => a),
showMutatedRequest: true, showMutatedRequest: true,
defaultModelRendering: "example",
defaultModelExpandDepth: 1,
// 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.

View File

@@ -22,6 +22,16 @@ export default function authorize ( { auth, authActions, errActions, configs, au
case "implicit": case "implicit":
query.push("response_type=token") query.push("response_type=token")
break break
case "clientCredentials":
// OAS3
authActions.authorizeApplication(auth)
return
case "authorizationCode":
// OAS3
query.push("response_type=code")
break
} }
if (typeof clientId === "string") { if (typeof clientId === "string") {
@@ -64,7 +74,8 @@ export default function authorize ( { auth, authActions, errActions, configs, au
} }
} }
let url = [schema.get("authorizationUrl"), query.join("&")].join("?") let authorizationUrl = schema.get("authorizationUrl")
let url = [authorizationUrl, query.join("&")].join(authorizationUrl.indexOf("?") === -1 ? "?" : "&")
// pass action authorizeOauth2 and authentication data through window // pass action authorizeOauth2 and authentication data through window
// to authorize with oauth2 // to authorize with oauth2

View File

@@ -22,7 +22,7 @@ export default {
securities.entrySeq().forEach( ([ key, security ]) => { securities.entrySeq().forEach( ([ key, security ]) => {
let type = security.getIn(["schema", "type"]) let type = security.getIn(["schema", "type"])
if ( type === "apiKey" ) { if ( type === "apiKey" || type === "http" ) {
map = map.set(key, security) map = map.set(key, security)
} else if ( type === "basic" ) { } else if ( type === "basic" ) {
let username = security.getIn(["value", "username"]) let username = security.getIn(["value", "username"])

View File

@@ -7,13 +7,16 @@ export default function downloadUrlPlugin (toolbox) {
let { fn } = toolbox let { fn } = toolbox
const actions = { const actions = {
download: (url)=> ({ errActions, specSelectors, specActions }) => { download: (url)=> ({ errActions, specSelectors, specActions, getConfigs }) => {
let { fetch } = fn let { fetch } = fn
const config = getConfigs()
url = url || specSelectors.url() url = url || specSelectors.url()
specActions.updateLoadingStatus("loading") specActions.updateLoadingStatus("loading")
fetch({ fetch({
url, url,
loadSpec: true, loadSpec: true,
requestInterceptor: config.requestInterceptor || (a => a),
responseInterceptor: config.responseInterceptor || (a => a),
credentials: "same-origin", credentials: "same-origin",
headers: { headers: {
"Accept": "application/json,*/*" "Accept": "application/json,*/*"

View File

@@ -0,0 +1,60 @@
import { createSelector } from "reselect"
import { List, Map, fromJS } from "immutable"
import { isOAS3 as isOAS3Helper } from "../helpers"
// Helpers
const state = state => state
function onlyOAS3(selector) {
return (ori, system) => (state, ...args) => {
const spec = system.getSystem().specSelectors.specJson()
if(isOAS3Helper(spec)) {
return selector(system, ...args)
} else {
return ori(...args)
}
}
}
export const definitionsToAuthorize = onlyOAS3(createSelector(
state,
({ specSelectors }) => {
// Coerce our OpenAPI 3.0 definitions into monoflow definitions
// that look like Swagger2 definitions.
let definitions = specSelectors.securityDefinitions()
let list = List()
definitions.entrySeq().forEach( ([ defName, definition ]) => {
const type = definition.get("type")
if(type === "oauth2") {
definition.get("flows").entrySeq().forEach(([flowKey, flowVal]) => {
let translatedDef = fromJS({
flow: flowKey,
authorizationUrl: flowVal.get("authorizationUrl"),
tokenUrl: flowVal.get("tokenUrl"),
scopes: flowVal.get("scopes"),
type: definition.get("type")
})
list = list.push(new Map({
[defName]: translatedDef.filter((v) => {
// filter out unset values, sometimes `authorizationUrl`
// and `tokenUrl` come out as `undefined` in the data
return v !== undefined
})
}))
})
}
if(type === "http" || type === "apiKey") {
list = list.push(new Map({
[defName]: definition
}))
}
})
return list
}
))

View File

@@ -0,0 +1,134 @@
import React from "react"
import PropTypes from "prop-types"
export default class HttpAuth extends React.Component {
static propTypes = {
authorized: PropTypes.object,
getComponent: PropTypes.func.isRequired,
errSelectors: PropTypes.object.isRequired,
schema: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
onChange: PropTypes.func
}
constructor(props, context) {
super(props, context)
let { name, schema } = this.props
let value = this.getValue()
this.state = {
name: name,
schema: schema,
value: value
}
}
getValue () {
let { name, authorized } = this.props
return authorized && authorized.getIn([name, "value"])
}
onChange =(e) => {
let { onChange } = this.props
let { value, name } = e.target
let newValue = this.state.value || {}
if(name) {
newValue[name] = value
} else {
newValue = value
}
this.setState({ value: newValue }, () => onChange(this.state))
}
render() {
let { schema, getComponent, errSelectors, name } = this.props
const Input = getComponent("Input")
const Row = getComponent("Row")
const Col = getComponent("Col")
const AuthError = getComponent("authError")
const Markdown = getComponent( "Markdown" )
const JumpToPath = getComponent("JumpToPath", true)
const scheme = schema.get("scheme")
let value = this.getValue()
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
if(scheme === "basic") {
let username = value ? value.get("username") : null
return <div>
<h4>
<code>{ name || schema.get("name") }</code>&nbsp;
(http, Basic)
<JumpToPath path={[ "securityDefinitions", name ]} />
</h4>
{ username && <h6>Authorized</h6> }
<Row>
<Markdown source={ schema.get("description") } />
</Row>
<Row>
<label>Username:</label>
{
username ? <code> { username } </code>
: <Col><Input type="text" required="required" name="username" onChange={ this.onChange }/></Col>
}
</Row>
<Row>
<label>Password:</label>
{
username ? <code> ****** </code>
: <Col><Input required="required"
autoComplete="new-password"
name="password"
type="password"
onChange={ this.onChange }/></Col>
}
</Row>
{
errors.valueSeq().map( (error, key) => {
return <AuthError error={ error }
key={ key }/>
} )
}
</div>
}
if(scheme === "bearer") {
return (
<div>
<h4>
<code>{ name || schema.get("name") }</code>&nbsp;
(http, Bearer)
<JumpToPath path={[ "securityDefinitions", name ]} />
</h4>
{ value && <h6>Authorized</h6>}
<Row>
<Markdown source={ schema.get("description") } />
</Row>
<Row>
<p>In: <code>{ schema.get("in") }</code></p>
</Row>
<Row>
<label>Value:</label>
{
value ? <code> ****** </code>
: <Col><Input type="text" onChange={ this.onChange }/></Col>
}
</Row>
{
errors.valueSeq().map( (error, key) => {
return <AuthError error={ error }
key={ key }/>
} )
}
</div>
)
}
return <div>
<em><b>{name}</b> HTTP authentication: unsupported or missing scheme</em>
</div>
}
}

View File

@@ -3,9 +3,11 @@ import RequestBody from "./request-body"
import OperationLink from "./operation-link.jsx" import OperationLink from "./operation-link.jsx"
import Servers from "./servers" import Servers from "./servers"
import RequestBodyEditor from "./request-body-editor" import RequestBodyEditor from "./request-body-editor"
import HttpAuth from "./http-auth"
export default { export default {
Callbacks, Callbacks,
HttpAuth,
RequestBody, RequestBody,
Servers, Servers,
RequestBodyEditor, RequestBodyEditor,

View File

@@ -6,6 +6,7 @@ import { OrderedMap } from "immutable"
const RequestBody = ({ const RequestBody = ({
requestBody, requestBody,
getComponent, getComponent,
getConfigs,
specSelectors, specSelectors,
contentType, contentType,
isExecute, isExecute,
@@ -27,6 +28,7 @@ const RequestBody = ({
} }
<ModelExample <ModelExample
getComponent={ getComponent } getComponent={ getComponent }
getConfigs={ getConfigs }
specSelectors={ specSelectors } specSelectors={ specSelectors }
expandDepth={1} expandDepth={1}
isExecute={isExecute} isExecute={isExecute}
@@ -46,6 +48,7 @@ const RequestBody = ({
RequestBody.propTypes = { RequestBody.propTypes = {
requestBody: ImPropTypes.orderedMap.isRequired, requestBody: ImPropTypes.orderedMap.isRequired,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
contentType: PropTypes.string.isRequired, contentType: PropTypes.string.isRequired,
isExecute: PropTypes.bool.isRequired, isExecute: PropTypes.bool.isRequired,

View File

@@ -1,6 +1,7 @@
// import reducers from "./reducers" // import reducers from "./reducers"
// import * as actions from "./actions" // import * as actions from "./actions"
import * as specWrapSelectors from "./spec-extensions/wrap-selectors" import * as specWrapSelectors from "./spec-extensions/wrap-selectors"
import * as authWrapSelectors from "./auth-extensions/wrap-selectors"
import * as specSelectors from "./spec-extensions/selectors" import * as specSelectors from "./spec-extensions/selectors"
import components from "./components" import components from "./components"
import wrapComponents from "./wrap-components" import wrapComponents from "./wrap-components"
@@ -17,6 +18,9 @@ export default function() {
wrapSelectors: specWrapSelectors, wrapSelectors: specWrapSelectors,
selectors: specSelectors selectors: specSelectors
}, },
auth: {
wrapSelectors: authWrapSelectors
},
oas3: { oas3: {
actions: oas3Actions, actions: oas3Actions,
reducers: oas3Reducers, reducers: oas3Reducers,

View File

@@ -48,6 +48,11 @@ export const definitions = onlyOAS3(createSelector(
spec => spec.getIn(["components", "schemas"]) || Map() spec => spec.getIn(["components", "schemas"]) || Map()
)) ))
export const securityDefinitions = onlyOAS3(createSelector(
spec,
spec => spec.getIn(["components", "securitySchemes"]) || Map()
))
export const host = OAS3NullSelector export const host = OAS3NullSelector
export const basePath = OAS3NullSelector export const basePath = OAS3NullSelector
export const consumes = OAS3NullSelector export const consumes = OAS3NullSelector

View File

@@ -0,0 +1,23 @@
import React from "react"
import { OAS3ComponentWrapFactory } from "../helpers"
export default OAS3ComponentWrapFactory(({ Ori, ...props }) => {
const {
schema, getComponent, errSelectors, authorized, onAuthChange, name
} = props
const HttpAuth = getComponent("HttpAuth")
const type = schema.get("type")
if(type === "http") {
return <HttpAuth key={ name }
schema={ schema }
name={ name }
errSelectors={ errSelectors }
authorized={ authorized }
getComponent={ getComponent }
onChange={ onAuthChange }/>
} else {
return <Ori {...props} />
}
})

View File

@@ -1,4 +1,5 @@
import Markdown from "./markdown" import Markdown from "./markdown"
import AuthItem from "./auth-item"
import parameters from "./parameters" 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"
@@ -6,6 +7,7 @@ import Model from "./model"
export default { export default {
Markdown, Markdown,
AuthItem,
parameters, parameters,
VersionStamp, VersionStamp,
model: Model, model: Model,

View File

@@ -1,11 +1,32 @@
import React from "react" import React from "react"
import PropTypes from "prop-types"
import ReactMarkdown from "react-markdown" import ReactMarkdown from "react-markdown"
import { Parser, HtmlRenderer } from "commonmark"
import { OAS3ComponentWrapFactory } from "../helpers" import { OAS3ComponentWrapFactory } from "../helpers"
import { sanitizer } from "core/components/providers/markdown" import { sanitizer } from "core/components/providers/markdown"
export default OAS3ComponentWrapFactory(({ source }) => { return source ? ( export const Markdown = ({ source }) => {
if ( source ) {
const parser = new Parser()
const writer = new HtmlRenderer()
const html = writer.render(parser.parse(source || ""))
const sanitized = sanitizer(html)
if ( !source || !html || !sanitized ) {
return null
}
return (
<ReactMarkdown <ReactMarkdown
source={sanitizer(source)} source={sanitized}
className={"renderedMarkdown"} className={"renderedMarkdown"}
/> />
) : null}) )
}
return null
}
Markdown.propTypes = {
source: PropTypes.string
}
export default OAS3ComponentWrapFactory(Markdown)

View File

@@ -49,7 +49,7 @@ class Parameters extends Component {
onChangeKey, onChangeKey,
} = this.props } = this.props
changeParam( onChangeKey, param.get("name"), value, isXml) changeParam( onChangeKey, param.get("name"), param.get("in"), value, isXml)
} }
onChangeConsumesWrapper = ( val ) => { onChangeConsumesWrapper = ( val ) => {

View File

@@ -130,17 +130,20 @@ export const formatIntoYaml = () => ({specActions, specSelectors}) => {
} }
} }
export function changeParam( path, paramName, value, isXml ){ export function changeParam( path, paramName, paramIn, value, isXml ){
return { return {
type: UPDATE_PARAM, type: UPDATE_PARAM,
payload:{ path, value, paramName, isXml } payload:{ path, value, paramName, paramIn, isXml }
} }
} }
export function validateParams( payload ){ export const validateParams = ( payload, isOAS3 ) =>{
return { return {
type: VALIDATE_PARAMS, type: VALIDATE_PARAMS,
payload:{ pathMethod: payload } payload:{
pathMethod: payload,
isOAS3
}
} }
} }
@@ -206,7 +209,6 @@ export const executeRequest = (req) =>
// if url is relative, parseUrl makes it absolute by inferring from `window.location` // if url is relative, parseUrl makes it absolute by inferring from `window.location`
req.contextUrl = parseUrl(specSelectors.url()).toString() req.contextUrl = parseUrl(specSelectors.url()).toString()
if(op && op.operationId) { if(op && op.operationId) {
req.operationId = op.operationId req.operationId = op.operationId
} else if(op && pathName && method) { } else if(op && pathName && method) {

View File

@@ -40,9 +40,10 @@ export default {
}, },
[UPDATE_PARAM]: ( state, {payload} ) => { [UPDATE_PARAM]: ( state, {payload} ) => {
let { path, paramName, value, isXml } = payload let { path, paramName, paramIn, value, isXml } = payload
return state.updateIn( [ "resolved", "paths", ...path, "parameters" ], fromJS([]), parameters => { return state.updateIn( [ "resolved", "paths", ...path, "parameters" ], fromJS([]), parameters => {
const index = parameters.findIndex(p => p.get( "name" ) === paramName ) const index = parameters.findIndex(p => p.get( "name" ) === paramName && p.get("in") === paramIn )
if (!(value instanceof win.File)) { if (!(value instanceof win.File)) {
value = fromJSOrdered( value ) value = fromJSOrdered( value )
} }
@@ -50,14 +51,14 @@ export default {
}) })
}, },
[VALIDATE_PARAMS]: ( state, { payload: { pathMethod } } ) => { [VALIDATE_PARAMS]: ( state, { payload: { pathMethod, isOAS3 } } ) => {
let operation = state.getIn( [ "resolved", "paths", ...pathMethod ] ) let operation = state.getIn( [ "resolved", "paths", ...pathMethod ] )
let isXml = /xml/i.test(operation.get("consumes_value")) let isXml = /xml/i.test(operation.get("consumes_value"))
return state.updateIn( [ "resolved", "paths", ...pathMethod, "parameters" ], fromJS([]), parameters => { return state.updateIn( [ "resolved", "paths", ...pathMethod, "parameters" ], fromJS([]), parameters => {
return parameters.withMutations( parameters => { return parameters.withMutations( parameters => {
for ( let i = 0, len = parameters.count(); i < len; i++ ) { for ( let i = 0, len = parameters.count(); i < len; i++ ) {
let errors = validateParam(parameters.get(i), isXml) let errors = validateParam(parameters.get(i), isXml, isOAS3)
parameters.setIn([i, "errors"], fromJS(errors)) parameters.setIn([i, "errors"], fromJS(errors))
} }
}) })

View File

@@ -260,10 +260,10 @@ export const allowTryItOutFor = () => {
} }
// Get the parameter value by parameter name // Get the parameter value by parameter name
export function getParameter(state, pathMethod, name) { export function getParameter(state, pathMethod, name, inType) {
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([])) let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
return params.filter( (p) => { return params.filter( (p) => {
return Map.isMap(p) && p.get("name") === name return Map.isMap(p) && p.get("name") === name && p.get("in") === inType
}).first() }).first()
} }
@@ -280,7 +280,7 @@ export function parameterValues(state, pathMethod, isXml) {
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([])) let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
return params.reduce( (hash, p) => { return params.reduce( (hash, p) => {
let value = isXml && p.get("in") === "body" ? p.get("value_xml") : p.get("value") let value = isXml && p.get("in") === "body" ? p.get("value_xml") : p.get("value")
return hash.set(p.get("name"), value) return hash.set(`${p.get("in")}.${p.get("name")}`, value)
}, fromJS({})) }, fromJS({}))
} }

View File

@@ -13,3 +13,7 @@ export const executeRequest = (ori, { specActions }) => (req) => {
specActions.logRequest(req) specActions.logRequest(req)
return ori(req) return ori(req)
} }
export const validateParams = (ori, { specSelectors }) => (req) => {
return ori(req, specSelectors.isOAS3())
}

View File

@@ -17,6 +17,7 @@ import AuthorizationPopup from "core/components/auth/authorization-popup"
import AuthorizeBtn from "core/components/auth/authorize-btn" import AuthorizeBtn from "core/components/auth/authorize-btn"
import AuthorizeOperationBtn from "core/components/auth/authorize-operation-btn" import AuthorizeOperationBtn from "core/components/auth/authorize-operation-btn"
import Auths from "core/components/auth/auths" import Auths from "core/components/auth/auths"
import AuthItem from "core/components/auth/auth-item"
import AuthError from "core/components/auth/error" import AuthError from "core/components/auth/error"
import ApiKeyAuth from "core/components/auth/api-key-auth" import ApiKeyAuth from "core/components/auth/api-key-auth"
import BasicAuth from "core/components/auth/basic-auth" import BasicAuth from "core/components/auth/basic-auth"
@@ -70,6 +71,7 @@ export default function() {
authorizeBtn: AuthorizeBtn, authorizeBtn: AuthorizeBtn,
authorizeOperationBtn: AuthorizeOperationBtn, authorizeOperationBtn: AuthorizeOperationBtn,
auths: Auths, auths: Auths,
AuthItem: AuthItem,
authError: AuthError, authError: AuthError,
oauth2: Oauth2, oauth2: Oauth2,
apiKeyAuth: ApiKeyAuth, apiKeyAuth: ApiKeyAuth,

View File

@@ -470,6 +470,18 @@ export const propChecker = (props, nextProps, objectList=[], ignoreList=[]) => {
|| objectList.some( objectPropName => !eq(props[objectPropName], nextProps[objectPropName]))) || objectList.some( objectPropName => !eq(props[objectPropName], nextProps[objectPropName])))
} }
export const validateMaximum = ( val, max ) => {
if (val > max) {
return "Value must be less than Maximum"
}
}
export const validateMinimum = ( val, min ) => {
if (val < min) {
return "Value must be greater than Minimum"
}
}
export const validateNumber = ( val ) => { export const validateNumber = ( val ) => {
if (!/^-?\d+(\.?\d+)?$/.test(val)) { if (!/^-?\d+(\.?\d+)?$/.test(val)) {
return "Value must be a number" return "Value must be a number"
@@ -500,12 +512,43 @@ export const validateString = ( val ) => {
} }
} }
export const validateDateTime = (val) => {
if (isNaN(Date.parse(val))) {
return "Value must be a DateTime"
}
}
export const validateGuid = (val) => {
if (!/^[{(]?[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}[)}]?$/.test(val)) {
return "Value must be a Guid"
}
}
export const validateMaxLength = (val, max) => {
if (val.length > max) {
return "Value must be less than MaxLength"
}
}
export const validateMinLength = (val, min) => {
if (val.length < min) {
return "Value must be greater than MinLength"
}
}
// validation of parameters before execute // validation of parameters before execute
export const validateParam = (param, isXml) => { export const validateParam = (param, isXml, isOAS3 = false) => {
let errors = [] let errors = []
let value = isXml && param.get("in") === "body" ? param.get("value_xml") : param.get("value") let value = isXml && param.get("in") === "body" ? param.get("value_xml") : param.get("value")
let required = param.get("required") let required = param.get("required")
let type = param.get("type")
let paramDetails = isOAS3 ? param.get("schema") : param
let maximum = paramDetails.get("maximum")
let minimum = paramDetails.get("minimum")
let type = paramDetails.get("type")
let format = paramDetails.get("format")
let maxLength = paramDetails.get("maxLength")
let minLength = paramDetails.get("minLength")
/* /*
If the parameter is required OR the parameter has a value (meaning optional, but filled in) If the parameter is required OR the parameter has a value (meaning optional, but filled in)
@@ -522,13 +565,40 @@ export const validateParam = (param, isXml) => {
let numberCheck = type === "number" && !validateNumber(value) // validateNumber returns undefined if the value is a number 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 let integerCheck = type === "integer" && !validateInteger(value) // validateInteger returns undefined if the value is an integer
if (maxLength || maxLength === 0) {
let err = validateMaxLength(value, maxLength)
if (err) errors.push(err)
}
if (minLength) {
let err = validateMinLength(value, minLength)
if (err) errors.push(err)
}
if ( required && !(stringCheck || arrayCheck || listCheck || fileCheck || booleanCheck || numberCheck || integerCheck) ) { 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 (maximum || maximum === 0) {
let err = validateMaximum(value, maximum)
if (err) errors.push(err)
}
if (minimum || minimum === 0) {
let err = validateMinimum(value, minimum)
if (err) errors.push(err)
}
if ( type === "string" ) { if ( type === "string" ) {
let err = validateString(value) let err
if (format === "date-time") {
err = validateDateTime(value)
} else if (format === "uuid") {
err = validateGuid(value)
} else {
err = validateString(value)
}
if (!err) return errors if (!err) return errors
errors.push(err) errors.push(err)
} else if ( type === "boolean" ) { } else if ( type === "boolean" ) {
@@ -548,7 +618,7 @@ export const validateParam = (param, isXml) => {
if ( !value.count() ) { return errors } if ( !value.count() ) { return errors }
itemType = param.getIn(["items", "type"]) itemType = paramDetails.getIn(["items", "type"])
value.forEach((item, index) => { value.forEach((item, index) => {
let err let err

View File

@@ -30,6 +30,10 @@ select
.opblock-body select .opblock-body select
{ {
min-width: 230px; min-width: 230px;
@media (max-width: 768px)
{
min-width: 180px;
}
} }
label label
@@ -56,6 +60,10 @@ input[type=file]
border: 1px solid #d9d9d9; border: 1px solid #d9d9d9;
border-radius: 4px; border-radius: 4px;
background: #fff; background: #fff;
@media (max-width: 768px) {
max-width: 175px;
}
&.invalid &.invalid
{ {

View File

@@ -250,10 +250,17 @@
.opblock-summary-path__deprecated .opblock-summary-path__deprecated
{ {
font-size: 16px; font-size: 16px;
@media (max-width: 768px) {
font-size: 12px;
}
display: flex; display: flex;
flex: 0 3 auto;
align-items: center; align-items: center;
word-break: break-all;
padding: 0 10px; padding: 0 10px;
@include text_code(); @include text_code();
@@ -540,14 +547,14 @@
.response-col_description__inner .response-col_description__inner
{ {
span div.markdown, div.renderedMarkdown
{ {
font-size: 12px; font-size: 12px;
font-style: italic; font-style: italic;
display: block; display: block;
margin: 10px 0; margin: 0;
padding: 10px; padding: 10px;
border-radius: 4px; border-radius: 4px;
@@ -765,14 +772,6 @@
} }
} }
.renderedMarkdown {
p {
@include text_body();
margin-top: 0px;
margin-bottom: 0px;
}
}
.response-content-type { .response-content-type {
padding-top: 1em; padding-top: 1em;

View File

@@ -237,7 +237,8 @@ span
.prop-name .prop-name
{ {
display: inline-block; display: inline-block;
width: 100px; margin-right: 1em;
width: 8em;
} }
.prop-type .prop-type

View File

@@ -1,6 +1,6 @@
.topbar .topbar
{ {
padding: 8px 30px; padding: 8px 0;
background-color: #89bf04; background-color: #89bf04;
.topbar-wrapper .topbar-wrapper
@@ -39,7 +39,6 @@
input[type=text] input[type=text]
{ {
width: 100%; width: 100%;
min-width: 350px;
margin: 0; margin: 0;
border: 2px solid #547f00; border: 2px solid #547f00;
@@ -84,7 +83,7 @@
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
padding: 4px 40px; padding: 4px 30px;
border: none; border: none;
border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0;

View File

@@ -0,0 +1,54 @@
/* eslint-env mocha */
import React from "react"
import expect from "expect"
import { render } from "enzyme"
import Markdown from "components/providers/markdown"
import { Markdown as OAS3Markdown } from "corePlugins/oas3/wrap-components/markdown.js"
describe("Markdown component", function() {
describe("Swagger 2.0", function() {
it("allows image elements", function() {
const str = `![Image alt text](http://image.source "Image title")`
const el = render(<Markdown source={str} />)
expect(el.html()).toEqual(`<div class="markdown"><p><img src="http://image.source" title="Image title"></p>\n</div>`)
})
it("allows heading elements", function() {
const str = `
# h1
## h2
### h3
#### h4
##### h5
###### h6`
const el = render(<Markdown source={str} />)
expect(el.html()).toEqual(`<div class="markdown"><h1>h1</h1>\n<h2>h2</h2>\n<h3>h3</h3>\n<h4>h4</h4>\n<h5>h5</h5>\n<h6>h6</h6>\n</div>`)
})
it("allows links", function() {
const str = `[Link](https://example.com/)`
const el = render(<Markdown source={str} />)
expect(el.html()).toEqual(`<div class="markdown"><p><a href="https://example.com/" target="_blank">Link</a></p>\n</div>`)
})
})
describe("OAS 3", function() {
it("allows image elements", function() {
const str = `![Image alt text](http://image.source "Image title")`
const el = render(<OAS3Markdown source={str} />)
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><p><img src="http://image.source" title="Image title"></p></div></div>`)
})
it("allows heading elements", function() {
const str = `
# h1
## h2
### h3
#### h4
##### h5
###### h6`
const el = render(<OAS3Markdown source={str} />)
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><h1>h1</h1>\n<h2>h2</h2>\n<h3>h3</h3>\n<h4>h4</h4>\n<h5>h5</h5>\n<h6>h6</h6></div></div>`)
})
})
})

View File

@@ -0,0 +1,116 @@
/* eslint-env mocha */
import React from "react"
import expect, { createSpy } from "expect"
import { shallow } from "enzyme"
import ModelExample from "components/model-example"
import ModelComponent from "components/model-wrapper"
describe("<ModelExample/>", function(){
// Given
let components = {
ModelWrapper: ModelComponent
}
let props = {
getComponent: (c) => {
return components[c]
},
specSelectors: {},
schema: {},
example: "{\"example\": \"value\"}",
isExecute: false,
getConfigs: () => ({
defaultModelRendering: "model",
defaultModelExpandDepth: 1
})
}
let exampleSelectedTestInputs = [
{ defaultModelRendering: "model", isExecute: true },
{ defaultModelRendering: "example", isExecute: true },
{ defaultModelRendering: "example", isExecute: false },
{ defaultModelRendering: "othervalue", isExecute: true },
{ defaultModelRendering: "othervalue", isExecute: false }
]
let modelSelectedTestInputs = [
{ defaultModelRendering: "model", isExecute: false }
]
it("renders model and example tabs", function(){
// When
let wrapper = shallow(<ModelExample {...props}/>)
// Then should render tabs
expect(wrapper.find("div > ul.tab").length).toEqual(1)
let tabs = wrapper.find("div > ul.tab").children()
expect(tabs.length).toEqual(2)
tabs.forEach((node) => {
expect(node.length).toEqual(1)
expect(node.name()).toEqual("li")
expect(node.hasClass("tabitem")).toEqual(true)
})
expect(tabs.at(0).text()).toEqual("Example Value")
expect(tabs.at(1).text()).toEqual("Model")
})
exampleSelectedTestInputs.forEach(function(testInputs) {
it("example tab is selected if isExecute = " + testInputs.isExecute + " and defaultModelRendering = " + testInputs.defaultModelRendering, function(){
// When
props.isExecute = testInputs.isExecute
props.getConfigs = () => ({
defaultModelRendering: testInputs.defaultModelRendering,
defaultModelExpandDepth: 1
})
let wrapper = shallow(<ModelExample {...props}/>)
// Then
let tabs = wrapper.find("div > ul.tab").children()
let exampleTab = tabs.at(0)
expect(exampleTab.hasClass("active")).toEqual(true)
let modelTab = tabs.at(1)
expect(modelTab.hasClass("active")).toEqual(false)
expect(wrapper.find("div > div").length).toEqual(1)
expect(wrapper.find("div > div").text()).toEqual(props.example)
})
})
modelSelectedTestInputs.forEach(function(testInputs) {
it("model tab is selected if isExecute = " + testInputs.isExecute + " and defaultModelRendering = " + testInputs.defaultModelRendering, function(){
// When
props.isExecute = testInputs.isExecute
props.getConfigs = () => ({
defaultModelRendering: testInputs.defaultModelRendering,
defaultModelExpandDepth: 1
})
let wrapper = shallow(<ModelExample {...props}/>)
// Then
let tabs = wrapper.find("div > ul.tab").children()
let exampleTab = tabs.at(0)
expect(exampleTab.hasClass("active")).toEqual(false)
let modelTab = tabs.at(1)
expect(modelTab.hasClass("active")).toEqual(true)
expect(wrapper.find("div > div").length).toEqual(1)
expect(wrapper.find("div > div").find(ModelComponent).props().expandDepth).toBe(1)
})
})
it("passes defaultModelExpandDepth to ModelComponent", function(){
// When
let expandDepth = 0
props.isExecute = false
props.getConfigs = () => ({
defaultModelRendering: "model",
defaultModelExpandDepth: expandDepth
})
let wrapper = shallow(<ModelExample {...props}/>)
// Then
expect(wrapper.find("div > div").find(ModelComponent).props().expandDepth).toBe(expandDepth)
})
})

51
test/components/models.js Normal file
View File

@@ -0,0 +1,51 @@
/* eslint-env mocha */
import React from "react"
import expect, { createSpy } from "expect"
import { shallow } from "enzyme"
import { fromJS } from "immutable"
import Models from "components/models"
import ModelCollpase from "components/model-collapse"
import ModelComponent from "components/model-wrapper"
describe("<Models/>", function(){
// Given
let components = {
Collapse: ModelCollpase,
ModelWrapper: ModelComponent
}
let props = {
getComponent: (c) => {
return components[c]
},
specSelectors: {
definitions: function() {
return fromJS({
def1: {},
def2: {}
})
}
},
layoutSelectors: {
isShown: createSpy()
},
layoutActions: {},
getConfigs: () => ({
docExpansion: "list",
defaultModelExpandDepth: 0
})
}
it("passes defaultModelExpandDepth to ModelWrapper", function(){
// When
let wrapper = shallow(<Models {...props}/>)
// Then should render tabs
expect(wrapper.find("ModelCollapse").length).toEqual(1)
expect(wrapper.find("ModelComponent").length).toBeGreaterThan(0)
wrapper.find("ModelComponent").forEach((modelWrapper) => {
expect(modelWrapper.props().expandDepth).toBe(0)
})
})
})

View File

@@ -0,0 +1,49 @@
/* eslint-env mocha */
import React from "react"
import expect from "expect"
import { shallow } from "enzyme"
import { fromJS } from "immutable"
import PrimitiveModel from "components/primitive-model"
describe("<PrimitiveModel/>", function() {
describe("Model name", function() {
const dummyComponent = () => null
const components = {
Markdown: dummyComponent,
EnumModel: dummyComponent
}
const props = {
getComponent: c => components[c],
name: "Name from props",
depth: 1,
schema: fromJS({
type: "string",
title: "Custom model title"
})
}
it("renders the schema's title", function() {
// When
const wrapper = shallow(<PrimitiveModel {...props}/>)
const modelTitleEl = wrapper.find("span.model-title")
expect(modelTitleEl.length).toEqual(1)
// Then
expect( modelTitleEl.text() ).toEqual( "Custom model title" )
})
it("falls back to the passed-in `name` prop for the title", function() {
// When
props.schema = fromJS({
type: "string"
})
const wrapper = shallow(<PrimitiveModel {...props}/>)
const modelTitleEl = wrapper.find("span.model-title")
expect(modelTitleEl.length).toEqual(1)
// Then
expect( modelTitleEl.text() ).toEqual( "Name from props" )
})
})
} )

View File

@@ -0,0 +1,39 @@
/* eslint-env mocha */
import expect, { createSpy } from "expect"
import { fromJS } from "immutable"
import win from "core/window"
import oauth2Authorize from "core/oauth2-authorize"
describe("oauth2", function () {
let mockSchema = {
flow: "accessCode",
authorizationUrl: "https://testAuthorizationUrl"
}
let authConfig = {
auth: { schema: { get: (key)=> mockSchema[key] } },
authActions: {},
errActions: {},
configs: { oauth2RedirectUrl: "" },
authConfigs: {}
}
describe("authorize redirect", function () {
it("should build authorize url", function() {
win.open = createSpy()
oauth2Authorize(authConfig)
expect(win.open.calls.length).toEqual(1)
expect(win.open.calls[0].arguments[0]).toMatch("https://testAuthorizationUrl?response_type=code&redirect_uri=&state=")
})
it("should append query paramters to authorizeUrl with query parameters", function() {
win.open = createSpy()
mockSchema.authorizationUrl = "https://testAuthorizationUrl?param=1"
oauth2Authorize(authConfig)
expect(win.open.calls.length).toEqual(1)
expect(win.open.calls[0].arguments[0]).toMatch("https://testAuthorizationUrl?param=1&response_type=code&redirect_uri=&state=")
})
})
})

View File

@@ -0,0 +1,116 @@
/* eslint-env mocha */
import expect, { createSpy } from "expect"
import { Map, fromJS } from "immutable"
import {
definitionsToAuthorize
} from "corePlugins/oas3/auth-extensions/wrap-selectors"
describe("oas3 plugin - auth extensions - wrapSelectors", function(){
describe("execute", function(){
it("should add `securities` to the oriAction call", function(){
// Given
const system = {
getSystem: () => system,
specSelectors: {
specJson: () => fromJS({
openapi: "3.0.0"
}),
securityDefinitions: () => {
return fromJS({
"oauth2AuthorizationCode": {
"type": "oauth2",
"flows": {
"authorizationCode": {
"authorizationUrl": "http://google.com/",
"tokenUrl": "http://google.com/",
"scopes": {
"myScope": "our only scope"
}
}
}
},
"oauth2Multiflow": {
"type": "oauth2",
"flows": {
"clientCredentials": {
"tokenUrl": "http://google.com/",
"scopes": {
"myScope": "our only scope"
}
},
"password": {
"tokenUrl": "http://google.com/",
"scopes": {
"myScope": "our only scope"
}
},
"authorizationCode": {
"authorizationUrl": "http://google.com/",
"tokenUrl": "http://google.com/",
"scopes": {
"myScope": "our only scope"
}
}
}
}
})
}
}
}
// When
let res = definitionsToAuthorize(() => null, system)()
// Then
expect(res.toJS()).toEqual([
{
oauth2AuthorizationCode: {
flow: "authorizationCode",
authorizationUrl: "http://google.com/",
tokenUrl: "http://google.com/",
scopes: {
"myScope": "our only scope"
},
type: "oauth2"
}
},
{
oauth2Multiflow: {
flow: "clientCredentials",
tokenUrl: "http://google.com/",
scopes: {
"myScope": "our only scope"
},
type: "oauth2"
}
},
{
oauth2Multiflow: {
flow: "password",
tokenUrl: "http://google.com/",
scopes: {
"myScope": "our only scope"
},
type: "oauth2"
}
},
{
oauth2Multiflow: {
flow: "authorizationCode",
authorizationUrl: "http://google.com/",
tokenUrl: "http://google.com/",
scopes: {
"myScope": "our only scope"
},
type: "oauth2"
}
},
])
})
})
})

View File

@@ -29,8 +29,8 @@ describe("spec plugin - selectors", function(){
"/one": { "/one": {
get: { get: {
parameters: [ parameters: [
{ name: "one", value: 1}, { name: "one", in: "query", value: 1},
{ name: "two", value: "duos"} { name: "two", in: "query", value: "duos"}
] ]
} }
} }
@@ -43,8 +43,8 @@ describe("spec plugin - selectors", function(){
// Then // Then
expect(paramValues.toJS()).toEqual({ expect(paramValues.toJS()).toEqual({
one: 1, "query.one": 1,
two: "duos" "query.two": "duos"
}) })
}) })

View File

@@ -1,7 +1,23 @@
/* eslint-env mocha */ /* eslint-env mocha */
import expect from "expect" import expect from "expect"
import { fromJS, OrderedMap } from "immutable" import { fromJS, OrderedMap } from "immutable"
import { mapToList, validateNumber, validateInteger, validateParam, validateFile, fromJSOrdered, getAcceptControllingResponse, createDeepLinkPath, escapeDeepLinkPath } from "core/utils" import {
mapToList,
validateMinLength,
validateMaxLength,
validateDateTime,
validateGuid,
validateNumber,
validateInteger,
validateParam,
validateFile,
validateMaximum,
validateMinimum,
fromJSOrdered,
getAcceptControllingResponse,
createDeepLinkPath,
escapeDeepLinkPath
} from "core/utils"
import win from "core/window" import win from "core/window"
describe("utils", function() { describe("utils", function() {
@@ -72,6 +88,36 @@ describe("utils", function() {
}) })
describe("validateMaximum", function() {
let errorMessage = "Value must be less than Maximum"
it("doesn't return for valid input", function() {
expect(validateMaximum(9, 10)).toBeFalsy()
expect(validateMaximum(19, 20)).toBeFalsy()
})
it("returns a message for invalid input", function() {
expect(validateMaximum(1, 0)).toEqual(errorMessage)
expect(validateMaximum(10, 9)).toEqual(errorMessage)
expect(validateMaximum(20, 19)).toEqual(errorMessage)
})
})
describe("validateMinimum", function() {
let errorMessage = "Value must be greater than Minimum"
it("doesn't return for valid input", function() {
expect(validateMinimum(2, 1)).toBeFalsy()
expect(validateMinimum(20, 10)).toBeFalsy()
})
it("returns a message for invalid input", function() {
expect(validateMinimum(-1, 0)).toEqual(errorMessage)
expect(validateMinimum(1, 2)).toEqual(errorMessage)
expect(validateMinimum(10, 20)).toEqual(errorMessage)
})
})
describe("validateNumber", function() { describe("validateNumber", function() {
let errorMessage = "Value must be a number" let errorMessage = "Value must be a number"
@@ -171,388 +217,498 @@ describe("utils", function() {
}) })
}) })
describe("validateDateTime", function() {
let errorMessage = "Value must be a DateTime"
it("doesn't return for valid dates", function() {
expect(validateDateTime("Mon, 25 Dec 1995 13:30:00 +0430")).toBeFalsy()
})
it("returns a message for invalid input'", function() {
expect(validateDateTime(null)).toEqual(errorMessage)
expect(validateDateTime("string")).toEqual(errorMessage)
})
})
describe("validateGuid", function() {
let errorMessage = "Value must be a Guid"
it("doesn't return for valid guid", function() {
expect(validateGuid("8ce4811e-cec5-4a29-891a-15d1917153c1")).toBeFalsy()
expect(validateGuid("{8ce4811e-cec5-4a29-891a-15d1917153c1}")).toBeFalsy()
})
it("returns a message for invalid input'", function() {
expect(validateGuid(1)).toEqual(errorMessage)
expect(validateGuid("string")).toEqual(errorMessage)
})
})
describe("validateMaxLength", function() {
let errorMessage = "Value must be less than MaxLength"
it("doesn't return for valid guid", function() {
expect(validateMaxLength("a", 1)).toBeFalsy()
expect(validateMaxLength("abc", 5)).toBeFalsy()
})
it("returns a message for invalid input'", function() {
expect(validateMaxLength("abc", 0)).toEqual(errorMessage)
expect(validateMaxLength("abc", 1)).toEqual(errorMessage)
expect(validateMaxLength("abc", 2)).toEqual(errorMessage)
})
})
describe("validateMinLength", function() {
let errorMessage = "Value must be greater than MinLength"
it("doesn't return for valid guid", function() {
expect(validateMinLength("a", 1)).toBeFalsy()
expect(validateMinLength("abc", 2)).toBeFalsy()
})
it("returns a message for invalid input'", function() {
expect(validateMinLength("abc", 5)).toEqual(errorMessage)
expect(validateMinLength("abc", 8)).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() { const assertValidateParam = (param, expectedError) => {
// invalid type // Swagger 2.0 version
result = validateParam( fromJS(param), false )
expect( result ).toEqual( expectedError )
// OAS3 version, using `schema` sub-object
let oas3Param = {
value: param.value,
required: param.required,
schema: {
...param,
value: undefined,
required: undefined
}
}
result = validateParam( fromJS(oas3Param), false, true )
expect( result ).toEqual( expectedError )
}
it("should check the isOAS3 flag when validating parameters", function() {
// This should "skip" validation because there is no `schema.type` property
// and we are telling `validateParam` this is an OAS3 spec
param = fromJS({ param = fromJS({
required: false, value: "",
type: undefined, required: true,
value: "" schema: {
notTheTypeProperty: "string"
}
}) })
result = validateParam( param, false ) result = validateParam( param, false, true )
expect( result ).toEqual( [] ) expect( result ).toEqual( [] )
}) })
it("validates required strings", function() { it("validates required strings", function() {
// invalid string // invalid string
param = fromJS({ param = {
required: true, required: true,
type: "string", type: "string",
value: "" value: ""
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// valid string // valid string
param = fromJS({ param = {
required: true, required: true,
type: "string", type: "string",
value: "test string" value: "test string"
}
assertValidateParam(param, [])
// valid string with min and max length
param = {
required: true,
type: "string",
value: "test string",
maxLength: 50,
minLength: 1
}
assertValidateParam(param, [])
}) })
result = validateParam( param, false )
expect( result ).toEqual( [] ) it("validates required strings with min and max length", function() {
// invalid string with max length
param = {
required: true,
type: "string",
value: "test string",
maxLength: 5
}
assertValidateParam(param, ["Value must be less than MaxLength"])
// invalid string with max length 0
param = {
required: true,
type: "string",
value: "test string",
maxLength: 0
}
assertValidateParam(param, ["Value must be less than MaxLength"])
// invalid string with min length
param = {
required: true,
type: "string",
value: "test string",
minLength: 50
}
assertValidateParam(param, ["Value must be greater than MinLength"])
}) })
it("validates optional strings", function() { it("validates optional strings", function() {
// valid (empty) string // valid (empty) string
param = fromJS({ param = {
required: false, required: false,
type: "string", type: "string",
value: "" value: ""
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// valid string // valid string
param = fromJS({ param = {
required: false, required: false,
type: "string", type: "string",
value: "test" value: "test"
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
it("validates required files", function() { it("validates required files", function() {
// invalid file // invalid file
param = fromJS({ param = {
required: true, required: true,
type: "file", type: "file",
value: undefined value: undefined
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// valid file // valid file
param = fromJS({ param = {
required: true, required: true,
type: "file", type: "file",
value: new win.File() value: new win.File()
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
it("validates optional files", function() { it("validates optional files", function() {
// invalid file // invalid file
param = fromJS({ param = {
required: false, required: false,
type: "file", type: "file",
value: "not a file" value: "not a file"
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Value must be a file"])
expect( result ).toEqual( ["Value must be a file"] )
// valid (empty) file // valid (empty) file
param = fromJS({ param = {
required: false, required: false,
type: "file", type: "file",
value: undefined value: undefined
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// valid file // valid file
param = fromJS({ param = {
required: false, required: false,
type: "file", type: "file",
value: new win.File() value: new win.File()
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
it("validates required arrays", function() { it("validates required arrays", function() {
// invalid (empty) array // invalid (empty) array
param = fromJS({ param = {
required: true, required: true,
type: "array", type: "array",
value: [] value: []
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// invalid (not an array) // invalid (not an array)
param = fromJS({ param = {
required: true, required: true,
type: "array", type: "array",
value: undefined value: undefined
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// invalid array, items do not match correct type // invalid array, items do not match correct type
param = fromJS({ param = {
required: true, required: true,
type: "array", type: "array",
value: [1], value: [1],
items: { items: {
type: "string" type: "string"
} }
}) }
result = validateParam( param, false ) assertValidateParam(param, [{index: 0, error: "Value must be a string"}])
expect( result ).toEqual( [{index: 0, error: "Value must be a string"}] )
// valid array, with no 'type' for items // valid array, with no 'type' for items
param = fromJS({ param = {
required: true, required: true,
type: "array", type: "array",
value: ["1"] value: ["1"]
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// valid array, items match type // valid array, items match type
param = fromJS({ param = {
required: true, required: true,
type: "array", type: "array",
value: ["1"], value: ["1"],
items: { items: {
type: "string" type: "string"
} }
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
it("validates optional arrays", function() { it("validates optional arrays", function() {
// valid, empty array // valid, empty array
param = fromJS({ param = {
required: false, required: false,
type: "array", type: "array",
value: [] value: []
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// invalid, items do not match correct type // invalid, items do not match correct type
param = fromJS({ param = {
required: false, required: false,
type: "array", type: "array",
value: ["number"], value: ["number"],
items: { items: {
type: "number" type: "number"
} }
}) }
result = validateParam( param, false ) assertValidateParam(param, [{index: 0, error: "Value must be a number"}])
expect( result ).toEqual( [{index: 0, error: "Value must be a number"}] )
// valid // valid
param = fromJS({ param = {
required: false, required: false,
type: "array", type: "array",
value: ["test"], value: ["test"],
items: { items: {
type: "string" type: "string"
} }
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
it("validates required booleans", function() { it("validates required booleans", function() {
// invalid boolean value // invalid boolean value
param = fromJS({ param = {
required: true, required: true,
type: "boolean", type: "boolean",
value: undefined value: undefined
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// invalid boolean value (not a boolean) // invalid boolean value (not a boolean)
param = fromJS({ param = {
required: true, required: true,
type: "boolean", type: "boolean",
value: "test string" value: "test string"
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// valid boolean value // valid boolean value
param = fromJS({ param = {
required: true, required: true,
type: "boolean", type: "boolean",
value: "true" value: "true"
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// valid boolean value // valid boolean value
param = fromJS({ param = {
required: true, required: true,
type: "boolean", type: "boolean",
value: false value: false
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
it("validates optional booleans", function() { it("validates optional booleans", function() {
// valid (empty) boolean value // valid (empty) boolean value
param = fromJS({ param = {
required: false, required: false,
type: "boolean", type: "boolean",
value: undefined value: undefined
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// invalid boolean value (not a boolean) // invalid boolean value (not a boolean)
param = fromJS({ param = {
required: false, required: false,
type: "boolean", type: "boolean",
value: "test string" value: "test string"
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Value must be a boolean"])
expect( result ).toEqual( ["Value must be a boolean"] )
// valid boolean value // valid boolean value
param = fromJS({ param = {
required: false, required: false,
type: "boolean", type: "boolean",
value: "true" value: "true"
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// valid boolean value // valid boolean value
param = fromJS({ param = {
required: false, required: false,
type: "boolean", type: "boolean",
value: false value: false
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
it("validates required numbers", function() { it("validates required numbers", function() {
// invalid number, string instead of a number // invalid number, string instead of a number
param = fromJS({ param = {
required: true, required: true,
type: "number", type: "number",
value: "test" value: "test"
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// invalid number, undefined value // invalid number, undefined value
param = fromJS({ param = {
required: true, required: true,
type: "number", type: "number",
value: undefined value: undefined
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// valid number // valid number with min and max
param = fromJS({ param = {
required: true, required: true,
type: "number", type: "number",
value: 10 value: 10,
}) minimum: 5,
result = validateParam( param, false ) maximum: 99
expect( result ).toEqual( [] ) }
assertValidateParam(param, [])
// valid negative number with min and max
param = {
required: true,
type: "number",
value: -10,
minimum: -50,
maximum: -5
}
assertValidateParam(param, [])
// invalid number with maximum:0
param = {
required: true,
type: "number",
value: 1,
maximum: 0
}
assertValidateParam(param, ["Value must be less than Maximum"])
// invalid number with minimum:0
param = {
required: true,
type: "number",
value: -10,
minimum: 0
}
assertValidateParam(param, ["Value must be greater than Minimum"])
}) })
it("validates optional numbers", function() { it("validates optional numbers", function() {
// invalid number, string instead of a number // invalid number, string instead of a number
param = fromJS({ param = {
required: false, required: false,
type: "number", type: "number",
value: "test" value: "test"
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Value must be a number"])
expect( result ).toEqual( ["Value must be a number"] )
// valid (empty) number // valid (empty) number
param = fromJS({ param = {
required: false, required: false,
type: "number", type: "number",
value: undefined value: undefined
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// valid number // valid number
param = fromJS({ param = {
required: false, required: false,
type: "number", type: "number",
value: 10 value: 10
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
it("validates required integers", function() { it("validates required integers", function() {
// invalid integer, string instead of an integer // invalid integer, string instead of an integer
param = fromJS({ param = {
required: true, required: true,
type: "integer", type: "integer",
value: "test" value: "test"
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// invalid integer, undefined value // invalid integer, undefined value
param = fromJS({ param = {
required: true, required: true,
type: "integer", type: "integer",
value: undefined value: undefined
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Required field is not provided"])
expect( result ).toEqual( ["Required field is not provided"] )
// valid integer // valid integer
param = fromJS({ param = {
required: true, required: true,
type: "integer", type: "integer",
value: 10 value: 10
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
it("validates optional integers", function() { it("validates optional integers", function() {
// invalid integer, string instead of an integer // invalid integer, string instead of an integer
param = fromJS({ param = {
required: false, required: false,
type: "integer", type: "integer",
value: "test" value: "test"
}) }
result = validateParam( param, false ) assertValidateParam(param, ["Value must be an integer"])
expect( result ).toEqual( ["Value must be an integer"] )
// valid (empty) integer // valid (empty) integer
param = fromJS({ param = {
required: false, required: false,
type: "integer", type: "integer",
value: undefined value: undefined
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
// integers // integers
param = fromJS({ param = {
required: false, required: false,
type: "integer", type: "integer",
value: 10 value: 10
}) }
result = validateParam( param, false ) assertValidateParam(param, [])
expect( result ).toEqual( [] )
}) })
}) })
@@ -583,7 +739,7 @@ describe("utils", function() {
}) })
}) })
describe.only("getAcceptControllingResponse", () => { describe("getAcceptControllingResponse", () => {
it("should return the first 2xx response with a media type", () => { it("should return the first 2xx response with a media type", () => {
const responses = fromJSOrdered({ const responses = fromJSOrdered({
"200": { "200": {

View File

@@ -0,0 +1,33 @@
/* eslint-env mocha */
import React from "react"
import expect from "expect"
import { render } from "enzyme"
import { fromJS } from "immutable"
import Info from "components/info"
import Markdown from "components/providers/markdown"
describe("<Info/> Sanitization", function(){
const dummyComponent = () => null
const components = {
Markdown
}
const props = {
getComponent: c => components[c] || dummyComponent,
info: fromJS({
title: "Test Title **strong** <script>alert(1)</script>",
description: "Description *with* <script>Markdown</script>"
}),
host: "example.test",
basePath: "/api"
}
it("renders sanitized .title content", function(){
let wrapper = render(<Info {...props}/>)
expect(wrapper.find(".title").html()).toEqual("Test Title **strong** &lt;script&gt;alert(1)&lt;/script&gt;")
})
it("renders sanitized .description content", function() {
let wrapper = render(<Info {...props}/>)
expect(wrapper.find(".description").html()).toEqual("<div class=\"markdown\"><p>Description <em>with</em> </p>\n</div>")
})
})

View File

@@ -0,0 +1,36 @@
/* eslint-env mocha */
import React from "react"
import expect from "expect"
import { render } from "enzyme"
import Markdown from "components/providers/markdown"
import { Markdown as OAS3Markdown } from "corePlugins/oas3/wrap-components/markdown.js"
describe("Markdown Script Sanitization", function() {
describe("Swagger 2.0", function() {
it("sanitizes <script> elements", function() {
const str = `script <script>alert(1)</script>`
const el = render(<Markdown source={str} />)
expect(el.html()).toEqual(`<div class="markdown"><p>script </p>\n</div>`)
})
it("sanitizes <img> elements", function() {
const str = `<img src=x onerror="alert('img-in-description')">`
const el = render(<Markdown source={str} />)
expect(el.html()).toEqual(`<div class="markdown"><p><img src="x"></p>\n</div>`)
})
})
describe("OAS 3", function() {
it("sanitizes <script> elements", function() {
const str = `script <script>alert(1)</script>`
const el = render(<OAS3Markdown source={str} />)
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><p>script </p></div></div>`)
})
it("sanitizes <img> elements", function() {
const str = `<img src=x onerror="alert('img-in-description')">`
const el = render(<OAS3Markdown source={str} />)
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><img src="x"></div></div>`)
})
})
})

View File

@@ -11,7 +11,7 @@ let rules = [
name: "[name].js" name: "[name].js"
} }
}, },
{ loader: "babel-loader" } { loader: "babel-loader?retainLines=true" }
] ]
} }
] ]

View File

@@ -11,7 +11,7 @@ let rules = [
name: "[name].js" name: "[name].js"
} }
}, },
{ loader: "babel-loader" } { loader: "babel-loader?retainLines=true" }
] ]
} }
] ]

View File

@@ -13,7 +13,7 @@ let rules = [
name: "[name].js" name: "[name].js"
} }
}, },
{ loader: "babel-loader" } { loader: "babel-loader?retainLines=true" }
] ]
} }
] ]

View File

@@ -9,13 +9,7 @@ const rules = [
inline: true inline: true
} }
}, },
{ loader: "babel-loader" } { loader: "babel-loader?retainLines=true" }
]
},
{ test: /\.(jsx)(\?.*)?$/,
use: [
{ loader: "react-hot-loader" },
{ loader: "babel-loader" }
] ]
}, },
{ test: /\.(css)(\?.*)?$/, { test: /\.(css)(\?.*)?$/,
@@ -48,7 +42,7 @@ module.exports = require("./make-webpack-config")(rules, {
_special: { _special: {
separateStylesheets: false, separateStylesheets: false,
}, },
devtool: "eval", devtool: "eval-source-map",
entry: { entry: {
"swagger-ui-bundle": [ "swagger-ui-bundle": [
"./src/polyfills", "./src/polyfills",