Merge branch 'master' into ft/default_model_rendering
This commit is contained in:
10
README.md
10
README.md
@@ -10,10 +10,10 @@
|
||||
|
||||
As a brand new version, written from the ground up, there are some known issues and unimplemented features. Check out the [Known Issues](#known-issues) section for more details.
|
||||
|
||||
This repo publishes to two different NPM packages:
|
||||
This repository publishes to two different NPM modules:
|
||||
|
||||
* [swagger-ui](https://www.npmjs.com/package/swagger-ui) is intended for use as a node module.
|
||||
* [swagger-ui-dist](https://www.npmjs.com/package/swagger-ui-dist) comes pre-bundled with all dependencies and can be incorporated directly in a webapp.
|
||||
* [swagger-ui](https://www.npmjs.com/package/swagger-ui) is a traditional npm module intended for use in JavaScript web application projects that are capable of resolving dependencies (via Webpack, Browserify, etc).
|
||||
* [swagger-ui-dist](https://www.npmjs.com/package/swagger-ui-dist) is a dependency-free module that includes everything you need to serve Swagger-UI in a server-side project, or a web project that can't resolve npm module dependencies.
|
||||
|
||||
For the older version of swagger-ui, refer to the [*2.x branch*](https://github.com/swagger-api/swagger-ui/tree/2.x).
|
||||
|
||||
@@ -22,7 +22,7 @@ The OpenAPI Specification has undergone 5 revisions since initial creation in 20
|
||||
|
||||
Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes
|
||||
------------------ | ------------ | -------------------------- | -----
|
||||
3.1.7 | 2017-08-25 | 2.0, 3.0 | [tag v3.1.7](https://github.com/swagger-api/swagger-ui/tree/v3.1.7)
|
||||
3.2.2 | 2017-09-22 | 2.0, 3.0 | [tag v3.2.2](https://github.com/swagger-api/swagger-ui/tree/v3.2.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)
|
||||
@@ -46,7 +46,7 @@ Will start nginx with swagger-ui on port 80.
|
||||
Or you can provide your own swagger.json on your host
|
||||
|
||||
```
|
||||
docker run -p 80:8080 -e "SWAGGER_JSON=/foo/swagger.json" -v /bar:/foo swaggerapi/swagger-ui
|
||||
docker run -p 80:8080 -e SWAGGER_JSON=/foo/swagger.json -v /bar:/foo swaggerapi/swagger-ui
|
||||
```
|
||||
|
||||
##### Prerequisites
|
||||
|
||||
@@ -11,7 +11,11 @@
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
qp = (window.location.hash || location.search).substring(1);
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1);
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&")
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
|
||||
|
||||
6
dist/oauth2-redirect.html
vendored
6
dist/oauth2-redirect.html
vendored
@@ -11,7 +11,11 @@
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
qp = (window.location.hash || location.search).substring(1);
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1);
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&")
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
|
||||
|
||||
27
dist/swagger-ui-bundle.js
vendored
27
dist/swagger-ui-bundle.js
vendored
File diff suppressed because one or more lines are too long
2
dist/swagger-ui-bundle.js.map
vendored
2
dist/swagger-ui-bundle.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;AAoyKA;;;;;;AAm/EA;;;;;;;;;;;;;;;;;;;;;;;;;;AAy7TA;;;;;;;;;;;;;;AAs8JA;;;;;;;;;AA2hpBA;;;;;AA41QA;AAm4DA;;;;;;AAo4YA;;;;;;AA8jaA;AAumvBA","sourceRoot":""}
|
||||
{"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;AA4kMA;;;;;;AA65DA;;;;;;;;;;;;;;;;;;;;;;;;;;AAq9TA;;;;;;;;;;;;;;AAs8JA;;;;;;;;;AAo/pBA;;;;;AAk6QA;;;;;AAynBA;AAi0CA;;;;;;AAq/YA;;;;;;AAojaA;AA8lvBA","sourceRoot":""}
|
||||
6
dist/swagger-ui-standalone-preset.js
vendored
6
dist/swagger-ui-standalone-preset.js
vendored
File diff suppressed because one or more lines are too long
2
dist/swagger-ui-standalone-preset.js.map
vendored
2
dist/swagger-ui-standalone-preset.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"swagger-ui-standalone-preset.js","sources":["webpack:///swagger-ui-standalone-preset.js"],"mappings":"AAAA;;;;;AA21CA;;;;;;AAypFA","sourceRoot":""}
|
||||
{"version":3,"file":"swagger-ui-standalone-preset.js","sources":["webpack:///swagger-ui-standalone-preset.js"],"mappings":"AAAA;;;;;AA80CA;;;;;;AAqpFA","sourceRoot":""}
|
||||
2
dist/swagger-ui.css
vendored
2
dist/swagger-ui.css
vendored
File diff suppressed because one or more lines are too long
4
dist/swagger-ui.js
vendored
4
dist/swagger-ui.js
vendored
File diff suppressed because one or more lines are too long
2
dist/swagger-ui.js.map
vendored
2
dist/swagger-ui.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AA8idA","sourceRoot":""}
|
||||
{"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AAkzeA","sourceRoot":""}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "swagger-ui",
|
||||
"version": "3.1.7",
|
||||
"version": "3.2.2",
|
||||
"main": "dist/swagger-ui.js",
|
||||
"repository": "git@github.com:swagger-api/swagger-ui.git",
|
||||
"contributors": [
|
||||
@@ -41,6 +41,9 @@
|
||||
"dependencies": {
|
||||
"base64-js": "^1.2.0",
|
||||
"brace": "0.7.0",
|
||||
"classnames": "^2.2.5",
|
||||
"commonmark": "^0.28.1",
|
||||
"css.escape": "1.5.1",
|
||||
"deep-extend": "0.4.1",
|
||||
"expect": "1.20.2",
|
||||
"getbase": "^2.8.2",
|
||||
@@ -65,17 +68,17 @@
|
||||
"react-motion": "0.4.4",
|
||||
"react-object-inspector": "0.2.1",
|
||||
"react-redux": "^4.x.x",
|
||||
"react-remarkable": "1.1.1",
|
||||
"react-split-pane": "0.1.57",
|
||||
"redux": "^3.x.x",
|
||||
"redux-immutable": "3.0.8",
|
||||
"redux-logger": "*",
|
||||
"remarkable": "^1.7.1",
|
||||
"reselect": "2.5.3",
|
||||
"sanitize-html": "^1.14.1",
|
||||
"scroll-to-element": "^2.0.0",
|
||||
"serialize-error": "2.0.0",
|
||||
"shallowequal": "0.2.2",
|
||||
"swagger-client": "3.0.20",
|
||||
"swagger-client": "^3.1.2",
|
||||
"url-parse": "^1.1.8",
|
||||
"whatwg-fetch": "0.11.1",
|
||||
"worker-loader": "^0.7.1",
|
||||
|
||||
@@ -28,15 +28,20 @@ export default class ArrayModel extends Component {
|
||||
<span className="model-title__text">{ title }</span>
|
||||
</span>
|
||||
|
||||
/*
|
||||
Note: we set `name={null}` in <Model> below because we don't want
|
||||
the name of the current Model passed (and displayed) as the name of the array element Model
|
||||
*/
|
||||
|
||||
return <span className="model">
|
||||
<ModelCollapse title={titleEl} collapsed={ depth > expandDepth } collapsedContent="[...]">
|
||||
[
|
||||
<span><Model { ...this.props } schema={ items } required={ false } depth={ depth + 1 } /></span>
|
||||
<span><Model { ...this.props } name={null} schema={ items } required={ false } depth={ depth + 1 } /></span>
|
||||
]
|
||||
{
|
||||
properties.size ? <span>
|
||||
{ properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={propStyle}>
|
||||
<br />{ `${key}:`}{ String(v) }</span>)
|
||||
<br />{ key }: { String(v) }</span>)
|
||||
}<br /></span>
|
||||
: null
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ export default class BaseLayout extends React.Component {
|
||||
errActions: PropTypes.object.isRequired,
|
||||
specActions: PropTypes.object.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
oas3Selectors: PropTypes.object.isRequired,
|
||||
oas3Actions: PropTypes.object.isRequired,
|
||||
layoutSelectors: PropTypes.object.isRequired,
|
||||
layoutActions: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired
|
||||
@@ -19,7 +21,14 @@ export default class BaseLayout extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
let { specSelectors, specActions, getComponent, layoutSelectors } = this.props
|
||||
let {
|
||||
specSelectors,
|
||||
specActions,
|
||||
getComponent,
|
||||
layoutSelectors,
|
||||
oas3Selectors,
|
||||
oas3Actions
|
||||
} = this.props
|
||||
|
||||
let info = specSelectors.info()
|
||||
let url = specSelectors.url()
|
||||
@@ -28,6 +37,7 @@ export default class BaseLayout extends React.Component {
|
||||
let securityDefinitions = specSelectors.securityDefinitions()
|
||||
let externalDocs = specSelectors.externalDocs()
|
||||
let schemes = specSelectors.schemes()
|
||||
let servers = specSelectors.servers()
|
||||
|
||||
let Info = getComponent("info")
|
||||
let Operations = getComponent("operations", true)
|
||||
@@ -35,6 +45,7 @@ export default class BaseLayout extends React.Component {
|
||||
let AuthorizeBtn = getComponent("authorizeBtn", true)
|
||||
let Row = getComponent("Row")
|
||||
let Col = getComponent("Col")
|
||||
let Servers = getComponent("Servers")
|
||||
let Errors = getComponent("errors", true)
|
||||
|
||||
let isLoading = specSelectors.loadingStatus() === "loading"
|
||||
@@ -82,6 +93,22 @@ export default class BaseLayout extends React.Component {
|
||||
</div>
|
||||
) : null }
|
||||
|
||||
{ servers && servers.size ? (
|
||||
<div className="server-container">
|
||||
<Col className="servers wrapper" mobile={12}>
|
||||
<Servers
|
||||
servers={servers}
|
||||
currentServer={oas3Selectors.selectedServer()}
|
||||
setSelectedServer={oas3Actions.setSelectedServer}
|
||||
setServerVariableValue={oas3Actions.setServerVariableValue}
|
||||
getServerVariable={oas3Selectors.serverVariableValue}
|
||||
getEffectiveServerValue={oas3Selectors.serverEffectiveValue}
|
||||
/>
|
||||
</Col>
|
||||
</div>
|
||||
|
||||
) : null}
|
||||
|
||||
{
|
||||
filter === null || filter === false ? null :
|
||||
<div className="filter-container">
|
||||
|
||||
@@ -30,39 +30,38 @@ export default class Model extends Component {
|
||||
|
||||
render () {
|
||||
let { getComponent, specSelectors, schema, required, name, isRef } = this.props
|
||||
let ObjectModel = getComponent("ObjectModel")
|
||||
let ArrayModel = getComponent("ArrayModel")
|
||||
let PrimitiveModel = getComponent("PrimitiveModel")
|
||||
const ObjectModel = getComponent("ObjectModel")
|
||||
const ArrayModel = getComponent("ArrayModel")
|
||||
const PrimitiveModel = getComponent("PrimitiveModel")
|
||||
let type = "object"
|
||||
let $$ref = schema && schema.get("$$ref")
|
||||
let modelName = $$ref && this.getModelName( $$ref )
|
||||
let modelSchema, type
|
||||
|
||||
// If we weren't passed a `name` but have a ref, grab the name from the ref
|
||||
if ( !name && $$ref ) {
|
||||
name = this.getModelName( $$ref )
|
||||
}
|
||||
// If we weren't passed a `schema` but have a ref, grab the schema from the ref
|
||||
if ( !schema && $$ref ) {
|
||||
schema = this.getRefSchema( name )
|
||||
}
|
||||
|
||||
const deprecated = specSelectors.isOAS3() && schema.get("deprecated")
|
||||
|
||||
if ( schema && (schema.get("type") || schema.get("properties")) ) {
|
||||
modelSchema = schema
|
||||
} else if ( $$ref ) {
|
||||
modelSchema = this.getRefSchema( modelName )
|
||||
}
|
||||
|
||||
type = modelSchema && modelSchema.get("type")
|
||||
if ( !type && modelSchema && modelSchema.get("properties") ) {
|
||||
type = "object"
|
||||
}
|
||||
isRef = isRef !== undefined ? isRef : !!$$ref
|
||||
type = schema && schema.get("type") || type
|
||||
|
||||
switch(type) {
|
||||
case "object":
|
||||
return <ObjectModel
|
||||
className="object" { ...this.props }
|
||||
schema={ modelSchema }
|
||||
name={ modelName || name }
|
||||
schema={ schema }
|
||||
name={ name }
|
||||
deprecated={deprecated}
|
||||
isRef={ isRef!== undefined ? isRef : !!$$ref } />
|
||||
isRef={ isRef } />
|
||||
case "array":
|
||||
return <ArrayModel
|
||||
className="array" { ...this.props }
|
||||
schema={ modelSchema }
|
||||
name={ modelName || name }
|
||||
schema={ schema }
|
||||
name={ name }
|
||||
deprecated={deprecated}
|
||||
required={ required } />
|
||||
case "string":
|
||||
@@ -73,9 +72,10 @@ export default class Model extends Component {
|
||||
return <PrimitiveModel
|
||||
{ ...this.props }
|
||||
getComponent={ getComponent }
|
||||
schema={ modelSchema }
|
||||
name={ modelName || name }
|
||||
schema={ schema }
|
||||
name={ name }
|
||||
deprecated={deprecated}
|
||||
required={ required }/> }
|
||||
required={ required }/>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ export default class Models extends Component {
|
||||
<ModelWrapper name={ name }
|
||||
expandDepth={ defaultModelExpandDepth }
|
||||
schema={ model }
|
||||
isRef={ true }
|
||||
getComponent={ getComponent }
|
||||
specSelectors={ specSelectors }/>
|
||||
</div>
|
||||
|
||||
@@ -28,6 +28,7 @@ export default class Operation extends PureComponent {
|
||||
authSelectors: PropTypes.object,
|
||||
specActions: PropTypes.object.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
oas3Actions: PropTypes.object.isRequired,
|
||||
layoutActions: PropTypes.object.isRequired,
|
||||
layoutSelectors: PropTypes.object.isRequired,
|
||||
fn: PropTypes.object.isRequired,
|
||||
@@ -117,7 +118,8 @@ export default class Operation extends PureComponent {
|
||||
specSelectors,
|
||||
authActions,
|
||||
authSelectors,
|
||||
getConfigs
|
||||
getConfigs,
|
||||
oas3Actions
|
||||
} = this.props
|
||||
|
||||
let summary = operation.get("summary")
|
||||
@@ -147,7 +149,7 @@ export default class Operation extends PureComponent {
|
||||
const isDeepLinkingEnabled = deepLinking && deepLinking !== "false"
|
||||
|
||||
// Merge in Live Response
|
||||
if(response && response.size > 0) {
|
||||
if(responses && response && response.size > 0) {
|
||||
let notDocumented = !responses.get(String(response.get("status")))
|
||||
response = response.set("notDocumented", notDocumented)
|
||||
}
|
||||
@@ -266,6 +268,7 @@ export default class Operation extends PureComponent {
|
||||
getComponent={ getComponent }
|
||||
getConfigs={ getConfigs }
|
||||
specSelectors={ specSelectors }
|
||||
oas3Actions={oas3Actions}
|
||||
specActions={ specActions }
|
||||
produces={ produces }
|
||||
producesValue={ operation.get("produces_value") }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { helpers } from "swagger-client"
|
||||
|
||||
import { createDeepLinkPath } from "core/utils"
|
||||
const { opId } = helpers
|
||||
|
||||
export default class Operations extends React.Component {
|
||||
@@ -9,6 +9,7 @@ export default class Operations extends React.Component {
|
||||
static propTypes = {
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
specActions: PropTypes.object.isRequired,
|
||||
oas3Actions: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
layoutSelectors: PropTypes.object.isRequired,
|
||||
layoutActions: PropTypes.object.isRequired,
|
||||
@@ -21,6 +22,7 @@ export default class Operations extends React.Component {
|
||||
let {
|
||||
specSelectors,
|
||||
specActions,
|
||||
oas3Actions,
|
||||
getComponent,
|
||||
layoutSelectors,
|
||||
layoutActions,
|
||||
@@ -34,6 +36,7 @@ export default class Operations extends React.Component {
|
||||
|
||||
const Operation = getComponent("operation")
|
||||
const Collapse = getComponent("Collapse")
|
||||
const Markdown = getComponent("Markdown")
|
||||
|
||||
let showSummary = layoutSelectors.showSummary()
|
||||
let {
|
||||
@@ -69,7 +72,7 @@ export default class Operations extends React.Component {
|
||||
let tagExternalDocsDescription = tagObj.getIn(["tagDetails", "externalDocs", "description"])
|
||||
let tagExternalDocsUrl = tagObj.getIn(["tagDetails", "externalDocs", "url"])
|
||||
|
||||
let isShownKey = ["operations-tag", tag]
|
||||
let isShownKey = ["operations-tag", createDeepLinkPath(tag)]
|
||||
let showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list")
|
||||
|
||||
return (
|
||||
@@ -87,7 +90,7 @@ export default class Operations extends React.Component {
|
||||
</a>
|
||||
{ !tagDescription ? null :
|
||||
<small>
|
||||
{ tagDescription }
|
||||
<Markdown source={tagDescription} />
|
||||
</small>
|
||||
}
|
||||
|
||||
@@ -124,7 +127,7 @@ export default class Operations extends React.Component {
|
||||
|
||||
const operationId =
|
||||
op.getIn(["operation", "operationId"]) || op.getIn(["operation", "__originalOperationId"]) || opId(op.get("operation"), path, method) || op.get("id")
|
||||
const isShownKey = ["operations", tag, operationId]
|
||||
const isShownKey = ["operations", createDeepLinkPath(tag), createDeepLinkPath(operationId)]
|
||||
|
||||
const allowTryItOut = specSelectors.allowTryItOutFor(op.get("path"), op.get("method"))
|
||||
const response = specSelectors.responseFor(op.get("path"), op.get("method"))
|
||||
@@ -147,6 +150,8 @@ export default class Operations extends React.Component {
|
||||
specActions={ specActions }
|
||||
specSelectors={ specSelectors }
|
||||
|
||||
oas3Actions={oas3Actions}
|
||||
|
||||
layoutActions={ layoutActions }
|
||||
layoutSelectors={ layoutSelectors }
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ export default class ParamBody extends PureComponent {
|
||||
|
||||
updateValues = (props) => {
|
||||
let { specSelectors, pathMethod, param, isExecute, consumesValue="" } = props
|
||||
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name")) : {}
|
||||
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name"), param.get("in")) : {}
|
||||
let isXml = /xml/i.test(consumesValue)
|
||||
let isJson = /json/i.test(consumesValue)
|
||||
let paramValue = isXml ? parameter.get("value_xml") : parameter.get("value")
|
||||
@@ -107,7 +107,7 @@ export default class ParamBody extends PureComponent {
|
||||
const HighlightCode = getComponent("highlightCode")
|
||||
const ContentType = getComponent("contentType")
|
||||
// for domains where specSelectors not passed
|
||||
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name")) : param
|
||||
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name"), param.get("in")) : param
|
||||
let errors = parameter.get("errors", List())
|
||||
let consumesValue = specSelectors.contentTypeValues(pathMethod).get("requestContentType")
|
||||
let consumes = this.props.consumes && this.props.consumes.size ? this.props.consumes : ParamBody.defaultProp.consumes
|
||||
|
||||
@@ -20,7 +20,7 @@ export default class ParameterRow extends Component {
|
||||
|
||||
let { specSelectors, pathMethod, param } = props
|
||||
let defaultValue = param.get("default")
|
||||
let parameter = specSelectors.getParameter(pathMethod, param.get("name"))
|
||||
let parameter = specSelectors.getParameter(pathMethod, param.get("name"), param.get("in"))
|
||||
let value = parameter ? parameter.get("value") : ""
|
||||
if ( defaultValue !== undefined && value === undefined ) {
|
||||
this.onChangeWrapper(defaultValue)
|
||||
@@ -31,7 +31,7 @@ export default class ParameterRow extends Component {
|
||||
let { specSelectors, pathMethod, param } = props
|
||||
let example = param.get("example")
|
||||
let defaultValue = param.get("default")
|
||||
let parameter = specSelectors.getParameter(pathMethod, param.get("name"))
|
||||
let parameter = specSelectors.getParameter(pathMethod, param.get("name"), param.get("in"))
|
||||
let paramValue = parameter ? parameter.get("value") : undefined
|
||||
let enumValue = parameter ? parameter.get("enum") : undefined
|
||||
let value
|
||||
@@ -82,13 +82,12 @@ export default class ParameterRow extends Component {
|
||||
const Markdown = getComponent("Markdown")
|
||||
|
||||
let schema = param.get("schema")
|
||||
|
||||
let type = isOAS3 && isOAS3() ? param.getIn(["schema", "type"]) : param.get("type")
|
||||
let isFormData = inType === "formData"
|
||||
let isFormDataSupported = "FormData" in win
|
||||
let required = param.get("required")
|
||||
let itemType = param.getIn(isOAS3 && isOAS3() ? ["schema", "items", "type"] : ["items", "type"])
|
||||
let parameter = specSelectors.getParameter(pathMethod, param.get("name"))
|
||||
let parameter = specSelectors.getParameter(pathMethod, param.get("name"), param.get("in"))
|
||||
let value = parameter ? parameter.get("value") : ""
|
||||
|
||||
return (
|
||||
|
||||
@@ -38,7 +38,7 @@ export default class Parameters extends Component {
|
||||
onChangeKey,
|
||||
} = this.props
|
||||
|
||||
changeParam( onChangeKey, param.get("name"), value, isXml)
|
||||
changeParam( onChangeKey, param.get("name"), param.get("in"), value, isXml)
|
||||
}
|
||||
|
||||
onChangeConsumesWrapper = ( val ) => {
|
||||
@@ -97,7 +97,7 @@ export default class Parameters extends Component {
|
||||
getComponent={ getComponent }
|
||||
getConfigs={ getConfigs }
|
||||
param={ parameter }
|
||||
key={ parameter.get( "name" ) }
|
||||
key={ `${parameter.get( "in" )}.${parameter.get("name")}` }
|
||||
onChange={ this.onChange }
|
||||
onChangeConsumes={this.onChangeConsumesWrapper}
|
||||
specSelectors={ specSelectors }
|
||||
|
||||
@@ -23,6 +23,7 @@ export default class Primitive extends Component {
|
||||
let format = schema.get("format")
|
||||
let xml = schema.get("xml")
|
||||
let enumArray = schema.get("enum")
|
||||
let title = schema.get("title") || name
|
||||
let description = schema.get("description")
|
||||
let properties = schema.filter( ( v, key) => ["enum", "type", "format", "description", "$$ref"].indexOf(key) === -1 )
|
||||
const Markdown = getComponent("Markdown")
|
||||
@@ -30,7 +31,7 @@ export default class Primitive extends Component {
|
||||
|
||||
return <span className="model">
|
||||
<span className="prop">
|
||||
{ name && <span className={`${depth === 1 && "model-title"} prop-name`}>{ name }</span> }
|
||||
{ name && <span className={`${depth === 1 && "model-title"} prop-name`}>{ title }</span> }
|
||||
<span className="prop-type">{ type }</span>
|
||||
{ format && <span className="prop-format">(${format})</span>}
|
||||
{
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import Remarkable from "react-remarkable"
|
||||
import Remarkable from "remarkable"
|
||||
import sanitize from "sanitize-html"
|
||||
|
||||
function Markdown({ source }) {
|
||||
const sanitized = sanitizer(source)
|
||||
const html = new Remarkable({
|
||||
html: true,
|
||||
typographer: true,
|
||||
breaks: true,
|
||||
linkify: true,
|
||||
linkTarget: "_blank"
|
||||
}).render(source)
|
||||
const sanitized = sanitizer(html)
|
||||
|
||||
// sometimes the sanitizer returns "undefined" as a string
|
||||
if(!source || !sanitized || sanitized === "undefined") {
|
||||
if ( !source || !html || !sanitized ) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <div className="markdown">
|
||||
<Remarkable
|
||||
options={{html: true, typographer: true, breaks: true, linkify: true, linkTarget: "_blank"}}
|
||||
source={sanitized}
|
||||
></Remarkable>
|
||||
</div>
|
||||
return (
|
||||
<div className="markdown" dangerouslySetInnerHTML={{ __html: sanitized }}></div>
|
||||
)
|
||||
}
|
||||
|
||||
Markdown.propTypes = {
|
||||
@@ -26,9 +29,9 @@ Markdown.propTypes = {
|
||||
export default Markdown
|
||||
|
||||
const sanitizeOptions = {
|
||||
allowedTags: sanitize.defaults.allowedTags.concat([ "img" ]),
|
||||
textFilter: function(text) {
|
||||
return text
|
||||
.replace(/"/g, "\"")
|
||||
return text.replace(/"/g, "\"")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,10 +49,10 @@ export default class ResponseBody extends React.Component {
|
||||
// Download
|
||||
} else if (
|
||||
/^application\/octet-stream/i.test(contentType) ||
|
||||
headers["Content-Disposition"] && (/attachment/i).test(headers["Content-Disposition"]) ||
|
||||
headers["content-disposition"] && (/attachment/i).test(headers["content-disposition"]) ||
|
||||
headers["Content-Description"] && (/File Transfer/i).test(headers["Content-Description"]) ||
|
||||
headers["content-description"] && (/File Transfer/i).test(headers["content-description"])) {
|
||||
(headers["Content-Disposition"] && (/attachment/i).test(headers["Content-Disposition"])) ||
|
||||
(headers["content-disposition"] && (/attachment/i).test(headers["content-disposition"])) ||
|
||||
(headers["Content-Description"] && (/File Transfer/i).test(headers["Content-Description"])) ||
|
||||
(headers["content-description"] && (/File Transfer/i).test(headers["content-description"]))) {
|
||||
|
||||
let contentLength = headers["content-length"] || headers["Content-Length"]
|
||||
if ( !(+contentLength) ) return null
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import cx from "classnames"
|
||||
import { fromJS, Seq } from "immutable"
|
||||
import { getSampleSchema } from "core/utils"
|
||||
import { getSampleSchema, fromJSOrdered } from "core/utils"
|
||||
|
||||
const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => {
|
||||
if ( examples && examples.size ) {
|
||||
@@ -47,24 +48,36 @@ export default class Response extends React.Component {
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
fn: PropTypes.object.isRequired,
|
||||
contentType: PropTypes.string
|
||||
contentType: PropTypes.string,
|
||||
controlsAcceptHeader: PropTypes.bool,
|
||||
onContentTypeChange: PropTypes.func
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
response: fromJS({}),
|
||||
onContentTypeChange: () => {}
|
||||
};
|
||||
|
||||
_onContentTypeChange = (value) => {
|
||||
const { onContentTypeChange, controlsAcceptHeader } = this.props
|
||||
this.setState({ responseContentType: value })
|
||||
onContentTypeChange({
|
||||
value: value,
|
||||
controlsAcceptHeader
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
code,
|
||||
response,
|
||||
className,
|
||||
|
||||
fn,
|
||||
getComponent,
|
||||
getConfigs,
|
||||
specSelectors,
|
||||
contentType
|
||||
contentType,
|
||||
controlsAcceptHeader
|
||||
} = this.props
|
||||
|
||||
let { inferSchema } = fn
|
||||
@@ -96,6 +109,14 @@ export default class Response extends React.Component {
|
||||
includeWriteOnly: true // writeOnly has no filtering effect in swagger 2.0
|
||||
}) : null
|
||||
}
|
||||
|
||||
if(examples) {
|
||||
examples = examples.map(example => {
|
||||
// Remove unwanted properties from examples
|
||||
return example.set ? example.set("$$ref", undefined) : example
|
||||
})
|
||||
}
|
||||
|
||||
let example = getExampleComponent( sampleResponse, examples, HighlightCode )
|
||||
|
||||
return (
|
||||
@@ -109,18 +130,25 @@ export default class Response extends React.Component {
|
||||
<Markdown source={ response.get( "description" ) } />
|
||||
</div>
|
||||
|
||||
{ isOAS3 ? <ContentType
|
||||
{ isOAS3 ?
|
||||
<div className={cx("response-content-type", {
|
||||
"controls-accept-header": controlsAcceptHeader
|
||||
})}>
|
||||
<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 }
|
||||
onChange={this._onContentTypeChange}
|
||||
/>
|
||||
{ controlsAcceptHeader ? <small>Controls <code>Accept</code> header.</small> : null }
|
||||
</div>
|
||||
: null }
|
||||
|
||||
{ example ? (
|
||||
<ModelExample
|
||||
getComponent={ getComponent }
|
||||
getConfigs={ getConfigs }
|
||||
specSelectors={ specSelectors }
|
||||
schema={ fromJS(schema) }
|
||||
schema={ fromJSOrdered(schema) }
|
||||
example={ example }/>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { fromJS } from "immutable"
|
||||
import { defaultStatusCode } from "core/utils"
|
||||
import { defaultStatusCode, getAcceptControllingResponse } from "core/utils"
|
||||
|
||||
export default class Responses extends React.Component {
|
||||
|
||||
@@ -15,6 +15,7 @@ export default class Responses extends React.Component {
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
specActions: PropTypes.object.isRequired,
|
||||
oas3Actions: PropTypes.object.isRequired,
|
||||
pathMethod: PropTypes.array.isRequired,
|
||||
displayRequestDuration: PropTypes.bool.isRequired,
|
||||
fn: PropTypes.object.isRequired
|
||||
@@ -29,8 +30,28 @@ export default class Responses extends React.Component {
|
||||
|
||||
onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue(this.props.pathMethod, val)
|
||||
|
||||
onResponseContentTypeChange = ({ controlsAcceptHeader, value }) => {
|
||||
const { oas3Actions, pathMethod } = this.props
|
||||
if(controlsAcceptHeader) {
|
||||
oas3Actions.setResponseContentType({
|
||||
value,
|
||||
pathMethod
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let { responses, request, tryItOutResponse, getComponent, getConfigs, specSelectors, fn, producesValue, displayRequestDuration } = this.props
|
||||
let {
|
||||
responses,
|
||||
request,
|
||||
tryItOutResponse,
|
||||
getComponent,
|
||||
getConfigs,
|
||||
specSelectors,
|
||||
fn,
|
||||
producesValue,
|
||||
displayRequestDuration
|
||||
} = this.props
|
||||
let defaultCode = defaultStatusCode( responses )
|
||||
|
||||
const ContentType = getComponent( "contentType" )
|
||||
@@ -39,6 +60,11 @@ export default class Responses extends React.Component {
|
||||
|
||||
let produces = this.props.produces && this.props.produces.size ? this.props.produces : Responses.defaultProps.produces
|
||||
|
||||
const isSpecOAS3 = specSelectors.isOAS3()
|
||||
|
||||
const acceptControllingResponse = isSpecOAS3 ?
|
||||
getAcceptControllingResponse(responses) : null
|
||||
|
||||
return (
|
||||
<div className="responses-wrapper">
|
||||
<div className="opblock-section-header">
|
||||
@@ -78,7 +104,6 @@ export default class Responses extends React.Component {
|
||||
<tbody>
|
||||
{
|
||||
responses.entrySeq().map( ([code, response]) => {
|
||||
|
||||
let className = tryItOutResponse && tryItOutResponse.get("status") == code ? "response_current" : ""
|
||||
return (
|
||||
<Response key={ code }
|
||||
@@ -88,6 +113,8 @@ export default class Responses extends React.Component {
|
||||
code={ code }
|
||||
response={ response }
|
||||
specSelectors={ specSelectors }
|
||||
controlsAcceptHeader={response === acceptControllingResponse}
|
||||
onContentTypeChange={this.onResponseContentTypeChange}
|
||||
contentType={ producesValue }
|
||||
getConfigs={ getConfigs }
|
||||
getComponent={ getComponent }/>
|
||||
|
||||
@@ -64,7 +64,8 @@ export default function authorize ( { auth, authActions, errActions, configs, au
|
||||
}
|
||||
}
|
||||
|
||||
let url = [schema.get("authorizationUrl"), query.join("&")].join("?")
|
||||
let authorizationUrl = schema.get("authorizationUrl")
|
||||
let url = [authorizationUrl, query.join("&")].join(authorizationUrl.indexOf("?") === -1 ? "?" : "&")
|
||||
|
||||
// pass action authorizeOauth2 and authentication data through window
|
||||
// to authorize with oauth2
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { setHash } from "./helpers"
|
||||
import { createDeepLinkPath } from "core/utils"
|
||||
|
||||
export const show = (ori, { getConfigs }) => (...args) => {
|
||||
ori(...args)
|
||||
@@ -19,12 +20,12 @@ export const show = (ori, { getConfigs }) => (...args) => {
|
||||
|
||||
if(type === "operations") {
|
||||
let [, tag, operationId] = thing
|
||||
setHash(`/${tag}/${operationId}`)
|
||||
setHash(`/${createDeepLinkPath(tag)}/${createDeepLinkPath(operationId)}`)
|
||||
}
|
||||
|
||||
if(type === "operations-tag") {
|
||||
let [, tag] = thing
|
||||
setHash(`/${tag}`)
|
||||
setHash(`/${createDeepLinkPath(tag)}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import scrollTo from "scroll-to-element"
|
||||
import { escapeDeepLinkPath } from "core/utils"
|
||||
|
||||
const SCROLL_OFFSET = -5
|
||||
let hasHashBeenParsed = false
|
||||
@@ -34,14 +35,14 @@ export const updateResolved = (ori, { layoutActions, getConfigs }) => (...args)
|
||||
layoutActions.show(["operations-tag", tag], true)
|
||||
layoutActions.show(["operations", tag, operationId], true)
|
||||
|
||||
scrollTo(`#operations-${tag}-${operationId}`, {
|
||||
scrollTo(`#operations-${escapeDeepLinkPath(tag)}-${escapeDeepLinkPath(operationId)}`, {
|
||||
offset: SCROLL_OFFSET
|
||||
})
|
||||
} else if(tag) {
|
||||
// Pre-expand and scroll to the tag
|
||||
layoutActions.show(["operations-tag", tag], true)
|
||||
|
||||
scrollTo(`#operations-tag-${tag}`, {
|
||||
scrollTo(`#operations-tag-${escapeDeepLinkPath(tag)}`, {
|
||||
offset: SCROLL_OFFSET
|
||||
})
|
||||
}
|
||||
|
||||
43
src/core/plugins/oas3/actions.js
Normal file
43
src/core/plugins/oas3/actions.js
Normal file
@@ -0,0 +1,43 @@
|
||||
// Actions conform to FSA (flux-standard-actions)
|
||||
// {type: string,payload: Any|Error, meta: obj, error: bool}
|
||||
|
||||
export const UPDATE_SELECTED_SERVER = "oas3_set_servers"
|
||||
export const UPDATE_REQUEST_BODY_VALUE = "oas3_set_request_body_value"
|
||||
export const UPDATE_REQUEST_CONTENT_TYPE = "oas3_set_request_content_type"
|
||||
export const UPDATE_RESPONSE_CONTENT_TYPE = "oas3_set_response_content_type"
|
||||
export const UPDATE_SERVER_VARIABLE_VALUE = "oas3_set_server_variable_value"
|
||||
|
||||
export function setSelectedServer (selectedServerUrl) {
|
||||
return {
|
||||
type: UPDATE_SELECTED_SERVER,
|
||||
payload: selectedServerUrl
|
||||
}
|
||||
}
|
||||
|
||||
export function setRequestBodyValue ({ value, pathMethod }) {
|
||||
return {
|
||||
type: UPDATE_REQUEST_BODY_VALUE,
|
||||
payload: { value, pathMethod }
|
||||
}
|
||||
}
|
||||
|
||||
export function setRequestContentType ({ value, pathMethod }) {
|
||||
return {
|
||||
type: UPDATE_REQUEST_CONTENT_TYPE,
|
||||
payload: { value, pathMethod }
|
||||
}
|
||||
}
|
||||
|
||||
export function setResponseContentType ({ value, pathMethod }) {
|
||||
return {
|
||||
type: UPDATE_RESPONSE_CONTENT_TYPE,
|
||||
payload: { value, pathMethod }
|
||||
}
|
||||
}
|
||||
|
||||
export function setServerVariableValue ({ server, key, val }) {
|
||||
return {
|
||||
type: UPDATE_SERVER_VARIABLE_VALUE,
|
||||
payload: { server, key, val }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
import Callbacks from "./callbacks"
|
||||
import RequestBody from "./request-body"
|
||||
import OperationLink from "./operation-link.jsx"
|
||||
import Servers from "./servers"
|
||||
import RequestBodyEditor from "./request-body-editor"
|
||||
|
||||
export default {
|
||||
Callbacks,
|
||||
RequestBody,
|
||||
Servers,
|
||||
RequestBodyEditor,
|
||||
operationLink: OperationLink
|
||||
}
|
||||
|
||||
114
src/core/plugins/oas3/components/request-body-editor.jsx
Normal file
114
src/core/plugins/oas3/components/request-body-editor.jsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import React, { PureComponent } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { fromJS } from "immutable"
|
||||
import { getSampleSchema } from "core/utils"
|
||||
|
||||
const NOOP = Function.prototype
|
||||
|
||||
export default class RequestBodyEditor extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
requestBody: PropTypes.object.isRequired,
|
||||
mediaType: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
isExecute: PropTypes.bool,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
mediaType: "application/json",
|
||||
requestBody: fromJS({}),
|
||||
onChange: NOOP,
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
|
||||
this.state = {
|
||||
isEditBox: false,
|
||||
value: ""
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setValueToSample.call(this)
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if(this.props.mediaType !== nextProps.mediaType) {
|
||||
// media type was changed
|
||||
this.setValueToSample(nextProps.mediaType)
|
||||
}
|
||||
|
||||
if(!this.props.isExecute && nextProps.isExecute) {
|
||||
// we just entered execute mode,
|
||||
// so enable editing for convenience
|
||||
this.setState({ isEditBox: true })
|
||||
}
|
||||
}
|
||||
|
||||
setValueToSample = (explicitMediaType) => {
|
||||
this.onChange(this.sample(explicitMediaType))
|
||||
}
|
||||
|
||||
sample = (explicitMediaType) => {
|
||||
let { requestBody, mediaType } = this.props
|
||||
let schema = requestBody.getIn(["content", explicitMediaType || mediaType, "schema"]).toJS()
|
||||
|
||||
return getSampleSchema(schema, explicitMediaType || mediaType, {
|
||||
includeWriteOnly: true
|
||||
})
|
||||
}
|
||||
|
||||
onChange = (value) => {
|
||||
this.setState({value})
|
||||
this.props.onChange(value)
|
||||
}
|
||||
|
||||
handleOnChange = e => {
|
||||
const { mediaType } = this.props
|
||||
const isJson = /json/i.test(mediaType)
|
||||
const inputValue = isJson ? e.target.value.trim() : e.target.value
|
||||
|
||||
this.onChange(inputValue)
|
||||
}
|
||||
|
||||
toggleIsEditBox = () => this.setState( state => ({isEditBox: !state.isEditBox}))
|
||||
|
||||
render() {
|
||||
let {
|
||||
isExecute,
|
||||
getComponent,
|
||||
} = this.props
|
||||
|
||||
const Button = getComponent("Button")
|
||||
const TextArea = getComponent("TextArea")
|
||||
const HighlightCode = getComponent("highlightCode")
|
||||
|
||||
let { value, isEditBox } = this.state
|
||||
|
||||
return (
|
||||
<div className="body-param">
|
||||
{
|
||||
isEditBox && isExecute
|
||||
? <TextArea className={"body-param__text"} value={value} onChange={ this.handleOnChange }/>
|
||||
: (value && <HighlightCode className="body-param__example"
|
||||
value={ value }/>)
|
||||
}
|
||||
<div className="body-param-options">
|
||||
{
|
||||
!isExecute ? null
|
||||
: <div className="body-param-edit">
|
||||
<Button className={isEditBox ? "btn cancel body-param__example-edit" : "btn edit body-param__example-edit"}
|
||||
onClick={this.toggleIsEditBox}>{ isEditBox ? "Cancel" : "Edit"}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,18 @@ 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 RequestBody = ({
|
||||
requestBody,
|
||||
getComponent,
|
||||
specSelectors,
|
||||
contentType,
|
||||
isExecute,
|
||||
onChange
|
||||
}) => {
|
||||
const Markdown = getComponent("Markdown")
|
||||
const ModelExample = getComponent("modelExample")
|
||||
const HighlightCode = getComponent("highlightCode")
|
||||
const RequestBodyEditor = getComponent("RequestBodyEditor")
|
||||
|
||||
const requestBodyDescription = (requestBody && requestBody.get("description")) || null
|
||||
const requestBodyContent = (requestBody && requestBody.get("content")) || new OrderedMap()
|
||||
@@ -16,10 +21,6 @@ const RequestBody = ({ requestBody, getComponent, specSelectors, contentType })
|
||||
|
||||
const mediaTypeValue = requestBodyContent.get(contentType)
|
||||
|
||||
const sampleSchema = getSampleSchema(mediaTypeValue.get("schema").toJS(), contentType, {
|
||||
includeWriteOnly: true
|
||||
})
|
||||
|
||||
return <div>
|
||||
{ requestBodyDescription &&
|
||||
<Markdown source={requestBodyDescription} />
|
||||
@@ -28,8 +29,16 @@ const RequestBody = ({ requestBody, getComponent, specSelectors, contentType })
|
||||
getComponent={ getComponent }
|
||||
specSelectors={ specSelectors }
|
||||
expandDepth={1}
|
||||
isExecute={isExecute}
|
||||
schema={mediaTypeValue.get("schema")}
|
||||
example={<HighlightCode value={sampleSchema} />}
|
||||
example={<RequestBodyEditor
|
||||
requestBody={requestBody}
|
||||
onChange={onChange}
|
||||
mediaType={contentType}
|
||||
getComponent={getComponent}
|
||||
isExecute={isExecute}
|
||||
specSelectors={specSelectors}
|
||||
/>}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
@@ -38,7 +47,9 @@ RequestBody.propTypes = {
|
||||
requestBody: ImPropTypes.orderedMap.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
contentType: PropTypes.string.isRequired
|
||||
contentType: PropTypes.string.isRequired,
|
||||
isExecute: PropTypes.bool.isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default RequestBody
|
||||
|
||||
155
src/core/plugins/oas3/components/servers.jsx
Normal file
155
src/core/plugins/oas3/components/servers.jsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import React from "react"
|
||||
import { OrderedMap } from "immutable"
|
||||
import PropTypes from "prop-types"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
|
||||
export default class Servers extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
servers: ImPropTypes.list.isRequired,
|
||||
currentServer: PropTypes.string.isRequired,
|
||||
setSelectedServer: PropTypes.func.isRequired,
|
||||
setServerVariableValue: PropTypes.func.isRequired,
|
||||
getServerVariable: PropTypes.func.isRequired,
|
||||
getEffectiveServerValue: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let { servers } = this.props
|
||||
|
||||
//fire 'change' event to set default 'value' of select
|
||||
this.setServer(servers.first().get("url"))
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
let {
|
||||
servers,
|
||||
setServerVariableValue,
|
||||
getServerVariable
|
||||
} = this.props
|
||||
|
||||
if(this.props.currentServer !== nextProps.currentServer) {
|
||||
// Server has changed, we may need to set default values
|
||||
let currentServerDefinition = servers
|
||||
.find(v => v.get("url") === nextProps.currentServer) || OrderedMap()
|
||||
|
||||
let currentServerVariableDefs = currentServerDefinition.get("variables") || OrderedMap()
|
||||
|
||||
currentServerVariableDefs.map((val, key) => {
|
||||
let currentValue = getServerVariable(nextProps.currentServer, key)
|
||||
// only set the default value if the user hasn't set one yet
|
||||
if(!currentValue) {
|
||||
setServerVariableValue({
|
||||
server: nextProps.currentServer,
|
||||
key,
|
||||
val: val.get("default") || ""
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onServerChange =( e ) => {
|
||||
this.setServer( e.target.value )
|
||||
|
||||
// set default variable values
|
||||
}
|
||||
|
||||
onServerVariableValueChange = ( e ) => {
|
||||
let {
|
||||
setServerVariableValue,
|
||||
currentServer
|
||||
} = this.props
|
||||
|
||||
let variableName = e.target.getAttribute("data-variable")
|
||||
let newVariableValue = e.target.value
|
||||
|
||||
if(typeof setServerVariableValue === "function") {
|
||||
setServerVariableValue({
|
||||
server: currentServer,
|
||||
key: variableName,
|
||||
val: newVariableValue
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
setServer = ( value ) => {
|
||||
let { setSelectedServer } = this.props
|
||||
|
||||
setSelectedServer(value)
|
||||
}
|
||||
|
||||
render() {
|
||||
let { servers,
|
||||
currentServer,
|
||||
getServerVariable,
|
||||
getEffectiveServerValue
|
||||
} = this.props
|
||||
|
||||
let currentServerDefinition = servers.find(v => v.get("url") === currentServer) || OrderedMap()
|
||||
|
||||
let currentServerVariableDefs = currentServerDefinition.get("variables") || OrderedMap()
|
||||
|
||||
let shouldShowVariableUI = currentServerVariableDefs.size !== 0
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor="servers">
|
||||
<span className="servers-title">Servers</span>
|
||||
<select onChange={ this.onServerChange }>
|
||||
{ servers.valueSeq().map(
|
||||
( server ) =>
|
||||
<option
|
||||
value={ server.get("url") }
|
||||
key={ server.get("url") }>
|
||||
{ server.get("url") }
|
||||
</option>
|
||||
).toArray()}
|
||||
</select>
|
||||
</label>
|
||||
{ shouldShowVariableUI ?
|
||||
<div>
|
||||
<h4>Server variables</h4>
|
||||
<div className={"computed-url"}>
|
||||
Computed URL:
|
||||
<code>
|
||||
{getEffectiveServerValue(currentServer)}
|
||||
</code>
|
||||
</div>
|
||||
<table>
|
||||
<tbody>
|
||||
{
|
||||
currentServerVariableDefs.map((val, name) => {
|
||||
return <tr key={name}>
|
||||
<td>{name}</td>
|
||||
<td>
|
||||
{ val.get("enum") ?
|
||||
<select data-variable={name} onChange={this.onServerVariableValueChange}>
|
||||
{val.get("enum").map(enumValue => {
|
||||
return <option
|
||||
selected={enumValue === getServerVariable(currentServer, name)}
|
||||
key={enumValue}
|
||||
value={enumValue}>
|
||||
{enumValue}
|
||||
</option>
|
||||
})}
|
||||
</select> :
|
||||
<input
|
||||
type={"text"}
|
||||
value={getServerVariable(currentServer, name) || ""}
|
||||
onChange={this.onServerVariableValueChange}
|
||||
data-variable={name}
|
||||
></input>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>: null
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
// import reducers from "./reducers"
|
||||
// import * as actions from "./actions"
|
||||
import * as wrapSelectors from "./wrap-selectors"
|
||||
import * as specWrapSelectors from "./spec-extensions/wrap-selectors"
|
||||
import * as specSelectors from "./spec-extensions/selectors"
|
||||
import components from "./components"
|
||||
import wrapComponents from "./wrap-components"
|
||||
import * as oas3Actions from "./actions"
|
||||
import * as oas3Selectors from "./selectors"
|
||||
import oas3Reducers from "./reducers"
|
||||
|
||||
export default function() {
|
||||
return {
|
||||
@@ -10,7 +14,13 @@ export default function() {
|
||||
wrapComponents,
|
||||
statePlugins: {
|
||||
spec: {
|
||||
wrapSelectors
|
||||
wrapSelectors: specWrapSelectors,
|
||||
selectors: specSelectors
|
||||
},
|
||||
oas3: {
|
||||
actions: oas3Actions,
|
||||
reducers: oas3Reducers,
|
||||
selectors: oas3Selectors,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
src/core/plugins/oas3/reducers.js
Normal file
28
src/core/plugins/oas3/reducers.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
UPDATE_SELECTED_SERVER,
|
||||
UPDATE_REQUEST_BODY_VALUE,
|
||||
UPDATE_REQUEST_CONTENT_TYPE,
|
||||
UPDATE_SERVER_VARIABLE_VALUE,
|
||||
UPDATE_RESPONSE_CONTENT_TYPE
|
||||
} from "./actions"
|
||||
|
||||
export default {
|
||||
[UPDATE_SELECTED_SERVER]: (state, { payload: selectedServerUrl } ) =>{
|
||||
return state.setIn( [ "selectedServer" ], selectedServerUrl)
|
||||
},
|
||||
[UPDATE_REQUEST_BODY_VALUE]: (state, { payload: { value, pathMethod } } ) =>{
|
||||
let [path, method] = pathMethod
|
||||
return state.setIn( [ "requestData", path, method, "bodyValue" ], value)
|
||||
},
|
||||
[UPDATE_REQUEST_CONTENT_TYPE]: (state, { payload: { value, pathMethod } } ) =>{
|
||||
let [path, method] = pathMethod
|
||||
return state.setIn( [ "requestData", path, method, "requestContentType" ], value)
|
||||
},
|
||||
[UPDATE_RESPONSE_CONTENT_TYPE]: (state, { payload: { value, pathMethod } } ) =>{
|
||||
let [path, method] = pathMethod
|
||||
return state.setIn( [ "requestData", path, method, "responseContentType" ], value)
|
||||
},
|
||||
[UPDATE_SERVER_VARIABLE_VALUE]: (state, { payload: { server, key, val } } ) =>{
|
||||
return state.setIn( [ "serverVariableValues", server, key ], val)
|
||||
},
|
||||
}
|
||||
58
src/core/plugins/oas3/selectors.js
Normal file
58
src/core/plugins/oas3/selectors.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import { OrderedMap } from "immutable"
|
||||
import { isOAS3 as isOAS3Helper } from "./helpers"
|
||||
|
||||
|
||||
// Helpers
|
||||
|
||||
function onlyOAS3(selector) {
|
||||
return (...args) => (system) => {
|
||||
const spec = system.getSystem().specSelectors.specJson()
|
||||
if(isOAS3Helper(spec)) {
|
||||
return selector(...args)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const selectedServer = onlyOAS3(state => {
|
||||
return state.getIn(["selectedServer"]) || ""
|
||||
}
|
||||
)
|
||||
|
||||
export const requestBodyValue = onlyOAS3((state, path, method) => {
|
||||
return state.getIn(["requestData", path, method, "bodyValue"]) || null
|
||||
}
|
||||
)
|
||||
|
||||
export const requestContentType = onlyOAS3((state, path, method) => {
|
||||
return state.getIn(["requestData", path, method, "requestContentType"]) || null
|
||||
}
|
||||
)
|
||||
|
||||
export const responseContentType = onlyOAS3((state, path, method) => {
|
||||
return state.getIn(["requestData", path, method, "responseContentType"]) || null
|
||||
}
|
||||
)
|
||||
|
||||
export const serverVariableValue = onlyOAS3((state, server, key) => {
|
||||
return state.getIn(["serverVariableValues", server, key]) || null
|
||||
}
|
||||
)
|
||||
|
||||
export const serverVariables = onlyOAS3((state, server) => {
|
||||
return state.getIn(["serverVariableValues", server]) || OrderedMap()
|
||||
}
|
||||
)
|
||||
|
||||
export const serverEffectiveValue = onlyOAS3((state, server) => {
|
||||
let varValues = state.getIn(["serverVariableValues", server]) || OrderedMap()
|
||||
let str = server
|
||||
|
||||
varValues.map((val, key) => {
|
||||
str = str.replace(new RegExp(`{${key}}`, "g"), val)
|
||||
})
|
||||
|
||||
return str
|
||||
}
|
||||
)
|
||||
50
src/core/plugins/oas3/spec-extensions/selectors.js
Normal file
50
src/core/plugins/oas3/spec-extensions/selectors.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import { createSelector } from "reselect"
|
||||
import { Map } from "immutable"
|
||||
import { isOAS3 as isOAS3Helper, isSwagger2 as isSwagger2Helper } from "../helpers"
|
||||
|
||||
|
||||
// Helpers
|
||||
|
||||
function onlyOAS3(selector) {
|
||||
return () => (system, ...args) => {
|
||||
const spec = system.getSystem().specSelectors.specJson()
|
||||
if(isOAS3Helper(spec)) {
|
||||
return selector(...args)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const state = state => {
|
||||
return state || Map()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// New selectors
|
||||
|
||||
export const servers = onlyOAS3(createSelector(
|
||||
spec,
|
||||
spec => spec.getIn(["servers"]) || Map()
|
||||
))
|
||||
|
||||
export const isSwagger2 = (ori, system) => () => {
|
||||
const spec = system.getSystem().specSelectors.specJson()
|
||||
return isSwagger2Helper(spec)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createSelector } from "reselect"
|
||||
import { Map } from "immutable"
|
||||
import { isOAS3 as isOAS3Helper, isSwagger2 as isSwagger2Helper } from "./helpers"
|
||||
import { isOAS3 as isOAS3Helper, isSwagger2 as isSwagger2Helper } from "../helpers"
|
||||
|
||||
|
||||
// Helpers
|
||||
@@ -56,6 +56,11 @@ export const schemes = OAS3NullSelector
|
||||
|
||||
// New selectors
|
||||
|
||||
export const servers = onlyOAS3(createSelector(
|
||||
spec,
|
||||
spec => spec.getIn(["servers"]) || Map()
|
||||
))
|
||||
|
||||
export const isOAS3 = (ori, system) => () => {
|
||||
const spec = system.getSystem().specSelectors.specJson()
|
||||
return isOAS3Helper(spec)
|
||||
@@ -3,7 +3,6 @@ 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,
|
||||
@@ -11,5 +10,4 @@ export default {
|
||||
VersionStamp,
|
||||
model: Model,
|
||||
onlineValidatorBadge: OnlineValidatorBadge,
|
||||
TryItOutButton
|
||||
}
|
||||
|
||||
@@ -1,11 +1,26 @@
|
||||
import React from "react"
|
||||
import ReactMarkdown from "react-markdown"
|
||||
import { Parser, HtmlRenderer } from "commonmark"
|
||||
import { OAS3ComponentWrapFactory } from "../helpers"
|
||||
import { sanitizer } from "core/components/providers/markdown"
|
||||
|
||||
export default OAS3ComponentWrapFactory(({ source }) => { return source ? (
|
||||
export default OAS3ComponentWrapFactory(({ source }) => {
|
||||
if ( source ) {
|
||||
const parser = new Parser()
|
||||
const writer = new HtmlRenderer()
|
||||
const html = writer.render(parser.parse(source || ""))
|
||||
const sanitized = sanitizer(html)
|
||||
|
||||
if ( !source || !html || !sanitized ) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<ReactMarkdown
|
||||
source={sanitizer(source)}
|
||||
source={sanitized}
|
||||
className={"renderedMarkdown"}
|
||||
/>
|
||||
) : null})
|
||||
)
|
||||
}
|
||||
return null
|
||||
})
|
||||
@@ -13,8 +13,7 @@ class Parameters extends Component {
|
||||
super(props)
|
||||
this.state = {
|
||||
callbackVisible: false,
|
||||
parametersVisible: true,
|
||||
requestBodyContentType: ""
|
||||
parametersVisible: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +23,8 @@ class Parameters extends Component {
|
||||
operation: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
oas3Actions: PropTypes.object.isRequired,
|
||||
oas3Selectors: PropTypes.object.isRequired,
|
||||
fn: PropTypes.object.isRequired,
|
||||
tryItOutEnabled: PropTypes.bool,
|
||||
allowTryItOut: PropTypes.bool,
|
||||
@@ -48,7 +49,7 @@ class Parameters extends Component {
|
||||
onChangeKey,
|
||||
} = this.props
|
||||
|
||||
changeParam( onChangeKey, param.get("name"), value, isXml)
|
||||
changeParam( onChangeKey, param.get("name"), param.get("in"), value, isXml)
|
||||
}
|
||||
|
||||
onChangeConsumesWrapper = ( val ) => {
|
||||
@@ -86,6 +87,8 @@ class Parameters extends Component {
|
||||
fn,
|
||||
getComponent,
|
||||
specSelectors,
|
||||
oas3Actions,
|
||||
oas3Selectors,
|
||||
pathMethod,
|
||||
operation
|
||||
} = this.props
|
||||
@@ -159,16 +162,22 @@ class Parameters extends Component {
|
||||
<h4 className={`opblock-title parameter__name ${requestBody.get("required") && "required"}`}>Request body</h4>
|
||||
<label>
|
||||
<ContentType
|
||||
value={this.state.requestBodyContentType}
|
||||
value={oas3Selectors.requestContentType(...pathMethod)}
|
||||
contentTypes={ requestBody.get("content").keySeq() }
|
||||
onChange={(val) => this.setState({ requestBodyContentType: val })}
|
||||
onChange={(value) => {
|
||||
oas3Actions.setRequestContentType({ value, pathMethod })
|
||||
}}
|
||||
className="body-param-content-type" />
|
||||
</label>
|
||||
</div>
|
||||
<div className="opblock-description-wrapper">
|
||||
<RequestBody
|
||||
requestBody={requestBody}
|
||||
contentType={this.state.requestBodyContentType}/>
|
||||
isExecute={isExecute}
|
||||
onChange={(value) => {
|
||||
oas3Actions.setRequestBodyValue({ value, pathMethod })
|
||||
}}
|
||||
contentType={oas3Selectors.requestContentType(...pathMethod)}/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { OAS3ComponentWrapFactory } from "../helpers"
|
||||
|
||||
export default OAS3ComponentWrapFactory(() => {
|
||||
return null
|
||||
})
|
||||
@@ -1,6 +1,7 @@
|
||||
import YAML from "js-yaml"
|
||||
import parseUrl from "url-parse"
|
||||
import serializeError from "serialize-error"
|
||||
import { isJSONObject } from "core/utils"
|
||||
|
||||
// Actions conform to FSA (flux-standard-actions)
|
||||
// {type: string,payload: Any|Error, meta: obj, error: bool}
|
||||
@@ -129,10 +130,10 @@ export const formatIntoYaml = () => ({specActions, specSelectors}) => {
|
||||
}
|
||||
}
|
||||
|
||||
export function changeParam( path, paramName, value, isXml ){
|
||||
export function changeParam( path, paramName, paramIn, value, isXml ){
|
||||
return {
|
||||
type: UPDATE_PARAM,
|
||||
payload:{ path, value, paramName, isXml }
|
||||
payload:{ path, value, paramName, paramIn, isXml }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +196,8 @@ export const logRequest = (req) => {
|
||||
|
||||
// Actually fire the request via fn.execute
|
||||
// (For debugging) and ease of testing
|
||||
export const executeRequest = (req) => ({fn, specActions, specSelectors, getConfigs}) => {
|
||||
export const executeRequest = (req) =>
|
||||
({fn, specActions, specSelectors, getConfigs, oas3Selectors}) => {
|
||||
let { pathName, method, operation } = req
|
||||
let { requestInterceptor, responseInterceptor } = getConfigs()
|
||||
|
||||
@@ -204,13 +206,27 @@ export const executeRequest = (req) => ({fn, specActions, specSelectors, getConf
|
||||
// if url is relative, parseUrl makes it absolute by inferring from `window.location`
|
||||
req.contextUrl = parseUrl(specSelectors.url()).toString()
|
||||
|
||||
|
||||
if(op && op.operationId) {
|
||||
req.operationId = op.operationId
|
||||
} else if(op && pathName && method) {
|
||||
req.operationId = fn.opId(op, pathName, method)
|
||||
}
|
||||
|
||||
if(specSelectors.isOAS3()) {
|
||||
// OAS3 request feature support
|
||||
req.server = oas3Selectors.selectedServer()
|
||||
req.serverVariables = oas3Selectors.serverVariables(req.server).toJS()
|
||||
req.requestContentType = oas3Selectors.requestContentType(pathName, method)
|
||||
req.responseContentType = oas3Selectors.responseContentType(pathName, method) || "*/*"
|
||||
const requestBody = oas3Selectors.requestBodyValue(pathName, method)
|
||||
|
||||
if(isJSONObject(requestBody)) {
|
||||
req.requestBody = JSON.parse(requestBody)
|
||||
} else {
|
||||
req.requestBody = requestBody
|
||||
}
|
||||
}
|
||||
|
||||
let parsedRequest = Object.assign({}, req)
|
||||
parsedRequest = fn.buildRequest(parsedRequest)
|
||||
|
||||
@@ -234,8 +250,12 @@ export const executeRequest = (req) => ({fn, specActions, specSelectors, getConf
|
||||
res.duration = Date.now() - startTime
|
||||
specActions.setResponse(req.pathName, req.method, res)
|
||||
} )
|
||||
.catch( err => specActions.setResponse(req.pathName, req.method, { error: true, err: serializeError(err) } ) )
|
||||
}
|
||||
.catch(
|
||||
err => specActions.setResponse(req.pathName, req.method, {
|
||||
error: true, err: serializeError(err)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// I'm using extras as a way to inject properties into the final, `execute` method - It's not great. Anyone have a better idea? @ponelat
|
||||
|
||||
@@ -40,9 +40,10 @@ export default {
|
||||
},
|
||||
|
||||
[UPDATE_PARAM]: ( state, {payload} ) => {
|
||||
let { path, paramName, value, isXml } = payload
|
||||
let { path, paramName, paramIn, value, isXml } = payload
|
||||
|
||||
return state.updateIn( [ "resolved", "paths", ...path, "parameters" ], fromJS([]), parameters => {
|
||||
const index = parameters.findIndex(p => p.get( "name" ) === paramName )
|
||||
const index = parameters.findIndex(p => p.get( "name" ) === paramName && p.get("in") === paramIn )
|
||||
if (!(value instanceof win.File)) {
|
||||
value = fromJSOrdered( value )
|
||||
}
|
||||
|
||||
@@ -260,10 +260,10 @@ export const allowTryItOutFor = () => {
|
||||
}
|
||||
|
||||
// Get the parameter value by parameter name
|
||||
export function getParameter(state, pathMethod, name) {
|
||||
export function getParameter(state, pathMethod, name, inType) {
|
||||
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
|
||||
return params.filter( (p) => {
|
||||
return Map.isMap(p) && p.get("name") === name
|
||||
return Map.isMap(p) && p.get("name") === name && p.get("in") === inType
|
||||
}).first()
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ export function parameterValues(state, pathMethod, isXml) {
|
||||
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
|
||||
return params.reduce( (hash, p) => {
|
||||
let value = isXml && p.get("in") === "body" ? p.get("value_xml") : p.get("value")
|
||||
return hash.set(p.get("name"), value)
|
||||
return hash.set(`${p.get("in")}.${p.get("name")}`, value)
|
||||
}, fromJS({}))
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,31 @@ import some from "lodash/some"
|
||||
import eq from "lodash/eq"
|
||||
import { memoizedSampleFromSchema, memoizedCreateXMLExample } from "core/plugins/samples/fn"
|
||||
import win from "./window"
|
||||
import cssEscape from "css.escape"
|
||||
|
||||
const DEFAULT_REPONSE_KEY = "default"
|
||||
|
||||
export const isImmutable = (maybe) => Im.Iterable.isIterable(maybe)
|
||||
|
||||
export function isJSONObject (str) {
|
||||
try {
|
||||
var o = JSON.parse(str)
|
||||
|
||||
// Handle non-exception-throwing cases:
|
||||
// Neither JSON.parse(false) or JSON.parse(1234) throw errors, hence the type-checking,
|
||||
// but... JSON.parse(null) returns null, and typeof null === "object",
|
||||
// so we must check for that, too. Thankfully, null is falsey, so this suffices:
|
||||
if (o && typeof o === "object") {
|
||||
return o
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export function objectify (thing) {
|
||||
if(!isObject(thing))
|
||||
return {}
|
||||
@@ -450,6 +470,18 @@ export const propChecker = (props, nextProps, objectList=[], ignoreList=[]) => {
|
||||
|| objectList.some( objectPropName => !eq(props[objectPropName], nextProps[objectPropName])))
|
||||
}
|
||||
|
||||
export const validateMaximum = ( val, max ) => {
|
||||
if (val > max) {
|
||||
return "Value must be less than Maximum"
|
||||
}
|
||||
}
|
||||
|
||||
export const validateMinimum = ( val, min ) => {
|
||||
if (val < min) {
|
||||
return "Value must be greater than Minimum"
|
||||
}
|
||||
}
|
||||
|
||||
export const validateNumber = ( val ) => {
|
||||
if (!/^-?\d+(\.?\d+)?$/.test(val)) {
|
||||
return "Value must be a number"
|
||||
@@ -480,12 +512,41 @@ export const validateString = ( val ) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const validateDateTime = (val) => {
|
||||
if (isNaN(Date.parse(val))) {
|
||||
return "Value must be a DateTime"
|
||||
}
|
||||
}
|
||||
|
||||
export const validateGuid = (val) => {
|
||||
if (!/^[{(]?[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}[)}]?$/.test(val)) {
|
||||
return "Value must be a Guid"
|
||||
}
|
||||
}
|
||||
|
||||
export const validateMaxLength = (val, max) => {
|
||||
if (val.length > max) {
|
||||
return "Value must be less than MaxLength"
|
||||
}
|
||||
}
|
||||
|
||||
export const validateMinLength = (val, min) => {
|
||||
if (val.length < min) {
|
||||
return "Value must be greater than MinLength"
|
||||
}
|
||||
}
|
||||
|
||||
// validation of parameters before execute
|
||||
export const validateParam = (param, isXml) => {
|
||||
let errors = []
|
||||
let value = isXml && param.get("in") === "body" ? param.get("value_xml") : param.get("value")
|
||||
let required = param.get("required")
|
||||
let maximum = param.get("maximum")
|
||||
let minimum = param.get("minimum")
|
||||
let type = param.get("type")
|
||||
let format = param.get("format")
|
||||
let maxLength = param.get("maxLength")
|
||||
let minLength = param.get("minLength")
|
||||
|
||||
/*
|
||||
If the parameter is required OR the parameter has a value (meaning optional, but filled in)
|
||||
@@ -502,13 +563,40 @@ export const validateParam = (param, isXml) => {
|
||||
let numberCheck = type === "number" && !validateNumber(value) // validateNumber returns undefined if the value is a number
|
||||
let integerCheck = type === "integer" && !validateInteger(value) // validateInteger returns undefined if the value is an integer
|
||||
|
||||
if (maxLength || maxLength === 0) {
|
||||
let err = validateMaxLength(value, maxLength)
|
||||
if (err) errors.push(err)
|
||||
}
|
||||
|
||||
if (minLength) {
|
||||
let err = validateMinLength(value, minLength)
|
||||
if (err) errors.push(err)
|
||||
}
|
||||
|
||||
if ( required && !(stringCheck || arrayCheck || listCheck || fileCheck || booleanCheck || numberCheck || integerCheck) ) {
|
||||
errors.push("Required field is not provided")
|
||||
return errors
|
||||
}
|
||||
|
||||
if (maximum || maximum === 0) {
|
||||
let err = validateMaximum(value, maximum)
|
||||
if (err) errors.push(err)
|
||||
}
|
||||
|
||||
if (minimum || minimum === 0) {
|
||||
let err = validateMinimum(value, minimum)
|
||||
if (err) errors.push(err)
|
||||
}
|
||||
|
||||
if ( type === "string" ) {
|
||||
let err = validateString(value)
|
||||
let err
|
||||
if (format === "date-time") {
|
||||
err = validateDateTime(value)
|
||||
} else if (format === "uuid") {
|
||||
err = validateGuid(value)
|
||||
} else {
|
||||
err = validateString(value)
|
||||
}
|
||||
if (!err) return errors
|
||||
errors.push(err)
|
||||
} else if ( type === "boolean" ) {
|
||||
@@ -631,3 +719,29 @@ export const shallowEqualKeys = (a,b, keys) => {
|
||||
return eq(a[key], b[key])
|
||||
})
|
||||
}
|
||||
|
||||
export function getAcceptControllingResponse(responses) {
|
||||
if(!Im.OrderedMap.isOrderedMap(responses)) {
|
||||
// wrong type!
|
||||
return null
|
||||
}
|
||||
|
||||
if(!responses.size) {
|
||||
// responses is empty
|
||||
return null
|
||||
}
|
||||
|
||||
const suitable2xxResponse = responses.find((res, k) => {
|
||||
return k.startsWith("2") && Object.keys(res.get("content") || {}).length > 0
|
||||
})
|
||||
|
||||
// try to find a suitable `default` responses
|
||||
const defaultResponse = responses.get("default") || Im.OrderedMap()
|
||||
const defaultResponseMediaTypes = (defaultResponse.get("content") || Im.OrderedMap()).keySeq().toJS()
|
||||
const suitableDefaultResponse = defaultResponseMediaTypes.length ? defaultResponse : null
|
||||
|
||||
return suitable2xxResponse || suitableDefaultResponse
|
||||
}
|
||||
|
||||
export const createDeepLinkPath = (str) => typeof str == "string" || str instanceof String ? str.trim().replace(/\s/g, "_") : ""
|
||||
export const escapeDeepLinkPath = (str) => cssEscape( createDeepLinkPath(str) )
|
||||
|
||||
@@ -252,8 +252,11 @@
|
||||
font-size: 16px;
|
||||
|
||||
display: flex;
|
||||
flex: 0 3 auto;
|
||||
align-items: center;
|
||||
|
||||
word-break: break-all;
|
||||
|
||||
padding: 0 10px;
|
||||
|
||||
@include text_code();
|
||||
@@ -636,6 +639,77 @@
|
||||
}
|
||||
}
|
||||
|
||||
.server-container
|
||||
{
|
||||
margin: 0 0 20px 0;
|
||||
padding: 30px 0;
|
||||
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 2px 0 rgba(0,0,0,.15);
|
||||
|
||||
.computed-url {
|
||||
margin: 2em 0;
|
||||
|
||||
code {
|
||||
color: grey;
|
||||
display: inline-block;
|
||||
padding: 4px;
|
||||
font-size: 16px;
|
||||
margin: 0 1em;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.servers
|
||||
{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.servers-title {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
> label
|
||||
{
|
||||
font-size: 12px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
margin: -20px 15px 0 0;
|
||||
|
||||
@include text_headline();
|
||||
|
||||
select
|
||||
{
|
||||
min-width: 130px;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
tr {
|
||||
width: 30em;
|
||||
}
|
||||
td {
|
||||
display: inline-block;
|
||||
max-width: 15em;
|
||||
vertical-align: middle;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
|
||||
&:first-of-type {
|
||||
padding-right: 2em;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.loading-container
|
||||
{
|
||||
@@ -704,6 +778,17 @@
|
||||
|
||||
.response-content-type {
|
||||
padding-top: 1em;
|
||||
|
||||
&.controls-accept-header {
|
||||
select {
|
||||
border-color: green;
|
||||
}
|
||||
|
||||
small {
|
||||
color: green;
|
||||
font-size: .7em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blinker
|
||||
|
||||
@@ -237,7 +237,8 @@ span
|
||||
.prop-name
|
||||
{
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
margin-right: 1em;
|
||||
width: 8em;
|
||||
}
|
||||
|
||||
.prop-type
|
||||
|
||||
49
test/components/primitive-model.js
Normal file
49
test/components/primitive-model.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/* eslint-env mocha */
|
||||
import React from "react"
|
||||
import expect from "expect"
|
||||
import { shallow } from "enzyme"
|
||||
import { fromJS } from "immutable"
|
||||
import PrimitiveModel from "components/primitive-model"
|
||||
|
||||
describe("<PrimitiveModel/>", function() {
|
||||
describe("Model name", function() {
|
||||
const dummyComponent = () => null
|
||||
const components = {
|
||||
Markdown: dummyComponent,
|
||||
EnumModel: dummyComponent
|
||||
}
|
||||
const props = {
|
||||
getComponent: c => components[c],
|
||||
name: "Name from props",
|
||||
depth: 1,
|
||||
schema: fromJS({
|
||||
type: "string",
|
||||
title: "Custom model title"
|
||||
})
|
||||
}
|
||||
|
||||
it("renders the schema's title", function() {
|
||||
// When
|
||||
const wrapper = shallow(<PrimitiveModel {...props}/>)
|
||||
const modelTitleEl = wrapper.find("span.model-title")
|
||||
expect(modelTitleEl.length).toEqual(1)
|
||||
|
||||
// Then
|
||||
expect( modelTitleEl.text() ).toEqual( "Custom model title" )
|
||||
})
|
||||
|
||||
it("falls back to the passed-in `name` prop for the title", function() {
|
||||
// When
|
||||
props.schema = fromJS({
|
||||
type: "string"
|
||||
})
|
||||
const wrapper = shallow(<PrimitiveModel {...props}/>)
|
||||
const modelTitleEl = wrapper.find("span.model-title")
|
||||
expect(modelTitleEl.length).toEqual(1)
|
||||
|
||||
// Then
|
||||
expect( modelTitleEl.text() ).toEqual( "Name from props" )
|
||||
})
|
||||
|
||||
})
|
||||
} )
|
||||
58
test/components/response.js
Normal file
58
test/components/response.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import React from "react"
|
||||
import expect from "expect"
|
||||
import { shallow } from "enzyme"
|
||||
import { fromJS } from "immutable"
|
||||
import Response from "components/response"
|
||||
import ModelExample from "components/model-example"
|
||||
import { inferSchema } from "corePlugins/samples/fn"
|
||||
|
||||
describe("<Response />", function() {
|
||||
const dummyComponent = () => null
|
||||
const components = {
|
||||
headers: dummyComponent,
|
||||
highlightCode: dummyComponent,
|
||||
modelExample: ModelExample,
|
||||
Markdown: dummyComponent,
|
||||
operationLink: dummyComponent,
|
||||
contentType: dummyComponent
|
||||
}
|
||||
const props = {
|
||||
getComponent: c => components[c],
|
||||
specSelectors: {
|
||||
isOAS3() {
|
||||
return false
|
||||
}
|
||||
},
|
||||
fn: {
|
||||
inferSchema
|
||||
},
|
||||
contentType: "application/json",
|
||||
className: "for-test",
|
||||
response: fromJS({
|
||||
type: "object",
|
||||
properties: {
|
||||
// Note reverse order: c, b, a
|
||||
"c": {
|
||||
type: "integer"
|
||||
},
|
||||
"b": {
|
||||
type: "boolean"
|
||||
},
|
||||
"a": {
|
||||
type: "string"
|
||||
}
|
||||
}
|
||||
}),
|
||||
code: "200"
|
||||
}
|
||||
|
||||
it("renders the model-example schema properties in order", function() {
|
||||
const wrapper = shallow(<Response {...props}/>)
|
||||
const renderedModelExample = wrapper.find(ModelExample)
|
||||
expect(renderedModelExample.length).toEqual(1)
|
||||
|
||||
// Assert the schema's properties have maintained their order
|
||||
const modelExampleSchemaProperties = renderedModelExample.props().schema.toJS().properties
|
||||
expect( Object.keys(modelExampleSchemaProperties) ).toEqual(["c", "b", "a"])
|
||||
})
|
||||
})
|
||||
39
test/core/oauth2-authorize.js
Normal file
39
test/core/oauth2-authorize.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/* eslint-env mocha */
|
||||
import expect, { createSpy } from "expect"
|
||||
import { fromJS } from "immutable"
|
||||
import win from "core/window"
|
||||
import oauth2Authorize from "core/oauth2-authorize"
|
||||
|
||||
describe("oauth2", function () {
|
||||
|
||||
let mockSchema = {
|
||||
flow: "accessCode",
|
||||
authorizationUrl: "https://testAuthorizationUrl"
|
||||
}
|
||||
|
||||
let authConfig = {
|
||||
auth: { schema: { get: (key)=> mockSchema[key] } },
|
||||
authActions: {},
|
||||
errActions: {},
|
||||
configs: { oauth2RedirectUrl: "" },
|
||||
authConfigs: {}
|
||||
}
|
||||
|
||||
describe("authorize redirect", function () {
|
||||
|
||||
it("should build authorize url", function() {
|
||||
win.open = createSpy()
|
||||
oauth2Authorize(authConfig)
|
||||
expect(win.open.calls.length).toEqual(1)
|
||||
expect(win.open.calls[0].arguments[0]).toMatch("https://testAuthorizationUrl?response_type=code&redirect_uri=&state=")
|
||||
})
|
||||
|
||||
it("should append query paramters to authorizeUrl with query parameters", function() {
|
||||
win.open = createSpy()
|
||||
mockSchema.authorizationUrl = "https://testAuthorizationUrl?param=1"
|
||||
oauth2Authorize(authConfig)
|
||||
expect(win.open.calls.length).toEqual(1)
|
||||
expect(win.open.calls[0].arguments[0]).toMatch("https://testAuthorizationUrl?param=1&response_type=code&redirect_uri=&state=")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -113,7 +113,8 @@ describe("spec plugin - actions", function(){
|
||||
spec: () => fromJS({}),
|
||||
parameterValues: () => fromJS({}),
|
||||
contentTypeValues: () => fromJS({}),
|
||||
url: () => fromJS({})
|
||||
url: () => fromJS({}),
|
||||
isOAS3: () => false
|
||||
},
|
||||
getConfigs: () => configs
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ describe("spec plugin - selectors", function(){
|
||||
"/one": {
|
||||
get: {
|
||||
parameters: [
|
||||
{ name: "one", value: 1},
|
||||
{ name: "two", value: "duos"}
|
||||
{ name: "one", in: "query", value: 1},
|
||||
{ name: "two", in: "query", value: "duos"}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -43,8 +43,8 @@ describe("spec plugin - selectors", function(){
|
||||
|
||||
// Then
|
||||
expect(paramValues.toJS()).toEqual({
|
||||
one: 1,
|
||||
two: "duos"
|
||||
"query.one": 1,
|
||||
"query.two": "duos"
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
/* eslint-env mocha */
|
||||
import expect from "expect"
|
||||
import { fromJS } from "immutable"
|
||||
import { mapToList, validateNumber, validateInteger, validateParam, validateFile, fromJSOrdered } from "core/utils"
|
||||
import { fromJS, OrderedMap } from "immutable"
|
||||
import {
|
||||
mapToList,
|
||||
validateMinLength,
|
||||
validateMaxLength,
|
||||
validateDateTime,
|
||||
validateGuid,
|
||||
validateNumber,
|
||||
validateInteger,
|
||||
validateParam,
|
||||
validateFile,
|
||||
validateMaximum,
|
||||
validateMinimum,
|
||||
fromJSOrdered,
|
||||
getAcceptControllingResponse,
|
||||
createDeepLinkPath,
|
||||
escapeDeepLinkPath
|
||||
} from "core/utils"
|
||||
import win from "core/window"
|
||||
|
||||
describe("utils", function() {
|
||||
@@ -72,6 +88,36 @@ describe("utils", function() {
|
||||
|
||||
})
|
||||
|
||||
describe("validateMaximum", function() {
|
||||
let errorMessage = "Value must be less than Maximum"
|
||||
|
||||
it("doesn't return for valid input", function() {
|
||||
expect(validateMaximum(9, 10)).toBeFalsy()
|
||||
expect(validateMaximum(19, 20)).toBeFalsy()
|
||||
})
|
||||
|
||||
it("returns a message for invalid input", function() {
|
||||
expect(validateMaximum(1, 0)).toEqual(errorMessage)
|
||||
expect(validateMaximum(10, 9)).toEqual(errorMessage)
|
||||
expect(validateMaximum(20, 19)).toEqual(errorMessage)
|
||||
})
|
||||
})
|
||||
|
||||
describe("validateMinimum", function() {
|
||||
let errorMessage = "Value must be greater than Minimum"
|
||||
|
||||
it("doesn't return for valid input", function() {
|
||||
expect(validateMinimum(2, 1)).toBeFalsy()
|
||||
expect(validateMinimum(20, 10)).toBeFalsy()
|
||||
})
|
||||
|
||||
it("returns a message for invalid input", function() {
|
||||
expect(validateMinimum(-1, 0)).toEqual(errorMessage)
|
||||
expect(validateMinimum(1, 2)).toEqual(errorMessage)
|
||||
expect(validateMinimum(10, 20)).toEqual(errorMessage)
|
||||
})
|
||||
})
|
||||
|
||||
describe("validateNumber", function() {
|
||||
let errorMessage = "Value must be a number"
|
||||
|
||||
@@ -171,6 +217,62 @@ describe("utils", function() {
|
||||
})
|
||||
})
|
||||
|
||||
describe("validateDateTime", function() {
|
||||
let errorMessage = "Value must be a DateTime"
|
||||
|
||||
it("doesn't return for valid dates", function() {
|
||||
expect(validateDateTime("Mon, 25 Dec 1995 13:30:00 +0430")).toBeFalsy()
|
||||
})
|
||||
|
||||
it("returns a message for invalid input'", function() {
|
||||
expect(validateDateTime(null)).toEqual(errorMessage)
|
||||
expect(validateDateTime("string")).toEqual(errorMessage)
|
||||
})
|
||||
})
|
||||
|
||||
describe("validateGuid", function() {
|
||||
let errorMessage = "Value must be a Guid"
|
||||
|
||||
it("doesn't return for valid guid", function() {
|
||||
expect(validateGuid("8ce4811e-cec5-4a29-891a-15d1917153c1")).toBeFalsy()
|
||||
expect(validateGuid("{8ce4811e-cec5-4a29-891a-15d1917153c1}")).toBeFalsy()
|
||||
})
|
||||
|
||||
it("returns a message for invalid input'", function() {
|
||||
expect(validateGuid(1)).toEqual(errorMessage)
|
||||
expect(validateGuid("string")).toEqual(errorMessage)
|
||||
})
|
||||
})
|
||||
|
||||
describe("validateMaxLength", function() {
|
||||
let errorMessage = "Value must be less than MaxLength"
|
||||
|
||||
it("doesn't return for valid guid", function() {
|
||||
expect(validateMaxLength("a", 1)).toBeFalsy()
|
||||
expect(validateMaxLength("abc", 5)).toBeFalsy()
|
||||
})
|
||||
|
||||
it("returns a message for invalid input'", function() {
|
||||
expect(validateMaxLength("abc", 0)).toEqual(errorMessage)
|
||||
expect(validateMaxLength("abc", 1)).toEqual(errorMessage)
|
||||
expect(validateMaxLength("abc", 2)).toEqual(errorMessage)
|
||||
})
|
||||
})
|
||||
|
||||
describe("validateMinLength", function() {
|
||||
let errorMessage = "Value must be greater than MinLength"
|
||||
|
||||
it("doesn't return for valid guid", function() {
|
||||
expect(validateMinLength("a", 1)).toBeFalsy()
|
||||
expect(validateMinLength("abc", 2)).toBeFalsy()
|
||||
})
|
||||
|
||||
it("returns a message for invalid input'", function() {
|
||||
expect(validateMinLength("abc", 5)).toEqual(errorMessage)
|
||||
expect(validateMinLength("abc", 8)).toEqual(errorMessage)
|
||||
})
|
||||
})
|
||||
|
||||
describe("validateParam", function() {
|
||||
let param = null
|
||||
let result = null
|
||||
@@ -204,6 +306,50 @@ describe("utils", function() {
|
||||
})
|
||||
result = validateParam( param, false )
|
||||
expect( result ).toEqual( [] )
|
||||
|
||||
// valid string with min and max length
|
||||
param = fromJS({
|
||||
required: true,
|
||||
type: "string",
|
||||
value: "test string",
|
||||
maxLength: 50,
|
||||
minLength: 1
|
||||
})
|
||||
result = validateParam( param, false )
|
||||
expect( result ).toEqual( [] )
|
||||
})
|
||||
|
||||
it("validates required strings with min and max length", function() {
|
||||
// invalid string with max length
|
||||
param = fromJS({
|
||||
required: true,
|
||||
type: "string",
|
||||
value: "test string",
|
||||
maxLength: 5
|
||||
})
|
||||
result = validateParam( param, false )
|
||||
expect( result ).toEqual( ["Value must be less than MaxLength"] )
|
||||
|
||||
// invalid string with max length 0
|
||||
param = fromJS({
|
||||
required: true,
|
||||
type: "string",
|
||||
value: "test string",
|
||||
maxLength: 0
|
||||
})
|
||||
result = validateParam( param, false )
|
||||
expect( result ).toEqual( ["Value must be less than MaxLength"] )
|
||||
|
||||
|
||||
// invalid string with min length
|
||||
param = fromJS({
|
||||
required: true,
|
||||
type: "string",
|
||||
value: "test string",
|
||||
minLength: 50
|
||||
})
|
||||
result = validateParam( param, false )
|
||||
expect( result ).toEqual( ["Value must be greater than MinLength"] )
|
||||
})
|
||||
|
||||
it("validates optional strings", function() {
|
||||
@@ -458,14 +604,47 @@ describe("utils", function() {
|
||||
result = validateParam( param, false )
|
||||
expect( result ).toEqual( ["Required field is not provided"] )
|
||||
|
||||
// valid number
|
||||
// valid number with min and max
|
||||
param = fromJS({
|
||||
required: true,
|
||||
type: "number",
|
||||
value: 10
|
||||
value: 10,
|
||||
minimum: 5,
|
||||
maximum: 99
|
||||
})
|
||||
result = validateParam( param, false )
|
||||
expect( result ).toEqual( [] )
|
||||
|
||||
// valid negative number with min and max
|
||||
param = fromJS({
|
||||
required: true,
|
||||
type: "number",
|
||||
value: -10,
|
||||
minimum: -50,
|
||||
maximum: -5
|
||||
})
|
||||
result = validateParam( param, false )
|
||||
expect( result ).toEqual( [] )
|
||||
|
||||
// invalid number with maximum:0
|
||||
param = fromJS({
|
||||
required: true,
|
||||
type: "number",
|
||||
value: 1,
|
||||
maximum: 0
|
||||
})
|
||||
result = validateParam( param, false )
|
||||
expect( result ).toEqual( ["Value must be less than Maximum"] )
|
||||
|
||||
// invalid number with minimum:0
|
||||
param = fromJS({
|
||||
required: true,
|
||||
type: "number",
|
||||
value: -10,
|
||||
minimum: 0
|
||||
})
|
||||
result = validateParam( param, false )
|
||||
expect( result ).toEqual( ["Value must be greater than Minimum"] )
|
||||
})
|
||||
|
||||
it("validates optional numbers", function() {
|
||||
@@ -582,4 +761,151 @@ describe("utils", function() {
|
||||
expect( result ).toEqual( [1, 1, 2, 3, 5, 8] )
|
||||
})
|
||||
})
|
||||
|
||||
describe("getAcceptControllingResponse", () => {
|
||||
it("should return the first 2xx response with a media type", () => {
|
||||
const responses = fromJSOrdered({
|
||||
"200": {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"201": {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expect(getAcceptControllingResponse(responses)).toEqual(responses.get("200"))
|
||||
})
|
||||
it("should skip 2xx responses without defined media types", () => {
|
||||
const responses = fromJSOrdered({
|
||||
"200": {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"201": {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expect(getAcceptControllingResponse(responses)).toEqual(responses.get("201"))
|
||||
})
|
||||
it("should default to the `default` response if it has defined media types", () => {
|
||||
const responses = fromJSOrdered({
|
||||
"200": {
|
||||
description: "quite empty"
|
||||
},
|
||||
"201": {
|
||||
description: "quite empty"
|
||||
},
|
||||
default: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expect(getAcceptControllingResponse(responses)).toEqual(responses.get("default"))
|
||||
})
|
||||
it("should return null if there are no suitable controlling responses", () => {
|
||||
const responses = fromJSOrdered({
|
||||
"200": {
|
||||
description: "quite empty"
|
||||
},
|
||||
"201": {
|
||||
description: "quite empty"
|
||||
},
|
||||
"default": {
|
||||
description: "also empty.."
|
||||
}
|
||||
})
|
||||
|
||||
expect(getAcceptControllingResponse(responses)).toBe(null)
|
||||
})
|
||||
it("should return null if an empty OrderedMap is passed", () => {
|
||||
const responses = fromJSOrdered()
|
||||
|
||||
expect(getAcceptControllingResponse(responses)).toBe(null)
|
||||
})
|
||||
it("should return null if anything except an OrderedMap is passed", () => {
|
||||
const responses = {}
|
||||
|
||||
expect(getAcceptControllingResponse(responses)).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe("createDeepLinkPath", function() {
|
||||
it("creates a deep link path replacing spaces with underscores", function() {
|
||||
const result = createDeepLinkPath("tag id with spaces")
|
||||
expect(result).toEqual("tag_id_with_spaces")
|
||||
})
|
||||
|
||||
it("trims input when creating a deep link path", function() {
|
||||
let result = createDeepLinkPath(" spaces before and after ")
|
||||
expect(result).toEqual("spaces_before_and_after")
|
||||
|
||||
result = createDeepLinkPath(" ")
|
||||
expect(result).toEqual("")
|
||||
})
|
||||
|
||||
it("creates a deep link path with special characters", function() {
|
||||
const result = createDeepLinkPath("!@#$%^&*(){}[]")
|
||||
expect(result).toEqual("!@#$%^&*(){}[]")
|
||||
})
|
||||
|
||||
it("returns an empty string for invalid input", function() {
|
||||
expect( createDeepLinkPath(null) ).toEqual("")
|
||||
expect( createDeepLinkPath(undefined) ).toEqual("")
|
||||
expect( createDeepLinkPath(1) ).toEqual("")
|
||||
expect( createDeepLinkPath([]) ).toEqual("")
|
||||
expect( createDeepLinkPath({}) ).toEqual("")
|
||||
})
|
||||
})
|
||||
|
||||
describe("escapeDeepLinkPath", function() {
|
||||
it("creates and escapes a deep link path", function() {
|
||||
const result = escapeDeepLinkPath("tag id with spaces?")
|
||||
expect(result).toEqual("tag_id_with_spaces\\?")
|
||||
})
|
||||
|
||||
it("escapes a deep link path that starts with a number", function() {
|
||||
const result = escapeDeepLinkPath("123")
|
||||
expect(result).toEqual("\\31 23")
|
||||
})
|
||||
|
||||
it("escapes a deep link path with a class selector", function() {
|
||||
const result = escapeDeepLinkPath("hello.world")
|
||||
expect(result).toEqual("hello\\.world")
|
||||
})
|
||||
|
||||
it("escapes a deep link path with an id selector", function() {
|
||||
const result = escapeDeepLinkPath("hello#world")
|
||||
expect(result).toEqual("hello\\#world")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user