Browse Source

merge master

bubble
Aaron Loo 7 years ago
parent
commit
700d547529
56 changed files with 1588 additions and 165 deletions
  1. +1
    -1
      .travis.yml
  2. +14
    -9
      README.md
  3. +16
    -8
      dist/swagger-ui-bundle.js
  4. +1
    -1
      dist/swagger-ui-bundle.js.map
  5. +1
    -1
      dist/swagger-ui-standalone-preset.js
  6. +1
    -1
      dist/swagger-ui.css
  7. +2
    -2
      dist/swagger-ui.js
  8. +1
    -1
      dist/swagger-ui.js.map
  9. +2
    -2
      package.json
  10. +4
    -2
      src/core/components/content-type.jsx
  11. +2
    -1
      src/core/components/info.jsx
  12. +4
    -3
      src/core/components/layout-utils.jsx
  13. +2
    -2
      src/core/components/model-example.jsx
  14. +26
    -8
      src/core/components/model.jsx
  15. +49
    -3
      src/core/components/object-model.jsx
  16. +1
    -0
      src/core/components/operation.jsx
  17. +18
    -0
      src/core/components/operations.jsx
  18. +3
    -1
      src/core/components/parameters.jsx
  19. +12
    -8
      src/core/components/providers/markdown.jsx
  20. +38
    -5
      src/core/components/response.jsx
  21. +3
    -2
      src/core/components/responses.jsx
  22. +12
    -0
      src/core/components/version-stamp.jsx
  23. +13
    -3
      src/core/index.js
  24. +13
    -11
      src/core/json-schema-components.js
  25. +11
    -1
      src/core/oauth2-authorize.js
  26. +16
    -1
      src/core/plugins/auth/actions.js
  27. +49
    -0
      src/core/plugins/oas3/components/callbacks.jsx
  28. +9
    -0
      src/core/plugins/oas3/components/index.js
  29. +37
    -0
      src/core/plugins/oas3/components/operation-link.jsx
  30. +42
    -0
      src/core/plugins/oas3/components/request-body.jsx
  31. +36
    -0
      src/core/plugins/oas3/helpers.js
  32. +17
    -0
      src/core/plugins/oas3/index.js
  33. +15
    -0
      src/core/plugins/oas3/wrap-components/index.js
  34. +11
    -0
      src/core/plugins/oas3/wrap-components/markdown.js
  35. +37
    -0
      src/core/plugins/oas3/wrap-components/model.jsx
  36. +5
    -0
      src/core/plugins/oas3/wrap-components/online-validator-badge.js
  37. +181
    -0
      src/core/plugins/oas3/wrap-components/parameters.jsx
  38. +5
    -0
      src/core/plugins/oas3/wrap-components/try-it-out-button.jsx
  39. +13
    -0
      src/core/plugins/oas3/wrap-components/version-stamp.jsx
  40. +67
    -0
      src/core/plugins/oas3/wrap-selectors.js
  41. +9
    -0
      src/core/plugins/spec/selectors.js
  42. +1
    -2
      src/core/plugins/view/root-injects.js
  43. +2
    -0
      src/core/presets/apis.js
  44. +3
    -1
      src/core/presets/base.js
  45. +59
    -2
      src/core/system.js
  46. +73
    -45
      src/core/utils.js
  47. +11
    -1
      src/style/_buttons.scss
  48. +5
    -4
      src/style/_form.scss
  49. +65
    -0
      src/style/_layout.scss
  50. +6
    -0
      src/style/_mixins.scss
  51. +20
    -0
      src/style/_models.scss
  52. +4
    -0
      src/style/_table.scss
  53. +3
    -0
      test/.eslintrc
  54. +116
    -0
      test/core/system/system.js
  55. +139
    -0
      test/core/system/wrapComponent.js
  56. +282
    -33
      test/core/utils.js

+ 1
- 1
.travis.yml View File

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


+ 14
- 9
README.md 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).

## 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.


+ 16
- 8
dist/swagger-ui-bundle.js
File diff suppressed because it is too large
View File


+ 1
- 1
dist/swagger-ui-bundle.js.map 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":""}

+ 1
- 1
dist/swagger-ui-standalone-preset.js
File diff suppressed because it is too large
View File


+ 1
- 1
dist/swagger-ui.css
File diff suppressed because it is too large
View File


