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"],
"plugins": [
"react"
"react",
"mocha"
],
"rules": {
@@ -32,6 +33,7 @@
"comma-dangle": 0,
"no-console": ["error", { allow: ["warn", "error"] }],
"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/).**
**👉🏼 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.
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-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](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) 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).
@@ -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
------------------ | ------------ | -------------------------- | -----
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)
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)
@@ -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
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({...})
// 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
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.
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
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
@@ -169,7 +171,7 @@ showMutatedRequest | If set to `true` (the default), uses the mutated request re
#### 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`:
```
```javascript
let preset = [
// TopbarPlugin,
ConfigsPlugin,

View File

@@ -3,6 +3,7 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<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 rel="stylesheet" type="text/css" href="./swagger-ui.css" >

View File

@@ -27,7 +27,10 @@
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) {
oauth2.errCb({
authId: oauth2.auth.name,

View File

@@ -27,7 +27,10 @@
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) {
oauth2.errCb({
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,

View File

@@ -1,6 +1,6 @@
{
"name": "swagger-ui",
"version": "3.2.0",
"version": "3.4.0",
"main": "dist/swagger-ui.js",
"repository": "git@github.com:swagger-api/swagger-ui.git",
"contributors": [
@@ -32,7 +32,7 @@
"test": "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-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",
"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",
@@ -42,6 +42,7 @@
"base64-js": "^1.2.0",
"brace": "0.7.0",
"classnames": "^2.2.5",
"commonmark": "^0.28.1",
"css.escape": "1.5.1",
"deep-extend": "0.4.1",
"expect": "1.20.2",
@@ -67,17 +68,17 @@
"react-motion": "0.4.4",
"react-object-inspector": "0.2.1",
"react-redux": "^4.x.x",
"react-remarkable": "1.1.1",
"react-split-pane": "0.1.57",
"redux": "^3.x.x",
"redux-immutable": "3.0.8",
"redux-logger": "*",
"remarkable": "^1.7.1",
"reselect": "2.5.3",
"sanitize-html": "^1.14.1",
"scroll-to-element": "^2.0.0",
"serialize-error": "2.0.0",
"shallowequal": "0.2.2",
"swagger-client": "3.1.0",
"swagger-client": "^3.3.0",
"url-parse": "^1.1.8",
"whatwg-fetch": "0.11.1",
"worker-loader": "^0.7.1",
@@ -104,6 +105,7 @@
"enzyme": "^2.7.1",
"eslint": "^4.1.1",
"eslint-plugin-import": "^2.6.0",
"eslint-plugin-mocha": "^4.11.0",
"eslint-plugin-react": "^7.1.0",
"extract-text-webpack-plugin": "^2.1.2",
"file-loader": "0.11.2",

View File

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

View File

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

View File

@@ -2,11 +2,6 @@ import React from "react"
import PropTypes from "prop-types"
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 {
static propTypes = {
name: PropTypes.string,
@@ -16,6 +11,7 @@ export default class Oauth2 extends React.Component {
authSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
errSelectors: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
errActions: PropTypes.object.isRequired,
getConfigs: PropTypes.any
}
@@ -83,7 +79,9 @@ export default class Oauth2 extends React.Component {
}
render() {
let { schema, getComponent, authSelectors, errSelectors, name } = this.props
let {
schema, getComponent, authSelectors, errSelectors, name, specSelectors
} = this.props
const Input = getComponent("Input")
const Row = getComponent("Row")
const Col = getComponent("Col")
@@ -92,6 +90,14 @@ export default class Oauth2 extends React.Component {
const JumpToPath = getComponent("JumpToPath", true)
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 scopes = schema.get("allowedScopes") || schema.get("scopes")
let authorizedAuth = authSelectors.authorized().get(name)
@@ -102,7 +108,7 @@ export default class Oauth2 extends React.Component {
return (
<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> }
{ description && <Markdown source={ schema.get("description") } /> }

View File

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

View File

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

View File

@@ -13,7 +13,7 @@ export default class Models extends Component {
render(){
let { specSelectors, getComponent, layoutSelectors, layoutActions, getConfigs } = this.props
let definitions = specSelectors.definitions()
let { docExpansion } = getConfigs()
let { docExpansion, defaultModelExpandDepth } = getConfigs()
let showModels = layoutSelectors.isShown("models", docExpansion === "full" || docExpansion === "list" )
const ModelWrapper = getComponent("ModelWrapper")
@@ -33,6 +33,7 @@ export default class Models extends Component {
definitions.entrySeq().map( ( [ name, model ])=>{
return <div className="model-container" key={ `models-section-${name}` }>
<ModelWrapper name={ name }
expandDepth={ defaultModelExpandDepth }
schema={ model }
getComponent={ getComponent }
specSelectors={ specSelectors }/>

View File

@@ -149,7 +149,7 @@ export default class Operation extends PureComponent {
const isDeepLinkingEnabled = deepLinking && deepLinking !== "false"
// Merge in Live Response
if(response && response.size > 0) {
if(responses && response && response.size > 0) {
let notDocumented = !responses.get(String(response.get("status")))
response = response.set("notDocumented", notDocumented)
}
@@ -224,6 +224,7 @@ export default class Operation extends PureComponent {
specActions={ specActions }
specSelectors={ specSelectors }
pathMethod={ [path, method] }
getConfigs={ getConfigs }
/>
{!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 Collapse = getComponent("Collapse")
const Markdown = getComponent("Markdown")
let showSummary = layoutSelectors.showSummary()
let {
@@ -89,7 +90,7 @@ export default class Operations extends React.Component {
</a>
{ !tagDescription ? null :
<small>
{ tagDescription }
<Markdown source={tagDescription} />
</small>
}

View File

@@ -47,7 +47,7 @@ export default class ParamBody extends PureComponent {
updateValues = (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 isJson = /json/i.test(consumesValue)
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 ContentType = getComponent("contentType")
// 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 consumesValue = specSelectors.contentTypeValues(pathMethod).get("requestContentType")
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,
onChangeConsumes: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
pathMethod: PropTypes.array.isRequired
pathMethod: PropTypes.array.isRequired,
getConfigs: PropTypes.func.isRequired
}
constructor(props, context) {
@@ -19,7 +20,7 @@ export default class ParameterRow extends Component {
let { specSelectors, pathMethod, param } = props
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") : ""
if ( defaultValue !== undefined && value === undefined ) {
this.onChangeWrapper(defaultValue)
@@ -30,7 +31,7 @@ export default class ParameterRow extends Component {
let { specSelectors, pathMethod, param } = props
let example = param.get("example")
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 enumValue = parameter ? parameter.get("enum") : undefined
let value
@@ -56,7 +57,7 @@ export default class ParameterRow extends Component {
}
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
@@ -86,7 +87,7 @@ export default class ParameterRow extends Component {
let isFormDataSupported = "FormData" in win
let required = param.get("required")
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") : ""
return (
@@ -114,12 +115,13 @@ export default class ParameterRow extends Component {
required={ required }
description={param.get("description") ? `${param.get("name")} - ${param.get("description")}` : `${param.get("name")}`}
onChange={ this.onChangeWrapper }
schema={ param }/>
schema={ isOAS3 && isOAS3() ? param.get("schema") : param }/>
}
{
bodyParam && schema ? <ModelExample getComponent={ getComponent }
getConfigs={ getConfigs }
isExecute={ isExecute }
specSelectors={ specSelectors }
schema={ schema }

View File

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

View File

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

View File

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

View File

@@ -49,10 +49,10 @@ export default class ResponseBody extends React.Component {
// Download
} else if (
/^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-Description"] && (/File Transfer/i).test(headers["Content-Description"]) ||
headers["content-description"] && (/File Transfer/i).test(headers["content-description"])) {
(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"]))) {
let contentLength = headers["content-length"] || headers["Content-Length"]
if ( !(+contentLength) ) return null
@@ -83,8 +83,12 @@ export default class ResponseBody extends React.Component {
// Anything else (CORS)
} else if (typeof content === "string") {
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>
} else {
// We don't know the contentType and there was no content returned
bodyEl = null
}
return ( !bodyEl ? null : <div>

View File

@@ -45,6 +45,7 @@ export default class Response extends React.Component {
response: PropTypes.object,
className: PropTypes.string,
getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired,
contentType: PropTypes.string,
@@ -73,6 +74,7 @@ export default class Response extends React.Component {
className,
fn,
getComponent,
getConfigs,
specSelectors,
contentType,
controlsAcceptHeader
@@ -107,6 +109,14 @@ export default class Response extends React.Component {
includeWriteOnly: true // writeOnly has no filtering effect in swagger 2.0
}) : 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 )
return (
@@ -136,6 +146,7 @@ export default class Response extends React.Component {
{ example ? (
<ModelExample
getComponent={ getComponent }
getConfigs={ getConfigs }
specSelectors={ specSelectors }
schema={ fromJSOrdered(schema) }
example={ example }/>

View File

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

View File

@@ -45,6 +45,8 @@ module.exports = function SwaggerUI(opts) {
requestInterceptor: (a => a),
responseInterceptor: (a => a),
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.
// 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":
query.push("response_type=token")
break
case "clientCredentials":
// OAS3
authActions.authorizeApplication(auth)
return
case "authorizationCode":
// OAS3
query.push("response_type=code")
break
}
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
// to authorize with oauth2

View File

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

View File

@@ -7,13 +7,16 @@ export default function downloadUrlPlugin (toolbox) {
let { fn } = toolbox
const actions = {
download: (url)=> ({ errActions, specSelectors, specActions }) => {
download: (url)=> ({ errActions, specSelectors, specActions, getConfigs }) => {
let { fetch } = fn
const config = getConfigs()
url = url || specSelectors.url()
specActions.updateLoadingStatus("loading")
fetch({
url,
loadSpec: true,
requestInterceptor: config.requestInterceptor || (a => a),
responseInterceptor: config.responseInterceptor || (a => a),
credentials: "same-origin",
headers: {
"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 Servers from "./servers"
import RequestBodyEditor from "./request-body-editor"
import HttpAuth from "./http-auth"
export default {
Callbacks,
HttpAuth,
RequestBody,
Servers,
RequestBodyEditor,

View File

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

View File

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

View File

@@ -48,6 +48,11 @@ export const definitions = onlyOAS3(createSelector(
spec => spec.getIn(["components", "schemas"]) || Map()
))
export const securityDefinitions = onlyOAS3(createSelector(
spec,
spec => spec.getIn(["components", "securitySchemes"]) || Map()
))
export const host = OAS3NullSelector
export const basePath = 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 AuthItem from "./auth-item"
import parameters from "./parameters"
import VersionStamp from "./version-stamp"
import OnlineValidatorBadge from "./online-validator-badge"
@@ -6,6 +7,7 @@ import Model from "./model"
export default {
Markdown,
AuthItem,
parameters,
VersionStamp,
model: Model,

View File

@@ -1,11 +1,32 @@
import React from "react"
import PropTypes from "prop-types"
import ReactMarkdown from "react-markdown"
import { Parser, HtmlRenderer } from "commonmark"
import { OAS3ComponentWrapFactory } from "../helpers"
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
source={sanitizer(source)}
source={sanitized}
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,
} = this.props
changeParam( onChangeKey, param.get("name"), value, isXml)
changeParam( onChangeKey, param.get("name"), param.get("in"), value, isXml)
}
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 {
type: UPDATE_PARAM,
payload:{ path, value, paramName, isXml }
payload:{ path, value, paramName, paramIn, isXml }
}
}
export function validateParams( payload ){
export const validateParams = ( payload, isOAS3 ) =>{
return {
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`
req.contextUrl = parseUrl(specSelectors.url()).toString()
if(op && op.operationId) {
req.operationId = op.operationId
} else if(op && pathName && method) {

View File

@@ -40,9 +40,10 @@ export default {
},
[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 => {
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)) {
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 isXml = /xml/i.test(operation.get("consumes_value"))
return state.updateIn( [ "resolved", "paths", ...pathMethod, "parameters" ], fromJS([]), parameters => {
return parameters.withMutations( parameters => {
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))
}
})

View File

@@ -260,10 +260,10 @@ export const allowTryItOutFor = () => {
}
// 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([]))
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()
}
@@ -280,7 +280,7 @@ export function parameterValues(state, pathMethod, isXml) {
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
return params.reduce( (hash, p) => {
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({}))
}

View File

@@ -13,3 +13,7 @@ export const executeRequest = (ori, { specActions }) => (req) => {
specActions.logRequest(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 AuthorizeOperationBtn from "core/components/auth/authorize-operation-btn"
import Auths from "core/components/auth/auths"
import AuthItem from "core/components/auth/auth-item"
import AuthError from "core/components/auth/error"
import ApiKeyAuth from "core/components/auth/api-key-auth"
import BasicAuth from "core/components/auth/basic-auth"
@@ -70,6 +71,7 @@ export default function() {
authorizeBtn: AuthorizeBtn,
authorizeOperationBtn: AuthorizeOperationBtn,
auths: Auths,
AuthItem: AuthItem,
authError: AuthError,
oauth2: Oauth2,
apiKeyAuth: ApiKeyAuth,

View File

@@ -470,6 +470,18 @@ export const propChecker = (props, nextProps, objectList=[], ignoreList=[]) => {
|| 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 ) => {
if (!/^-?\d+(\.?\d+)?$/.test(val)) {
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
export const validateParam = (param, isXml) => {
export const validateParam = (param, isXml, isOAS3 = false) => {
let errors = []
let value = isXml && param.get("in") === "body" ? param.get("value_xml") : param.get("value")
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)
@@ -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 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) ) {
errors.push("Required field is not provided")
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" ) {
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
errors.push(err)
} else if ( type === "boolean" ) {
@@ -548,7 +618,7 @@ export const validateParam = (param, isXml) => {
if ( !value.count() ) { return errors }
itemType = param.getIn(["items", "type"])
itemType = paramDetails.getIn(["items", "type"])
value.forEach((item, index) => {
let err

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
.topbar
{
padding: 8px 30px;
padding: 8px 0;
background-color: #89bf04;
.topbar-wrapper
@@ -39,7 +39,6 @@
input[type=text]
{
width: 100%;
min-width: 350px;
margin: 0;
border: 2px solid #547f00;
@@ -84,7 +83,7 @@
font-size: 16px;
font-weight: bold;
padding: 4px 40px;
padding: 4px 30px;
border: none;
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": {
get: {
parameters: [
{ name: "one", value: 1},
{ name: "two", value: "duos"}
{ name: "one", in: "query", value: 1},
{ name: "two", in: "query", value: "duos"}
]
}
}
@@ -43,8 +43,8 @@ describe("spec plugin - selectors", function(){
// Then
expect(paramValues.toJS()).toEqual({
one: 1,
two: "duos"
"query.one": 1,
"query.two": "duos"
})
})

View File

@@ -1,7 +1,23 @@
/* eslint-env mocha */
import expect from "expect"
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"
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() {
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() {
let param = null
let result = null
it("skips validation when `type` is not specified", function() {
// invalid type
const assertValidateParam = (param, expectedError) => {
// 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({
required: false,
type: undefined,
value: ""
value: "",
required: true,
schema: {
notTheTypeProperty: "string"
}
})
result = validateParam( param, false )
result = validateParam( param, false, true )
expect( result ).toEqual( [] )
})
it("validates required strings", function() {
// invalid string
param = fromJS({
param = {
required: true,
type: "string",
value: ""
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
}
assertValidateParam(param, ["Required field is not provided"])
// valid string
param = fromJS({
param = {
required: true,
type: "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() {
// valid (empty) string
param = fromJS({
param = {
required: false,
type: "string",
value: ""
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}
assertValidateParam(param, [])
// valid string
param = fromJS({
param = {
required: false,
type: "string",
value: "test"
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}
assertValidateParam(param, [])
})
it("validates required files", function() {
// invalid file
param = fromJS({
param = {
required: true,
type: "file",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
}
assertValidateParam(param, ["Required field is not provided"])
// valid file
param = fromJS({
param = {
required: true,
type: "file",
value: new win.File()
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}
assertValidateParam(param, [])
})
it("validates optional files", function() {
// invalid file
param = fromJS({
param = {
required: false,
type: "file",
value: "not a file"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Value must be a file"] )
}
assertValidateParam(param, ["Value must be a file"])
// valid (empty) file
param = fromJS({
param = {
required: false,
type: "file",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}
assertValidateParam(param, [])
// valid file
param = fromJS({
param = {
required: false,
type: "file",
value: new win.File()
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}
assertValidateParam(param, [])
})
it("validates required arrays", function() {
// invalid (empty) array
param = fromJS({
param = {
required: true,
type: "array",
value: []
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
}
assertValidateParam(param, ["Required field is not provided"])
// invalid (not an array)
param = fromJS({
param = {
required: true,
type: "array",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
}
assertValidateParam(param, ["Required field is not provided"])
// invalid array, items do not match correct type
param = fromJS({
param = {
required: true,
type: "array",
value: [1],
items: {
type: "string"
}
})
result = validateParam( param, false )
expect( result ).toEqual( [{index: 0, error: "Value must be a string"}] )
}
assertValidateParam(param, [{index: 0, error: "Value must be a string"}])
// valid array, with no 'type' for items
param = fromJS({
param = {
required: true,
type: "array",
value: ["1"]
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}
assertValidateParam(param, [])
// valid array, items match type
param = fromJS({
param = {
required: true,
type: "array",
value: ["1"],
items: {
type: "string"
}
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}
assertValidateParam(param, [])
})
it("validates optional arrays", function() {
// valid, empty array
param = fromJS({
param = {
required: false,
type: "array",
value: []
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}
assertValidateParam(param, [])
// invalid, items do not match correct type
param = fromJS({
param = {
required: false,
type: "array",
value: ["number"],
items: {
type: "number"
}
})
result = validateParam( param, false )
expect( result ).toEqual( [{index: 0, error: "Value must be a number"}] )
}
assertValidateParam(param, [{index: 0, error: "Value must be a number"}])
// valid
param = fromJS({
param = {
required: false,
type: "array",
value: ["test"],
items: {
type: "string"
}
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}
assertValidateParam(param, [])
})
it("validates required booleans", function() {
// invalid boolean value
param = fromJS({
param = {
required: true,
type: "boolean",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
}
assertValidateParam(param, ["Required field is not provided"])
// invalid boolean value (not a boolean)
param = fromJS({
param = {
required: true,
type: "boolean",
value: "test string"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
}
assertValidateParam(param, ["Required field is not provided"])
// valid boolean value
param = fromJS({
param = {
required: true,
type: "boolean",
value: "true"
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}
assertValidateParam(param, [])
// valid boolean value
param = fromJS({
param = {
required: true,
type: "boolean",
value: false
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}
assertValidateParam(param, [])
})
it("validates optional booleans", function() {
// valid (empty) boolean value
param = fromJS({
param = {
required: false,
type: "boolean",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}
assertValidateParam(param, [])
// invalid boolean value (not a boolean)
param = fromJS({
param = {
required: false,
type: "boolean",
value: "test string"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Value must be a boolean"] )
}
assertValidateParam(param, ["Value must be a boolean"])
// valid boolean value
param = fromJS({
param = {
required: false,
type: "boolean",
value: "true"
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}
assertValidateParam(param, [])
// valid boolean value
param = fromJS({
param = {
required: false,
type: "boolean",
value: false
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}
assertValidateParam(param, [])
})
it("validates required numbers", function() {
// invalid number, string instead of a number
param = fromJS({
param = {
required: true,
type: "number",
value: "test"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
}
assertValidateParam(param, ["Required field is not provided"])
// invalid number, undefined value
param = fromJS({
param = {
required: true,
type: "number",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
}
assertValidateParam(param, ["Required field is not provided"])
// valid number
param = fromJS({
// valid number with min and max
param = {
required: true,
type: "number",
value: 10
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
value: 10,
minimum: 5,
maximum: 99
}
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() {
// invalid number, string instead of a number
param = fromJS({
param = {
required: false,
type: "number",
value: "test"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Value must be a number"] )
}
assertValidateParam(param, ["Value must be a number"])
// valid (empty) number
param = fromJS({
param = {
required: false,
type: "number",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}
assertValidateParam(param, [])
// valid number
param = fromJS({
param = {
required: false,
type: "number",
value: 10
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}
assertValidateParam(param, [])
})
it("validates required integers", function() {
// invalid integer, string instead of an integer
param = fromJS({
param = {
required: true,
type: "integer",
value: "test"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
}
assertValidateParam(param, ["Required field is not provided"])
// invalid integer, undefined value
param = fromJS({
param = {
required: true,
type: "integer",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
}
assertValidateParam(param, ["Required field is not provided"])
// valid integer
param = fromJS({
param = {
required: true,
type: "integer",
value: 10
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}
assertValidateParam(param, [])
})
it("validates optional integers", function() {
// invalid integer, string instead of an integer
param = fromJS({
param = {
required: false,
type: "integer",
value: "test"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Value must be an integer"] )
}
assertValidateParam(param, ["Value must be an integer"])
// valid (empty) integer
param = fromJS({
param = {
required: false,
type: "integer",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}
assertValidateParam(param, [])
// integers
param = fromJS({
param = {
required: false,
type: "integer",
value: 10
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}
assertValidateParam(param, [])
})
})
@@ -583,7 +739,7 @@ describe("utils", function() {
})
})
describe.only("getAcceptControllingResponse", () => {
describe("getAcceptControllingResponse", () => {
it("should return the first 2xx response with a media type", () => {
const responses = fromJSOrdered({
"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"
}
},
{ loader: "babel-loader" }
{ loader: "babel-loader?retainLines=true" }
]
}
]

View File

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

View File

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

View File

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