@@ -16,7 +16,7 @@ deploy: | |||
email: apiteam@swagger.io | |||
skip_cleanup: true | |||
api_key: | |||
secure: "IJkLaACa+rfERf1O5nwlqOyuo9sbul3FBhBt4Un9P+DvEet3AoDPV9NQVLd8SkmQYKGbGQWF4BIdjrO5nqFD6Te+JTeUX5Uo/DFS/fu9qw1xv0dQpvbJFuoYnnFlbzGTEs4CFa8lbu3ZromFHQGOQxRobjsG1Kf0dWFSSzmND3g=" | |||
secure: "YKk5L1BL4oAixvLjWp+i85fNFXK85HKOlUt6QypkZkt23My5aywuYsv5VCLjjOtuWc72zbmOzP82DTBsuRswCRViXWCiNYhl42QTdvadHu0uIlM/FL6aNlvPpzXIws4bMvz1aYOTzFTnSnNuvCTzF1daW0+2ClOo3r0nLEdDfFg=" | |||
on: | |||
tags: true | |||
repo: swagger-api/swagger-ui | |||
@@ -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). | |||
## 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 | |||
3.0.21 | 2017-07-24 | 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) | | |||
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.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) | | |||
Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes | |||
------------------ | ------------ | -------------------------- | ----- | |||
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) | |||
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) | |||
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.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 | |||
@@ -96,7 +97,8 @@ To use swagger-ui's bundles, you should take a look at the [source of swagger-ui | |||
#### OAuth2 configuration | |||
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 | |||
--- | --- | |||
@@ -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 | |||
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 | |||
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({...}) | |||
@@ -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. | |||
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. | |||
domNode | The HTML DOM element inside which SwaggerUi will put the user interface for swagger. Overrides `dom_id`. | |||
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. | |||
@@ -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":""} |
@@ -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":""} |
@@ -1,6 +1,6 @@ | |||
{ | |||
"name": "swagger-ui", | |||
"version": "3.0.21", | |||
"version": "3.1.2", | |||
"main": "dist/swagger-ui.js", | |||
"repository": "git@github.com:swagger-api/swagger-ui.git", | |||
"contributors": [ | |||
@@ -43,7 +43,6 @@ | |||
"ieee754": "^1.1.8", | |||
"immutable": "^3.x.x", | |||
"js-yaml": "^3.5.5", | |||
"less": "2.7.1", | |||
"lodash": "4.17.2", | |||
"matcher": "^0.1.2", | |||
"memoizee": "0.4.1", | |||
@@ -58,6 +57,7 @@ | |||
"react-height": "^2.0.0", | |||
"react-hot-loader": "1.3.1", | |||
"react-immutable-proptypes": "2.1.0", | |||
"react-markdown": "^2.5.0", | |||
"react-motion": "0.4.4", | |||
"react-object-inspector": "0.2.1", | |||
"react-redux": "^4.x.x", | |||
@@ -8,7 +8,7 @@ const noop = ()=>{} | |||
export default class ContentType extends React.Component { | |||
static propTypes = { | |||
contentTypes: PropTypes.oneOfType([ImPropTypes.list, ImPropTypes.set]), | |||
contentTypes: PropTypes.oneOfType([ImPropTypes.list, ImPropTypes.set, ImPropTypes.seq]), | |||
value: PropTypes.string, | |||
onChange: PropTypes.func, | |||
className: PropTypes.string | |||
@@ -22,7 +22,9 @@ export default class ContentType extends React.Component { | |||
componentDidMount() { | |||
// Needed to populate the form, initially | |||
this.props.onChange(this.props.contentTypes.first()) | |||
if(this.props.contentTypes) { | |||
this.props.onChange(this.props.contentTypes.first()) | |||
} | |||
} | |||
onChangeWrapper = e => this.props.onChange(e.target.value) | |||
@@ -88,12 +88,13 @@ export default class Info extends React.Component { | |||
const { url:externalDocsUrl, description:externalDocsDescription } = (externalDocs || fromJS({})).toJS() | |||
const Markdown = getComponent("Markdown") | |||
const VersionStamp = getComponent("VersionStamp") | |||
return ( | |||
<div className="info"> | |||
<hgroup className="main"> | |||
<h2 className="title" >{ title } | |||
{ version && <small><pre className="version"> { version } </pre></small> } | |||
{ version && <VersionStamp version={version}></VersionStamp> } | |||
</h2> | |||
{ host || basePath ? <Path host={ host } basePath={ basePath } /> : null } | |||
{ url && <a target="_blank" href={ url }><span className="url"> { url } </span></a> } | |||
@@ -129,7 +129,8 @@ export class Select extends React.Component { | |||
value: PropTypes.any, | |||
onChange: PropTypes.func, | |||
multiple: PropTypes.bool, | |||
allowEmptyValue: PropTypes.bool | |||
allowEmptyValue: PropTypes.bool, | |||
className: PropTypes.string | |||
} | |||
static defaultProps = { | |||
@@ -142,7 +143,7 @@ export class Select extends React.Component { | |||
let value | |||
if (props.value !== undefined) { | |||
if (props.value) { | |||
value = props.value | |||
} else { | |||
value = props.multiple ? [""] : "" | |||
@@ -178,7 +179,7 @@ export class Select extends React.Component { | |||
let value = this.state.value.toJS ? this.state.value.toJS() : this.state.value | |||
return ( | |||
<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 } | |||
{ | |||
allowedValues.map(function (item, key) { | |||
@@ -35,9 +35,9 @@ export default class ModelExample extends React.Component { | |||
<li className={ "tabitem" + ( isExecute || this.state.activeTab === "example" ? " active" : "") }> | |||
<a className="tablinks" data-name="example" onClick={ this.activeTab }>Example Value</a> | |||
</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> | |||
</li> | |||
</li> : null } | |||
</ul> | |||
<div> | |||
{ | |||
@@ -17,6 +17,9 @@ export default class Model extends Component { | |||
if ( ref.indexOf("#/definitions/") !== -1 ) { | |||
return ref.replace(/^.*#\/definitions\//, "") | |||
} | |||
if ( ref.indexOf("#/components/schemas/") !== -1 ) { | |||
return ref.replace("#/components/schemas/", "") | |||
} | |||
} | |||
getRefSchema =( model )=> { | |||
@@ -26,7 +29,7 @@ export default class Model extends Component { | |||
} | |||
render () { | |||
let { schema, getComponent, required, name, isRef } = this.props | |||
let { getComponent, specSelectors, schema, required, name, isRef } = this.props | |||
let ObjectModel = getComponent("ObjectModel") | |||
let ArrayModel = getComponent("ArrayModel") | |||
let PrimitiveModel = getComponent("PrimitiveModel") | |||
@@ -34,6 +37,8 @@ export default class Model extends Component { | |||
let modelName = $$ref && this.getModelName( $$ref ) | |||
let modelSchema, type | |||
const deprecated = specSelectors.isOAS3() && schema.get("deprecated") | |||
if ( schema && (schema.get("type") || schema.get("properties")) ) { | |||
modelSchema = schema | |||
} else if ( $$ref ) { | |||
@@ -47,17 +52,30 @@ export default class Model extends Component { | |||
switch(type) { | |||
case "object": | |||
return <ObjectModel className="object" { ...this.props } schema={ modelSchema } | |||
name={ name || modelName } required={ required } | |||
isRef={ isRef!== undefined ? isRef : !!$$ref }/> | |||
return <ObjectModel | |||
className="object" { ...this.props } | |||
schema={ modelSchema } | |||
name={ name || modelName } | |||
deprecated={deprecated} | |||
isRef={ isRef!== undefined ? isRef : !!$$ref } /> | |||
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 "number": | |||
case "integer": | |||
case "boolean": | |||
default: | |||
return <PrimitiveModel getComponent={ getComponent } schema={ modelSchema } required={ required }/> | |||
} | |||
return <PrimitiveModel | |||
{ ...this.props } | |||
getComponent={ getComponent } | |||
schema={ modelSchema } | |||
name={ name || modelName } | |||
deprecated={deprecated} | |||
required={ required }/> } | |||
} | |||
} | |||
} |
@@ -18,7 +18,7 @@ export default class ObjectModel extends Component { | |||
render(){ | |||
let { schema, name, isRef, getComponent, depth, ...props } = this.props | |||
let { expandDepth } = this.props | |||
let { expandDepth, specSelectors } = this.props | |||
let description = schema.get("description") | |||
let properties = schema.get("properties") | |||
let additionalProperties = schema.get("additionalProperties") | |||
@@ -37,7 +37,11 @@ export default class ObjectModel extends Component { | |||
isRef ? <JumpToPathSection name={ name }/> : "" | |||
} | |||
</span>) | |||
const anyOf = specSelectors.isOAS3() ? schema.get("anyOf") : null | |||
const oneOf = specSelectors.isOAS3() ? schema.get("oneOf") : null | |||
const not = specSelectors.isOAS3() ? schema.get("not") : null | |||
const titleEl = title && <span className="model-title"> | |||
{ isRef && schema.get("$$ref") && <span className="model-hint">{ schema.get("$$ref") }</span> } | |||
<span className="model-title__text">{ title }</span> | |||
@@ -95,6 +99,48 @@ export default class ObjectModel extends Component { | |||
</td> | |||
</tr> | |||
} | |||
{ | |||
!anyOf ? null | |||
: <tr> | |||
<td>{ "anyOf ->" }</td> | |||
<td> | |||
{anyOf.map((schema, k) => { | |||
return <div key={k}><Model { ...props } required={ false } | |||
getComponent={ getComponent } | |||
schema={ schema } | |||
depth={ depth + 1 } /></div> | |||
})} | |||
</td> | |||
</tr> | |||
} | |||
{ | |||
!oneOf ? null | |||
: <tr> | |||
<td>{ "oneOf ->" }</td> | |||
<td> | |||
{oneOf.map((schema, k) => { | |||
return <div key={k}><Model { ...props } required={ false } | |||
getComponent={ getComponent } | |||
schema={ schema } | |||
depth={ depth + 1 } /></div> | |||
})} | |||
</td> | |||
</tr> | |||
} | |||
{ | |||
!not ? null | |||
: <tr> | |||
<td>{ "not ->" }</td> | |||
<td> | |||
{not.map((schema, k) => { | |||
return <div key={k}><Model { ...props } required={ false } | |||
getComponent={ getComponent } | |||
schema={ schema } | |||
depth={ depth + 1 } /></div> | |||
})} | |||
</td> | |||
</tr> | |||
} | |||
</tbody></table> | |||
} | |||
</span> | |||
@@ -102,4 +148,4 @@ export default class ObjectModel extends Component { | |||
</ModelCollapse> | |||
</span> | |||
} | |||
} | |||
} |
@@ -210,6 +210,7 @@ export default class Operation extends PureComponent { | |||
} | |||
<Parameters | |||
parameters={parameters} | |||
operation={operation} | |||
onChangeKey={onChangeKey} | |||
onTryoutClick = { this.onTryoutClick } | |||
onCancelClick = { this.onCancelClick } | |||
@@ -66,6 +66,8 @@ export default class Operations extends React.Component { | |||
taggedOps.map( (tagObj, tag) => { | |||
let operations = tagObj.get("operations") | |||
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 showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list") | |||
@@ -89,6 +91,22 @@ export default class Operations extends React.Component { | |||
</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)}> | |||
<svg className="arrow" width="20" height="20"> | |||
<use xlinkHref={showTag ? "#large-arrow-down" : "#large-arrow"} /> | |||
@@ -72,7 +72,9 @@ export default class Parameters extends Component { | |||
return ( | |||
<div className="opblock-section"> | |||
<div className="opblock-section-header"> | |||
<h4 className="opblock-title">Parameters</h4> | |||
<div className="tab-header"> | |||
<h4 className="opblock-title">Parameters</h4> | |||
</div> | |||
{ allowTryItOut ? ( | |||
<TryItOutButton enabled={ tryItOutEnabled } onCancelClick={ onCancelClick } onTryoutClick={ onTryoutClick } /> | |||
) : null } | |||
@@ -3,15 +3,8 @@ import PropTypes from "prop-types" | |||
import Remarkable from "react-remarkable" | |||
import sanitize from "sanitize-html" | |||
const sanitizeOptions = { | |||
textFilter: function(text) { | |||
return text | |||
.replace(/"/g, "\"") | |||
} | |||
} | |||
function Markdown({ source }) { | |||
const sanitized = sanitize(source, sanitizeOptions) | |||
const sanitized = sanitizer(source) | |||
// sometimes the sanitizer returns "undefined" as a string | |||
if(!source || !sanitized || sanitized === "undefined") { | |||
@@ -31,3 +24,14 @@ Markdown.propTypes = { | |||
} | |||
export default Markdown | |||
const sanitizeOptions = { | |||
textFilter: function(text) { | |||
return text | |||
.replace(/"/g, "\"") | |||
} | |||
} | |||
export function sanitizer(str) { | |||
return sanitize(str, sanitizeOptions) | |||
} |
@@ -1,6 +1,6 @@ | |||
import React from "react" | |||
import PropTypes from "prop-types" | |||
import { fromJS } from "immutable" | |||
import { fromJS, Seq } from "immutable" | |||
import { getSampleSchema } from "core/utils" | |||
const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => { | |||
@@ -31,6 +31,13 @@ const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => { | |||
} | |||
export default class Response extends React.Component { | |||
constructor(props, context) { | |||
super(props, context) | |||
this.state = { | |||
responseContentType: "" | |||
} | |||
} | |||
static propTypes = { | |||
code: PropTypes.string.isRequired, | |||
@@ -59,16 +66,29 @@ export default class Response extends React.Component { | |||
} = this.props | |||
let { inferSchema } = fn | |||
let { isOAS3 } = specSelectors | |||
let schema = inferSchema(response.toJS()) | |||
let headers = response.get("headers") | |||
let examples = response.get("examples") | |||
let links = response.get("links") | |||
const Headers = getComponent("headers") | |||
const HighlightCode = getComponent("highlightCode") | |||
const ModelExample = getComponent("modelExample") | |||
const Markdown = getComponent( "Markdown" ) | |||
let sampleResponse = schema ? getSampleSchema(schema, contentType, { includeReadOnly: true }) : null | |||
const OperationLink = getComponent("operationLink") | |||
const ContentType = getComponent("contentType") | |||
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 ) | |||
return ( | |||
@@ -82,6 +102,12 @@ export default class Response extends React.Component { | |||
<Markdown source={ response.get( "description" ) } /> | |||
</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 ? ( | |||
<ModelExample | |||
getComponent={ getComponent } | |||
@@ -94,8 +120,15 @@ export default class Response extends React.Component { | |||
<Headers headers={ headers }/> | |||
) : 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> | |||
) | |||
} | |||
@@ -42,13 +42,13 @@ export default class Responses extends React.Component { | |||
<div className="responses-wrapper"> | |||
<div className="opblock-section-header"> | |||
<h4>Responses</h4> | |||
<label> | |||
{ specSelectors.isOAS3() ? null : <label> | |||
<span>Response content type</span> | |||
<ContentType value={producesValue} | |||
onChange={this.onChangeProducesWrapper} | |||
contentTypes={produces} | |||
className="execute-content-type"/> | |||
</label> | |||
</label> } | |||
</div> | |||
<div className="responses-inner"> | |||
{ | |||
@@ -68,6 +68,7 @@ export default class Responses extends React.Component { | |||
<tr className="responses-header"> | |||
<td className="col col_header response-col_status">Code</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> | |||
</thead> | |||
<tbody> | |||
@@ -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 |
@@ -23,6 +23,7 @@ module.exports = function SwaggerUI(opts) { | |||
const defaults = { | |||
// Some general settings, that we floated to the top | |||
dom_id: null, | |||
domNode: null, | |||
spec: {}, | |||
url: "", | |||
urls: null, | |||
@@ -99,6 +100,12 @@ module.exports = function SwaggerUI(opts) { | |||
let localConfig = system.specSelectors.getLocalConfig ? system.specSelectors.getLocalConfig() : {} | |||
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) | |||
if (fetchedConfig !== null) { | |||
@@ -112,10 +119,13 @@ module.exports = function SwaggerUI(opts) { | |||
} | |||
} | |||
if(mergedConfig.dom_id) { | |||
system.render(mergedConfig.dom_id, "App") | |||
if(mergedConfig.domNode) { | |||
system.render(mergedConfig.domNode, "App") | |||
} else if(mergedConfig.dom_id) { | |||
let domNode = document.querySelector(mergedConfig.dom_id) | |||
system.render(domNode, "App") | |||
} else { | |||
console.error("Skipped rendering: no `dom_id` was specified") | |||
console.error("Skipped rendering: no `dom_id` or `domNode` was specified") | |||
} | |||
return system | |||
@@ -57,7 +57,8 @@ export class JsonSchema_string extends Component { | |||
if ( enumValue ) { | |||
const Select = getComponent("Select") | |||
return (<Select allowedValues={ enumValue } | |||
return (<Select className={ errors.length ? "invalid" : ""} | |||
allowedValues={ enumValue } | |||
value={ value } | |||
allowEmptyValue={ !required } | |||
onChange={ this.onEnumChange }/>) | |||
@@ -121,6 +122,7 @@ export class JsonSchema_array extends PureComponent { | |||
render() { | |||
let { getComponent, required, schema, fn } = this.props | |||
let errors = schema.errors || [] | |||
let itemSchema = fn.inferSchema(schema.items) | |||
const JsonSchemaForm = getComponent("JsonSchemaForm") | |||
@@ -131,19 +133,17 @@ export class JsonSchema_array extends PureComponent { | |||
if ( enumValue ) { | |||
const Select = getComponent("Select") | |||
return (<Select multiple={ true } | |||
return (<Select className={ errors.length ? "invalid" : ""} | |||
multiple={ true } | |||
value={ value } | |||
allowedValues={ enumValue } | |||
allowEmptyValue={ !required } | |||
onChange={ this.onEnumChange }/>) | |||
} | |||
let errors = schema.errors || [] | |||
return ( | |||
<div> | |||
{ !value || value.count() < 1 ? | |||
(errors.length ? <span style={{ color: "red", fortWeight: "bold" }}>{ errors[0] }</span> : null) : | |||
{ !value || value.count() < 1 ? null : | |||
value.map( (item,i) => { | |||
let schema = Object.assign({}, itemSchema) | |||
if ( errors.length ) { | |||
@@ -153,12 +153,12 @@ export class JsonSchema_array extends PureComponent { | |||
return ( | |||
<div key={i} className="json-schema-form-item"> | |||
<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> | |||
) | |||
}).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> | |||
) | |||
} | |||
@@ -170,12 +170,14 @@ export class JsonSchema_boolean extends Component { | |||
onEnumChange = (val) => this.props.onChange(val) | |||
render() { | |||
let { getComponent, required, value } = this.props | |||
let { getComponent, value, schema } = this.props | |||
let errors = schema.errors || [] | |||
const Select = getComponent("Select") | |||
return (<Select value={ String(value) } | |||
return (<Select className={ errors.length ? "invalid" : ""} | |||
value={ String(value) } | |||
allowedValues={ fromJS(["true", "false"]) } | |||
allowEmptyValue={ !required } | |||
allowEmptyValue={ true } | |||
onChange={ this.onEnumChange }/>) | |||
} | |||
} |
@@ -68,11 +68,21 @@ export default function authorize ( { auth, authActions, errActions, configs, au | |||
// pass action authorizeOauth2 and authentication data through window | |||
// 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 = { | |||
auth: auth, | |||
state: state, | |||
redirectUrl: redirectUrl, | |||
callback: flow === "implicit" ? authActions.preAuthorizeImplicit : authActions.authorizeAccessCode, | |||
callback: callback, | |||
errCb: errActions.newAuthErr | |||
} | |||
@@ -111,7 +111,7 @@ export const authorizeApplication = ( auth ) => ( { authActions } ) => { | |||
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 form = { | |||
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}) | |||
} | |||
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 } ) => { | |||
let { body, query={}, headers={}, name, url, auth } = data | |||
let { additionalQueryStringParams } = authSelectors.getConfigs() || {} | |||
@@ -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 |
@@ -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 | |||
} |
@@ -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 |
@@ -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 |
@@ -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 | |||
} | |||
} | |||
} |
@@ -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 | |||
} | |||
} | |||
} | |||
} |
@@ -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 | |||
} |
@@ -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}) |
@@ -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) |
@@ -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) |
@@ -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) |
@@ -0,0 +1,5 @@ | |||
import { OAS3ComponentWrapFactory } from "../helpers" | |||
export default OAS3ComponentWrapFactory(() => { | |||
return null | |||
}) |
@@ -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> | |||
}) |
@@ -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) | |||
} |
@@ -46,6 +46,15 @@ export const spec = state => { | |||
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( | |||
spec, | |||
spec => returnSelfOrNewMap(spec && spec.get("info")) | |||
@@ -58,8 +58,7 @@ export const makeMappedContainer = (getSystem, getStore, memGetComponent, getCom | |||
} | |||
export const render = (getSystem, getStore, getComponent, getComponents, dom) => { | |||
let domNode = document.querySelector(dom) | |||
export const render = (getSystem, getStore, getComponent, getComponents, domNode) => { | |||
let App = (getComponent(getSystem, getStore, getComponents, "App", "root")) | |||
ReactDOM.render(( <App/> ), domNode) | |||
} | |||
@@ -1,4 +1,5 @@ | |||
import BasePreset from "./base" | |||
import OAS3Plugin from "../plugins/oas3" | |||
// Just the base, for now. | |||
@@ -6,5 +7,6 @@ export default function PresetApis() { | |||
return [ | |||
BasePreset, | |||
OAS3Plugin | |||
] | |||
} |
@@ -52,6 +52,7 @@ import ObjectModel from "core/components/object-model" | |||
import ArrayModel from "core/components/array-model" | |||
import PrimitiveModel from "core/components/primitive-model" | |||
import TryItOutButton from "core/components/try-it-out-button" | |||
import VersionStamp from "core/components/version-stamp" | |||
import Markdown from "core/components/providers/markdown" | |||
@@ -105,7 +106,8 @@ export default function() { | |||
PrimitiveModel, | |||
TryItOutButton, | |||
Markdown, | |||
BaseLayout | |||
BaseLayout, | |||
VersionStamp | |||
} | |||
} | |||
@@ -76,7 +76,7 @@ export default class Store { | |||
this.boundSystem = Object.assign({}, | |||
this.getRootInjects(), | |||
this.getWrappedAndBoundActions(dispatch), | |||
this.getBoundSelectors(getState, this.getSystem), | |||
this.getWrappedAndBoundSelectors(getState, this.getSystem), | |||
this.getStateThunks(getState), | |||
this.getFn(), | |||
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) { | |||
return Object.keys(this.system.statePlugins).reduce((obj, key) => { | |||
obj[key] = state.get(key) | |||
@@ -197,8 +227,17 @@ export default class Store { | |||
} | |||
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 | |||
} | |||
@@ -291,6 +330,24 @@ function systemExtend(dest={}, src={}) { | |||
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 | |||
// Modifies `src` | |||
// 80% of this code is just safe traversal. We need to address that ( ie: use a lib ) | |||
@@ -468,6 +468,18 @@ export const validateFile = ( val ) => { | |||
} | |||
} | |||
export const validateBoolean = ( val ) => { | |||
if ( !(val === "true" || val === "false" || val === true || val === false) ) { | |||
return "Value must be a boolean" | |||
} | |||
} | |||
export const validateString = ( val ) => { | |||
if ( val && typeof val !== "string" ) { | |||
return "Value must be a string" | |||
} | |||
} | |||
// validation of parameters before execute | |||
export const validateParam = (param, isXml) => { | |||
let errors = [] | |||
@@ -475,53 +487,69 @@ export const validateParam = (param, isXml) => { | |||
let required = param.get("required") | |||
let type = param.get("type") | |||
let stringCheck = type === "string" && !value | |||
let arrayCheck = type === "array" && Array.isArray(value) && !value.length | |||
let listCheck = type === "array" && Im.List.isList(value) && !value.count() | |||
let fileCheck = type === "file" && !(value instanceof win.File) | |||
let nullUndefinedCheck = value === null || value === undefined | |||
if ( required && (stringCheck || arrayCheck || listCheck || fileCheck || nullUndefinedCheck) ) { | |||
errors.push("Required field is not provided") | |||
return errors | |||
} | |||
if ( value === null || value === undefined ) { | |||
return errors | |||
} | |||
if ( type === "number" ) { | |||
let err = validateNumber(value) | |||
if (!err) return errors | |||
errors.push(err) | |||
} else if ( type === "integer" ) { | |||
let err = validateInteger(value) | |||
if (!err) return errors | |||
errors.push(err) | |||
} else if ( type === "array" ) { | |||
let itemType | |||
if ( !value.count() ) { return errors } | |||
itemType = param.getIn(["items", "type"]) | |||
value.forEach((item, index) => { | |||
let err | |||
/* | |||
If the parameter is required OR the parameter has a value (meaning optional, but filled in) | |||
then we should do our validation routine. | |||
Only bother validating the parameter if the type was specified. | |||
*/ | |||
if ( type && (required || value) ) { | |||
// These checks should evaluate to true if the parameter's value is valid | |||
let stringCheck = type === "string" && value && !validateString(value) | |||
let arrayCheck = type === "array" && Array.isArray(value) && value.length | |||
let listCheck = type === "array" && Im.List.isList(value) && value.count() | |||
let fileCheck = type === "file" && value instanceof win.File | |||
let booleanCheck = type === "boolean" && !validateBoolean(value) | |||
let numberCheck = type === "number" && !validateNumber(value) // validateNumber returns undefined if the value is a number | |||
let integerCheck = type === "integer" && !validateInteger(value) // validateInteger returns undefined if the value is an integer | |||
if ( required && !(stringCheck || arrayCheck || listCheck || fileCheck || booleanCheck || numberCheck || integerCheck) ) { | |||
errors.push("Required field is not provided") | |||
return errors | |||
} | |||
if (itemType === "number") { | |||
err = validateNumber(item) | |||
} else if (itemType === "integer") { | |||
err = validateInteger(item) | |||
} | |||
if ( type === "string" ) { | |||
let err = validateString(value) | |||
if (!err) return errors | |||
errors.push(err) | |||
} else if ( type === "boolean" ) { | |||
let err = validateBoolean(value) | |||
if (!err) return errors | |||
errors.push(err) | |||
} else if ( type === "number" ) { | |||
let err = validateNumber(value) | |||
if (!err) return errors | |||
errors.push(err) | |||
} else if ( type === "integer" ) { | |||
let err = validateInteger(value) | |||
if (!err) return errors | |||
errors.push(err) | |||
} else if ( type === "array" ) { | |||
let itemType | |||
if ( !value.count() ) { return errors } | |||
itemType = param.getIn(["items", "type"]) | |||
value.forEach((item, index) => { | |||
let err | |||
if (itemType === "number") { | |||
err = validateNumber(item) | |||
} else if (itemType === "integer") { | |||
err = validateInteger(item) | |||
} else if (itemType === "string") { | |||
err = validateString(item) | |||
} | |||
if ( err ) { | |||
errors.push({ index: index, error: err}) | |||
} | |||
}) | |||
} else if ( type === "file" ) { | |||
let err = validateFile(value) | |||
if (!err) return errors | |||
errors.push(err) | |||
if ( err ) { | |||
errors.push({ index: index, error: err}) | |||
} | |||
}) | |||
} else if ( type === "file" ) { | |||
let err = validateFile(value) | |||
if (!err) return errors | |||
errors.push(err) | |||
} | |||
} | |||
return errors | |||
@@ -14,6 +14,12 @@ | |||
@include text_headline(); | |||
&.btn-sm | |||
{ | |||
font-size: 12px; | |||
padding: 4px 23px; | |||
} | |||
&[disabled] | |||
{ | |||
cursor: not-allowed; | |||
@@ -165,6 +171,10 @@ | |||
button | |||
{ | |||
cursor: pointer; | |||
outline: none; | |||
&.invalid | |||
{ | |||
@include invalidFormElement(); | |||
} | |||
} |
@@ -21,6 +21,10 @@ select | |||
background: #f7f7f7; | |||
} | |||
&.invalid { | |||
@include invalidFormElement(); | |||
} | |||
} | |||
.opblock-body select | |||
@@ -55,10 +59,7 @@ input[type=file] | |||
&.invalid | |||
{ | |||
animation: shake .4s 1; | |||
border-color: $_color-delete; | |||
background: lighten($_color-delete, 35%); | |||
@include invalidFormElement(); | |||
} | |||
} | |||
@@ -74,6 +74,11 @@ body | |||
{ | |||
border-color: $color; | |||
} | |||
.tab-header .tab-item.active h4 span:after | |||
{ | |||
background: $color; | |||
} | |||
} | |||
@@ -144,6 +149,51 @@ body | |||
border-radius: 4px; | |||
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 | |||
{ | |||
@@ -160,6 +210,8 @@ body | |||
padding: 8px 20px; | |||
min-height: 50px; | |||
background: rgba(#fff,.8); | |||
box-shadow: 0 1px 2px rgba(#000,.1); | |||
@@ -172,6 +224,7 @@ body | |||
align-items: center; | |||
margin: 0; | |||
margin-left: auto; | |||
@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 | |||
{ | |||
@@ -166,3 +166,9 @@ $browser-context: 16; | |||
@warn 'Breakpoint mixin supports: tablet, mobile, desktop'; | |||
} | |||
} | |||
@mixin invalidFormElement() { | |||
animation: shake .4s 1; | |||
border-color: $_color-delete; | |||
background: lighten($_color-delete, 35%); | |||
} |
@@ -3,6 +3,13 @@ | |||
font-size: 12px; | |||
font-weight: 300; | |||
.deprecated | |||
{ | |||
span, td { | |||
color: #aaa !important; | |||
} | |||
} | |||
@include text_code(); | |||
&-toggle | |||
{ | |||
@@ -192,6 +199,11 @@ section.models | |||
position: relative; | |||
top: 4px; | |||
} | |||
&.deprecated | |||
{ | |||
opacity: .5; | |||
} | |||
} | |||
@@ -202,6 +214,14 @@ section.models | |||
@include text_headline(#555); | |||
} | |||
.model-deprecated-warning | |||
{ | |||
font-size: 16px; | |||
font-weight: 600; | |||
margin-right: 1em; | |||
@include text_headline($_color-delete); | |||
} | |||
span | |||
{ | |||
@@ -97,6 +97,10 @@ table | |||
width: 100%; | |||
max-width: 340px; | |||
} | |||
select { | |||
border-width: 1px; | |||
} | |||
} | |||
.parameter__name | |||
@@ -1,2 +1,5 @@ | |||
env: | |||
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 |
@@ -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) | |||
}) | |||
}) | |||
}) | |||
}) |
@@ -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") | |||
}) | |||
}) |
@@ -175,7 +175,19 @@ describe("utils", function() { | |||
let param = 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() { | |||
// invalid string | |||
param = fromJS({ | |||
required: true, | |||
type: "string", | |||
@@ -183,9 +195,39 @@ describe("utils", function() { | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( ["Required field is not provided"] ) | |||
// valid string | |||
param = fromJS({ | |||
required: true, | |||
type: "string", | |||
value: "test string" | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( [] ) | |||
}) | |||
it("validates optional strings", function() { | |||
// valid (empty) string | |||
param = fromJS({ | |||
required: false, | |||
type: "string", | |||
value: "" | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( [] ) | |||
// valid string | |||
param = fromJS({ | |||
required: false, | |||
type: "string", | |||
value: "test" | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( [] ) | |||
}) | |||
it("validates required files", function() { | |||
// invalid file | |||
param = fromJS({ | |||
required: true, | |||
type: "file", | |||
@@ -193,9 +235,48 @@ describe("utils", function() { | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( ["Required field is not provided"] ) | |||
// valid file | |||
param = fromJS({ | |||
required: true, | |||
type: "file", | |||
value: new win.File() | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( [] ) | |||
}) | |||
it("validates optional files", function() { | |||
// invalid file | |||
param = fromJS({ | |||
required: false, | |||
type: "file", | |||
value: "not a file" | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( ["Value must be a file"] ) | |||
// valid (empty) file | |||
param = fromJS({ | |||
required: false, | |||
type: "file", | |||
value: undefined | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( [] ) | |||
// valid file | |||
param = fromJS({ | |||
required: false, | |||
type: "file", | |||
value: new win.File() | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( [] ) | |||
}) | |||
it("validates required arrays", function() { | |||
// invalid (empty) array | |||
param = fromJS({ | |||
required: true, | |||
type: "array", | |||
@@ -204,17 +285,191 @@ describe("utils", function() { | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( ["Required field is not provided"] ) | |||
// invalid (not an array) | |||
param = fromJS({ | |||
required: true, | |||
type: "array", | |||
value: undefined | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( ["Required field is not provided"] ) | |||
// invalid array, items do not match correct type | |||
param = fromJS({ | |||
required: true, | |||
type: "array", | |||
value: [1], | |||
items: { | |||
type: "string" | |||
} | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( [{index: 0, error: "Value must be a string"}] ) | |||
// valid array, with no 'type' for items | |||
param = fromJS({ | |||
required: true, | |||
type: "array", | |||
value: ["1"] | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( [] ) | |||
// valid array, items match type | |||
param = fromJS({ | |||
required: true, | |||
type: "array", | |||
value: ["1"], | |||
items: { | |||
type: "string" | |||
} | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( [] ) | |||
}) | |||
it("validates optional arrays", function() { | |||
// valid, empty array | |||
param = fromJS({ | |||
required: false, | |||
type: "array", | |||
value: [] | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( [] ) | |||
// invalid, items do not match correct type | |||
param = fromJS({ | |||
required: false, | |||
type: "array", | |||
value: ["number"], | |||
items: { | |||
type: "number" | |||
} | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( [{index: 0, error: "Value must be a number"}] ) | |||
// valid | |||
param = fromJS({ | |||
required: false, | |||
type: "array", | |||
value: ["test"], | |||
items: { | |||
type: "string" | |||
} | |||
}) | |||
result = validateParam( param, false ) | |||
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 numbers", function() { | |||
// string instead of a number | |||
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", | |||
@@ -223,7 +478,7 @@ describe("utils", function() { | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( ["Value must be a number"] ) | |||
// undefined value | |||
// valid (empty) number | |||
param = fromJS({ | |||
required: false, | |||
type: "number", | |||
@@ -232,78 +487,72 @@ describe("utils", function() { | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( [] ) | |||
// null value | |||
// valid number | |||
param = fromJS({ | |||
required: false, | |||
type: "number", | |||
value: null | |||
value: 10 | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( [] ) | |||
}) | |||
it("validates integers", function() { | |||
// string instead of integer | |||
it("validates required integers", function() { | |||
// invalid integer, string instead of an integer | |||
param = fromJS({ | |||
required: false, | |||
required: true, | |||
type: "integer", | |||
value: "test" | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( ["Value must be an integer"] ) | |||
expect( result ).toEqual( ["Required field is not provided"] ) | |||
// undefined value | |||
// invalid integer, undefined value | |||
param = fromJS({ | |||
required: false, | |||
required: true, | |||
type: "integer", | |||
value: undefined | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( [] ) | |||
expect( result ).toEqual( ["Required field is not provided"] ) | |||
// null value | |||
// valid integer | |||
param = fromJS({ | |||
required: false, | |||
required: true, | |||
type: "integer", | |||
value: null | |||
value: 10 | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( [] ) | |||
}) | |||
it("validates arrays", function() { | |||
// empty array | |||
it("validates optional integers", function() { | |||
// invalid integer, string instead of an integer | |||
param = fromJS({ | |||
required: false, | |||
type: "array", | |||
value: [] | |||
type: "integer", | |||
value: "test" | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( [] ) | |||
expect( result ).toEqual( ["Value must be an integer"] ) | |||
// numbers | |||
// valid (empty) integer | |||
param = fromJS({ | |||
required: false, | |||
type: "array", | |||
value: ["number"], | |||
items: { | |||
type: "number" | |||
} | |||
type: "integer", | |||
value: undefined | |||
}) | |||
result = validateParam( param, false ) | |||
expect( result ).toEqual( [{index: 0, error: "Value must be a number"}] ) | |||
expect( result ).toEqual( [] ) | |||
// integers | |||
param = fromJS({ | |||
required: false, | |||
type: "array", | |||
value: ["not", "numbers"], | |||
items: { | |||
type: "integer" | |||
} | |||
type: "integer", | |||
value: 10 | |||
}) | |||
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( [] ) | |||
}) | |||
}) | |||