Merge branch 'master' into feature/request-url

This commit is contained in:
kyle
2017-09-29 14:38:42 -07:00
committed by GitHub
44 changed files with 1045 additions and 156 deletions

View File

@@ -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. 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](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) comes pre-bundled with all dependencies and can be incorporated directly in a webapp. * [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). 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 Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes
------------------ | ------------ | -------------------------- | ----- ------------------ | ------------ | -------------------------- | -----
3.2.0 | 2017-09-08 | 2.0, 3.0 | [tag v3.2.0](https://github.com/swagger-api/swagger-ui/tree/v3.2.0) 3.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) 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.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.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 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 ##### Prerequisites
@@ -151,6 +151,8 @@ domNode | The HTML DOM element inside which SwaggerUi will put the user interfac
oauth2RedirectUrl | OAuth redirect URL oauth2RedirectUrl | OAuth redirect URL
tagsSorter | Apply a sort to the tag list of each API. It can be 'alpha' (sort by paths alphanumerically) or a function (see [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) to learn how to write a sort function). Two tag name strings are passed to the sorter for each pass. Default is the order determined by Swagger-UI. tagsSorter | Apply a sort to the tag list of each API. It can be 'alpha' (sort by paths alphanumerically) or a function (see [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) to learn how to write a sort function). Two tag name strings are passed to the sorter for each pass. Default is the order determined by Swagger-UI.
operationsSorter | Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically), 'method' (sort by HTTP method) or a function (see Array.prototype.sort() to know how sort function works). Default is the order returned by the server unchanged. operationsSorter | Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically), 'method' (sort by HTTP method) or a function (see Array.prototype.sort() to know how sort function works). Default is the order returned by the server unchanged.
defaultModelRendering | Controls how models are shown when the API is first rendered. (The user can always switch the rendering for a given model by clicking the 'Model' and 'Example Value' links.) It can be set to 'model' or 'example', and the default is 'example'.
defaultModelExpandDepth | The default expansion depth for models. The default value is 1.
configUrl | Configs URL configUrl | Configs URL
parameterMacro | MUST be a function. Function to set default value to parameters. Accepts two arguments parameterMacro(operation, parameter). Operation and parameter are objects passed for context, both remain immutable parameterMacro | MUST be a function. Function to set default value to parameters. Accepts two arguments parameterMacro(operation, parameter). Operation and parameter are objects passed for context, both remain immutable
modelPropertyMacro | MUST be a function. Function to set default values to each property in model. Accepts one argument modelPropertyMacro(property), property is immutable modelPropertyMacro | MUST be a function. Function to set default values to each property in model. Accepts one argument modelPropertyMacro(property), property is immutable

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;AAu7LA;;;;;;AA65DA;;;;;;;;;;;;;;;;;;;;;;;;;;AA68TA;;;;;;;;;;;;;;AAs8JA;;;;;;;;;AA69pBA;;;;;AA81QA;AAm4DA;;;;;;AAo4YA;;;;;;AA0iaA;AA4lvBA","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":""}

File diff suppressed because one or more lines are too long

2
dist/swagger-ui.css vendored

File diff suppressed because one or more lines are too long

