merge master

This commit is contained in:
Aaron Loo
2017-08-03 07:32:32 -07:00
56 changed files with 1601 additions and 179 deletions

View File

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

View File

@@ -18,16 +18,17 @@ This repo publishes to two different NPM packages:
For the older version of swagger-ui, refer to the [*2.x branch*](https://github.com/swagger-api/swagger-ui/tree/2.x). For the older version of swagger-ui, refer to the [*2.x branch*](https://github.com/swagger-api/swagger-ui/tree/2.x).
## Compatibility ## Compatibility
The OpenAPI Specification has undergone 4 revisions since initial creation in 2010. Compatibility between swagger-ui and the OpenAPI Specification is as follows: The OpenAPI Specification has undergone 5 revisions since initial creation in 2010. Compatibility between swagger-ui and the OpenAPI Specification is as follows:
Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes | Status Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes
------------------ | ------------ | -------------------------- | ----- | ------ ------------------ | ------------ | -------------------------- | -----
3.0.21 | 2017-07-24 | 2.0 | [tag v3.0.21](https://github.com/swagger-api/swagger-ui/tree/v3.0.21) | 3.1.2 | 2017-07-31 | 2.0, 3.0 | [tag v3.1.2](https://github.com/swagger-api/swagger-ui/tree/v3.1.2)
2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10) | 3.0.21 | 2017-07-26 | 2.0 | [tag v3.0.21](https://github.com/swagger-api/swagger-ui/tree/v3.0.21)
2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5) | 2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10)
2.0.24 | 2014-09-12 | 1.1, 1.2 | [tag v2.0.24](https://github.com/swagger-api/swagger-ui/tree/v2.0.24) | 2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5)
1.0.13 | 2013-03-08 | 1.1, 1.2 | [tag v1.0.13](https://github.com/swagger-api/swagger-ui/tree/v1.0.13) | 2.0.24 | 2014-09-12 | 1.1, 1.2 | [tag v2.0.24](https://github.com/swagger-api/swagger-ui/tree/v2.0.24)
1.0.1 | 2011-10-11 | 1.0, 1.1 | [tag v1.0.1](https://github.com/swagger-api/swagger-ui/tree/v1.0.1) | 1.0.13 | 2013-03-08 | 1.1, 1.2 | [tag v1.0.13](https://github.com/swagger-api/swagger-ui/tree/v1.0.13)
1.0.1 | 2011-10-11 | 1.0, 1.1 | [tag v1.0.1](https://github.com/swagger-api/swagger-ui/tree/v1.0.1)
### How to run ### How to run
@@ -96,7 +97,8 @@ To use swagger-ui's bundles, you should take a look at the [source of swagger-ui
#### OAuth2 configuration #### OAuth2 configuration
You can configure OAuth2 authorization by calling `initOAuth` method with passed configs under the instance of `SwaggerUIBundle` You can configure OAuth2 authorization by calling `initOAuth` method with passed configs under the instance of `SwaggerUIBundle`
default `client_id` and `client_secret`, `realm`, an application name `appName`, `scopeSeparator`, `additionalQueryStringParams`. default `client_id` and `client_secret`, `realm`, an application name `appName`, `scopeSeparator`, `additionalQueryStringParams`,
`useBasicAuthenticationWithAccessCodeGrant`.
Config Name | Description Config Name | Description
--- | --- --- | ---
@@ -106,6 +108,7 @@ realm | realm query parameter (for oauth1) added to `authorizationUrl` and `toke
appName | application name, displayed in authorization popup. MUST be a string appName | application name, displayed in authorization popup. MUST be a string
scopeSeparator | scope separator for passing scopes, encoded before calling, default value is a space (encoded value `%20`). MUST be a string scopeSeparator | scope separator for passing scopes, encoded before calling, default value is a space (encoded value `%20`). MUST be a string
additionalQueryStringParams | Additional query parameters added to `authorizationUrl` and `tokenUrl`. MUST be an object additionalQueryStringParams | Additional query parameters added to `authorizationUrl` and `tokenUrl`. MUST be an object
useBasicAuthenticationWithAccessCodeGrant | Only activated for the `accessCode` flow. During the `authorization_code` request to the `tokenUrl`, pass the [Client Password](https://tools.ietf.org/html/rfc6749#section-2.3.1) using the HTTP Basic Authentication scheme (`Authorization` header with `Basic base64encoded[client_id:client_secret]`). The default is `false`
``` ```
const ui = SwaggerUIBundle({...}) const ui = SwaggerUIBundle({...})
@@ -135,6 +138,7 @@ urls.primaryName | When using `urls`, you can use this subparameter. If the valu
spec | A JSON object describing the OpenAPI Specification. When used, the `url` parameter will not be parsed. This is useful for testing manually-generated specifications without hosting them. spec | A JSON object describing the OpenAPI Specification. When used, the `url` parameter will not be parsed. This is useful for testing manually-generated specifications without hosting them.
validatorUrl | By default, Swagger-UI attempts to validate specs against swagger.io's online validator. You can use this parameter to set a different validator URL, for example for locally deployed validators ([Validator Badge](https://github.com/swagger-api/validator-badge)). Setting it to `null` will disable validation. validatorUrl | By default, Swagger-UI attempts to validate specs against swagger.io's online validator. You can use this parameter to set a different validator URL, for example for locally deployed validators ([Validator Badge](https://github.com/swagger-api/validator-badge)). Setting it to `null` will disable validation.
dom_id | The id of a dom element inside which SwaggerUi will put the user interface for swagger. dom_id | The id of a dom element inside which SwaggerUi will put the user interface for swagger.
domNode | The HTML DOM element inside which SwaggerUi will put the user interface for swagger. Overrides `dom_id`.
oauth2RedirectUrl | OAuth redirect URL oauth2RedirectUrl | OAuth redirect URL
tagsSorter | Apply a sort to the tag list of each API. It can be 'alpha' (sort by paths alphanumerically) or a function (see [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) to learn how to write a sort function). Two tag name strings are passed to the sorter for each pass. Default is the order determined by Swagger-UI. 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.

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;AAoyKA;;;;;;AAmtEA;;;;;;;;;;;;;;;;;;;;;;;;;;AAiqTA;;;;;;;;;;;;;;AA+5JA;;;;;;;;;AA2vnBA;;;;;AA6kQA;;;;;;AA+gXA","sourceRoot":""} {"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;AAoyKA;;;;;;AAy+EA;;;;;;;;;;;;;;;;;;;;;;;;;;AAw1TA;;;;;;;;;;;;;;AAs8JA;;;;;;;;;AAy6oBA;;;;;AAqqQA;AAm4DA;;;;;;AAo4YA;;;;;;AA8jaA;AAumvBA","sourceRoot":""}

File diff suppressed because one or more lines are too long

2
dist/swagger-ui.css vendored

File diff suppressed because one or more lines are too long

4
dist/swagger-ui.js vendored

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "swagger-ui", "name": "swagger-ui",
"version": "3.0.21", "version": "3.1.2",
"main": "dist/swagger-ui.js", "main": "dist/swagger-ui.js",
"repository": "git@github.com:swagger-api/swagger-ui.git", "repository": "git@github.com:swagger-api/swagger-ui.git",
"contributors": [ "contributors": [
@@ -43,7 +43,6 @@
"ieee754": "^1.1.8", "ieee754": "^1.1.8",
"immutable": "^3.x.x", "immutable": "^3.x.x",
"js-yaml": "^3.5.5", "js-yaml": "^3.5.5",
"less": "2.7.1",
"lodash": "4.17.2", "lodash": "4.17.2",
"matcher": "^0.1.2", "matcher": "^0.1.2",
"memoizee": "0.4.1", "memoizee": "0.4.1",
@@ -58,6 +57,7 @@
"react-height": "^2.0.0", "react-height": "^2.0.0",
"react-hot-loader": "1.3.1", "react-hot-loader": "1.3.1",
"react-immutable-proptypes": "2.1.0", "react-immutable-proptypes": "2.1.0",
"react-markdown": "^2.5.0",
"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",

View File

@@ -8,7 +8,7 @@ const noop = ()=>{}
export default class ContentType extends React.Component { export default class ContentType extends React.Component {
static propTypes = { static propTypes = {
contentTypes: PropTypes.oneOfType([ImPropTypes.list, ImPropTypes.set]), contentTypes: PropTypes.oneOfType([ImPropTypes.list, ImPropTypes.set, ImPropTypes.seq]),
value: PropTypes.string, value: PropTypes.string,
onChange: PropTypes.func, onChange: PropTypes.func,
className: PropTypes.string className: PropTypes.string
@@ -22,8 +22,10 @@ export default class ContentType extends React.Component {
componentDidMount() { componentDidMount() {
// Needed to populate the form, initially // Needed to populate the form, initially
if(this.props.contentTypes) {
this.props.onChange(this.props.contentTypes.first()) this.props.onChange(this.props.contentTypes.first())
} }
}
onChangeWrapper = e => this.props.onChange(e.target.value) onChangeWrapper = e => this.props.onChange(e.target.value)

View File

@@ -88,12 +88,13 @@ export default class Info extends React.Component {
const { url:externalDocsUrl, description:externalDocsDescription } = (externalDocs || fromJS({})).toJS() const { url:externalDocsUrl, description:externalDocsDescription } = (externalDocs || fromJS({})).toJS()
const Markdown = getComponent("Markdown") const Markdown = getComponent("Markdown")
const VersionStamp = getComponent("VersionStamp")
return ( return (
<div className="info"> <div className="info">
<hgroup className="main"> <hgroup className="main">
<h2 className="title" >{ title } <h2 className="title" >{ title }
{ version && <small><pre className="version"> { version } </pre></small> } { version && <VersionStamp version={version}></VersionStamp> }
</h2> </h2>
{ host || basePath ? <Path host={ host } basePath={ basePath } /> : null } { host || basePath ? <Path host={ host } basePath={ basePath } /> : null }
{ url && <a target="_blank" href={ url }><span className="url"> { url } </span></a> } { url && <a target="_blank" href={ url }><span className="url"> { url } </span></a> }

View File

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

View File

@@ -35,9 +35,9 @@ export default class ModelExample extends React.Component {
<li className={ "tabitem" + ( isExecute || this.state.activeTab === "example" ? " active" : "") }> <li className={ "tabitem" + ( isExecute || this.state.activeTab === "example" ? " active" : "") }>
<a className="tablinks" data-name="example" onClick={ this.activeTab }>Example Value</a> <a className="tablinks" data-name="example" onClick={ this.activeTab }>Example Value</a>
</li> </li>
<li className={ "tabitem" + ( !isExecute && this.state.activeTab === "model" ? " active" : "") }> { schema ? <li className={ "tabitem" + ( !isExecute && this.state.activeTab === "model" ? " active" : "") }>
<a className={ "tablinks" + ( isExecute ? " inactive" : "" )} data-name="model" onClick={ this.activeTab }>Model</a> <a className={ "tablinks" + ( isExecute ? " inactive" : "" )} data-name="model" onClick={ this.activeTab }>Model</a>
</li> </li> : null }
</ul> </ul>
<div> <div>
{ {

View File

@@ -17,6 +17,9 @@ export default class Model extends Component {
if ( ref.indexOf("#/definitions/") !== -1 ) { if ( ref.indexOf("#/definitions/") !== -1 ) {
return ref.replace(/^.*#\/definitions\//, "") return ref.replace(/^.*#\/definitions\//, "")
} }
if ( ref.indexOf("#/components/schemas/") !== -1 ) {
return ref.replace("#/components/schemas/", "")
}
} }
getRefSchema =( model )=> { getRefSchema =( model )=> {
@@ -26,7 +29,7 @@ export default class Model extends Component {
} }
render () { render () {
let { schema, getComponent, required, name, isRef } = this.props let { getComponent, specSelectors, schema, required, name, isRef } = this.props
let ObjectModel = getComponent("ObjectModel") let ObjectModel = getComponent("ObjectModel")
let ArrayModel = getComponent("ArrayModel") let ArrayModel = getComponent("ArrayModel")
let PrimitiveModel = getComponent("PrimitiveModel") let PrimitiveModel = getComponent("PrimitiveModel")
@@ -34,6 +37,8 @@ export default class Model extends Component {
let modelName = $$ref && this.getModelName( $$ref ) let modelName = $$ref && this.getModelName( $$ref )
let modelSchema, type let modelSchema, type
const deprecated = specSelectors.isOAS3() && schema.get("deprecated")
if ( schema && (schema.get("type") || schema.get("properties")) ) { if ( schema && (schema.get("type") || schema.get("properties")) ) {
modelSchema = schema modelSchema = schema
} else if ( $$ref ) { } else if ( $$ref ) {
@@ -47,17 +52,30 @@ export default class Model extends Component {
switch(type) { switch(type) {
case "object": case "object":
return <ObjectModel className="object" { ...this.props } schema={ modelSchema } return <ObjectModel
name={ name || modelName } required={ required } className="object" { ...this.props }
schema={ modelSchema }
name={ name || modelName }
deprecated={deprecated}
isRef={ isRef!== undefined ? isRef : !!$$ref } /> isRef={ isRef!== undefined ? isRef : !!$$ref } />
case "array": case "array":
return <ArrayModel className="array" { ...this.props } schema={ modelSchema } required={ required } /> return <ArrayModel
className="array" { ...this.props }
schema={ modelSchema }
name={ name || modelName }
deprecated={deprecated}
required={ required } />
case "string": case "string":
case "number": case "number":
case "integer": case "integer":
case "boolean": case "boolean":
default: default:
return <PrimitiveModel getComponent={ getComponent } schema={ modelSchema } required={ required }/> return <PrimitiveModel
} { ...this.props }
getComponent={ getComponent }
schema={ modelSchema }
name={ name || modelName }
deprecated={deprecated}
required={ required }/> }
} }
} }

View File

@@ -18,7 +18,7 @@ export default class ObjectModel extends Component {
render(){ render(){
let { schema, name, isRef, getComponent, depth, ...props } = this.props let { schema, name, isRef, getComponent, depth, ...props } = this.props
let { expandDepth } = this.props let { expandDepth, specSelectors } = this.props
let description = schema.get("description") let description = schema.get("description")
let properties = schema.get("properties") let properties = schema.get("properties")
let additionalProperties = schema.get("additionalProperties") let additionalProperties = schema.get("additionalProperties")
@@ -38,6 +38,10 @@ export default class ObjectModel extends Component {
} }
</span>) </span>)
const anyOf = specSelectors.isOAS3() ? schema.get("anyOf") : null
const oneOf = specSelectors.isOAS3() ? schema.get("oneOf") : null
const not = specSelectors.isOAS3() ? schema.get("not") : null
const titleEl = title && <span className="model-title"> const titleEl = title && <span className="model-title">
{ isRef && schema.get("$$ref") && <span className="model-hint">{ schema.get("$$ref") }</span> } { isRef && schema.get("$$ref") && <span className="model-hint">{ schema.get("$$ref") }</span> }
<span className="model-title__text">{ title }</span> <span className="model-title__text">{ title }</span>
@@ -95,6 +99,48 @@ export default class ObjectModel extends Component {
</td> </td>
</tr> </tr>
} }
{
!anyOf ? null
: <tr>
<td>{ "anyOf ->" }</td>
<td>
{anyOf.map((schema, k) => {
return <div key={k}><Model { ...props } required={ false }
getComponent={ getComponent }
schema={ schema }
depth={ depth + 1 } /></div>
})}
</td>
</tr>
}
{
!oneOf ? null
: <tr>
<td>{ "oneOf ->" }</td>
<td>
{oneOf.map((schema, k) => {
return <div key={k}><Model { ...props } required={ false }
getComponent={ getComponent }
schema={ schema }
depth={ depth + 1 } /></div>
})}
</td>
</tr>
}
{
!not ? null
: <tr>
<td>{ "not ->" }</td>
<td>
{not.map((schema, k) => {
return <div key={k}><Model { ...props } required={ false }
getComponent={ getComponent }
schema={ schema }
depth={ depth + 1 } /></div>
})}
</td>
</tr>
}
</tbody></table> </tbody></table>
} }
</span> </span>

View File

@@ -210,6 +210,7 @@ export default class Operation extends PureComponent {
} }
<Parameters <Parameters
parameters={parameters} parameters={parameters}
operation={operation}
onChangeKey={onChangeKey} onChangeKey={onChangeKey}
onTryoutClick = { this.onTryoutClick } onTryoutClick = { this.onTryoutClick }
onCancelClick = { this.onCancelClick } onCancelClick = { this.onCancelClick }

View File

@@ -66,6 +66,8 @@ export default class Operations extends React.Component {
taggedOps.map( (tagObj, tag) => { taggedOps.map( (tagObj, tag) => {
let operations = tagObj.get("operations") let operations = tagObj.get("operations")
let tagDescription = tagObj.getIn(["tagDetails", "description"], null) let tagDescription = tagObj.getIn(["tagDetails", "description"], null)
let tagExternalDocsDescription = tagObj.getIn(["tagDetails", "externalDocs", "description"])
let tagExternalDocsUrl = tagObj.getIn(["tagDetails", "externalDocs", "url"])
let isShownKey = ["operations-tag", tag] let isShownKey = ["operations-tag", tag]
let showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list") let showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list")
@@ -89,6 +91,22 @@ export default class Operations extends React.Component {
</small> </small>
} }
<div>
{ !tagExternalDocsDescription ? null :
<small>
{ tagExternalDocsDescription }
{ tagExternalDocsUrl ? ": " : null }
{ tagExternalDocsUrl ?
<a
href={tagExternalDocsUrl}
onClick={(e) => e.stopPropagation()}
target={"_blank"}
>{tagExternalDocsUrl}</a> : null
}
</small>
}
</div>
<button className="expand-operation" title="Expand operation" onClick={() => layoutActions.show(isShownKey, !showTag)}> <button className="expand-operation" title="Expand operation" onClick={() => layoutActions.show(isShownKey, !showTag)}>
<svg className="arrow" width="20" height="20"> <svg className="arrow" width="20" height="20">
<use xlinkHref={showTag ? "#large-arrow-down" : "#large-arrow"} /> <use xlinkHref={showTag ? "#large-arrow-down" : "#large-arrow"} />

View File

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

View File

@@ -3,15 +3,8 @@ import PropTypes from "prop-types"
import Remarkable from "react-remarkable" import Remarkable from "react-remarkable"
import sanitize from "sanitize-html" import sanitize from "sanitize-html"
const sanitizeOptions = {
textFilter: function(text) {
return text
.replace(/&quot;/g, "\"")
}
}
function Markdown({ source }) { function Markdown({ source }) {
const sanitized = sanitize(source, sanitizeOptions) const sanitized = sanitizer(source)
// sometimes the sanitizer returns "undefined" as a string // sometimes the sanitizer returns "undefined" as a string
if(!source || !sanitized || sanitized === "undefined") { if(!source || !sanitized || sanitized === "undefined") {
@@ -31,3 +24,14 @@ Markdown.propTypes = {
} }
export default Markdown export default Markdown
const sanitizeOptions = {
textFilter: function(text) {
return text
.replace(/&quot;/g, "\"")
}
}
export function sanitizer(str) {
return sanitize(str, sanitizeOptions)
}

View File

@@ -1,6 +1,6 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { fromJS } from "immutable" import { fromJS, Seq } from "immutable"
import { getSampleSchema } from "core/utils" import { getSampleSchema } from "core/utils"
const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => { const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => {
@@ -31,6 +31,13 @@ const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => {
} }
export default class Response extends React.Component { export default class Response extends React.Component {
constructor(props, context) {
super(props, context)
this.state = {
responseContentType: ""
}
}
static propTypes = { static propTypes = {
code: PropTypes.string.isRequired, code: PropTypes.string.isRequired,
@@ -59,16 +66,29 @@ export default class Response extends React.Component {
} = this.props } = this.props
let { inferSchema } = fn let { inferSchema } = fn
let { isOAS3 } = specSelectors
let schema = inferSchema(response.toJS())
let headers = response.get("headers") let headers = response.get("headers")
let examples = response.get("examples") let examples = response.get("examples")
let links = response.get("links")
const Headers = getComponent("headers") const Headers = getComponent("headers")
const HighlightCode = getComponent("highlightCode") const HighlightCode = getComponent("highlightCode")
const ModelExample = getComponent("modelExample") const ModelExample = getComponent("modelExample")
const Markdown = getComponent( "Markdown" ) const Markdown = getComponent( "Markdown" )
const OperationLink = getComponent("operationLink")
const ContentType = getComponent("contentType")
let sampleResponse = schema ? getSampleSchema(schema, contentType, { includeReadOnly: true }) : null var sampleResponse
var schema
if(isOAS3()) {
let oas3SchemaForContentType = response.getIn(["content", this.state.responseContentType, "schema"])
sampleResponse = oas3SchemaForContentType ? getSampleSchema(oas3SchemaForContentType.toJS(), this.state.responseContentType, { includeReadOnly: true }) : null
schema = oas3SchemaForContentType ? inferSchema(oas3SchemaForContentType.toJS()) : null
} else {
schema = inferSchema(response.toJS())
sampleResponse = schema ? getSampleSchema(schema, contentType, { includeReadOnly: true }) : null
}
let example = getExampleComponent( sampleResponse, examples, HighlightCode ) let example = getExampleComponent( sampleResponse, examples, HighlightCode )
return ( return (
@@ -82,6 +102,12 @@ export default class Response extends React.Component {
<Markdown source={ response.get( "description" ) } /> <Markdown source={ response.get( "description" ) } />
</div> </div>
{ isOAS3 ? <ContentType
value={this.state.responseContentType}
contentTypes={ response.get("content") ? response.get("content").keySeq() : Seq() }
onChange={(val) => this.setState({ responseContentType: val })}
className="response-content-type" /> : null }
{ example ? ( { example ? (
<ModelExample <ModelExample
getComponent={ getComponent } getComponent={ getComponent }
@@ -94,8 +120,15 @@ export default class Response extends React.Component {
<Headers headers={ headers }/> <Headers headers={ headers }/>
) : null} ) : null}
</td>
</td>
{specSelectors.isOAS3() ? <td>
{ links ?
links.toSeq().map((link, key) => {
return <OperationLink key={key} name={key} link={ link }/>
})
: <i>No links</i>}
</td> : null}
</tr> </tr>
) )
} }

View File

@@ -42,13 +42,13 @@ export default class Responses extends React.Component {
<div className="responses-wrapper"> <div className="responses-wrapper">
<div className="opblock-section-header"> <div className="opblock-section-header">
<h4>Responses</h4> <h4>Responses</h4>
<label> { specSelectors.isOAS3() ? null : <label>
<span>Response content type</span> <span>Response content type</span>
<ContentType value={producesValue} <ContentType value={producesValue}
onChange={this.onChangeProducesWrapper} onChange={this.onChangeProducesWrapper}
contentTypes={produces} contentTypes={produces}
className="execute-content-type"/> className="execute-content-type"/>
</label> </label> }
</div> </div>
<div className="responses-inner"> <div className="responses-inner">
{ {
@@ -68,6 +68,7 @@ export default class Responses extends React.Component {
<tr className="responses-header"> <tr className="responses-header">
<td className="col col_header response-col_status">Code</td> <td className="col col_header response-col_status">Code</td>
<td className="col col_header response-col_description">Description</td> <td className="col col_header response-col_description">Description</td>
{ specSelectors.isOAS3() ? <td className="col col_header response-col_description">Links</td> : null }
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

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

View File

@@ -23,6 +23,7 @@ module.exports = function SwaggerUI(opts) {
const defaults = { const defaults = {
// Some general settings, that we floated to the top // Some general settings, that we floated to the top
dom_id: null, dom_id: null,
domNode: null,
spec: {}, spec: {},
url: "", url: "",
urls: null, urls: null,
@@ -99,6 +100,12 @@ module.exports = function SwaggerUI(opts) {
let localConfig = system.specSelectors.getLocalConfig ? system.specSelectors.getLocalConfig() : {} let localConfig = system.specSelectors.getLocalConfig ? system.specSelectors.getLocalConfig() : {}
let mergedConfig = deepExtend({}, localConfig, constructorConfig, fetchedConfig || {}, queryConfig) let mergedConfig = deepExtend({}, localConfig, constructorConfig, fetchedConfig || {}, queryConfig)
// deep extend mangles domNode, we need to set it manually
if(opts.domNode) {
mergedConfig.domNode = opts.domNode
}
store.setConfigs(mergedConfig) store.setConfigs(mergedConfig)
if (fetchedConfig !== null) { if (fetchedConfig !== null) {
@@ -112,10 +119,13 @@ module.exports = function SwaggerUI(opts) {
} }
} }
if(mergedConfig.dom_id) { if(mergedConfig.domNode) {
system.render(mergedConfig.dom_id, "App") system.render(mergedConfig.domNode, "App")
} else if(mergedConfig.dom_id) {
let domNode = document.querySelector(mergedConfig.dom_id)
system.render(domNode, "App")
} else { } else {
console.error("Skipped rendering: no `dom_id` was specified") console.error("Skipped rendering: no `dom_id` or `domNode` was specified")
} }
return system return system

View File

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

View File

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

View File

@@ -111,7 +111,7 @@ export const authorizeApplication = ( auth ) => ( { authActions } ) => {
return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth, headers }) return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth, headers })
} }
export const authorizeAccessCode = ( { auth, redirectUrl } ) => ( { authActions } ) => { export const authorizeAccessCodeWithFormParams = ( { auth, redirectUrl } ) => ( { authActions } ) => {
let { schema, name, clientId, clientSecret } = auth let { schema, name, clientId, clientSecret } = auth
let form = { let form = {
grant_type: "authorization_code", grant_type: "authorization_code",
@@ -124,6 +124,21 @@ export const authorizeAccessCode = ( { auth, redirectUrl } ) => ( { authActions
return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth}) return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth})
} }
export const authorizeAccessCodeWithBasicAuthentication = ( { auth, redirectUrl } ) => ( { authActions } ) => {
let { schema, name, clientId, clientSecret } = auth
let headers = {
Authorization: "Basic " + btoa(clientId + ":" + clientSecret)
}
let form = {
grant_type: "authorization_code",
code: auth.code,
client_id: clientId,
redirect_uri: redirectUrl
}
return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth, headers})
}
export const authorizeRequest = ( data ) => ( { fn, authActions, errActions, authSelectors } ) => { export const authorizeRequest = ( data ) => ( { fn, authActions, errActions, authSelectors } ) => {
let { body, query={}, headers={}, name, url, auth } = data let { body, query={}, headers={}, name, url, auth } = data
let { additionalQueryStringParams } = authSelectors.getConfigs() || {} let { additionalQueryStringParams } = authSelectors.getConfigs() || {}

View File

@@ -0,0 +1,49 @@
import React from "react"
import PropTypes from "prop-types"
const Callbacks = (props) => {
let { callbacks, getComponent } = props
// const Markdown = getComponent("Markdown")
const Operation = getComponent("operation", true)
if(!callbacks) {
return <span>No callbacks</span>
}
let callbackElements = callbacks.map((callback, callbackName) => {
return <div key={callbackName}>
<h2>{callbackName}</h2>
{ callback.map((pathItem, pathItemName) => {
return <div key={pathItemName}>
{ pathItem.map((operation, method) => {
return <Operation
operation={operation}
key={method}
method={method}
isShownKey={["callbacks", operation.get("id"), callbackName]}
path={pathItemName}
allowTryItOut={false}
{...props}></Operation>
// return <pre>{JSON.stringify(operation)}</pre>
}) }
</div>
}) }
</div>
// return <div>
// <h2>{name}</h2>
// {callback.description && <Markdown source={callback.description}/>}
// <pre>{JSON.stringify(callback)}</pre>
// </div>
})
return <div>
{callbackElements}
</div>
}
Callbacks.propTypes = {
getComponent: PropTypes.func.isRequired,
callbacks: PropTypes.array.isRequired
}
export default Callbacks

View File

@@ -0,0 +1,9 @@
import Callbacks from "./callbacks"
import RequestBody from "./request-body"
import OperationLink from "./operation-link.jsx"
export default {
Callbacks,
RequestBody,
operationLink: OperationLink
}

View File

@@ -0,0 +1,37 @@
import React, { Component } from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
class OperationLink extends Component {
render() {
const { link, name } = this.props
let targetOp = link.get("operationId") || link.get("operationRef")
let parameters = link.get("parameters") && link.get("parameters").toJS()
let description = link.get("description")
return <span>
<div style={{ padding: "5px 2px" }}>{name}{description ? `: ${description}` : ""}</div>
<pre>
Operation `{targetOp}`<br /><br />
Parameters {padString(0, JSON.stringify(parameters, null, 2)) || "{}"}<br />
</pre>
</span>
}
}
function padString(n, string) {
if(typeof string !== "string") { return "" }
return string
.split("\n")
.map((line, i) => i > 0 ? Array(n + 1).join(" ") + line : line)
.join("\n")
}
OperationLink.propTypes = {
link: ImPropTypes.orderedMap.isRequired,
name: PropTypes.String
}
export default OperationLink

View File

@@ -0,0 +1,42 @@
import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { OrderedMap } from "immutable"
import { getSampleSchema } from "core/utils"
const RequestBody = ({ requestBody, getComponent, specSelectors, contentType }) => {
const Markdown = getComponent("Markdown")
const ModelExample = getComponent("modelExample")
const HighlightCode = getComponent("highlightCode")
const requestBodyDescription = (requestBody && requestBody.get("description")) || null
const requestBodyContent = (requestBody && requestBody.get("content")) || new OrderedMap()
contentType = contentType || requestBodyContent.keySeq().first()
const mediaTypeValue = requestBodyContent.get(contentType)
const sampleSchema = getSampleSchema(mediaTypeValue.get("schema").toJS(), contentType)
return <div>
{ requestBodyDescription &&
<Markdown source={requestBodyDescription} />
}
<ModelExample
getComponent={ getComponent }
specSelectors={ specSelectors }
expandDepth={1}
schema={mediaTypeValue.get("schema")}
example={<HighlightCode value={sampleSchema} />}
/>
</div>
}
RequestBody.propTypes = {
requestBody: ImPropTypes.orderedMap.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
contentType: PropTypes.string.isRequired
}
export default RequestBody

View File

@@ -0,0 +1,36 @@
import React from "react"
export function isOAS3(jsSpec) {
const oasVersion = jsSpec.get("openapi")
if(!oasVersion) {
return false
}
return oasVersion.startsWith("3.0.0")
}
export function isSwagger2(jsSpec) {
const swaggerVersion = jsSpec.get("swagger")
if(!swaggerVersion) {
return false
}
return swaggerVersion.startsWith("2")
}
export function OAS3ComponentWrapFactory(Component) {
return (Ori, system) => (props) => {
if(system && system.specSelectors && system.specSelectors.specJson) {
const spec = system.specSelectors.specJson()
if(isOAS3(spec)) {
return <Component {...props} {...system} Ori={Ori}></Component>
} else {
return <Ori {...props}></Ori>
}
} else {
console.warn("OAS3 wrapper: couldn't get spec")
return null
}
}
}

View File

@@ -0,0 +1,17 @@
// import reducers from "./reducers"
// import * as actions from "./actions"
import * as wrapSelectors from "./wrap-selectors"
import components from "./components"
import wrapComponents from "./wrap-components"
export default function() {
return {
components,
wrapComponents,
statePlugins: {
spec: {
wrapSelectors
}
}
}
}

View File

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

View File

@@ -0,0 +1,11 @@
import React from "react"
import ReactMarkdown from "react-markdown"
import { OAS3ComponentWrapFactory } from "../helpers"
import { sanitizer } from "core/components/providers/markdown"
export default OAS3ComponentWrapFactory(({ source }) => { return source ? (
<ReactMarkdown
source={sanitizer(source)}
className={"renderedMarkdown"}
/>
) : null})

View File

@@ -0,0 +1,37 @@
import React, { Component } from "react"
import PropTypes from "prop-types"
import { OAS3ComponentWrapFactory } from "../helpers"
import { Model } from "core/components/model"
class ModelComponent extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
name: PropTypes.string,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
expandDepth: PropTypes.number
}
render(){
let { schema } = this.props
let classes = ["model-box"]
let isDeprecated = schema.get("deprecated") === true
let message = null
if(isDeprecated) {
classes.push("deprecated")
message = <span className="model-deprecated-warning">Deprecated:</span>
}
return <div className={classes.join(" ")}>
{message}
<Model { ...this.props }
depth={ 1 }
expandDepth={ this.props.expandDepth || 0 }
/>
</div>
}
}
export default OAS3ComponentWrapFactory(ModelComponent)

View File

@@ -0,0 +1,5 @@
import { OAS3ComponentWrapFactory } from "../helpers"
// We're disabling the Online Validator Badge until the online validator
// can handle OAS3 specs.
export default OAS3ComponentWrapFactory(() => null)

View File

@@ -0,0 +1,181 @@
import React, { Component } from "react"
import PropTypes from "prop-types"
import Im, { Map } from "immutable"
import ImPropTypes from "react-immutable-proptypes"
import { OAS3ComponentWrapFactory } from "../helpers"
// More readable, just iterate over maps, only
const eachMap = (iterable, fn) => iterable.valueSeq().filter(Im.Map.isMap).map(fn)
class Parameters extends Component {
constructor(props) {
super(props)
this.state = {
callbackVisible: false,
parametersVisible: true,
requestBodyContentType: ""
}
}
static propTypes = {
parameters: ImPropTypes.list.isRequired,
specActions: PropTypes.object.isRequired,
operation: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired,
tryItOutEnabled: PropTypes.bool,
allowTryItOut: PropTypes.bool,
onTryoutClick: PropTypes.func,
onCancelClick: PropTypes.func,
onChangeKey: PropTypes.array,
pathMethod: PropTypes.array.isRequired
}
static defaultProps = {
onTryoutClick: Function.prototype,
onCancelClick: Function.prototype,
tryItOutEnabled: false,
allowTryItOut: true,
onChangeKey: [],
}
onChange = ( param, value, isXml ) => {
let {
specActions: { changeParam },
onChangeKey,
} = this.props
changeParam( onChangeKey, param.get("name"), value, isXml)
}
onChangeConsumesWrapper = ( val ) => {
let {
specActions: { changeConsumesValue },
onChangeKey
} = this.props
changeConsumesValue(onChangeKey, val)
}
toggleTab = (tab) => {
if(tab === "parameters"){
return this.setState({
parametersVisible: true,
callbackVisible: false
})
}else if(tab === "callbacks"){
return this.setState({
callbackVisible: true,
parametersVisible: false
})
}
}
render(){
let {
onTryoutClick,
onCancelClick,
parameters,
allowTryItOut,
tryItOutEnabled,
fn,
getComponent,
specSelectors,
pathMethod,
operation
} = this.props
const ParameterRow = getComponent("parameterRow")
const TryItOutButton = getComponent("TryItOutButton")
const ContentType = getComponent("contentType")
const Callbacks = getComponent("Callbacks", true)
const RequestBody = getComponent("RequestBody", true)
const isExecute = tryItOutEnabled && allowTryItOut
const { isOAS3 } = specSelectors
const requestBody = operation.get("requestBody")
return (
<div className="opblock-section">
<div className="opblock-section-header">
<div className="tab-header">
<div onClick={() => this.toggleTab("parameters")} className={`tab-item ${this.state.parametersVisible && "active"}`}>
<h4 className="opblock-title"><span>Parameters</span></h4>
</div>
{ operation.get("callbacks") ?
(
<div onClick={() => this.toggleTab("callbacks")} className={`tab-item ${this.state.callbackVisible && "active"}`}>
<h4 className="opblock-title"><span>Callbacks</span></h4>
</div>
) : null
}
</div>
{ allowTryItOut ? (
<TryItOutButton enabled={ tryItOutEnabled } onCancelClick={ onCancelClick } onTryoutClick={ onTryoutClick } />
) : null }
</div>
{this.state.parametersVisible ? <div className="parameters-container">
{ !parameters.count() ? <div className="opblock-description-wrapper"><p>No parameters</p></div> :
<div className="table-container">
<table className="parameters">
<thead>
<tr>
<th className="col col_header parameters-col_name">Name</th>
<th className="col col_header parameters-col_description">Description</th>
</tr>
</thead>
<tbody>
{
eachMap(parameters, (parameter) => (
<ParameterRow fn={ fn }
getComponent={ getComponent }
param={ parameter }
key={ parameter.get( "name" ) }
onChange={ this.onChange }
onChangeConsumes={this.onChangeConsumesWrapper}
specSelectors={ specSelectors }
pathMethod={ pathMethod }
isExecute={ isExecute }/>
)).toArray()
}
</tbody>
</table>
</div>
}
</div> : "" }
{this.state.callbackVisible ? <div className="callbacks-container opblock-description-wrapper">
<Callbacks callbacks={Map(operation.get("callbacks"))} />
</div> : "" }
{
isOAS3() && requestBody && this.state.parametersVisible &&
<div className="opblock-section">
<div className="opblock-section-header">
<h4 className={`opblock-title parameter__name ${requestBody.get("required") && "required"}`}>Request body</h4>
<label>
<ContentType
value={this.state.requestBodyContentType}
contentTypes={ requestBody.get("content").keySeq() }
onChange={(val) => this.setState({ requestBodyContentType: val })}
className="body-param-content-type" />
</label>
</div>
<div className="opblock-description-wrapper">
<RequestBody
requestBody={requestBody}
contentType={this.state.requestBodyContentType}/>
</div>
</div>
}
</div>
)
}
}
export default OAS3ComponentWrapFactory(Parameters)

View File

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

View File

@@ -0,0 +1,13 @@
import React from "react"
import { OAS3ComponentWrapFactory } from "../helpers"
export default OAS3ComponentWrapFactory((props) => {
const { Ori } = props
return <span>
<Ori {...props} />
<small style={{ backgroundColor: "#89bf04" }}>
<pre className="version">OAS3</pre>
</small>
</span>
})

View File

@@ -0,0 +1,67 @@
import { createSelector } from "reselect"
import { Map } from "immutable"
import { isOAS3 as isOAS3Helper, isSwagger2 as isSwagger2Helper } from "./helpers"
// Helpers
function onlyOAS3(selector) {
return (ori, system) => (...args) => {
const spec = system.getSystem().specSelectors.specJson()
if(isOAS3Helper(spec)) {
return selector(...args)
} else {
return ori(...args)
}
}
}
const state = state => {
return state || Map()
}
const nullSelector = createSelector(() => null)
const OAS3NullSelector = onlyOAS3(nullSelector)
const specJson = createSelector(
state,
spec => spec.get("json", Map())
)
const specResolved = createSelector(
state,
spec => spec.get("resolved", Map())
)
const spec = state => {
let res = specResolved(state)
if(res.count() < 1)
res = specJson(state)
return res
}
// Wrappers
export const definitions = onlyOAS3(createSelector(
spec,
spec => spec.getIn(["components", "schemas"]) || Map()
))
export const host = OAS3NullSelector
export const basePath = OAS3NullSelector
export const consumes = OAS3NullSelector
export const produces = OAS3NullSelector
export const schemes = OAS3NullSelector
// New selectors
export const isOAS3 = (ori, system) => () => {
const spec = system.getSystem().specSelectors.specJson()
return isOAS3Helper(spec)
}
export const isSwagger2 = (ori, system) => () => {
const spec = system.getSystem().specSelectors.specJson()
return isSwagger2Helper(spec)
}

View File

@@ -46,6 +46,15 @@ export const spec = state => {
return res return res
} }
export const isOAS3 = createSelector(
// isOAS3 is stubbed out here to work around an issue with injecting more selectors
// in the OAS3 plugin, and to ensure that the function is always available.
// It's not perfect, but our hybrid (core+plugin code) implementation for OAS3
// needs this. //KS
spec,
() => false
)
export const info = createSelector( export const info = createSelector(
spec, spec,
spec => returnSelfOrNewMap(spec && spec.get("info")) spec => returnSelfOrNewMap(spec && spec.get("info"))

View File

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

View File

@@ -1,4 +1,5 @@
import BasePreset from "./base" import BasePreset from "./base"
import OAS3Plugin from "../plugins/oas3"
// Just the base, for now. // Just the base, for now.
@@ -6,5 +7,6 @@ export default function PresetApis() {
return [ return [
BasePreset, BasePreset,
OAS3Plugin
] ]
} }

View File

@@ -52,6 +52,7 @@ import ObjectModel from "core/components/object-model"
import ArrayModel from "core/components/array-model" import ArrayModel from "core/components/array-model"
import PrimitiveModel from "core/components/primitive-model" import PrimitiveModel from "core/components/primitive-model"
import TryItOutButton from "core/components/try-it-out-button" import TryItOutButton from "core/components/try-it-out-button"
import VersionStamp from "core/components/version-stamp"
import Markdown from "core/components/providers/markdown" import Markdown from "core/components/providers/markdown"
@@ -105,7 +106,8 @@ export default function() {
PrimitiveModel, PrimitiveModel,
TryItOutButton, TryItOutButton,
Markdown, Markdown,
BaseLayout BaseLayout,
VersionStamp
} }
} }

View File

@@ -76,7 +76,7 @@ export default class Store {
this.boundSystem = Object.assign({}, this.boundSystem = Object.assign({},
this.getRootInjects(), this.getRootInjects(),
this.getWrappedAndBoundActions(dispatch), this.getWrappedAndBoundActions(dispatch),
this.getBoundSelectors(getState, this.getSystem), this.getWrappedAndBoundSelectors(getState, this.getSystem),
this.getStateThunks(getState), this.getStateThunks(getState),
this.getFn(), this.getFn(),
this.getConfigs() this.getConfigs()
@@ -176,6 +176,36 @@ export default class Store {
}) })
} }
getWrappedAndBoundSelectors(getState, getSystem) {
let selectorGroups = this.getBoundSelectors(getState, getSystem)
return objMap(selectorGroups, (selectors, selectorGroupName) => {
let stateName = [selectorGroupName.slice(0, -9)] // selectors = 9 chars
let wrappers = this.system.statePlugins[stateName].wrapSelectors
if(wrappers) {
return objMap(selectors, (selector, selectorName) => {
let wrap = wrappers[selectorName]
if(!wrap) {
return selector
}
if(!Array.isArray(wrap)) {
wrap = [wrap]
}
return wrap.reduce((acc, fn) => {
let wrappedSelector = (...args) => {
return fn(acc, this.getSystem())(getState().getIn(stateName), ...args)
}
if(!isFn(wrappedSelector)) {
throw new TypeError("wrapSelector needs to return a function that returns a new function (ie the wrapped action)")
}
return wrappedSelector
}, selector || Function.prototype)
})
}
return selectors
})
}
getStates(state) { getStates(state) {
return Object.keys(this.system.statePlugins).reduce((obj, key) => { return Object.keys(this.system.statePlugins).reduce((obj, key) => {
obj[key] = state.get(key) obj[key] = state.get(key)
@@ -197,8 +227,17 @@ export default class Store {
} }
getComponents(component) { getComponents(component) {
if(typeof component !== "undefined") const res = this.system.components[component]
if(Array.isArray(res)) {
return res.reduce((ori, wrapper) => {
return wrapper(ori, this.getSystem())
})
}
if(typeof component !== "undefined") {
return this.system.components[component] return this.system.components[component]
}
return this.system.components return this.system.components
} }
@@ -291,6 +330,24 @@ function systemExtend(dest={}, src={}) {
return dest return dest
} }
// Wrap components
// Parses existing components in the system, and prepares them for wrapping via getComponents
if(src.wrapComponents) {
objMap(src.wrapComponents, (wrapperFn, key) => {
const ori = dest.components[key]
if(ori && Array.isArray(ori)) {
dest.components[key] = ori.concat([wrapperFn])
} else if(ori) {
dest.components[key] = [ori, wrapperFn]
} else {
dest.components[key] = null
}
})
delete src.wrapComponents
}
// Account for wrapActions, make it an array and append to it // Account for wrapActions, make it an array and append to it
// Modifies `src` // Modifies `src`
// 80% of this code is just safe traversal. We need to address that ( ie: use a lib ) // 80% of this code is just safe traversal. We need to address that ( ie: use a lib )

View File

@@ -468,6 +468,18 @@ export const validateFile = ( val ) => {
} }
} }
export const validateBoolean = ( val ) => {
if ( !(val === "true" || val === "false" || val === true || val === false) ) {
return "Value must be a boolean"
}
}
export const validateString = ( val ) => {
if ( val && typeof val !== "string" ) {
return "Value must be a string"
}
}
// validation of parameters before execute // validation of parameters before execute
export const validateParam = (param, isXml) => { export const validateParam = (param, isXml) => {
let errors = [] let errors = []
@@ -475,22 +487,35 @@ export const validateParam = (param, isXml) => {
let required = param.get("required") let required = param.get("required")
let type = param.get("type") let type = param.get("type")
let stringCheck = type === "string" && !value /*
let arrayCheck = type === "array" && Array.isArray(value) && !value.length If the parameter is required OR the parameter has a value (meaning optional, but filled in)
let listCheck = type === "array" && Im.List.isList(value) && !value.count() then we should do our validation routine.
let fileCheck = type === "file" && !(value instanceof win.File) Only bother validating the parameter if the type was specified.
let nullUndefinedCheck = value === null || value === undefined */
if ( type && (required || value) ) {
// These checks should evaluate to true if the parameter's value is valid
let stringCheck = type === "string" && value && !validateString(value)
let arrayCheck = type === "array" && Array.isArray(value) && value.length
let listCheck = type === "array" && Im.List.isList(value) && value.count()
let fileCheck = type === "file" && value instanceof win.File
let booleanCheck = type === "boolean" && !validateBoolean(value)
let numberCheck = type === "number" && !validateNumber(value) // validateNumber returns undefined if the value is a number
let integerCheck = type === "integer" && !validateInteger(value) // validateInteger returns undefined if the value is an integer
if ( required && (stringCheck || arrayCheck || listCheck || fileCheck || nullUndefinedCheck) ) { 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 ( value === null || value === undefined ) { if ( type === "string" ) {
return errors let err = validateString(value)
} if (!err) return errors
errors.push(err)
if ( type === "number" ) { } else if ( type === "boolean" ) {
let err = validateBoolean(value)
if (!err) return errors
errors.push(err)
} else if ( type === "number" ) {
let err = validateNumber(value) let err = validateNumber(value)
if (!err) return errors if (!err) return errors
errors.push(err) errors.push(err)
@@ -512,6 +537,8 @@ export const validateParam = (param, isXml) => {
err = validateNumber(item) err = validateNumber(item)
} else if (itemType === "integer") { } else if (itemType === "integer") {
err = validateInteger(item) err = validateInteger(item)
} else if (itemType === "string") {
err = validateString(item)
} }
if ( err ) { if ( err ) {
@@ -523,6 +550,7 @@ export const validateParam = (param, isXml) => {
if (!err) return errors if (!err) return errors
errors.push(err) errors.push(err)
} }
}
return errors return errors
} }

View File

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

View File

@@ -21,6 +21,10 @@ select
background: #f7f7f7; background: #f7f7f7;
} }
&.invalid {
@include invalidFormElement();
}
} }
.opblock-body select .opblock-body select
@@ -55,10 +59,7 @@ input[type=file]
&.invalid &.invalid
{ {
animation: shake .4s 1; @include invalidFormElement();
border-color: $_color-delete;
background: lighten($_color-delete, 35%);
} }
} }

View File

@@ -74,6 +74,11 @@ body
{ {
border-color: $color; border-color: $color;
} }
.tab-header .tab-item.active h4 span:after
{
background: $color;
}
} }
@@ -144,6 +149,51 @@ body
border-radius: 4px; border-radius: 4px;
box-shadow: 0 0 3px rgba(#000,.19); box-shadow: 0 0 3px rgba(#000,.19);
.tab-header
{
display: flex;
flex: 1;
.tab-item
{
padding: 0 40px;
cursor: pointer;
&:first-of-type
{
padding: 0 40px 0 0;
}
&.active
{
h4
{
span
{
position: relative;
&:after
{
position: absolute;
bottom: -15px;
left: 50%;
width: 120%;
height: 4px;
content: '';
transform: translateX(-50%);
background: #888;
}
}
}
}
}
}
&.is-open &.is-open
{ {
@@ -160,6 +210,8 @@ body
padding: 8px 20px; padding: 8px 20px;
min-height: 50px;
background: rgba(#fff,.8); background: rgba(#fff,.8);
box-shadow: 0 1px 2px rgba(#000,.1); box-shadow: 0 1px 2px rgba(#000,.1);
@@ -172,6 +224,7 @@ body
align-items: center; align-items: center;
margin: 0; margin: 0;
margin-left: auto;
@include text_headline(); @include text_headline();
@@ -643,6 +696,18 @@ body
} }
} }
.renderedMarkdown {
p {
@include text_body();
font-size: 14px;
margin-top: 0px;
margin-bottom: 0px;
}
}
.response-content-type {
padding-top: 1em;
}
@keyframes blinker @keyframes blinker
{ {

View File

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

View File

@@ -3,6 +3,13 @@
font-size: 12px; font-size: 12px;
font-weight: 300; font-weight: 300;
.deprecated
{
span, td {
color: #aaa !important;
}
}
@include text_code(); @include text_code();
&-toggle &-toggle
{ {
@@ -192,6 +199,11 @@ section.models
position: relative; position: relative;
top: 4px; top: 4px;
} }
&.deprecated
{
opacity: .5;
}
} }
@@ -202,6 +214,14 @@ section.models
@include text_headline(#555); @include text_headline(#555);
} }
.model-deprecated-warning
{
font-size: 16px;
font-weight: 600;
margin-right: 1em;
@include text_headline($_color-delete);
}
span span
{ {

View File

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

View File

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

View File

@@ -326,6 +326,122 @@ describe("bound system", function(){
}) })
describe("wrapSelectors", () => {
it("should wrap a selector and provide a reference to the original", function(){
// Given
const system = new System({
plugins: [
{
statePlugins: {
doge: {
selectors: {
wow: () => (system) => {
return "original"
}
}
}
}
},
{
statePlugins: {
doge: {
wrapSelectors: {
wow: (ori) => (system) => {
// Then
return ori() + " wrapper"
}
}
}
}
}
]
})
// When
var res = system.getSystem().dogeSelectors.wow(1)
expect(res).toEqual("original wrapper")
})
it("should provide a live reference to the system to a wrapper", function(done){
// Given
const mySystem = new System({
plugins: [
{
statePlugins: {
doge: {
selectors: {
wow: () => (system) => {
return "original"
}
}
}
}
},
{
statePlugins: {
doge: {
wrapSelectors: {
wow: (ori, system) => () => {
// Then
expect(mySystem.getSystem()).toEqual(system.getSystem())
done()
return ori() + " wrapper"
}
}
}
}
}
]
})
mySystem.getSystem().dogeSelectors.wow(1)
})
it("should provide the state as the first argument to the inner function", function(done){
// Given
const mySystem = new System({
state: {
doge: {
abc: "123"
}
},
plugins: [
{
statePlugins: {
doge: {
selectors: {
wow: () => (system) => {
return "original"
}
}
}
}
},
{
statePlugins: {
doge: {
wrapSelectors: {
wow: (ori, system) => (dogeState) => {
// Then
expect(dogeState.toJS().abc).toEqual("123")
done()
return ori() + " wrapper"
}
}
}
}
}
]
})
mySystem.getSystem().dogeSelectors.wow(1)
})
})
}) })
}) })

View File

@@ -0,0 +1,139 @@
import React from "react"
import expect from "expect"
import { render } from "enzyme"
import System from "core/system"
describe("wrapComponents", () => {
describe("should wrap a component and provide a reference to the original", () => {
it("with stateless components", function(){
// Given
const system = new System({
plugins: [
{
components: {
wow: ({ name }) => <div>{name} component</div>
}
},
{
wrapComponents: {
wow: (OriginalComponent) => (props) => {
return <container>
<OriginalComponent {...props}></OriginalComponent>
<OriginalComponent name="Wrapped"></OriginalComponent>
</container>
}
}
}
]
})
// When
var Component = system.getSystem().getComponents("wow")
const wrapper = render(<Component name="Normal" />)
const container = wrapper.children().first()
expect(container[0].name).toEqual("container")
const children = container.children()
expect(children.length).toEqual(2)
expect(children.eq(0).text()).toEqual("Normal component")
expect(children.eq(1).text()).toEqual("Wrapped component")
})
it("with React classes", function(){
class MyComponent extends React.Component {
render() {
return <div>{this.props.name} component</div>
}
}
// Given
const system = new System({
plugins: [
{
components: {
wow: MyComponent
}
},
{
wrapComponents: {
wow: (OriginalComponent) => {
return class WrapperComponent extends React.Component {
render() {
return <container>
<OriginalComponent {...this.props}></OriginalComponent>
<OriginalComponent name="Wrapped"></OriginalComponent>
</container>
}
}
}
}
}
]
})
// When
var Component = system.getSystem().getComponents("wow")
const wrapper = render(<Component name="Normal" />)
const container = wrapper.children().first()
expect(container[0].name).toEqual("container")
const children = container.children()
expect(children.length).toEqual(2)
expect(children.eq(0).text()).toEqual("Normal component")
expect(children.eq(1).text()).toEqual("Wrapped component")
})
})
it("should provide a reference to the system to the wrapper", function(){
// Given
const mySystem = new System({
plugins: [
{
// Make a selector
statePlugins: {
doge: {
selectors: {
wow: () => () => {
return "WOW much data"
}
}
}
}
},
{
// Create a component
components: {
wow: () => <div>Original component</div>
}
},
{
// Wrap the component and use the system
wrapComponents: {
wow: (OriginalComponent, system) => (props) => {
return <container>
<OriginalComponent {...props}></OriginalComponent>
<div>{system.dogeSelectors.wow()}</div>
</container>
}
}
}
]
})
// Then
var Component = mySystem.getSystem().getComponents("wow")
const wrapper = render(<Component name="Normal" />)
const container = wrapper.children().first()
expect(container[0].name).toEqual("container")
const children = container.children()
expect(children.length).toEqual(2)
expect(children.eq(0).text()).toEqual("Original component")
expect(children.eq(1).text()).toEqual("WOW much data")
})
})

View File

@@ -175,7 +175,19 @@ describe("utils", function() {
let param = null let param = null
let result = null let result = null
it("skips validation when `type` is not specified", function() {
// invalid type
param = fromJS({
required: false,
type: undefined,
value: ""
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
it("validates required strings", function() { it("validates required strings", function() {
// invalid string
param = fromJS({ param = fromJS({
required: true, required: true,
type: "string", type: "string",
@@ -183,9 +195,39 @@ describe("utils", function() {
}) })
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] ) expect( result ).toEqual( ["Required field is not provided"] )
// valid string
param = fromJS({
required: true,
type: "string",
value: "test string"
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
it("validates optional strings", function() {
// valid (empty) string
param = fromJS({
required: false,
type: "string",
value: ""
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
// valid string
param = fromJS({
required: false,
type: "string",
value: "test"
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}) })
it("validates required files", function() { it("validates required files", function() {
// invalid file
param = fromJS({ param = fromJS({
required: true, required: true,
type: "file", type: "file",
@@ -193,9 +235,48 @@ describe("utils", function() {
}) })
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] ) expect( result ).toEqual( ["Required field is not provided"] )
// valid file
param = fromJS({
required: true,
type: "file",
value: new win.File()
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
it("validates optional files", function() {
// invalid file
param = fromJS({
required: false,
type: "file",
value: "not a file"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Value must be a file"] )
// valid (empty) file
param = fromJS({
required: false,
type: "file",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
// valid file
param = fromJS({
required: false,
type: "file",
value: new win.File()
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}) })
it("validates required arrays", function() { it("validates required arrays", function() {
// invalid (empty) array
param = fromJS({ param = fromJS({
required: true, required: true,
type: "array", type: "array",
@@ -204,75 +285,51 @@ describe("utils", function() {
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] ) expect( result ).toEqual( ["Required field is not provided"] )
// invalid (not an array)
param = fromJS({ param = fromJS({
required: true, required: true,
type: "array", type: "array",
value: [] value: undefined
}) })
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] ) expect( result ).toEqual( ["Required field is not provided"] )
})
it("validates numbers", function() { // invalid array, items do not match correct type
// string instead of a number
param = fromJS({ param = fromJS({
required: false, required: true,
type: "number", type: "array",
value: "test" value: [1],
items: {
type: "string"
}
}) })
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( ["Value must be a number"] ) expect( result ).toEqual( [{index: 0, error: "Value must be a string"}] )
// undefined value // valid array, with no 'type' for items
param = fromJS({ param = fromJS({
required: false, required: true,
type: "number", type: "array",
value: undefined value: ["1"]
}) })
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( [] ) expect( result ).toEqual( [] )
// null value // valid array, items match type
param = fromJS({ param = fromJS({
required: false, required: true,
type: "number", type: "array",
value: null value: ["1"],
items: {
type: "string"
}
}) })
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( [] ) expect( result ).toEqual( [] )
}) })
it("validates integers", function() { it("validates optional arrays", function() {
// string instead of integer // valid, empty array
param = fromJS({
required: false,
type: "integer",
value: "test"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Value must be an integer"] )
// undefined value
param = fromJS({
required: false,
type: "integer",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
// null value
param = fromJS({
required: false,
type: "integer",
value: null
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
it("validates arrays", function() {
// empty array
param = fromJS({ param = fromJS({
required: false, required: false,
type: "array", type: "array",
@@ -281,7 +338,7 @@ describe("utils", function() {
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( [] ) expect( result ).toEqual( [] )
// numbers // invalid, items do not match correct type
param = fromJS({ param = fromJS({
required: false, required: false,
type: "array", type: "array",
@@ -293,17 +350,209 @@ describe("utils", function() {
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( [{index: 0, error: "Value must be a number"}] ) expect( result ).toEqual( [{index: 0, error: "Value must be a number"}] )
// integers // valid
param = fromJS({ param = fromJS({
required: false, required: false,
type: "array", type: "array",
value: ["not", "numbers"], value: ["test"],
items: { items: {
type: "integer" type: "string"
} }
}) })
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( [{index: 0, error: "Value must be an integer"}, {index: 1, error: "Value must be an integer"}] ) expect( result ).toEqual( [] )
})
it("validates required booleans", function() {
// invalid boolean value
param = fromJS({
required: true,
type: "boolean",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
// invalid boolean value (not a boolean)
param = fromJS({
required: true,
type: "boolean",
value: "test string"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
// valid boolean value
param = fromJS({
required: true,
type: "boolean",
value: "true"
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
// valid boolean value
param = fromJS({
required: true,
type: "boolean",
value: false
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
it("validates optional booleans", function() {
// valid (empty) boolean value
param = fromJS({
required: false,
type: "boolean",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
// invalid boolean value (not a boolean)
param = fromJS({
required: false,
type: "boolean",
value: "test string"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Value must be a boolean"] )
// valid boolean value
param = fromJS({
required: false,
type: "boolean",
value: "true"
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
// valid boolean value
param = fromJS({
required: false,
type: "boolean",
value: false
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
it("validates required numbers", function() {
// invalid number, string instead of a number
param = fromJS({
required: true,
type: "number",
value: "test"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
// invalid number, undefined value
param = fromJS({
required: true,
type: "number",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
// valid number
param = fromJS({
required: true,
type: "number",
value: 10
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
it("validates optional numbers", function() {
// invalid number, string instead of a number
param = fromJS({
required: false,
type: "number",
value: "test"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Value must be a number"] )
// valid (empty) number
param = fromJS({
required: false,
type: "number",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
// valid number
param = fromJS({
required: false,
type: "number",
value: 10
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
it("validates required integers", function() {
// invalid integer, string instead of an integer
param = fromJS({
required: true,
type: "integer",
value: "test"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
// invalid integer, undefined value
param = fromJS({
required: true,
type: "integer",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] )
// valid integer
param = fromJS({
required: true,
type: "integer",
value: 10
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
it("validates optional integers", function() {
// invalid integer, string instead of an integer
param = fromJS({
required: false,
type: "integer",
value: "test"
})
result = validateParam( param, false )
expect( result ).toEqual( ["Value must be an integer"] )
// valid (empty) integer
param = fromJS({
required: false,
type: "integer",
value: undefined
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
// integers
param = fromJS({
required: false,
type: "integer",
value: 10
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
}) })
}) })