+ 2
- 2
dist/swagger-ui.js
File diff suppressed because it is too large
View File


+ 1
- 1
dist/swagger-ui.js.map 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":""}

+ 2
- 2
package.json View File

@@ -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",


+ 4
- 2
src/core/components/content-type.jsx View File

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


+ 2
- 1
src/core/components/info.jsx View File

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


+ 4
- 3
src/core/components/layout-utils.jsx View File

@@ -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) {


+ 2
- 2
src/core/components/model-example.jsx View File

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


+ 26
- 8
src/core/components/model.jsx View File

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

+ 49
- 3
src/core/components/object-model.jsx View File

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

+ 1
- 0
src/core/components/operation.jsx View File

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


+ 18
- 0
src/core/components/operations.jsx View File

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


+ 3
- 1
src/core/components/parameters.jsx View File

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


+ 12
- 8
src/core/components/providers/markdown.jsx View File

@@ -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(/&quot;/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(/&quot;/g, "\"")
}
}

export function sanitizer(str) {
return sanitize(str, sanitizeOptions)
}

+ 38
- 5
src/core/components/response.jsx View File

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


+ 3
- 2
src/core/components/responses.jsx View File

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


+ 12
- 0
src/core/components/version-stamp.jsx 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

+ 13
- 3
src/core/index.js View File

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


+ 13
- 11
src/core/json-schema-components.js View File

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

+ 11
- 1
src/core/oauth2-authorize.js View File

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



+ 16
- 1
src/core/plugins/auth/actions.js View File

@@ -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() || {}


+ 49
- 0
src/core/plugins/oas3/components/callbacks.jsx 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

+ 9
- 0
src/core/plugins/oas3/components/index.js 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
}

+ 37
- 0
src/core/plugins/oas3/components/operation-link.jsx 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

+ 42
- 0
src/core/plugins/oas3/components/request-body.jsx 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

+ 36
- 0
src/core/plugins/oas3/helpers.js 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
}
}
}

+ 17
- 0
src/core/plugins/oas3/index.js 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
}
}
}
}

+ 15
- 0
src/core/plugins/oas3/wrap-components/index.js 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
}

+ 11
- 0
src/core/plugins/oas3/wrap-components/markdown.js 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})

+ 37
- 0
src/core/plugins/oas3/wrap-components/model.jsx 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)

+ 5
- 0
src/core/plugins/oas3/wrap-components/online-validator-badge.js 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)

+ 181
- 0
src/core/plugins/oas3/wrap-components/parameters.jsx 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)

+ 5
- 0
src/core/plugins/oas3/wrap-components/try-it-out-button.jsx View File

@@ -0,0 +1,5 @@
import { OAS3ComponentWrapFactory } from "../helpers"

export default OAS3ComponentWrapFactory(() => {
return null
})

+ 13
- 0
src/core/plugins/oas3/wrap-components/version-stamp.jsx 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>
})

+ 67
- 0
src/core/plugins/oas3/wrap-selectors.js 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)
}

+ 9
- 0
src/core/plugins/spec/selectors.js View File

@@ -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"))


+ 1
- 2
src/core/plugins/view/root-injects.js View File

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


+ 2
- 0
src/core/presets/apis.js View File

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

+ 3
- 1
src/core/presets/base.js View File

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



+ 59
- 2
src/core/system.js View File

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


+ 73
- 45
src/core/utils.js 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
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


+ 11
- 1
src/style/_buttons.scss View File

@@ -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();
}
}

+ 5
- 4
src/style/_form.scss View File

@@ -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();
}
}



+ 65
- 0
src/style/_layout.scss View File

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


+ 6
- 0
src/style/_mixins.scss View File

@@ -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%);
}

+ 20
- 0
src/style/_models.scss View File

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


+ 4
- 0
src/style/_table.scss View File

@@ -97,6 +97,10 @@ table
width: 100%;
max-width: 340px;
}

select {
border-width: 1px;
}
}

.parameter__name


+ 3
- 0
test/.eslintrc View File

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

test/core/system.js → test/core/system/system.js 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)
})
})

})

})

+ 139
- 0
test/core/system/wrapComponent.js 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")
})
})

+ 282
- 33
test/core/utils.js View File

@@ -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( [] )
})
})



Loading…
Cancel
Save