4
dist/swagger-ui.js vendored

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "swagger-ui", "name": "swagger-ui",
"version": "3.2.0", "version": "3.2.2",
"main": "dist/swagger-ui.js", "main": "dist/swagger-ui.js",
"repository": "git@github.com:swagger-api/swagger-ui.git", "repository": "git@github.com:swagger-api/swagger-ui.git",
"contributors": [ "contributors": [
@@ -41,6 +41,9 @@
"dependencies": { "dependencies": {
"base64-js": "^1.2.0", "base64-js": "^1.2.0",
"brace": "0.7.0", "brace": "0.7.0",
"classnames": "^2.2.5",
"commonmark": "^0.28.1",
"css.escape": "1.5.1",
"deep-extend": "0.4.1", "deep-extend": "0.4.1",
"expect": "1.20.2", "expect": "1.20.2",
"getbase": "^2.8.2", "getbase": "^2.8.2",
@@ -65,17 +68,17 @@
"react-motion": "0.4.4", "react-motion": "0.4.4",
"react-object-inspector": "0.2.1", "react-object-inspector": "0.2.1",
"react-redux": "^4.x.x", "react-redux": "^4.x.x",
"react-remarkable": "1.1.1",
"react-split-pane": "0.1.57", "react-split-pane": "0.1.57",
"redux": "^3.x.x", "redux": "^3.x.x",
"redux-immutable": "3.0.8", "redux-immutable": "3.0.8",
"redux-logger": "*", "redux-logger": "*",
"remarkable": "^1.7.1",
"reselect": "2.5.3", "reselect": "2.5.3",
"sanitize-html": "^1.14.1", "sanitize-html": "^1.14.1",
"scroll-to-element": "^2.0.0", "scroll-to-element": "^2.0.0",
"serialize-error": "2.0.0", "serialize-error": "2.0.0",
"shallowequal": "0.2.2", "shallowequal": "0.2.2",
"swagger-client": "3.1.0", "swagger-client": "^3.1.2",
"url-parse": "^1.1.8", "url-parse": "^1.1.8",
"whatwg-fetch": "0.11.1", "whatwg-fetch": "0.11.1",
"worker-loader": "^0.7.1", "worker-loader": "^0.7.1",

View File

@@ -16,10 +16,12 @@ export default class ArrayModel extends Component {
render(){ render(){
let { getComponent, schema, depth, expandDepth, name } = this.props let { getComponent, schema, depth, expandDepth, name } = this.props
let description = schema.get("description")
let items = schema.get("items") let items = schema.get("items")
let title = schema.get("title") || name let title = schema.get("title") || name
let properties = schema.filter( ( v, key) => ["type", "items", "$$ref"].indexOf(key) === -1 ) let properties = schema.filter( ( v, key) => ["type", "items", "description", "$$ref"].indexOf(key) === -1 )
const Markdown = getComponent("Markdown")
const ModelCollapse = getComponent("ModelCollapse") const ModelCollapse = getComponent("ModelCollapse")
const Model = getComponent("Model") const Model = getComponent("Model")
@@ -28,18 +30,25 @@ export default class ArrayModel extends Component {
<span className="model-title__text">{ title }</span> <span className="model-title__text">{ title }</span>
</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"> return <span className="model">
<ModelCollapse title={titleEl} collapsed={ depth > expandDepth } collapsedContent="[...]"> <ModelCollapse title={titleEl} collapsed={ depth > expandDepth } collapsedContent="[...]">
[ [
<span><Model { ...this.props } schema={ items } required={ false } depth={ depth + 1 } /></span> {
properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }>
<br />{ key }: { String(v) }</span>)
: null
}
{
!description ? null :
<Markdown source={ description } />
}
<span><Model { ...this.props } name={null} schema={ items } required={ false } depth={ depth + 1 } /></span>
] ]
{
properties.size ? <span>
{ properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={propStyle}>
<br />{ `${key}:`}{ String(v) }</span>)
}<br /></span>
: null
}
</ModelCollapse> </ModelCollapse>
</span> </span>
} }

View File

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

View File

@@ -30,39 +30,38 @@ export default class Model extends Component {
render () { render () {
let { getComponent, specSelectors, schema, required, name, isRef } = this.props let { getComponent, specSelectors, schema, required, name, isRef } = this.props
let ObjectModel = getComponent("ObjectModel") const ObjectModel = getComponent("ObjectModel")
let ArrayModel = getComponent("ArrayModel") const ArrayModel = getComponent("ArrayModel")
let PrimitiveModel = getComponent("PrimitiveModel") const PrimitiveModel = getComponent("PrimitiveModel")
let type = "object"
let $$ref = schema && schema.get("$$ref") 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") const deprecated = specSelectors.isOAS3() && schema.get("deprecated")
isRef = isRef !== undefined ? isRef : !!$$ref
if ( schema && (schema.get("type") || schema.get("properties")) ) { type = schema && schema.get("type") || type
modelSchema = schema
} else if ( $$ref ) {
modelSchema = this.getRefSchema( modelName )
}
type = modelSchema && modelSchema.get("type")
if ( !type && modelSchema && modelSchema.get("properties") ) {
type = "object"
}
switch(type) { switch(type) {
case "object": case "object":
return <ObjectModel return <ObjectModel
className="object" { ...this.props } className="object" { ...this.props }
schema={ modelSchema } schema={ schema }
name={ modelName || name } name={ name }
deprecated={deprecated} deprecated={deprecated}
isRef={ isRef!== undefined ? isRef : !!$$ref } /> isRef={ isRef } />
case "array": case "array":
return <ArrayModel return <ArrayModel
className="array" { ...this.props } className="array" { ...this.props }
schema={ modelSchema } schema={ schema }
name={ modelName || name } name={ name }
deprecated={deprecated} deprecated={deprecated}
required={ required } /> required={ required } />
case "string": case "string":
@@ -73,9 +72,10 @@ export default class Model extends Component {
return <PrimitiveModel return <PrimitiveModel
{ ...this.props } { ...this.props }
getComponent={ getComponent } getComponent={ getComponent }
schema={ modelSchema } schema={ schema }
name={ modelName || name } name={ name }
deprecated={deprecated} deprecated={deprecated}
required={ required }/> } required={ required }/>
}
} }
} }

View File

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

View File

@@ -28,6 +28,7 @@ export default class Operation extends PureComponent {
authSelectors: PropTypes.object, authSelectors: PropTypes.object,
specActions: PropTypes.object.isRequired, specActions: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
oas3Actions: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired, layoutActions: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired, layoutSelectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired, fn: PropTypes.object.isRequired,
@@ -117,7 +118,8 @@ export default class Operation extends PureComponent {
specSelectors, specSelectors,
authActions, authActions,
authSelectors, authSelectors,
getConfigs getConfigs,
oas3Actions
} = this.props } = this.props
let summary = operation.get("summary") let summary = operation.get("summary")
@@ -147,7 +149,7 @@ export default class Operation extends PureComponent {
const isDeepLinkingEnabled = deepLinking && deepLinking !== "false" const isDeepLinkingEnabled = deepLinking && deepLinking !== "false"
// Merge in Live Response // Merge in Live Response
if(response && response.size > 0) { if(responses && response && response.size > 0) {
let notDocumented = !responses.get(String(response.get("status"))) let notDocumented = !responses.get(String(response.get("status")))
response = response.set("notDocumented", notDocumented) response = response.set("notDocumented", notDocumented)
} }
@@ -222,6 +224,7 @@ export default class Operation extends PureComponent {
specActions={ specActions } specActions={ specActions }
specSelectors={ specSelectors } specSelectors={ specSelectors }
pathMethod={ [path, method] } pathMethod={ [path, method] }
getConfigs={ getConfigs }
/> />
{!tryItOutEnabled || !allowTryItOut ? null : schemes && schemes.size ? <div className="opblock-schemes"> {!tryItOutEnabled || !allowTryItOut ? null : schemes && schemes.size ? <div className="opblock-schemes">
@@ -265,6 +268,7 @@ export default class Operation extends PureComponent {
getComponent={ getComponent } getComponent={ getComponent }
getConfigs={ getConfigs } getConfigs={ getConfigs }
specSelectors={ specSelectors } specSelectors={ specSelectors }
oas3Actions={oas3Actions}
specActions={ specActions } specActions={ specActions }
produces={ produces } produces={ produces }
producesValue={ operation.get("produces_value") } producesValue={ operation.get("produces_value") }

View File

@@ -1,7 +1,7 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { helpers } from "swagger-client" import { helpers } from "swagger-client"
import { createDeepLinkPath } from "core/utils"
const { opId } = helpers const { opId } = helpers
export default class Operations extends React.Component { export default class Operations extends React.Component {
@@ -9,6 +9,7 @@ export default class Operations extends React.Component {
static propTypes = { static propTypes = {
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired, specActions: PropTypes.object.isRequired,
oas3Actions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
layoutSelectors: PropTypes.object.isRequired, layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired, layoutActions: PropTypes.object.isRequired,
@@ -21,6 +22,7 @@ export default class Operations extends React.Component {
let { let {
specSelectors, specSelectors,
specActions, specActions,
oas3Actions,
getComponent, getComponent,
layoutSelectors, layoutSelectors,
layoutActions, layoutActions,
@@ -34,6 +36,7 @@ export default class Operations extends React.Component {
const Operation = getComponent("operation") const Operation = getComponent("operation")
const Collapse = getComponent("Collapse") const Collapse = getComponent("Collapse")
const Markdown = getComponent("Markdown")
let showSummary = layoutSelectors.showSummary() let showSummary = layoutSelectors.showSummary()
let { let {
@@ -69,7 +72,7 @@ export default class Operations extends React.Component {
let tagExternalDocsDescription = tagObj.getIn(["tagDetails", "externalDocs", "description"]) let tagExternalDocsDescription = tagObj.getIn(["tagDetails", "externalDocs", "description"])
let tagExternalDocsUrl = tagObj.getIn(["tagDetails", "externalDocs", "url"]) 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") let showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list")
return ( return (
@@ -87,7 +90,7 @@ export default class Operations extends React.Component {
</a> </a>
{ !tagDescription ? null : { !tagDescription ? null :
<small> <small>
{ tagDescription } <Markdown source={tagDescription} />
</small> </small>
} }
@@ -124,7 +127,7 @@ export default class Operations extends React.Component {
const operationId = const operationId =
op.getIn(["operation", "operationId"]) || op.getIn(["operation", "__originalOperationId"]) || opId(op.get("operation"), path, method) || op.get("id") 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 allowTryItOut = specSelectors.allowTryItOutFor(op.get("path"), op.get("method"))
const response = specSelectors.responseFor(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 } specActions={ specActions }
specSelectors={ specSelectors } specSelectors={ specSelectors }
oas3Actions={oas3Actions}
layoutActions={ layoutActions } layoutActions={ layoutActions }
layoutSelectors={ layoutSelectors } layoutSelectors={ layoutSelectors }

View File

@@ -47,7 +47,7 @@ export default class ParamBody extends PureComponent {
updateValues = (props) => { updateValues = (props) => {
let { specSelectors, pathMethod, param, isExecute, consumesValue="" } = 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 isXml = /xml/i.test(consumesValue)
let isJson = /json/i.test(consumesValue) let isJson = /json/i.test(consumesValue)
let paramValue = isXml ? parameter.get("value_xml") : parameter.get("value") 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 HighlightCode = getComponent("highlightCode")
const ContentType = getComponent("contentType") const ContentType = getComponent("contentType")
// for domains where specSelectors not passed // 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 errors = parameter.get("errors", List())
let consumesValue = specSelectors.contentTypeValues(pathMethod).get("requestContentType") let consumesValue = specSelectors.contentTypeValues(pathMethod).get("requestContentType")
let consumes = this.props.consumes && this.props.consumes.size ? this.props.consumes : ParamBody.defaultProp.consumes let consumes = this.props.consumes && this.props.consumes.size ? this.props.consumes : ParamBody.defaultProp.consumes

View File

@@ -11,7 +11,8 @@ export default class ParameterRow extends Component {
isExecute: PropTypes.bool, isExecute: PropTypes.bool,
onChangeConsumes: PropTypes.func.isRequired, onChangeConsumes: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
pathMethod: PropTypes.array.isRequired pathMethod: PropTypes.array.isRequired,
getConfigs: PropTypes.func.isRequired
} }
constructor(props, context) { constructor(props, context) {
@@ -19,7 +20,7 @@ export default class ParameterRow extends Component {
let { specSelectors, pathMethod, param } = props let { specSelectors, pathMethod, param } = props
let defaultValue = param.get("default") 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") : "" let value = parameter ? parameter.get("value") : ""
if ( defaultValue !== undefined && value === undefined ) { if ( defaultValue !== undefined && value === undefined ) {
this.onChangeWrapper(defaultValue) this.onChangeWrapper(defaultValue)
@@ -30,7 +31,7 @@ export default class ParameterRow extends Component {
let { specSelectors, pathMethod, param } = props let { specSelectors, pathMethod, param } = props
let example = param.get("example") let example = param.get("example")
let defaultValue = param.get("default") 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 paramValue = parameter ? parameter.get("value") : undefined
let enumValue = parameter ? parameter.get("enum") : undefined let enumValue = parameter ? parameter.get("enum") : undefined
let value let value
@@ -56,7 +57,7 @@ export default class ParameterRow extends Component {
} }
render() { render() {
let {param, onChange, getComponent, isExecute, fn, onChangeConsumes, specSelectors, pathMethod} = this.props let {param, onChange, getComponent, getConfigs, isExecute, fn, onChangeConsumes, specSelectors, pathMethod} = this.props
let { isOAS3 } = specSelectors let { isOAS3 } = specSelectors
@@ -81,13 +82,12 @@ export default class ParameterRow extends Component {
const Markdown = getComponent("Markdown") const Markdown = getComponent("Markdown")
let schema = param.get("schema") let schema = param.get("schema")
let type = isOAS3 && isOAS3() ? param.getIn(["schema", "type"]) : param.get("type") let type = isOAS3 && isOAS3() ? param.getIn(["schema", "type"]) : param.get("type")
let isFormData = inType === "formData" let isFormData = inType === "formData"
let isFormDataSupported = "FormData" in win let isFormDataSupported = "FormData" in win
let required = param.get("required") let required = param.get("required")
let itemType = param.getIn(isOAS3 && isOAS3() ? ["schema", "items", "type"] : ["items", "type"]) 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") : "" let value = parameter ? parameter.get("value") : ""
return ( return (
@@ -121,6 +121,7 @@ export default class ParameterRow extends Component {
{ {
bodyParam && schema ? <ModelExample getComponent={ getComponent } bodyParam && schema ? <ModelExample getComponent={ getComponent }
getConfigs={ getConfigs }
isExecute={ isExecute } isExecute={ isExecute }
specSelectors={ specSelectors } specSelectors={ specSelectors }
schema={ schema } schema={ schema }

View File

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

View File

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

View File

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

View File

@@ -49,10 +49,10 @@ export default class ResponseBody extends React.Component {
// Download // Download
} else if ( } else if (
/^application\/octet-stream/i.test(contentType) || /^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-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-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"] let contentLength = headers["content-length"] || headers["Content-Length"]
if ( !(+contentLength) ) return null if ( !(+contentLength) ) return null

View File

@@ -1,7 +1,8 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import cx from "classnames"
import { fromJS, Seq } from "immutable" import { fromJS, Seq } from "immutable"
import { getSampleSchema } from "core/utils" import { getSampleSchema, fromJSOrdered } from "core/utils"
const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => { const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => {
if ( examples && examples.size ) { if ( examples && examples.size ) {
@@ -44,25 +45,39 @@ export default class Response extends React.Component {
response: PropTypes.object, response: PropTypes.object,
className: PropTypes.string, className: PropTypes.string,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired, fn: PropTypes.object.isRequired,
contentType: PropTypes.string contentType: PropTypes.string,
controlsAcceptHeader: PropTypes.bool,
onContentTypeChange: PropTypes.func
} }
static defaultProps = { static defaultProps = {
response: fromJS({}), response: fromJS({}),
onContentTypeChange: () => {}
}; };
_onContentTypeChange = (value) => {
const { onContentTypeChange, controlsAcceptHeader } = this.props
this.setState({ responseContentType: value })
onContentTypeChange({
value: value,
controlsAcceptHeader
})
}
render() { render() {
let { let {
code, code,
response, response,
className, className,
fn, fn,
getComponent, getComponent,
getConfigs,
specSelectors, specSelectors,
contentType contentType,
controlsAcceptHeader
} = this.props } = this.props
let { inferSchema } = fn let { inferSchema } = fn
@@ -94,6 +109,14 @@ export default class Response extends React.Component {
includeWriteOnly: true // writeOnly has no filtering effect in swagger 2.0 includeWriteOnly: true // writeOnly has no filtering effect in swagger 2.0
}) : null }) : 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 ) let example = getExampleComponent( sampleResponse, examples, HighlightCode )
return ( return (
@@ -107,17 +130,25 @@ export default class Response extends React.Component {
<Markdown source={ response.get( "description" ) } /> <Markdown source={ response.get( "description" ) } />
</div> </div>
{ isOAS3 ? <ContentType { isOAS3 ?
value={this.state.responseContentType} <div className={cx("response-content-type", {
contentTypes={ response.get("content") ? response.get("content").keySeq() : Seq() } "controls-accept-header": controlsAcceptHeader
onChange={(val) => this.setState({ responseContentType: val })} })}>
className="response-content-type" /> : null } <ContentType
value={this.state.responseContentType}
contentTypes={ response.get("content") ? response.get("content").keySeq() : Seq() }
onChange={this._onContentTypeChange}
/>
{ controlsAcceptHeader ? <small>Controls <code>Accept</code> header.</small> : null }
</div>
: null }
{ example ? ( { example ? (
<ModelExample <ModelExample
getComponent={ getComponent } getComponent={ getComponent }
getConfigs={ getConfigs }
specSelectors={ specSelectors } specSelectors={ specSelectors }
schema={ fromJS(schema) } schema={ fromJSOrdered(schema) }
example={ example }/> example={ example }/>
) : null} ) : null}

View File

@@ -1,7 +1,7 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { fromJS } from "immutable" import { fromJS } from "immutable"
import { defaultStatusCode } from "core/utils" import { defaultStatusCode, getAcceptControllingResponse } from "core/utils"
export default class Responses extends React.Component { export default class Responses extends React.Component {
@@ -12,12 +12,13 @@ export default class Responses extends React.Component {
produces: PropTypes.object, produces: PropTypes.object,
producesValue: PropTypes.any, producesValue: PropTypes.any,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired, specActions: PropTypes.object.isRequired,
oas3Actions: PropTypes.object.isRequired,
pathMethod: PropTypes.array.isRequired, pathMethod: PropTypes.array.isRequired,
displayRequestDuration: PropTypes.bool.isRequired, displayRequestDuration: PropTypes.bool.isRequired,
fn: PropTypes.object.isRequired, fn: PropTypes.object.isRequired
getConfigs: PropTypes.func.isRequired
} }
static defaultProps = { static defaultProps = {
@@ -29,8 +30,28 @@ export default class Responses extends React.Component {
onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue(this.props.pathMethod, val) 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() { 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 ) let defaultCode = defaultStatusCode( responses )
const ContentType = getComponent( "contentType" ) 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 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 ( return (
<div className="responses-wrapper"> <div className="responses-wrapper">
<div className="opblock-section-header"> <div className="opblock-section-header">
@@ -78,7 +104,6 @@ export default class Responses extends React.Component {
<tbody> <tbody>
{ {
responses.entrySeq().map( ([code, response]) => { responses.entrySeq().map( ([code, response]) => {
let className = tryItOutResponse && tryItOutResponse.get("status") == code ? "response_current" : "" let className = tryItOutResponse && tryItOutResponse.get("status") == code ? "response_current" : ""
return ( return (
<Response key={ code } <Response key={ code }
@@ -88,7 +113,10 @@ export default class Responses extends React.Component {
code={ code } code={ code }
response={ response } response={ response }
specSelectors={ specSelectors } specSelectors={ specSelectors }
controlsAcceptHeader={response === acceptControllingResponse}
onContentTypeChange={this.onResponseContentTypeChange}
contentType={ producesValue } contentType={ producesValue }
getConfigs={ getConfigs }
getComponent={ getComponent }/> getComponent={ getComponent }/>
) )
}).toArray() }).toArray()

View File

@@ -45,6 +45,8 @@ module.exports = function SwaggerUI(opts) {
requestInterceptor: (a => a), requestInterceptor: (a => a),
responseInterceptor: (a => a), responseInterceptor: (a => a),
showMutatedRequest: true, showMutatedRequest: true,
defaultModelRendering: "example",
defaultModelExpandDepth: 1,
// Initial set of plugins ( TODO rename this, or refactor - we don't need presets _and_ plugins. Its just there for performance. // Initial set of plugins ( TODO rename this, or refactor - we don't need presets _and_ plugins. Its just there for performance.
// Instead, we can compile the first plugin ( it can be a collection of plugins ), then batch the rest. // Instead, we can compile the first plugin ( it can be a collection of plugins ), then batch the rest.

View File

@@ -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 // pass action authorizeOauth2 and authentication data through window
// to authorize with oauth2 // to authorize with oauth2

View File

@@ -1,4 +1,5 @@
import { setHash } from "./helpers" import { setHash } from "./helpers"
import { createDeepLinkPath } from "core/utils"
export const show = (ori, { getConfigs }) => (...args) => { export const show = (ori, { getConfigs }) => (...args) => {
ori(...args) ori(...args)
@@ -19,12 +20,12 @@ export const show = (ori, { getConfigs }) => (...args) => {
if(type === "operations") { if(type === "operations") {
let [, tag, operationId] = thing let [, tag, operationId] = thing
setHash(`/${tag}/${operationId}`) setHash(`/${createDeepLinkPath(tag)}/${createDeepLinkPath(operationId)}`)
} }
if(type === "operations-tag") { if(type === "operations-tag") {
let [, tag] = thing let [, tag] = thing
setHash(`/${tag}`) setHash(`/${createDeepLinkPath(tag)}`)
} }
} }

View File

@@ -1,4 +1,5 @@
import scrollTo from "scroll-to-element" import scrollTo from "scroll-to-element"
import { escapeDeepLinkPath } from "core/utils"
const SCROLL_OFFSET = -5 const SCROLL_OFFSET = -5
let hasHashBeenParsed = false let hasHashBeenParsed = false
@@ -34,14 +35,14 @@ export const updateResolved = (ori, { layoutActions, getConfigs }) => (...args)
layoutActions.show(["operations-tag", tag], true) layoutActions.show(["operations-tag", tag], true)
layoutActions.show(["operations", tag, operationId], true) layoutActions.show(["operations", tag, operationId], true)
scrollTo(`#operations-${tag}-${operationId}`, { scrollTo(`#operations-${escapeDeepLinkPath(tag)}-${escapeDeepLinkPath(operationId)}`, {
offset: SCROLL_OFFSET offset: SCROLL_OFFSET
}) })
} else if(tag) { } else if(tag) {
// Pre-expand and scroll to the tag // Pre-expand and scroll to the tag
layoutActions.show(["operations-tag", tag], true) layoutActions.show(["operations-tag", tag], true)
scrollTo(`#operations-tag-${tag}`, { scrollTo(`#operations-tag-${escapeDeepLinkPath(tag)}`, {
offset: SCROLL_OFFSET offset: SCROLL_OFFSET
}) })
} }

View File

@@ -4,6 +4,7 @@
export const UPDATE_SELECTED_SERVER = "oas3_set_servers" export const UPDATE_SELECTED_SERVER = "oas3_set_servers"
export const UPDATE_REQUEST_BODY_VALUE = "oas3_set_request_body_value" 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_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 const UPDATE_SERVER_VARIABLE_VALUE = "oas3_set_server_variable_value"
export function setSelectedServer (selectedServerUrl) { export function setSelectedServer (selectedServerUrl) {
@@ -27,6 +28,13 @@ export function setRequestContentType ({ value, pathMethod }) {
} }
} }
export function setResponseContentType ({ value, pathMethod }) {
return {
type: UPDATE_RESPONSE_CONTENT_TYPE,
payload: { value, pathMethod }
}
}
export function setServerVariableValue ({ server, key, val }) { export function setServerVariableValue ({ server, key, val }) {
return { return {
type: UPDATE_SERVER_VARIABLE_VALUE, type: UPDATE_SERVER_VARIABLE_VALUE,

View File

@@ -2,7 +2,8 @@ import {
UPDATE_SELECTED_SERVER, UPDATE_SELECTED_SERVER,
UPDATE_REQUEST_BODY_VALUE, UPDATE_REQUEST_BODY_VALUE,
UPDATE_REQUEST_CONTENT_TYPE, UPDATE_REQUEST_CONTENT_TYPE,
UPDATE_SERVER_VARIABLE_VALUE UPDATE_SERVER_VARIABLE_VALUE,
UPDATE_RESPONSE_CONTENT_TYPE
} from "./actions" } from "./actions"
export default { export default {
@@ -17,6 +18,10 @@ export default {
let [path, method] = pathMethod let [path, method] = pathMethod
return state.setIn( [ "requestData", path, method, "requestContentType" ], value) 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 } } ) =>{ [UPDATE_SERVER_VARIABLE_VALUE]: (state, { payload: { server, key, val } } ) =>{
return state.setIn( [ "serverVariableValues", server, key ], val) return state.setIn( [ "serverVariableValues", server, key ], val)
}, },

View File

@@ -30,6 +30,11 @@ export const requestContentType = onlyOAS3((state, path, method) => {
} }
) )
export const responseContentType = onlyOAS3((state, path, method) => {
return state.getIn(["requestData", path, method, "responseContentType"]) || null
}
)
export const serverVariableValue = onlyOAS3((state, server, key) => { export const serverVariableValue = onlyOAS3((state, server, key) => {
return state.getIn(["serverVariableValues", server, key]) || null return state.getIn(["serverVariableValues", server, key]) || null
} }

View File

@@ -1,11 +1,26 @@
import React from "react" import React from "react"
import ReactMarkdown from "react-markdown" import ReactMarkdown from "react-markdown"
import { Parser, HtmlRenderer } from "commonmark"
import { OAS3ComponentWrapFactory } from "../helpers" import { OAS3ComponentWrapFactory } from "../helpers"
import { sanitizer } from "core/components/providers/markdown" import { sanitizer } from "core/components/providers/markdown"
export default OAS3ComponentWrapFactory(({ source }) => { return source ? ( export default OAS3ComponentWrapFactory(({ source }) => {
<ReactMarkdown if ( source ) {
source={sanitizer(source)} const parser = new Parser()
className={"renderedMarkdown"} const writer = new HtmlRenderer()
/> const html = writer.render(parser.parse(source || ""))
) : null}) const sanitized = sanitizer(html)
if ( !source || !html || !sanitized ) {
return null
}
return (
<ReactMarkdown
source={sanitized}
className={"renderedMarkdown"}
/>
)
}
return null
})

View File

@@ -49,7 +49,7 @@ class Parameters extends Component {
onChangeKey, onChangeKey,
} = this.props } = this.props
changeParam( onChangeKey, param.get("name"), value, isXml) changeParam( onChangeKey, param.get("name"), param.get("in"), value, isXml)
} }
onChangeConsumesWrapper = ( val ) => { onChangeConsumesWrapper = ( val ) => {

View File

@@ -130,10 +130,10 @@ export const formatIntoYaml = () => ({specActions, specSelectors}) => {
} }
} }
export function changeParam( path, paramName, value, isXml ){ export function changeParam( path, paramName, paramIn, value, isXml ){
return { return {
type: UPDATE_PARAM, type: UPDATE_PARAM,
payload:{ path, value, paramName, isXml } payload:{ path, value, paramName, paramIn, isXml }
} }
} }
@@ -206,7 +206,6 @@ export const executeRequest = (req) =>
// if url is relative, parseUrl makes it absolute by inferring from `window.location` // if url is relative, parseUrl makes it absolute by inferring from `window.location`
req.contextUrl = parseUrl(specSelectors.url()).toString() req.contextUrl = parseUrl(specSelectors.url()).toString()
if(op && op.operationId) { if(op && op.operationId) {
req.operationId = op.operationId req.operationId = op.operationId
} else if(op && pathName && method) { } else if(op && pathName && method) {
@@ -218,6 +217,7 @@ export const executeRequest = (req) =>
req.server = oas3Selectors.selectedServer() req.server = oas3Selectors.selectedServer()
req.serverVariables = oas3Selectors.serverVariables(req.server).toJS() req.serverVariables = oas3Selectors.serverVariables(req.server).toJS()
req.requestContentType = oas3Selectors.requestContentType(pathName, method) req.requestContentType = oas3Selectors.requestContentType(pathName, method)
req.responseContentType = oas3Selectors.responseContentType(pathName, method) || "*/*"
const requestBody = oas3Selectors.requestBodyValue(pathName, method) const requestBody = oas3Selectors.requestBodyValue(pathName, method)
if(isJSONObject(requestBody)) { if(isJSONObject(requestBody)) {

View File

@@ -40,9 +40,10 @@ export default {
}, },
[UPDATE_PARAM]: ( state, {payload} ) => { [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 => { 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)) { if (!(value instanceof win.File)) {
value = fromJSOrdered( value ) value = fromJSOrdered( value )
} }

View File

@@ -260,10 +260,10 @@ export const allowTryItOutFor = () => {
} }
// Get the parameter value by parameter name // 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([])) let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
return params.filter( (p) => { 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() }).first()
} }
@@ -280,7 +280,7 @@ export function parameterValues(state, pathMethod, isXml) {
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([])) let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
return params.reduce( (hash, p) => { return params.reduce( (hash, p) => {
let value = isXml && p.get("in") === "body" ? p.get("value_xml") : p.get("value") 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({})) }, fromJS({}))
} }

View File

@@ -8,6 +8,7 @@ import some from "lodash/some"
import eq from "lodash/eq" import eq from "lodash/eq"
import { memoizedSampleFromSchema, memoizedCreateXMLExample } from "core/plugins/samples/fn" import { memoizedSampleFromSchema, memoizedCreateXMLExample } from "core/plugins/samples/fn"
import win from "./window" import win from "./window"
import cssEscape from "css.escape"
const DEFAULT_REPONSE_KEY = "default" const DEFAULT_REPONSE_KEY = "default"
@@ -469,6 +470,18 @@ export const propChecker = (props, nextProps, objectList=[], ignoreList=[]) => {
|| objectList.some( objectPropName => !eq(props[objectPropName], nextProps[objectPropName]))) || 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 ) => { export const validateNumber = ( val ) => {
if (!/^-?\d+(\.?\d+)?$/.test(val)) { if (!/^-?\d+(\.?\d+)?$/.test(val)) {
return "Value must be a number" return "Value must be a number"
@@ -499,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 // validation of parameters before execute
export const validateParam = (param, isXml) => { export const validateParam = (param, isXml) => {
let errors = [] let errors = []
let value = isXml && param.get("in") === "body" ? param.get("value_xml") : param.get("value") let value = isXml && param.get("in") === "body" ? param.get("value_xml") : param.get("value")
let required = param.get("required") let required = param.get("required")
let maximum = param.get("maximum")
let minimum = param.get("minimum")
let type = param.get("type") 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) If the parameter is required OR the parameter has a value (meaning optional, but filled in)
@@ -521,13 +563,40 @@ export const validateParam = (param, isXml) => {
let numberCheck = type === "number" && !validateNumber(value) // validateNumber returns undefined if the value is a number 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 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) ) { if ( required && !(stringCheck || arrayCheck || listCheck || fileCheck || booleanCheck || numberCheck || integerCheck) ) {
errors.push("Required field is not provided") errors.push("Required field is not provided")
return errors return errors
} }
if (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" ) { 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 if (!err) return errors
errors.push(err) errors.push(err)
} else if ( type === "boolean" ) { } else if ( type === "boolean" ) {
@@ -650,3 +719,29 @@ export const shallowEqualKeys = (a,b, keys) => {
return eq(a[key], b[key]) 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) )

View File

@@ -252,8 +252,11 @@
font-size: 16px; font-size: 16px;
display: flex; display: flex;
flex: 0 3 auto;
align-items: center; align-items: center;
word-break: break-all;
padding: 0 10px; padding: 0 10px;
@include text_code(); @include text_code();
@@ -775,6 +778,17 @@
.response-content-type { .response-content-type {
padding-top: 1em; padding-top: 1em;
&.controls-accept-header {
select {
border-color: green;
}
small {
color: green;
font-size: .7em;
}
}
} }
@keyframes blinker @keyframes blinker

View File

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

View File

@@ -0,0 +1,116 @@
/* eslint-env mocha */
import React from "react"
import expect, { createSpy } from "expect"
import { shallow } from "enzyme"
import ModelExample from "components/model-example"
import ModelComponent from "components/model-wrapper"
describe("<ModelExample/>", function(){
// Given
let components = {
ModelWrapper: ModelComponent
}
let props = {
getComponent: (c) => {
return components[c]
},
specSelectors: {},
schema: {},
example: "{\"example\": \"value\"}",
isExecute: false,
getConfigs: () => ({
defaultModelRendering: "model",
defaultModelExpandDepth: 1
})
}
let exampleSelectedTestInputs = [
{ defaultModelRendering: "model", isExecute: true },
{ defaultModelRendering: "example", isExecute: true },
{ defaultModelRendering: "example", isExecute: false },
{ defaultModelRendering: "othervalue", isExecute: true },
{ defaultModelRendering: "othervalue", isExecute: false }
]
let modelSelectedTestInputs = [
{ defaultModelRendering: "model", isExecute: false }
]
it("renders model and example tabs", function(){
// When
let wrapper = shallow(<ModelExample {...props}/>)
// Then should render tabs
expect(wrapper.find("div > ul.tab").length).toEqual(1)
let tabs = wrapper.find("div > ul.tab").children()
expect(tabs.length).toEqual(2)
tabs.forEach((node) => {
expect(node.length).toEqual(1)
expect(node.name()).toEqual("li")
expect(node.hasClass("tabitem")).toEqual(true)
})
expect(tabs.at(0).text()).toEqual("Example Value")
expect(tabs.at(1).text()).toEqual("Model")
})
exampleSelectedTestInputs.forEach(function(testInputs) {
it("example tab is selected if isExecute = " + testInputs.isExecute + " and defaultModelRendering = " + testInputs.defaultModelRendering, function(){
// When
props.isExecute = testInputs.isExecute
props.getConfigs = () => ({
defaultModelRendering: testInputs.defaultModelRendering,
defaultModelExpandDepth: 1
})
let wrapper = shallow(<ModelExample {...props}/>)
// Then
let tabs = wrapper.find("div > ul.tab").children()
let exampleTab = tabs.at(0)
expect(exampleTab.hasClass("active")).toEqual(true)
let modelTab = tabs.at(1)
expect(modelTab.hasClass("active")).toEqual(false)
expect(wrapper.find("div > div").length).toEqual(1)
expect(wrapper.find("div > div").text()).toEqual(props.example)
})
})
modelSelectedTestInputs.forEach(function(testInputs) {
it("model tab is selected if isExecute = " + testInputs.isExecute + " and defaultModelRendering = " + testInputs.defaultModelRendering, function(){
// When
props.isExecute = testInputs.isExecute
props.getConfigs = () => ({
defaultModelRendering: testInputs.defaultModelRendering,
defaultModelExpandDepth: 1
})
let wrapper = shallow(<ModelExample {...props}/>)
// Then
let tabs = wrapper.find("div > ul.tab").children()
let exampleTab = tabs.at(0)
expect(exampleTab.hasClass("active")).toEqual(false)
let modelTab = tabs.at(1)
expect(modelTab.hasClass("active")).toEqual(true)
expect(wrapper.find("div > div").length).toEqual(1)
expect(wrapper.find("div > div").find(ModelComponent).props().expandDepth).toBe(1)
})
})
it("passes defaultModelExpandDepth to ModelComponent", function(){
// When
let expandDepth = 0
props.isExecute = false
props.getConfigs = () => ({
defaultModelRendering: "model",
defaultModelExpandDepth: expandDepth
})
let wrapper = shallow(<ModelExample {...props}/>)
// Then
expect(wrapper.find("div > div").find(ModelComponent).props().expandDepth).toBe(expandDepth)
})
})

51
test/components/models.js Normal file
View File

@@ -0,0 +1,51 @@
/* eslint-env mocha */
import React from "react"
import expect, { createSpy } from "expect"
import { shallow } from "enzyme"
import { fromJS } from "immutable"
import Models from "components/models"
import ModelCollpase from "components/model-collapse"
import ModelComponent from "components/model-wrapper"
describe("<Models/>", function(){
// Given
let components = {
Collapse: ModelCollpase,
ModelWrapper: ModelComponent
}
let props = {
getComponent: (c) => {
return components[c]
},
specSelectors: {
definitions: function() {
return fromJS({
def1: {},
def2: {}
})
}
},
layoutSelectors: {
isShown: createSpy()
},
layoutActions: {},
getConfigs: () => ({
docExpansion: "list",
defaultModelExpandDepth: 0
})
}
it("passes defaultModelExpandDepth to ModelWrapper", function(){
// When
let wrapper = shallow(<Models {...props}/>)
// Then should render tabs
expect(wrapper.find("ModelCollapse").length).toEqual(1)
expect(wrapper.find("ModelComponent").length).toBeGreaterThan(0)
wrapper.find("ModelComponent").forEach((modelWrapper) => {
expect(modelWrapper.props().expandDepth).toBe(0)
})
})
})

View File

@@ -0,0 +1,49 @@
/* eslint-env mocha */
import React from "react"
import expect from "expect"
import { shallow } from "enzyme"
import { fromJS } from "immutable"
import PrimitiveModel from "components/primitive-model"
describe("<PrimitiveModel/>", function() {
describe("Model name", function() {
const dummyComponent = () => null
const components = {
Markdown: dummyComponent,
EnumModel: dummyComponent
}
const props = {
getComponent: c => components[c],
name: "Name from props",
depth: 1,
schema: fromJS({
type: "string",
title: "Custom model title"
})
}
it("renders the schema's title", function() {
// When
const wrapper = shallow(<PrimitiveModel {...props}/>)
const modelTitleEl = wrapper.find("span.model-title")
expect(modelTitleEl.length).toEqual(1)
// Then
expect( modelTitleEl.text() ).toEqual( "Custom model title" )
})
it("falls back to the passed-in `name` prop for the title", function() {
// When
props.schema = fromJS({
type: "string"
})
const wrapper = shallow(<PrimitiveModel {...props}/>)
const modelTitleEl = wrapper.find("span.model-title")
expect(modelTitleEl.length).toEqual(1)
// Then
expect( modelTitleEl.text() ).toEqual( "Name from props" )
})
})
} )

View File

@@ -0,0 +1,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"])
})
})

View File

@@ -0,0 +1,39 @@
/* eslint-env mocha */
import expect, { createSpy } from "expect"
import { fromJS } from "immutable"
import win from "core/window"
import oauth2Authorize from "core/oauth2-authorize"
describe("oauth2", function () {
let mockSchema = {
flow: "accessCode",
authorizationUrl: "https://testAuthorizationUrl"
}
let authConfig = {
auth: { schema: { get: (key)=> mockSchema[key] } },
authActions: {},
errActions: {},
configs: { oauth2RedirectUrl: "" },
authConfigs: {}
}
describe("authorize redirect", function () {
it("should build authorize url", function() {
win.open = createSpy()
oauth2Authorize(authConfig)
expect(win.open.calls.length).toEqual(1)
expect(win.open.calls[0].arguments[0]).toMatch("https://testAuthorizationUrl?response_type=code&redirect_uri=&state=")
})
it("should append query paramters to authorizeUrl with query parameters", function() {
win.open = createSpy()
mockSchema.authorizationUrl = "https://testAuthorizationUrl?param=1"
oauth2Authorize(authConfig)
expect(win.open.calls.length).toEqual(1)
expect(win.open.calls[0].arguments[0]).toMatch("https://testAuthorizationUrl?param=1&response_type=code&redirect_uri=&state=")
})
})
})

View File

@@ -29,8 +29,8 @@ describe("spec plugin - selectors", function(){
"/one": { "/one": {
get: { get: {
parameters: [ parameters: [
{ name: "one", value: 1}, { name: "one", in: "query", value: 1},
{ name: "two", value: "duos"} { name: "two", in: "query", value: "duos"}
] ]
} }
} }
@@ -43,8 +43,8 @@ describe("spec plugin - selectors", function(){
// Then // Then
expect(paramValues.toJS()).toEqual({ expect(paramValues.toJS()).toEqual({
one: 1, "query.one": 1,
two: "duos" "query.two": "duos"
}) })
}) })

View File

@@ -1,7 +1,23 @@
/* eslint-env mocha */ /* eslint-env mocha */
import expect from "expect" import expect from "expect"
import { fromJS } from "immutable" import { fromJS, OrderedMap } from "immutable"
import { mapToList, validateNumber, validateInteger, validateParam, validateFile, fromJSOrdered } from "core/utils" import {
mapToList,
validateMinLength,
validateMaxLength,
validateDateTime,
validateGuid,
validateNumber,
validateInteger,
validateParam,
validateFile,
validateMaximum,
validateMinimum,
fromJSOrdered,
getAcceptControllingResponse,
createDeepLinkPath,
escapeDeepLinkPath
} from "core/utils"
import win from "core/window" import win from "core/window"
describe("utils", function() { 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() { describe("validateNumber", function() {
let errorMessage = "Value must be a number" let errorMessage = "Value must be a number"
@@ -158,7 +204,7 @@ describe("utils", function() {
}) })
}) })
describe("validateFile", function() { describe("validateFile", function() {
let errorMessage = "Value must be a file" let errorMessage = "Value must be a file"
it("validates against objects which are instances of 'File'", function() { it("validates against objects which are instances of 'File'", function() {
@@ -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() { describe("validateParam", function() {
let param = null let param = null
let result = null let result = null
@@ -196,7 +298,7 @@ describe("utils", function() {
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] ) expect( result ).toEqual( ["Required field is not provided"] )
// valid string // valid string
param = fromJS({ param = fromJS({
required: true, required: true,
type: "string", type: "string",
@@ -204,6 +306,50 @@ describe("utils", function() {
}) })
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( [] ) 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() { it("validates optional strings", function() {
@@ -458,14 +604,47 @@ describe("utils", function() {
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( ["Required field is not provided"] ) expect( result ).toEqual( ["Required field is not provided"] )
// valid number // valid number with min and max
param = fromJS({ param = fromJS({
required: true, required: true,
type: "number", type: "number",
value: 10 value: 10,
minimum: 5,
maximum: 99
}) })
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( [] ) 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() { it("validates optional numbers", function() {
@@ -581,5 +760,152 @@ describe("utils", function() {
const result = fromJSOrdered(param).toJS() const result = fromJSOrdered(param).toJS()
expect( result ).toEqual( [1, 1, 2, 3, 5, 8] ) 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")
})
})
}) })