Merge branch 'master' into array-model-markdown-description

This commit is contained in:
kyle
2017-09-29 14:06:01 -07:00
committed by GitHub
65 changed files with 1957 additions and 218 deletions

View File

@@ -14,6 +14,10 @@ or anything that violates the specifications.
<!--- Provide a general summary of the issue in the title above --> <!--- Provide a general summary of the issue in the title above -->
<!---
If you aren't sure what Swagger-UI version, see this guide: https://github.com/swagger-api/swagger-ui/blob/master/docs/version-detection.md
--->
| Q | A | Q | A
| ------------------------------- | ------- | ------------------------------- | -------
@@ -21,7 +25,7 @@ or anything that violates the specifications.
| Which Swagger/OpenAPI version? | | Which Swagger/OpenAPI version? |
| Which Swagger-UI version? | | Which Swagger-UI version? |
| How did you install Swagger-UI? | | How did you install Swagger-UI? |
| Which broswer & version? | | Which browser & version? |
| Which operating system? | | Which operating system? |

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.1.4 | 2017-08-05 | 2.0, 3.0 | [tag v3.1.4](https://github.com/swagger-api/swagger-ui/tree/v3.1.4) 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
@@ -160,6 +162,9 @@ displayRequestDuration | Controls the display of the request duration (in millis
maxDisplayedTags | If set, limits the number of tagged operations displayed to at most this many. The default is to show all operations. maxDisplayedTags | If set, limits the number of tagged operations displayed to at most this many. The default is to show all operations.
filter | If set, enables filtering. The top bar will show an edit box that you can use to filter the tagged operations that are shown. Can be true/false to enable or disable, or an explicit filter string in which case filtering will be enabled using that string as the filter expression. Filtering is case sensitive matching the filter expression anywhere inside the tag. filter | If set, enables filtering. The top bar will show an edit box that you can use to filter the tagged operations that are shown. Can be true/false to enable or disable, or an explicit filter string in which case filtering will be enabled using that string as the filter expression. Filtering is case sensitive matching the filter expression anywhere inside the tag.
deepLinking | If set to `true`, enables dynamic deep linking for tags and operations. [Docs](https://github.com/swagger-api/swagger-ui/blob/master/docs/deep-linking.md) deepLinking | If set to `true`, enables dynamic deep linking for tags and operations. [Docs](https://github.com/swagger-api/swagger-ui/blob/master/docs/deep-linking.md)
requestInterceptor | MUST be a function. Function to intercept try-it-out requests. Accepts one argument requestInterceptor(request) and must return the potentially modified request.
responseInterceptor | MUST be a function. Function to intercept try-it-out responses. Accepts one argument responseInterceptor(response) and must return the potentially modified response.
showMutatedRequest | If set to `true` (the default), uses the mutated request returned from a rquestInterceptor to produce the curl command in the UI, otherwise the request before the requestInterceptor was applied is used.
### Plugins ### Plugins

View File

@@ -11,7 +11,11 @@
var redirectUrl = oauth2.redirectUrl; var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr; 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 = qp.split("&")
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';}) arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})

View File

@@ -11,7 +11,11 @@
var redirectUrl = oauth2.redirectUrl; var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr; 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 = qp.split("&")
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';}) arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;AAoyKA;;;;;;AAy+EA;;;;;;;;;;;;;;;;;;;;;;;;;;AAw1TA;;;;;;;;;;;;;;AAs8JA;;;;;;;;;AAw6oBA;;;;;AAirQA;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":""}

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"swagger-ui-standalone-preset.js","sources":["webpack:///swagger-ui-standalone-preset.js"],"mappings":"AAAA;;;;;AA00CA;;;;;;AAmlFA","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

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;;;;;;AAowcA","sourceRoot":""} {"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AAkzeA","sourceRoot":""}

BIN
docs/images/swagger-ui2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

BIN
docs/images/swagger-ui3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

54
docs/version-detection.md Normal file
View File

@@ -0,0 +1,54 @@
# Detecting your Swagger-UI version
At times, you're going to need to know which version of Swagger-UI you use.
The first step would be to detect which major version you currently use, as the method of detecting the version has changed. If your Swagger-UI has been heavily modified and you cannot detect from the look and feel which major version you use, you'd have to try both methods to get the exact version.
To help you visually detect which version you're using, we've included supporting images.
# Swagger-UI 3.X
![Swagger-UI 3](images/swagger-ui3.png)
Some distinct identifiers to Swagger-UI 3.X:
- The API version appears as a badge next to its title.
- If there are schemes or authorizations, they'd appear in a bar above the operations.
- Try it out functionality is not enabled by default.
- All the response codes in the operations appear at after the parameters.
- There's a models section after the operations.
If you've determined this is the version you have, to find the exact version:
- Open your browser's web console (changes between browsers)
- Type `versions` in the console and execute the call.
- You might need to expand the result, until you get a string similar to `swaggerUi : Object { version: "3.1.6", gitRevision: "g786cd47", gitDirty: true, … }`.
- The version taken from that example would be `3.1.6`.
Note: This functionality was added in 3.0.8. If you're unable to execute it, you're likely to use an older version, and in that case the first step would be to upgrade.
# Swagger-UI 2.X and under
![Swagger-UI 2](images/swagger-ui2.png)
Some distinct identifiers to Swagger-UI 3.X:
- The API version appears at the bottom of the page.
- Schemes are not rendered.
- Authorization, if rendered, will appear next to the navigation bar.
- Try it out functionality is enabled by default.
- The successful response code would appear above the parameters, the rest below them.
- There's no models section after the operations.
If you've determined this is the version you have, to find the exact version:
- Navigate to the sources of the UI. Either on your disk or via the view page source functionality in your browser.
- Find an open the `swagger-ui.js`
- At the top of the page, there would be a comment containing the exact version of swagger-ui. This example shows version `2.2.9`:
```
/**
* swagger-ui - Swagger UI is a dependency-free collection of HTML, JavaScript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API
* @version v2.2.9
* @link http://swagger.io
* @license Apache-2.0
*/
```

View File

@@ -1,6 +1,6 @@
{ {
"name": "swagger-ui", "name": "swagger-ui",
"version": "3.1.4", "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.0.19", "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

@@ -30,6 +30,11 @@ 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="[...]">
[ [
@@ -42,7 +47,7 @@ export default class ArrayModel extends Component {
!description ? null : !description ? null :
<Markdown source={ description } /> <Markdown source={ description } />
} }
<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>
] ]
</ModelCollapse> </ModelCollapse>
</span> </span>

View File

@@ -8,6 +8,8 @@ export default class BaseLayout extends React.Component {
errActions: PropTypes.object.isRequired, errActions: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired, specActions: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
oas3Selectors: PropTypes.object.isRequired,
oas3Actions: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired, layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired, layoutActions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired getComponent: PropTypes.func.isRequired
@@ -19,7 +21,14 @@ export default class BaseLayout extends React.Component {
} }
render() { render() {
let { specSelectors, specActions, getComponent, layoutSelectors } = this.props let {
specSelectors,
specActions,
getComponent,
layoutSelectors,
oas3Selectors,
oas3Actions
} = this.props
let info = specSelectors.info() let info = specSelectors.info()
let url = specSelectors.url() let url = specSelectors.url()
@@ -28,6 +37,7 @@ export default class BaseLayout extends React.Component {
let securityDefinitions = specSelectors.securityDefinitions() let securityDefinitions = specSelectors.securityDefinitions()
let externalDocs = specSelectors.externalDocs() let externalDocs = specSelectors.externalDocs()
let schemes = specSelectors.schemes() let schemes = specSelectors.schemes()
let servers = specSelectors.servers()
let Info = getComponent("info") let Info = getComponent("info")
let Operations = getComponent("operations", true) let Operations = getComponent("operations", true)
@@ -35,6 +45,7 @@ export default class BaseLayout extends React.Component {
let AuthorizeBtn = getComponent("authorizeBtn", true) let AuthorizeBtn = getComponent("authorizeBtn", true)
let Row = getComponent("Row") let Row = getComponent("Row")
let Col = getComponent("Col") let Col = getComponent("Col")
let Servers = getComponent("Servers")
let Errors = getComponent("errors", true) let Errors = getComponent("errors", true)
let isLoading = specSelectors.loadingStatus() === "loading" let isLoading = specSelectors.loadingStatus() === "loading"
@@ -82,6 +93,22 @@ export default class BaseLayout extends React.Component {
</div> </div>
) : null } ) : 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 : filter === null || filter === false ? null :
<div className="filter-container"> <div className="filter-container">

View File

@@ -29,13 +29,18 @@ Duration.propTypes = {
export default class LiveResponse extends React.Component { export default class LiveResponse extends React.Component {
static propTypes = { static propTypes = {
response: PropTypes.object.isRequired, response: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
pathMethod: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
displayRequestDuration: PropTypes.bool.isRequired displayRequestDuration: PropTypes.bool.isRequired,
getConfigs: PropTypes.func.isRequired
} }
render() { render() {
const { request, response, getComponent, displayRequestDuration } = this.props const { response, getComponent, getConfigs, displayRequestDuration, specSelectors, pathMethod } = this.props
const { showMutatedRequest } = getConfigs()
const curlRequest = showMutatedRequest ? specSelectors.mutatedRequestFor(pathMethod[0], pathMethod[1]) : specSelectors.requestFor(pathMethod[0], pathMethod[1])
const status = response.get("status") const status = response.get("status")
const url = response.get("url") const url = response.get("url")
const headers = response.get("headers").toJS() const headers = response.get("headers").toJS()
@@ -55,7 +60,7 @@ export default class LiveResponse extends React.Component {
return ( return (
<div> <div>
{ request && <Curl request={ request }/> } { curlRequest && <Curl request={ curlRequest }/> }
<h4>Server response</h4> <h4>Server response</h4>
<table className="responses-table"> <table className="responses-table">
<thead> <thead>

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)
} }
@@ -161,12 +163,12 @@ export default class Operation extends PureComponent {
<div className={`opblock-summary opblock-summary-${method}`} onClick={this.toggleShown} > <div className={`opblock-summary opblock-summary-${method}`} onClick={this.toggleShown} >
<span className="opblock-summary-method">{method.toUpperCase()}</span> <span className="opblock-summary-method">{method.toUpperCase()}</span>
<span className={ deprecated ? "opblock-summary-path__deprecated" : "opblock-summary-path" } > <span className={ deprecated ? "opblock-summary-path__deprecated" : "opblock-summary-path" } >
<a <a
className="nostyle" className="nostyle"
onClick={(e) => e.preventDefault()} onClick={isDeepLinkingEnabled ? (e) => e.preventDefault() : null}
href={ isDeepLinkingEnabled ? `#/${isShownKey[1]}/${isShownKey[2]}` : ""} > href={isDeepLinkingEnabled ? `#/${isShownKey[1]}/${isShownKey[2]}` : null}>
<span>{path}</span> <span>{path}</span>
</a> </a>
<JumpToPath path={jumpToKey} /> <JumpToPath path={jumpToKey} />
</span> </span>
@@ -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">
@@ -263,7 +266,9 @@ export default class Operation extends PureComponent {
request={ request } request={ request }
tryItOutResponse={ response } tryItOutResponse={ response }
getComponent={ getComponent } getComponent={ getComponent }
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 (
@@ -81,13 +84,13 @@ export default class Operations extends React.Component {
id={isShownKey.join("-")}> id={isShownKey.join("-")}>
<a <a
className="nostyle" className="nostyle"
onClick={(e) => e.preventDefault()} onClick={isDeepLinkingEnabled ? (e) => e.preventDefault() : null}
href={ isDeepLinkingEnabled ? `#/${tag}` : ""}> href= {isDeepLinkingEnabled ? `#/${tag}` : null}>
<span>{tag}</span> <span>{tag}</span>
</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,8 +12,10 @@ 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
@@ -28,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, 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" )
@@ -38,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">
@@ -57,6 +84,9 @@ export default class Responses extends React.Component {
<LiveResponse request={ request } <LiveResponse request={ request }
response={ tryItOutResponse } response={ tryItOutResponse }
getComponent={ getComponent } getComponent={ getComponent }
getConfigs={ getConfigs }
specSelectors={ specSelectors }
pathMethod={ this.props.pathMethod }
displayRequestDuration={ displayRequestDuration } /> displayRequestDuration={ displayRequestDuration } />
<h4>Responses</h4> <h4>Responses</h4>
</div> </div>
@@ -74,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 }
@@ -84,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

@@ -42,6 +42,11 @@ module.exports = function SwaggerUI(opts) {
displayOperationId: false, displayOperationId: false,
displayRequestDuration: false, displayRequestDuration: false,
deepLinking: false, deepLinking: false,
requestInterceptor: (a => a),
responseInterceptor: (a => a),
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.
@@ -64,6 +69,9 @@ module.exports = function SwaggerUI(opts) {
let queryConfig = parseSearch() let queryConfig = parseSearch()
const domNode = opts.domNode
delete opts.domNode
const constructorConfig = deepExtend({}, defaults, opts, queryConfig) const constructorConfig = deepExtend({}, defaults, opts, queryConfig)
const storeConfigs = deepExtend({}, constructorConfig.store, { const storeConfigs = deepExtend({}, constructorConfig.store, {
@@ -107,8 +115,8 @@ module.exports = function SwaggerUI(opts) {
let mergedConfig = deepExtend({}, localConfig, constructorConfig, fetchedConfig || {}, queryConfig) let mergedConfig = deepExtend({}, localConfig, constructorConfig, fetchedConfig || {}, queryConfig)
// deep extend mangles domNode, we need to set it manually // deep extend mangles domNode, we need to set it manually
if(opts.domNode) { if(domNode) {
mergedConfig.domNode = opts.domNode mergedConfig.domNode = domNode
} }
store.setConfigs(mergedConfig) store.setConfigs(mergedConfig)

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

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

View File

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

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

View File

@@ -2,13 +2,18 @@ import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes" import ImPropTypes from "react-immutable-proptypes"
import { OrderedMap } from "immutable" import { OrderedMap } from "immutable"
import { getSampleSchema } from "core/utils"
const RequestBody = ({
const RequestBody = ({ requestBody, getComponent, specSelectors, contentType }) => { requestBody,
getComponent,
specSelectors,
contentType,
isExecute,
onChange
}) => {
const Markdown = getComponent("Markdown") const Markdown = getComponent("Markdown")
const ModelExample = getComponent("modelExample") const ModelExample = getComponent("modelExample")
const HighlightCode = getComponent("highlightCode") const RequestBodyEditor = getComponent("RequestBodyEditor")
const requestBodyDescription = (requestBody && requestBody.get("description")) || null const requestBodyDescription = (requestBody && requestBody.get("description")) || null
const requestBodyContent = (requestBody && requestBody.get("content")) || new OrderedMap() const requestBodyContent = (requestBody && requestBody.get("content")) || new OrderedMap()
@@ -16,10 +21,6 @@ const RequestBody = ({ requestBody, getComponent, specSelectors, contentType })
const mediaTypeValue = requestBodyContent.get(contentType) const mediaTypeValue = requestBodyContent.get(contentType)
const sampleSchema = getSampleSchema(mediaTypeValue.get("schema").toJS(), contentType, {
includeWriteOnly: true
})
return <div> return <div>
{ requestBodyDescription && { requestBodyDescription &&
<Markdown source={requestBodyDescription} /> <Markdown source={requestBodyDescription} />
@@ -28,8 +29,16 @@ const RequestBody = ({ requestBody, getComponent, specSelectors, contentType })
getComponent={ getComponent } getComponent={ getComponent }
specSelectors={ specSelectors } specSelectors={ specSelectors }
expandDepth={1} expandDepth={1}
isExecute={isExecute}
schema={mediaTypeValue.get("schema")} schema={mediaTypeValue.get("schema")}
example={<HighlightCode value={sampleSchema} />} example={<RequestBodyEditor
requestBody={requestBody}
onChange={onChange}
mediaType={contentType}
getComponent={getComponent}
isExecute={isExecute}
specSelectors={specSelectors}
/>}
/> />
</div> </div>
} }
@@ -38,7 +47,9 @@ RequestBody.propTypes = {
requestBody: ImPropTypes.orderedMap.isRequired, requestBody: ImPropTypes.orderedMap.isRequired,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
contentType: PropTypes.string.isRequired contentType: PropTypes.string.isRequired,
isExecute: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired
} }
export default RequestBody export default RequestBody

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

View File

@@ -1,8 +1,12 @@
// import reducers from "./reducers" // import reducers from "./reducers"
// import * as actions from "./actions" // 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 components from "./components"
import wrapComponents from "./wrap-components" import wrapComponents from "./wrap-components"
import * as oas3Actions from "./actions"
import * as oas3Selectors from "./selectors"
import oas3Reducers from "./reducers"
export default function() { export default function() {
return { return {
@@ -10,7 +14,13 @@ export default function() {
wrapComponents, wrapComponents,
statePlugins: { statePlugins: {
spec: { spec: {
wrapSelectors wrapSelectors: specWrapSelectors,
selectors: specSelectors
},
oas3: {
actions: oas3Actions,
reducers: oas3Reducers,
selectors: oas3Selectors,
} }
} }
} }

View 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)
},
}

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

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

View File

@@ -1,6 +1,6 @@
import { createSelector } from "reselect" import { createSelector } from "reselect"
import { Map } from "immutable" import { Map } from "immutable"
import { isOAS3 as isOAS3Helper, isSwagger2 as isSwagger2Helper } from "./helpers" import { isOAS3 as isOAS3Helper, isSwagger2 as isSwagger2Helper } from "../helpers"
// Helpers // Helpers
@@ -56,6 +56,11 @@ export const schemes = OAS3NullSelector
// New selectors // New selectors
export const servers = onlyOAS3(createSelector(
spec,
spec => spec.getIn(["servers"]) || Map()
))
export const isOAS3 = (ori, system) => () => { export const isOAS3 = (ori, system) => () => {
const spec = system.getSystem().specSelectors.specJson() const spec = system.getSystem().specSelectors.specJson()
return isOAS3Helper(spec) return isOAS3Helper(spec)

View File

@@ -3,7 +3,6 @@ import parameters from "./parameters"
import VersionStamp from "./version-stamp" import VersionStamp from "./version-stamp"
import OnlineValidatorBadge from "./online-validator-badge" import OnlineValidatorBadge from "./online-validator-badge"
import Model from "./model" import Model from "./model"
import TryItOutButton from "./try-it-out-button"
export default { export default {
Markdown, Markdown,
@@ -11,5 +10,4 @@ export default {
VersionStamp, VersionStamp,
model: Model, model: Model,
onlineValidatorBadge: OnlineValidatorBadge, onlineValidatorBadge: OnlineValidatorBadge,
TryItOutButton
} }

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

@@ -13,8 +13,7 @@ class Parameters extends Component {
super(props) super(props)
this.state = { this.state = {
callbackVisible: false, callbackVisible: false,
parametersVisible: true, parametersVisible: true
requestBodyContentType: ""
} }
} }
@@ -24,6 +23,8 @@ class Parameters extends Component {
operation: PropTypes.object.isRequired, operation: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
oas3Actions: PropTypes.object.isRequired,
oas3Selectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired, fn: PropTypes.object.isRequired,
tryItOutEnabled: PropTypes.bool, tryItOutEnabled: PropTypes.bool,
allowTryItOut: PropTypes.bool, allowTryItOut: PropTypes.bool,
@@ -48,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 ) => {
@@ -86,6 +87,8 @@ class Parameters extends Component {
fn, fn,
getComponent, getComponent,
specSelectors, specSelectors,
oas3Actions,
oas3Selectors,
pathMethod, pathMethod,
operation operation
} = this.props } = this.props
@@ -159,16 +162,22 @@ class Parameters extends Component {
<h4 className={`opblock-title parameter__name ${requestBody.get("required") && "required"}`}>Request body</h4> <h4 className={`opblock-title parameter__name ${requestBody.get("required") && "required"}`}>Request body</h4>
<label> <label>
<ContentType <ContentType
value={this.state.requestBodyContentType} value={oas3Selectors.requestContentType(...pathMethod)}
contentTypes={ requestBody.get("content").keySeq() } contentTypes={ requestBody.get("content").keySeq() }
onChange={(val) => this.setState({ requestBodyContentType: val })} onChange={(value) => {
oas3Actions.setRequestContentType({ value, pathMethod })
}}
className="body-param-content-type" /> className="body-param-content-type" />
</label> </label>
</div> </div>
<div className="opblock-description-wrapper"> <div className="opblock-description-wrapper">
<RequestBody <RequestBody
requestBody={requestBody} requestBody={requestBody}
contentType={this.state.requestBodyContentType}/> isExecute={isExecute}
onChange={(value) => {
oas3Actions.setRequestBodyValue({ value, pathMethod })
}}
contentType={oas3Selectors.requestContentType(...pathMethod)}/>
</div> </div>
</div> </div>
} }

View File

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

View File

@@ -1,6 +1,7 @@
import YAML from "js-yaml" import YAML from "js-yaml"
import parseUrl from "url-parse" import parseUrl from "url-parse"
import serializeError from "serialize-error" import serializeError from "serialize-error"
import { isJSONObject } from "core/utils"
// Actions conform to FSA (flux-standard-actions) // Actions conform to FSA (flux-standard-actions)
// {type: string,payload: Any|Error, meta: obj, error: bool} // {type: string,payload: Any|Error, meta: obj, error: bool}
@@ -12,6 +13,7 @@ export const UPDATE_PARAM = "spec_update_param"
export const VALIDATE_PARAMS = "spec_validate_param" export const VALIDATE_PARAMS = "spec_validate_param"
export const SET_RESPONSE = "spec_set_response" export const SET_RESPONSE = "spec_set_response"
export const SET_REQUEST = "spec_set_request" export const SET_REQUEST = "spec_set_request"
export const SET_MUTATED_REQUEST = "spec_set_mutated_request"
export const LOG_REQUEST = "spec_log_request" export const LOG_REQUEST = "spec_log_request"
export const CLEAR_RESPONSE = "spec_clear_response" export const CLEAR_RESPONSE = "spec_clear_response"
export const CLEAR_REQUEST = "spec_clear_request" export const CLEAR_REQUEST = "spec_clear_request"
@@ -128,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 }
} }
} }
@@ -177,6 +179,13 @@ export const setRequest = ( path, method, req ) => {
} }
} }
export const setMutatedRequest = ( path, method, req ) => {
return {
payload: { path, method, req },
type: SET_MUTATED_REQUEST
}
}
// This is for debugging, remove this comment if you depend on this action // This is for debugging, remove this comment if you depend on this action
export const logRequest = (req) => { export const logRequest = (req) => {
return { return {
@@ -187,37 +196,67 @@ export const logRequest = (req) => {
// Actually fire the request via fn.execute // Actually fire the request via fn.execute
// (For debugging) and ease of testing // (For debugging) and ease of testing
export const executeRequest = (req) => ({fn, specActions, specSelectors}) => { export const executeRequest = (req) =>
let { pathName, method, operation } = req ({fn, specActions, specSelectors, getConfigs, oas3Selectors}) => {
let { pathName, method, operation } = req
let { requestInterceptor, responseInterceptor } = getConfigs()
let op = operation.toJS() let op = operation.toJS()
// 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) {
req.operationId = op.operationId
} else if(op && pathName && method) {
req.operationId = fn.opId(op, pathName, method)
}
if(op && op.operationId) { if(specSelectors.isOAS3()) {
req.operationId = op.operationId // OAS3 request feature support
} else if(op && pathName && method) { req.server = oas3Selectors.selectedServer()
req.operationId = fn.opId(op, pathName, method) 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)
specActions.setRequest(req.pathName, req.method, parsedRequest)
let requestInterceptorWrapper = function(r) {
let mutatedRequest = requestInterceptor.apply(this, [r])
let parsedMutatedRequest = Object.assign({}, mutatedRequest)
specActions.setMutatedRequest(req.pathName, req.method, parsedMutatedRequest)
return mutatedRequest
}
req.requestInterceptor = requestInterceptorWrapper
req.responseInterceptor = responseInterceptor
// track duration of request
const startTime = Date.now()
return fn.execute(req)
.then( res => {
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)
})
)
} }
let parsedRequest = Object.assign({}, req)
parsedRequest = fn.buildRequest(parsedRequest)
specActions.setRequest(req.pathName, req.method, parsedRequest)
// track duration of request
const startTime = Date.now()
return fn.execute(req)
.then( res => {
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) } ) )
}
// 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 // 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
export const execute = ( { path, method, ...extras }={} ) => (system) => { export const execute = ( { path, method, ...extras }={} ) => (system) => {

View File

@@ -3,13 +3,14 @@ import { fromJSOrdered, validateParam } from "core/utils"
import win from "../../window" import win from "../../window"
import { import {
UPDATE_SPEC, UPDATE_SPEC,
UPDATE_URL, UPDATE_URL,
UPDATE_JSON, UPDATE_JSON,
UPDATE_PARAM, UPDATE_PARAM,
VALIDATE_PARAMS, VALIDATE_PARAMS,
SET_RESPONSE, SET_RESPONSE,
SET_REQUEST, SET_REQUEST,
SET_MUTATED_REQUEST,
UPDATE_RESOLVED, UPDATE_RESOLVED,
UPDATE_OPERATION_VALUE, UPDATE_OPERATION_VALUE,
CLEAR_RESPONSE, CLEAR_RESPONSE,
@@ -39,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 )
} }
@@ -101,6 +103,10 @@ export default {
return state.setIn( [ "requests", path, method ], fromJSOrdered(req)) return state.setIn( [ "requests", path, method ], fromJSOrdered(req))
}, },
[SET_MUTATED_REQUEST]: (state, { payload: { req, path, method } } ) =>{
return state.setIn( [ "mutatedRequests", path, method ], fromJSOrdered(req))
},
[UPDATE_OPERATION_VALUE]: (state, { payload: { path, value, key } }) => { [UPDATE_OPERATION_VALUE]: (state, { payload: { path, value, key } }) => {
let operationPath = ["resolved", "paths", ...path] let operationPath = ["resolved", "paths", ...path]
if(!state.getIn(operationPath)) { if(!state.getIn(operationPath)) {

View File

@@ -237,6 +237,11 @@ export const requests = createSelector(
state => state.get( "requests", Map() ) state => state.get( "requests", Map() )
) )
export const mutatedRequests = createSelector(
state,
state => state.get( "mutatedRequests", Map() )
)
export const responseFor = (state, path, method) => { export const responseFor = (state, path, method) => {
return responses(state).getIn([path, method], null) return responses(state).getIn([path, method], null)
} }
@@ -245,16 +250,20 @@ export const requestFor = (state, path, method) => {
return requests(state).getIn([path, method], null) return requests(state).getIn([path, method], null)
} }
export const mutatedRequestFor = (state, path, method) => {
return mutatedRequests(state).getIn([path, method], null)
}
export const allowTryItOutFor = () => { export const allowTryItOutFor = () => {
// This is just a hook for now. // This is just a hook for now.
return true return true
} }
// 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()
} }
@@ -271,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,11 +8,31 @@ 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"
export const isImmutable = (maybe) => Im.Iterable.isIterable(maybe) 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) { export function objectify (thing) {
if(!isObject(thing)) if(!isObject(thing))
return {} return {}
@@ -450,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"
@@ -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 // 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)
@@ -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 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" ) {
@@ -631,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

@@ -1,2 +1,5 @@
// Promise global, Used ( at least ) by 'whatwg-fetch'. And required by IE 11 // Promise global, Used ( at least ) by 'whatwg-fetch'. And required by IE 11
require("core-js/fn/promise")
if(!window.Promise) {
require("core-js/fn/promise")
}

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();
@@ -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 .loading-container
{ {
@@ -704,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
@@ -726,12 +811,12 @@ section
a.nostyle { a.nostyle {
text-decoration: inherit; text-decoration: inherit;
color: inherit; color: inherit;
cursor: auto; cursor: pointer;
display: inline; display: inline;
&:visited { &:visited {
text-decoration: inherit; text-decoration: inherit;
color: inherit; color: inherit;
cursor: auto; cursor: pointer;
} }
} }

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,85 @@
/* eslint-env mocha */
import React from "react"
import { fromJSOrdered } from "core/utils"
import expect, { createSpy } from "expect"
import { shallow } from "enzyme"
import Curl from "components/curl"
import LiveResponse from "components/live-response"
import ResponseBody from "components/response-body"
describe("<LiveResponse/>", function(){
let request = fromJSOrdered({
credentials: "same-origin",
headers: {
accept: "application/xml"
},
url: "http://petstore.swagger.io/v2/pet/1"
})
let mutatedRequest = fromJSOrdered({
credentials: "same-origin",
headers: {
accept: "application/xml",
mutated: "header"
},
url: "http://petstore.swagger.io/v2/pet/1"
})
let requests = {
request: request,
mutatedRequest: mutatedRequest
}
const tests = [
{ showMutatedRequest: true, expected: { request: "mutatedRequest", requestForCalls: 0, mutatedRequestForCalls: 1 } },
{ showMutatedRequest: false, expected: { request: "request", requestForCalls: 1, mutatedRequestForCalls: 0 } }
]
tests.forEach(function(test) {
it("passes " + test.expected.request + " to Curl when showMutatedRequest = " + test.showMutatedRequest, function() {
// Given
let response = fromJSOrdered({
status: 200,
url: "http://petstore.swagger.io/v2/pet/1",
headers: {},
text: "<response/>",
})
let mutatedRequestForSpy = createSpy().andReturn(mutatedRequest)
let requestForSpy = createSpy().andReturn(request)
let components = {
curl: Curl,
responseBody: ResponseBody
}
let props = {
response: response,
specSelectors: {
mutatedRequestFor: mutatedRequestForSpy,
requestFor: requestForSpy,
},
pathMethod: [ "/one", "get" ],
getComponent: (c) => {
return components[c]
},
displayRequestDuration: true,
getConfigs: () => ({ showMutatedRequest: test.showMutatedRequest })
}
// When
let wrapper = shallow(<LiveResponse {...props}/>)
// Then
expect(mutatedRequestForSpy.calls.length).toEqual(test.expected.mutatedRequestForCalls)
expect(requestForSpy.calls.length).toEqual(test.expected.requestForCalls)
const curl = wrapper.find(Curl)
expect(curl.length).toEqual(1)
expect(curl.props().request).toBe(requests[test.expected.request])
})
})
})

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

@@ -93,6 +93,55 @@ describe("spec plugin - actions", function(){
}) })
}) })
it("should pass requestInterceptor/responseInterceptor to fn.execute", function(){
// Given
let configs = {
requestInterceptor: createSpy(),
responseInterceptor: createSpy()
}
const system = {
fn: {
buildRequest: createSpy(),
execute: createSpy().andReturn(Promise.resolve())
},
specActions: {
executeRequest: createSpy(),
setMutatedRequest: createSpy(),
setRequest: createSpy()
},
specSelectors: {
spec: () => fromJS({}),
parameterValues: () => fromJS({}),
contentTypeValues: () => fromJS({}),
url: () => fromJS({}),
isOAS3: () => false
},
getConfigs: () => configs
}
// When
let executeFn = executeRequest({
pathName: "/one",
method: "GET",
operation: fromJS({operationId: "getOne"})
})
let res = executeFn(system)
// Then
expect(system.fn.execute.calls.length).toEqual(1)
expect(system.fn.execute.calls[0].arguments[0]).toIncludeKey("requestInterceptor")
expect(system.fn.execute.calls[0].arguments[0]).toInclude({
responseInterceptor: configs.responseInterceptor
})
expect(system.specActions.setMutatedRequest.calls.length).toEqual(0)
expect(system.specActions.setRequest.calls.length).toEqual(1)
let wrappedRequestInterceptor = system.fn.execute.calls[0].arguments[0].requestInterceptor
wrappedRequestInterceptor(system.fn.execute.calls[0].arguments[0])
expect(configs.requestInterceptor.calls.length).toEqual(1)
expect(system.specActions.setMutatedRequest.calls.length).toEqual(1)
expect(system.specActions.setRequest.calls.length).toEqual(1)
})
}) })
xit("should call specActions.setResponse, when fn.execute resolves", function(){ xit("should call specActions.setResponse, when fn.execute resolves", function(){

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