Swagger toegevoegd voor API documentatie op /docs (en redirect vanaf /)

This commit is contained in:
2017-06-07 08:28:33 +02:00
parent 3fa2b09eef
commit 9fe4d895b3
178 changed files with 20825 additions and 0 deletions

1
api/public/api.json Normal file

File diff suppressed because one or more lines are too long

1
api/public/docs Symbolic link
View File

@@ -0,0 +1 @@
../swagger-ui/dist

38
api/swagger-ui/.babelrc Normal file
View File

@@ -0,0 +1,38 @@
{
"presets": [
"es2015",
"react",
"stage-0"
],
"plugins": [
[
"module-alias",
[
{
"expose": "components",
"src": "src/core/components"
},
{
"expose": "core",
"src": "src/core"
},
{
"expose": "img",
"src": "src/img"
},
{
"expose": "corePlugins",
"src": "src/core/plugins"
},
{
"expose": "less",
"src": "src/less"
},
{
"expose": "base",
"src": "npm:getbase/src/less/base"
}
]
]
]
}

View File

@@ -0,0 +1,10 @@
root = true
[*]
end_of_line = lf
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

37
api/swagger-ui/.eslintrc Normal file
View File

@@ -0,0 +1,37 @@
{
"parser": "babel-eslint",
"env": {
"browser": true,
"node": true,
"es6": true
},
"parserOptions": {
"ecmaFeatures": {
"jsx": true
}
},
"extends": ["eslint:recommended", "plugin:react/recommended"],
"plugins": [
"react"
],
"rules": {
"semi": [2, "never"],
"strict": 0,
"quotes": 2,
"no-unused-vars": 2,
"no-multi-spaces": 1,
"camelcase": 1,
"no-use-before-define": [2,"nofunc"],
"no-underscore-dangle": 0,
"no-unused-expressions": 1,
"comma-dangle": 0,
"no-console": ["error", { allow: ["warn", "error"] }],
"react/jsx-no-bind": 1,
"react/display-name": 0
}
}

View File

@@ -0,0 +1,3 @@
When reporting an issue, please provide the following details:
- swagger-ui version
- a swagger file reproducing the issue

6
api/swagger-ui/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
node_modules
.idea
.deps_check
.DS_Store
npm-debug.log*
.eslintcache

View File

@@ -0,0 +1,8 @@
*
*/
!README.md
!package.json
!dist/swagger-ui.js
!dist/swagger-ui.js.map
!dist/swagger-ui.css
!dist/swagger-ui.css.map

View File

@@ -0,0 +1,50 @@
language: node_js
node_js:
- '6.9'
services:
- docker
branches:
only:
- master
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/
before_deploy:
- npm run build
env:
- DOCKER_IMAGE_NAME=swaggerapi/swagger-ui
deploy:
- provider: npm
email: apiteam@swagger.io
skip_cleanup: true
api_key:
secure: "IJkLaACa+rfERf1O5nwlqOyuo9sbul3FBhBt4Un9P+DvEet3AoDPV9NQVLd8SkmQYKGbGQWF4BIdjrO5nqFD6Te+JTeUX5Uo/DFS/fu9qw1xv0dQpvbJFuoYnnFlbzGTEs4CFa8lbu3ZromFHQGOQxRobjsG1Kf0dWFSSzmND3g="
on:
tags: true
repo: swagger-api/swagger-ui
node: '6.9'
- provider: script
skip_cleanup: true
script: swagger-ui-dist-package/deploy.sh
on:
tags: true
repo: swagger-api/swagger-ui
node: '6.9'
after_success:
- if [ $DOCKER_HUB_USERNAME ]; then
docker login --email=$DOCKER_HUB_EMAIL --username=$DOCKER_HUB_USERNAME --password=$DOCKER_HUB_PASSWORD;
if [ ! -z "$TRAVIS_TAG" ]; then
DOCKER_IMAGE_TAG=$TRAVIS_TAG;
else
DOCKER_IMAGE_TAG=unstable;
fi;
docker build -t $DOCKER_IMAGE_NAME .;
if [ ! -z "$TRAVIS_TAG" ]; then
docker tag $DOCKER_IMAGE_NAME $DOCKER_IMAGE_NAME:$DOCKER_IMAGE_TAG;
docker push $DOCKER_IMAGE_NAME:$DOCKER_IMAGE_TAG;
docker tag $DOCKER_IMAGE_NAME $DOCKER_IMAGE_NAME:latest;
docker push $DOCKER_IMAGE_NAME:latest;
else
docker tag $DOCKER_IMAGE_NAME $DOCKER_IMAGE_NAME:$DOCKER_IMAGE_TAG;
docker push $DOCKER_IMAGE_NAME:$DOCKER_IMAGE_TAG;
fi;
fi;

28
api/swagger-ui/Dockerfile Normal file
View File

@@ -0,0 +1,28 @@
FROM alpine:3.4
MAINTAINER fehguy
ENV VERSION "v2.2.10"
ENV FOLDER "swagger-ui-2.2.10"
ENV API_URL "http://petstore.swagger.io/v2/swagger.json"
ENV API_KEY "**None**"
ENV OAUTH_CLIENT_ID "**None**"
ENV OAUTH_CLIENT_SECRET "**None**"
ENV OAUTH_REALM "**None**"
ENV OAUTH_APP_NAME "**None**"
ENV OAUTH_ADDITIONAL_PARAMS "**None**"
ENV SWAGGER_JSON "/app/swagger.json"
ENV PORT 80
RUN apk add --update nginx
RUN mkdir -p /run/nginx
COPY nginx.conf /etc/nginx/
# copy swagger files to the `/js` folder
ADD ./dist/* /usr/share/nginx/html/
ADD ./docker-run.sh /usr/share/nginx/
EXPOSE 8080
CMD ["sh", "/usr/share/nginx/docker-run.sh"]

11
api/swagger-ui/LICENSE Normal file
View File

@@ -0,0 +1,11 @@
Copyright 2017 SmartBear Software
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at [apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

225
api/swagger-ui/README.md Normal file
View File

@@ -0,0 +1,225 @@
# Swagger UI
[![NPM version](https://badge.fury.io/js/swagger-ui.svg)](http://badge.fury.io/js/swagger-ui)
## New!
**This is the new version of swagger-ui, 3.x. Want to learn more? Check out our [FAQ](http://swagger.io/new-ui-faq/).**
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.
For the older version of swagger-ui, refer to the [*2.x branch*](https://github.com/swagger-api/swagger-ui/tree/2.x).
## Compatibility
The OpenAPI Specification has undergone 4 revisions since initial creation in 2010. Compatibility between swagger-ui and the OpenAPI Specification is as follows:
Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes | Status
------------------ | ------------ | -------------------------- | ----- | ------
3.0.13 | 2017-06-02 | 2.0 | [tag v3.0.13](https://github.com/swagger-api/swagger-ui/tree/v3.0.13) |
2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10) |
2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5) |
2.0.24 | 2014-09-12 | 1.1, 1.2 | [tag v2.0.24](https://github.com/swagger-api/swagger-ui/tree/v2.0.24) |
1.0.13 | 2013-03-08 | 1.1, 1.2 | [tag v1.0.13](https://github.com/swagger-api/swagger-ui/tree/v1.0.13) |
1.0.1 | 2011-10-11 | 1.0, 1.1 | [tag v1.0.1](https://github.com/swagger-api/swagger-ui/tree/v1.0.1) |
### How to run
##### Easy start! Docker
You can pull a pre-built docker image of the swagger-ui directly from Dockerhub:
```
docker pull swaggerapi/swagger-ui
docker run -p 80:8080 swaggerapi/swagger-ui
```
Will start nginx with swagger-ui on port 80.
##### Prerequisites
- Node 6.x
- NPM 3.x
If you just want to see your specs, open `dist/index.html` in your browser directly from your filesystem.
If you'd like to make modifications to the codebase, run the dev server with: `npm run dev`. A development server will open on `3200`.
If you'd like to rebuild the `/dist` folder with your codebase changes, run `npm run build`.
##### Browser support
Swagger UI works in the latest versions of Chrome, Safari, Firefox, Edge and IE11.
### Known Issues
To help with the migration, here are the currently known issues with 3.X. This list will update regularly, and will not include features that were not implemented in previous versions.
- Only part of the [parameters](#parameters) previously supported are available.
- The JSON Form Editor is not implemented.
- Shebang URL support for operations is missing.
- Support for `collectionFormat` is partial.
- l10n (translations) is not implemented.
- Relative path support for external files is not implemented.
### SwaggerUIBundle
To use swagger-ui's bundles, you should take a look at the [source of swagger-ui html page](https://github.com/swagger-api/swagger-ui/blob/master/dist/index.html) and customize it. This basically requires you to instantiate a SwaggerUi object as below:
```javascript
const ui = SwaggerUIBundle({
url: "http://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
```
#### OAuth2 configuration
You can configure OAuth2 authorization by calling `initOAuth` method with passed configs under the instance of `SwaggerUIBundle`
default `client_id` and `client_secret`, `realm`, an application name `appName`, `scopeSeparator`, `additionalQueryStringParams`.
Config Name | Description
--- | ---
client_id | Default clientId. MUST be a string
client_secret | Default clientSecret. MUST be a string
realm | realm query parameter (for oauth1) added to `authorizationUrl` and `tokenUrl` . MUST be a string
appName | application name, displayed in authorization popup. MUST be a string
scopeSeparator | scope separator for passing scopes, encoded before calling, default value is a space (encoded value `%20`). MUST be a string
additionalQueryStringParams | Additional query parameters added to `authorizationUrl` and `tokenUrl`. MUST be an object
```
const ui = SwaggerUIBundle({...})
// Method can be called in any place after calling constructor SwaggerUIBundle
ui.initOAuth({
clientId: "your-client-id",
clientSecret: "your-client-secret-if-required",
realm: "your-realms",
appName: "your-app-name",
scopeSeparator: " ",
additionalQueryStringParams: {test: "hello"}
})
```
If you'd like to use the bundle files via npm, check out the [`swagger-ui-dist` package](https://www.npmjs.com/package/swagger-ui-dist).
#### Parameters
Parameter Name | Description
--- | ---
url | The url pointing to API definition (normally `swagger.json` or `swagger.yaml`).
spec | A JSON object describing the OpenAPI Specification. When used, the `url` parameter will not be parsed. This is useful for testing manually-generated specifications without hosting them.
validatorUrl | By default, Swagger-UI attempts to validate specs against swagger.io's online validator. You can use this parameter to set a different validator URL, for example for locally deployed validators ([Validator Badge](https://github.com/swagger-api/validator-badge)). Setting it to `null` will disable validation.
dom_id | The id of a dom element inside which SwaggerUi will put the user interface for swagger.
oauth2RedirectUrl | OAuth redirect URL
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.
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
modelPropertyMacro | MUST be a function. Function to set default values to each property in model. Accepts one argument modelPropertyMacro(property), property is immutable
docExpansion | Controls the default expansion setting for the operations and tags. It can be 'list' (expands only the tags), 'full' (expands the tags and operations) or 'none' (expands nothing). The default is 'list'.
displayOperationId | Controls the display of operationId in operations list. The default is `false`.
### Plugins
#### Topbar plugin
Topbar plugin enables top bar with input for spec path and explore button. By default the plugin is enabled, and to disable it you need to remove Topbar plugin from presets in `src/standalone/index.js`:
```
let preset = [
// TopbarPlugin,
ConfigsPlugin,
() => {
return {
components: { StandaloneLayout }
}
}
]
```
#### Configs plugin
Configs plugin allows to fetch external configs instead of passing them to `SwaggerUIBundle`. Fetched configs support two formats: JSON or yaml. The plugin is enabled by default.
There are three options of passing config:
- add a query parameter `config` with URL to a server where the configs are hosted. For ex. http://petstore.swagger.io/?config=http://localhost:3001/config.yaml
- add a config `configUrl` with URL to SwaggerUIBundle
- change default configs in `swagger-config.yaml` *Note: after changing, the project must be re-built*
These options can be used altogether, the order of inheritance is following (from the lowest priority to the highest):
`swagger-config.yaml` -> config passed to `SwaggerUIBundle` -> config fetched from `configUrl` passed to `SwaggerUIBundle` -> config fetched from URL passed as a query parameter `config`
## CORS Support
CORS is a technique to prevent websites from doing bad things with your personal data. Most browsers + JavaScript toolkits not only support CORS but enforce it, which has implications for your API server which supports Swagger.
You can read about CORS here: http://www.w3.org/TR/cors.
There are two cases where no action is needed for CORS support:
1. swagger-ui is hosted on the same server as the application itself (same host *and* port).
2. The application is located behind a proxy that enables the required CORS headers. This may already be covered within your organization.
Otherwise, CORS support needs to be enabled for:
1. Your Swagger docs. For Swagger 2.0 it's the `swagger.json`/`swagger.yaml` and any externally `$ref`ed docs.
2. For the `Try it now` button to work, CORS needs to be enabled on your API endpoints as well.
### Testing CORS Support
You can verify CORS support with one of three techniques:
- Curl your API and inspect the headers. For instance:
```bash
$ curl -I "http://petstore.swagger.io/v2/swagger.json"
HTTP/1.1 200 OK
Date: Sat, 31 Jan 2015 23:05:44 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, PUT, PATCH, OPTIONS
Access-Control-Allow-Headers: Content-Type, api_key, Authorization
Content-Type: application/json
Content-Length: 0
```
This tells us that the petstore resource listing supports OPTIONS, and the following headers: `Content-Type`, `api_key`, `Authorization`.
- Try swagger-ui from your file system and look at the debug console. If CORS is not enabled, you'll see something like this:
```
XMLHttpRequest cannot load http://sad.server.com/v2/api-docs. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.
```
Swagger-UI cannot easily show this error state.
- Using the http://www.test-cors.org website. Keep in mind this will show a successful result even if `Access-Control-Allow-Headers` is not available, which is still required for Swagger-UI to function properly.
### Enabling CORS
The method of enabling CORS depends on the server and/or framework you use to host your application. http://enable-cors.org provides information on how to enable CORS in some common web servers.
Other servers/frameworks may provide you information on how to enable it specifically in their use case.
### CORS and Header Parameters
Swagger lets you easily send headers as parameters to requests. The name of these headers *MUST* be supported in your CORS configuration as well. From our example above:
```
Access-Control-Allow-Headers: Content-Type, api_key, Authorization
```
Only headers with these names will be allowed to be sent by Swagger-UI.
## License
Copyright 2017 SmartBear Software
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at [apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,28 @@
function extsToRegExp(exts) {
return new RegExp("\\.(" + exts.map(function(ext) {
return ext.replace(/\./g, "\\.");
}).join("|") + ")(\\?.*)?$");
}
module.exports = function loadersByExtension(obj) {
var loaders = [];
Object.keys(obj).forEach(function(key) {
var exts = key.split("|");
var value = obj[key];
var entry = {
extensions: exts,
test: extsToRegExp(exts)
};
if(Array.isArray(value)) {
entry.loaders = value;
} else if(typeof value === "string") {
entry.loader = value;
} else {
Object.keys(value).forEach(function(valueKey) {
entry[valueKey] = value[valueKey];
});
}
loaders.push(entry);
});
return loaders;
};

View File

@@ -0,0 +1,40 @@
{
"name": "swagger-api/swagger-ui",
"description": " Swagger UI is a collection of HTML, Javascript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.",
"keywords": [
"Swagger",
"OpenAPI",
"specification",
"documentation",
"API",
"UI"
],
"homepage": "http://swagger.io",
"license": "Apache-2.0",
"authors": [
{
"name": "Anna Bodnia",
"email": "anna.bodnia@gmail.com"
},
{
"name": "Buu Nguyen",
"email": "buunguyen@gmail.com"
},
{
"name": "Josh Ponelat",
"email": "jponelat@gmail.com"
},
{
"name": "Kyle Shockey",
"email": "kyleshockey1@gmail.com"
},
{
"name": "Robert Barnwell",
"email": "robert@robertismy.name"
},
{
"name": "Sahar Jafari",
"email": "shr.jafari@gmail.com"
}
]
}

View File

@@ -0,0 +1,104 @@
<!-- HTML for dev server -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body {
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
</symbol>
</defs>
</svg>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js"> </script>
<script src="./swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
window["SwaggerUIBundle"] = window["swagger-ui-bundle"]
window["SwaggerUIStandalonePreset"] = window["swagger-ui-standalone-preset"]
// Build a system
const ui = SwaggerUIBundle({
url: "http://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
ui.initOAuth({
clientId: "your-client-id",
clientSecret: "your-client-secret-if-required",
realm: "your-realms",
appName: "your-app-name",
scopeSeparator: " ",
additionalQueryStringParams: {}
})
}
</script>
</body>
</html>

View File

@@ -0,0 +1,53 @@
<!doctype html>
<html lang="en-US">
<body onload="run()">
</body>
</html>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
qp = (window.location.hash || location.search).substring(1);
arr = qp.split("&")
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value)
}
) : {}
isValid = qp.state === sentState
if (oauth2.auth.schema.get("flow") === "accessCode" && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: "Authorization failed: no accessCode received from the server"
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
</script>

BIN
api/swagger-ui/dist/favicon-16x16.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

BIN
api/swagger-ui/dist/favicon-32x32.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

93
api/swagger-ui/dist/index.html vendored Normal file
View File

@@ -0,0 +1,93 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body {
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
</symbol>
</defs>
</svg>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js"> </script>
<script src="./swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
url: "../api.json",
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
}
</script>
</body>
</html>

View File

@@ -0,0 +1,53 @@
<!doctype html>
<html lang="en-US">
<body onload="run()">
</body>
</html>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var isValid, qp, arr;
qp = (window.location.hash || location.search).substring(1);
arr = qp.split("&")
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value)
}
) : {}
isValid = qp.state === sentState
if (oauth2.auth.schema.get("flow") === "accessCode" && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback(oauth2.auth);
} else {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: "Authorization failed: no accessCode received from the server"
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid});
}
window.close();
}
</script>

108
api/swagger-ui/dist/swagger-ui-bundle.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;AAu/FA;AA6+FA;;;;;;;;;;;;;;;;;;;;;;;;;;AAyTA;;;;;;AAoIA;AAi7FA;AAmtCA;AAi0IA;AA0oJA;AAgwFA;AAyrGA;AA0lFA;AA4nFA;AA+9CA;AA+gDA;AAwrCA;AA60EA;;;;;AA6oCA;AAsyJA;;;;;;;;;;;;;;AA64EA;AA4mIA;AAquJA;AA2qHA;AA2mGA;AAiiEA;AAq4DA;AAg3DA;AAoPA;;;;;;AAk7FA;AA07FA;;;;;AAi8CA;AAgsFA;AAs2CA;AAglCA;AAu9CA;AAy8EA;AAsiCA;AA+yFA;;;;;;;;;AAgkDA;AA2zIA;AAu7FA;AAmrFA;AAu0EA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"version":3,"file":"swagger-ui-standalone-preset.js","sources":["webpack:///swagger-ui-standalone-preset.js"],"mappings":"AAAA;;;;;AA8QA;AAmvGA;AAuxFA;;;;;;AAocA;AAkvFA;AAu+CA;AAo+CA;AAgrCA;AAuyEA","sourceRoot":""}

2
api/swagger-ui/dist/swagger-ui.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"version":3,"file":"swagger-ui.css","sources":[],"mappings":"","sourceRoot":""}

15
api/swagger-ui/dist/swagger-ui.js vendored Normal file

File diff suppressed because one or more lines are too long

1
api/swagger-ui/dist/swagger-ui.js.map vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AA0yCA;AAoyHA;AAmyHA;AAykGA;AA+9BA;AA6iCA;AAojCA;AAu5BA","sourceRoot":""}

View File

@@ -0,0 +1,48 @@
#! /bin/sh
set -e
INDEX_FILE=/usr/share/nginx/html/index.html
replace_in_index () {
if [ "$1" != "**None**" ]; then
sed -i "s|/\*||g" $INDEX_FILE
sed -i "s|\*/||g" $INDEX_FILE
sed -i "s|$1|$2|g" $INDEX_FILE
fi
}
replace_or_delete_in_index () {
if [ -z "$2" ]; then
sed -i "/$1/d" $INDEX_FILE
else
replace_in_index $1 $2
fi
}
replace_in_index myApiKeyXXXX123456789 $API_KEY
replace_or_delete_in_index your-client-id $OAUTH_CLIENT_ID
replace_or_delete_in_index your-client-secret-if-required $OAUTH_CLIENT_SECRET
replace_or_delete_in_index your-realms $OAUTH_REALM
replace_or_delete_in_index your-app-name $OAUTH_APP_NAME
if [ "$OAUTH_ADDITIONAL_PARAMS" != "**None**" ]; then
replace_in_index "additionalQueryStringParams: {}" "additionalQueryStringParams: {$OAUTH_ADDITIONAL_PARAMS}"
fi
if [[ -f $SWAGGER_JSON ]]; then
sed -i "s|http://petstore.swagger.io/v2/swagger.json|swagger.json|g" $INDEX_FILE
sed -i "s|http://example.com/api|swagger.json|g" $INDEX_FILE
else
sed -i "s|http://petstore.swagger.io/v2/swagger.json|$API_URL|g" $INDEX_FILE
sed -i "s|http://example.com/api|$API_URL|g" $INDEX_FILE
fi
if [[ -n "$VALIDATOR_URL" ]]; then
sed -i "s|.*validatorUrl:.*$||g" $INDEX_FILE
TMP_VU="$VALIDATOR_URL"
[[ "$VALIDATOR_URL" != "null" && "$VALIDATOR_URL" != "undefined" ]] && TMP_VU="\"${VALIDATOR_URL}\""
sed -i "s|\(url: .*,\)|\1\n validatorUrl: ${TMP_VU},|g" $INDEX_FILE
unset TMP_VU
fi
exec nginx -g 'daemon off;'

View File

@@ -0,0 +1,165 @@
var path = require('path')
var webpack = require('webpack')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var deepExtend = require('deep-extend')
var autoprefixer = require('autoprefixer')
const {gitDescribeSync} = require('git-describe');
var loadersByExtension = require('./build-tools/loadersByExtension')
var pkg = require('./package.json')
let gitInfo
try {
gitInfo = gitDescribeSync(__dirname)
} catch(e) {
gitInfo = {
hash: 'noGit',
dirty: false
}
}
module.exports = function(options) {
// Special options, that have logic in this file
// ...with defaults
var specialOptions = deepExtend({}, {
hot: false,
separateStylesheets: true,
minimize: false,
longTermCaching: false,
sourcemaps: false,
}, options._special)
var loadersMap = {
'js(x)?': {
loader: 'babel?retainLines=true',
include: [ path.join(__dirname, 'src') ],
},
'json': 'json-loader',
'txt|yaml': 'raw-loader',
'png|jpg|jpeg|gif|svg': specialOptions.disableAssets ? 'null-loader' : 'url-loader?limit=10000',
'woff|woff2': specialOptions.disableAssets ? 'null-loader' : 'url-loader?limit=100000',
'ttf|eot': specialOptions.disableAssets ? 'null-loader' : 'file-loader',
"worker.js": ["worker-loader?inline=true", "babel"]
}
var plugins = []
if( specialOptions.separateStylesheets ) {
plugins.push(new ExtractTextPlugin('[name].css' + (specialOptions.longTermCaching ? '?[contenthash]' : ''), {
allChunks: true
}))
}
if( specialOptions.minimize ) {
plugins.push(
new webpack.optimize.UglifyJsPlugin({
compressor: {
warnings: false
}
}),
new webpack.optimize.DedupePlugin()
)
plugins.push( new webpack.NoErrorsPlugin())
}
plugins.push(
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: specialOptions.minimize ? JSON.stringify('production') : null,
WEBPACK_INLINE_STYLES: !Boolean(specialOptions.separateStylesheets)
},
'buildInfo': JSON.stringify({
PACKAGE_VERSION: (pkg.version),
GIT_COMMIT: gitInfo.hash,
GIT_DIRTY: gitInfo.dirty
})
}))
var cssLoader = 'css-loader!postcss-loader'
var completeStylesheetLoaders = deepExtend({
'css': cssLoader,
'scss': cssLoader + '!' + 'sass-loader?outputStyle=expanded&sourceMap=true&sourceMapContents=true',
'less': cssLoader + '!' + 'less-loader',
}, specialOptions.stylesheetLoaders)
if(specialOptions.cssModules) {
cssLoader = cssLoader + '?module' + (specialOptions.minimize ? '' : '&localIdentName=[path][name]---[local]---[hash:base64:5]')
}
Object.keys(completeStylesheetLoaders).forEach(function(ext) {
var ori = completeStylesheetLoaders[ext]
if(specialOptions.separateStylesheets) {
completeStylesheetLoaders[ext] = ExtractTextPlugin.extract('style-loader', ori)
} else {
completeStylesheetLoaders[ext] = 'style-loader!' + ori
}
})
var loaders = loadersByExtension(deepExtend({}, loadersMap, specialOptions.loaders, completeStylesheetLoaders))
var extraLoaders = (options.module || {} ).loaders
if(Array.isArray(extraLoaders)) {
loaders = loaders.concat(extraLoaders)
delete options.module.loaders
}
var completeConfig = deepExtend({
entry: {},
output: {
path: path.join(__dirname, 'dist'),
publicPath: '/',
filename: '[name].js',
chunkFilename: '[name].js'
},
target: 'web',
// yaml-js has a reference to `fs`, this is a workaround
node: {
fs: 'empty'
},
module: {
loaders: loaders,
},
resolveLoader: {
root: path.join(__dirname, 'node_modules'),
},
externals: {
'buffertools': true // json-react-schema/deeper depends on buffertools, which fails.
},
resolve: {
root: path.join(__dirname, './src'),
modulesDirectories: ['node_modules'],
extensions: ["", ".web.js", ".js", ".jsx", ".json", ".less"],
packageAlias: 'browser',
alias: {
base: "getbase/src/less/base"
}
},
postcss: function() {
return [autoprefixer]
},
devtool: specialOptions.sourcemaps ? 'cheap-module-source-map' : null,
plugins,
}, options)
return completeConfig
}

49
api/swagger-ui/nginx.conf Normal file
View File

@@ -0,0 +1,49 @@
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8080;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
#
# Custom headers and headers various browsers *should* be OK with but aren't
#
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
#
# Tell client that this pre-flight info is valid for 20 days
#
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
}
}
}

7033
api/swagger-ui/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

140
api/swagger-ui/package.json Normal file
View File

@@ -0,0 +1,140 @@
{
"name": "swagger-ui",
"version": "3.0.13",
"main": "dist/swagger-ui.js",
"repository": "git@github.com:swagger-api/swagger-ui.git",
"contributors": [
"(in alphabetical order)",
"Anna Bodnia <anna.bodnia@gmail.com>",
"Buu Nguyen <buunguyen@gmail.com>",
"Josh Ponelat <jponelat@gmail.com>",
"Kyle Shockey <kyleshockey1@gmail.com>",
"Robert Barnwell <robert@robertismy.name>",
"Sahar Jafari <shr.jafari@gmail.com>"
],
"license": "Apache-2.0",
"scripts": {
"build": "npm run build-core && npm run build-bundle && npm run build-standalone",
"build-bundle": "webpack --config webpack-dist-bundle.config.js --colors",
"build-core": "webpack --config webpack-dist.config.js --colors",
"build-standalone": "webpack --config webpack-dist-standalone.config.js --colors",
"predev": "npm install",
"dev": "npm-run-all --parallel hot-server open-localhost",
"watch": "webpack --config webpack-watch.config.js --watch --progress",
"open-localhost": "node -e 'require(\"open\")(\"http://localhost:3200\")'",
"hot-server": "webpack-dev-server --host 0.0.0.0 --config webpack-hot-dev-server.config.js --inline --hot --progress --content-base dev-helpers/",
"deps-license": "license-checker --production --csv --out $npm_package_config_deps_check_dir/licenses.csv && license-checker --development --csv --out $npm_package_config_deps_check_dir/licenses-dev.csv",
"deps-size": "webpack -p --config webpack.check.js --json | webpack-bundle-size-analyzer >| $npm_package_config_deps_check_dir/sizes.txt",
"deps-check": "npm run deps-license && npm run deps-size",
"lint": "eslint --cache --ext '.js,.jsx' src test",
"lint-errors": "eslint --cache --quiet --ext '.js,.jsx' src test",
"lint-fix": "eslint --cache --ext '.js,.jsx' src test --fix",
"test": "npm run lint-errors && npm run just-test-in-node",
"test-in-node": "npm run lint-errors && npm run just-test-in-node",
"just-test": "karma start --config karma.conf.js",
"just-test-in-node": "mocha --recursive --compilers js:babel-core/register test/core test/components"
},
"dependencies": {
"babel-polyfill": "^6.23.0",
"base64-js": "^1.2.0",
"brace": "0.7.0",
"deep-extend": "0.4.1",
"expect": "1.20.2",
"getbase": "^2.8.2",
"ieee754": "^1.1.8",
"immutable": "^3.x.x",
"js-yaml": "^3.5.5",
"less": "2.7.1",
"lodash": "4.17.2",
"matcher": "^0.1.2",
"memoizee": "0.4.1",
"promise-worker": "^1.1.1",
"react": "^15.4.0",
"react-addons-perf": "0.14.8",
"react-addons-shallow-compare": "0.14.8",
"react-addons-test-utils": "^15.4.0",
"react-collapse": "2.3.1",
"react-dom": "^15.4.0",
"react-height": "^2.0.0",
"react-hot-loader": "1.3.1",
"react-immutable-proptypes": "2.1.0",
"react-motion": "0.4.4",
"react-object-inspector": "0.2.1",
"react-redux": "^4.x.x",
"react-remarkable": "1.1.1",
"react-split-pane": "0.1.57",
"redux": "^3.x.x",
"redux-immutable": "3.0.8",
"redux-logger": "*",
"reselect": "2.5.3",
"sanitize-html": "^1.14.1",
"serialize-error": "2.0.0",
"shallowequal": "0.2.2",
"swagger-client": "~3.0.13",
"url-parse": "^1.1.8",
"whatwg-fetch": "0.11.1",
"worker-loader": "^0.7.1",
"xml": "1.0.1",
"yaml-js": "^0.1.3"
},
"devDependencies": {
"autoprefixer": "6.6.1",
"babel-core": "^6.23.1",
"babel-eslint": "^7.1.1",
"babel-loader": "^6.3.2",
"babel-plugin-module-alias": "^1.6.0",
"babel-preset-es2015": "^6.22.0",
"babel-preset-es2015-ie": "^6.6.2",
"babel-preset-react": "^6.23.0",
"babel-preset-stage-0": "^6.22.0",
"babel-runtime": "^6.23.0",
"css-loader": "0.22.0",
"deep-extend": "^0.4.1",
"deepmerge": "^1.3.2",
"enzyme": "^2.7.1",
"eslint": "^2.13.1",
"eslint-plugin-react": "^6.10.3",
"extract-text-webpack-plugin": "0.8.2",
"file-loader": "0.8.4",
"git-describe": "^4.0.1",
"html-webpack-plugin": "^2.28.0",
"imports-loader": "0.6.5",
"json-loader": "0.5.3",
"karma": "^0.13.22",
"karma-chrome-launcher": "^0.2.3",
"karma-mocha": "^0.2.2",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "1.8.0",
"less": "2.5.3",
"less-loader": "2.2.1",
"license-checker": "^8.0.4",
"mocha": "^2.5.3",
"node-sass": "^4.5.0",
"npm-run-all": "3.1.1",
"null-loader": "0.1.1",
"open": "0.0.5",
"postcss-loader": "0.7.0",
"raw-loader": "0.5.1",
"react-hot-loader": "^1.3.1",
"react-test-renderer": "^15.5.4",
"rimraf": "^2.6.0",
"sass-loader": "^6.0.2",
"standard": "^8.6.0",
"standard-loader": "^5.0.0",
"style-loader": "0.13.0",
"url-loader": "0.5.6",
"webpack": "^1.14.0",
"webpack-bundle-size-analyzer": "^2.5.0"
},
"config": {
"deps_check_dir": ".deps_check"
},
"browserslist": [
"> 1%",
"last 2 versions",
"IE 10"
],
"optionalDependencies": {
"webpack-dev-server": "1.14.0"
}
}

View File

@@ -0,0 +1,28 @@
name: swagger-ui
version: master
summary: The World's Most Popular API Framework
description: |
Swagger UI is part of the Swagger project. The Swagger project allows you to
produce, visualize and consume your OWN RESTful services. No proxy or 3rd
party services required. Do it your own way.
Swagger UI is a dependency-free collection of HTML, Javascript, and CSS
assets that dynamically generate beautiful documentation and sandbox from a
Swagger-compliant API. Because Swagger UI has no dependencies, you can host
it in any server environment, or on your local machine.
grade: devel
confinement: strict
apps:
swagger-ui:
command: sh -c \"cd $SNAP/lib/node_modules/swagger-ui/dist && http-server -a localhost -p 8080\"
daemon: simple
plugs: [network, network-bind]
parts:
swagger-ui:
source: .
plugin: nodejs
npm-run: [build]
node-packages: [handlebars, http-server]

View File

@@ -0,0 +1,6 @@
/* global ace */
ace.define("ace/snippets/yaml",
["require","exports","module"], function(e,t,n){ // eslint-disable-line no-unused-vars
t.snippetText=undefined
t.scope="yaml"
})

View File

@@ -0,0 +1,27 @@
import React, { PropTypes } from "react"
export default class App extends React.Component {
getLayout() {
let { getComponent, layoutSelectors } = this.props
const layoutName = layoutSelectors.current()
const Component = getComponent(layoutName, true)
return Component ? Component : ()=> <h1> No layout defined for "{layoutName}" </h1>
}
render() {
const Layout = this.getLayout()
return (
<Layout/>
)
}
}
App.propTypes = {
getComponent: PropTypes.func.isRequired,
layoutSelectors: PropTypes.object.isRequired,
}
App.defaultProps = {
}

View File

@@ -0,0 +1,80 @@
import React, { PropTypes } from "react"
export default class ApiKeyAuth extends React.Component {
static propTypes = {
authorized: PropTypes.object,
getComponent: PropTypes.func.isRequired,
errSelectors: PropTypes.object.isRequired,
schema: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
onChange: PropTypes.func
}
constructor(props, context) {
super(props, context)
let { name, schema } = this.props
let value = this.getValue()
this.state = {
name: name,
schema: schema,
value: value
}
}
getValue () {
let { name, authorized } = this.props
return authorized && authorized.getIn([name, "value"])
}
onChange =(e) => {
let { onChange } = this.props
let value = e.target.value
let newState = Object.assign({}, this.state, { value: value })
this.setState(newState)
onChange(newState)
}
render() {
let { schema, getComponent, errSelectors, name } = this.props
const Input = getComponent("Input")
const Row = getComponent("Row")
const Col = getComponent("Col")
const AuthError = getComponent("authError")
const Markdown = getComponent( "Markdown" )
const JumpToPath = getComponent("JumpToPath", true)
let value = this.getValue()
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
return (
<div>
<h4>Api key authorization<JumpToPath path={[ "securityDefinitions", name ]} /></h4>
{ value && <h6>Authorized</h6>}
<Row>
<Markdown source={ schema.get("description") } />
</Row>
<Row>
<p>Name: <code>{ schema.get("name") }</code></p>
</Row>
<Row>
<p>In: <code>{ schema.get("in") }</code></p>
</Row>
<Row>
<label>Value:</label>
{
value ? <code> ****** </code>
: <Col><Input type="text" onChange={ this.onChange }/></Col>
}
</Row>
{
errors.valueSeq().map( (error, key) => {
return <AuthError error={ error }
key={ key }/>
} )
}
</div>
)
}
}

View File

@@ -0,0 +1,59 @@
import React, { PropTypes } from "react"
export default class AuthorizationPopup extends React.Component {
close =() => {
let { authActions } = this.props
authActions.showDefinitions(false)
}
render() {
let { authSelectors, authActions, getComponent, errSelectors, specSelectors, fn: { AST } } = this.props
let definitions = authSelectors.shownDefinitions()
const Auths = getComponent("auths")
return (
<div className="dialog-ux">
<div className="backdrop-ux"></div>
<div className="modal-ux">
<div className="modal-dialog-ux">
<div className="modal-ux-inner">
<div className="modal-ux-header">
<h3>Available authorizations</h3>
<button type="button" className="close-modal" onClick={ this.close }>
<svg width="20" height="20">
<use xlinkHref="#close" />
</svg>
</button>
</div>
<div className="modal-ux-content">
{
definitions.valueSeq().map(( definition, key ) => {
return <Auths key={ key }
AST={AST}
definitions={ definition }
getComponent={ getComponent }
errSelectors={ errSelectors }
authSelectors={ authSelectors }
authActions={ authActions }
specSelectors={ specSelectors }/>
})
}
</div>
</div>
</div>
</div>
</div>
)
}
static propTypes = {
fn: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
authSelectors: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
errSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
}
}

View File

@@ -0,0 +1,42 @@
import React, { PropTypes } from "react"
export default class AuthorizeBtn extends React.Component {
static propTypes = {
className: PropTypes.string
}
onClick =() => {
let { authActions, authSelectors } = this.props
let definitions = authSelectors.definitionsToAuthorize()
authActions.showDefinitions(definitions)
}
render() {
let { authSelectors, getComponent } = this.props
//must be moved out of button component
const AuthorizationPopup = getComponent("authorizationPopup", true)
let showPopup = !!authSelectors.shownDefinitions()
let isAuthorized = !!authSelectors.authorized().size
return (
<div className="auth-wrapper">
<button className={isAuthorized ? "btn authorize locked" : "btn authorize unlocked"} onClick={ this.onClick }>
<span>Authorize</span>
<svg width="20" height="20">
<use xlinkHref={ isAuthorized ? "#locked" : "#unlocked" } />
</svg>
</button>
{ showPopup && <AuthorizationPopup /> }
</div>
)
}
static propTypes = {
getComponent: PropTypes.func.isRequired,
authSelectors: PropTypes.object.isRequired,
errActions: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
}
}

View File

@@ -0,0 +1,38 @@
import React, { PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"
export default class AuthorizeOperationBtn extends React.Component {
onClick =(e) => {
e.stopPropagation()
let { security, authActions, authSelectors } = this.props
let definitions = authSelectors.getDefinitionsByNames(security)
authActions.showDefinitions(definitions)
}
render() {
let { security, authSelectors } = this.props
let isAuthorized = authSelectors.isAuthorized(security)
if(isAuthorized === null) {
return null
}
return (
<button className={isAuthorized ? "authorization__btn locked" : "authorization__btn unlocked"} onClick={ this.onClick }>
<svg width="20" height="20">
<use xlinkHref={ isAuthorized ? "#locked" : "#unlocked" } />
</svg>
</button>
)
}
static propTypes = {
authSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
security: ImPropTypes.iterable.isRequired
}
}

View File

@@ -0,0 +1,136 @@
import React, { PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"
export default class Auths extends React.Component {
static propTypes = {
definitions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
authSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired
}
constructor(props, context) {
super(props, context)
this.state = {}
}
onAuthChange =(auth) => {
let { name } = auth
this.setState({ [name]: auth })
}
submitAuth =(e) => {
e.preventDefault()
let { authActions } = this.props
authActions.authorize(this.state)
}
logoutClick =(e) => {
e.preventDefault()
let { authActions, definitions } = this.props
let auths = definitions.map( (val, key) => {
return key
}).toArray()
authActions.logout(auths)
}
render() {
let { definitions, getComponent, authSelectors, errSelectors } = this.props
const ApiKeyAuth = getComponent("apiKeyAuth")
const BasicAuth = getComponent("basicAuth")
const Oauth2 = getComponent("oauth2", true)
const Button = getComponent("Button")
let authorized = authSelectors.authorized()
let authorizedAuth = definitions.filter( (definition, key) => {
return !!authorized.get(key)
})
let nonOauthDefinitions = definitions.filter( schema => schema.get("type") !== "oauth2")
let oauthDefinitions = definitions.filter( schema => schema.get("type") === "oauth2")
return (
<div className="auth-container">
{
!!nonOauthDefinitions.size && <form onSubmit={ this.submitAuth }>
{
nonOauthDefinitions.map( (schema, name) => {
let type = schema.get("type")
let authEl
switch(type) {
case "apiKey": authEl = <ApiKeyAuth key={ name }
schema={ schema }
name={ name }
errSelectors={ errSelectors }
authorized={ authorized }
getComponent={ getComponent }
onChange={ this.onAuthChange } />
break
case "basic": authEl = <BasicAuth key={ name }
schema={ schema }
name={ name }
errSelectors={ errSelectors }
authorized={ authorized }
getComponent={ getComponent }
onChange={ this.onAuthChange } />
break
default: authEl = <div key={ name }>Unknown security definition type { type }</div>
}
return (<div key={`${name}-jump`}>
{ authEl }
</div>)
}).toArray()
}
<div className="auth-btn-wrapper">
{
nonOauthDefinitions.size === authorizedAuth.size ? <Button className="btn modal-btn auth" onClick={ this.logoutClick }>Logout</Button>
: <Button type="submit" className="btn modal-btn auth authorize">Authorize</Button>
}
</div>
</form>
}
{
oauthDefinitions && oauthDefinitions.size ? <div>
<div className="scope-def">
<p>Scopes are used to grant an application different levels of access to data on behalf of the end user. Each API may declare one or more scopes.</p>
<p>API requires the following scopes. Select which ones you want to grant to Swagger UI.</p>
</div>
{
definitions.filter( schema => schema.get("type") === "oauth2")
.map( (schema, name) =>{
return (<div key={ name }>
<Oauth2 authorized={ authorized }
schema={ schema }
name={ name } />
</div>)
}
).toArray()
}
</div> : null
}
</div>
)
}
static propTypes = {
errSelectors: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
authSelectors: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
definitions: ImPropTypes.iterable.isRequired
}
}

View File

@@ -0,0 +1,100 @@
import React, { PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"
export default class BasicAuth extends React.Component {
static propTypes = {
authorized: PropTypes.object,
getComponent: PropTypes.func.isRequired,
schema: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired
}
constructor(props, context) {
super(props, context)
let { schema, name } = this.props
let value = this.getValue()
let username = value.username
this.state = {
name: name,
schema: schema,
value: !username ? {} : {
username: username
}
}
}
getValue () {
let { authorized, name } = this.props
return authorized && authorized.getIn([name, "value"]) || {}
}
onChange =(e) => {
let { onChange } = this.props
let { value, name } = e.target
let newValue = this.state.value
newValue[name] = value
this.setState({ value: newValue })
onChange(this.state)
}
render() {
let { schema, getComponent, name, errSelectors } = this.props
const Input = getComponent("Input")
const Row = getComponent("Row")
const Col = getComponent("Col")
const AuthError = getComponent("authError")
const JumpToPath = getComponent("JumpToPath", true)
const Markdown = getComponent( "Markdown" )
let username = this.getValue().username
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
return (
<div>
<h4>Basic authorization<JumpToPath path={[ "securityDefinitions", name ]} /></h4>
{ username && <h6>Authorized</h6> }
<Row>
<Markdown source={ schema.get("description") } />
</Row>
<Row>
<label>Username:</label>
{
username ? <code> { username } </code>
: <Col><Input type="text" required="required" name="username" onChange={ this.onChange }/></Col>
}
</Row>
<Row>
<label>Password:</label>
{
username ? <code> ****** </code>
: <Col><Input required="required"
autoComplete="new-password"
name="password"
type="password"
onChange={ this.onChange }/></Col>
}
</Row>
{
errors.valueSeq().map( (error, key) => {
return <AuthError error={ error }
key={ key }/>
} )
}
</div>
)
}
static propTypes = {
name: PropTypes.string.isRequired,
errSelectors: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
onChange: PropTypes.func,
schema: ImPropTypes.map,
authorized: ImPropTypes.map
}
}

View File

@@ -0,0 +1,23 @@
import React, { PropTypes } from "react"
export default class AuthError extends React.Component {
static propTypes = {
error: PropTypes.object.isRequired
}
render() {
let { error } = this.props
let level = error.get("level")
let message = error.get("message")
let source = error.get("source")
return (
<div className="errors" style={{ backgroundColor: "#ffeeee", color: "red", margin: "1em" }}>
<b style={{ textTransform: "capitalize", marginRight: "1em"}} >{ source } { level }</b>
<span>{ message }</span>
</div>
)
}
}

View File

@@ -0,0 +1,231 @@
import React, { PropTypes } from "react"
import oauth2Authorize from "core/oauth2-authorize"
const IMPLICIT = "implicit"
const ACCESS_CODE = "accessCode"
const PASSWORD = "password"
const APPLICATION = "application"
export default class Oauth2 extends React.Component {
static propTypes = {
name: PropTypes.string,
authorized: PropTypes.object,
getComponent: PropTypes.func.isRequired,
schema: PropTypes.object.isRequired,
authSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
errSelectors: PropTypes.object.isRequired,
errActions: PropTypes.object.isRequired,
getConfigs: PropTypes.any
}
constructor(props, context) {
super(props, context)
let { name, schema, authorized, authSelectors } = this.props
let auth = authorized && authorized.get(name)
let authConfigs = authSelectors.getConfigs() || {}
let username = auth && auth.get("username") || ""
let clientId = auth && auth.get("clientId") || authConfigs.clientId || ""
let clientSecret = auth && auth.get("clientSecret") || authConfigs.clientSecret || ""
let passwordType = auth && auth.get("passwordType") || "basic"
this.state = {
appName: authConfigs.appName,
name: name,
schema: schema,
scopes: [],
clientId: clientId,
clientSecret: clientSecret,
username: username,
password: "",
passwordType: passwordType
}
}
authorize =() => {
let { authActions, errActions, getConfigs, authSelectors } = this.props
let configs = getConfigs()
let authConfigs = authSelectors.getConfigs()
errActions.clear({authId: name,type: "auth", source: "auth"})
oauth2Authorize({auth: this.state, authActions, errActions, configs, authConfigs })
}
onScopeChange =(e) => {
let { target } = e
let { checked } = target
let scope = target.dataset.value
if ( checked && this.state.scopes.indexOf(scope) === -1 ) {
let newScopes = this.state.scopes.concat([scope])
this.setState({ scopes: newScopes })
} else if ( !checked && this.state.scopes.indexOf(scope) > -1) {
this.setState({ scopes: this.state.scopes.filter((val) => val !== scope) })
}
}
onInputChange =(e) => {
let { target : { dataset : { name }, value } } = e
let state = {
[name]: value
}
this.setState(state)
}
logout =(e) => {
e.preventDefault()
let { authActions, errActions, name } = this.props
errActions.clear({authId: name, type: "auth", source: "auth"})
authActions.logout([ name ])
}
render() {
let { schema, getComponent, authSelectors, errSelectors, name } = this.props
const Input = getComponent("Input")
const Row = getComponent("Row")
const Col = getComponent("Col")
const Button = getComponent("Button")
const AuthError = getComponent("authError")
const JumpToPath = getComponent("JumpToPath", true)
const Markdown = getComponent( "Markdown" )
let flow = schema.get("flow")
let scopes = schema.get("allowedScopes") || schema.get("scopes")
let authorizedAuth = authSelectors.authorized().get(name)
let isAuthorized = !!authorizedAuth
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
let isValid = !errors.filter( err => err.get("source") === "validation").size
return (
<div>
<h4>OAuth2.0 <JumpToPath path={[ "securityDefinitions", name ]} /></h4>
{ !this.state.appName ? null : <h5>Application: { this.state.appName } </h5> }
<Markdown source={ schema.get("description") } />
{ isAuthorized && <h6>Authorized</h6> }
{ ( flow === IMPLICIT || flow === ACCESS_CODE ) && <p>Authorization URL: <code>{ schema.get("authorizationUrl") }</code></p> }
{ ( flow === PASSWORD || flow === ACCESS_CODE || flow === APPLICATION ) && <p>Token URL:<code> { schema.get("tokenUrl") }</code></p> }
<p className="flow">Flow: <code>{ schema.get("flow") }</code></p>
{
flow !== PASSWORD ? null
: <Row>
<Row>
<label htmlFor="oauth_username">username:</label>
{
isAuthorized ? <code> { this.state.username } </code>
: <Col tablet={10} desktop={10}>
<input id="oauth_username" type="text" data-name="username" onChange={ this.onInputChange }/>
</Col>
}
</Row>
{
}
<Row>
<label htmlFor="oauth_password">password:</label>
{
isAuthorized ? <code> ****** </code>
: <Col tablet={10} desktop={10}>
<input id="oauth_password" type="password" data-name="password" onChange={ this.onInputChange }/>
</Col>
}
</Row>
<Row>
<label htmlFor="password_type">type:</label>
{
isAuthorized ? <code> { this.state.passwordType } </code>
: <Col tablet={10} desktop={10}>
<select id="password_type" data-name="passwordType" onChange={ this.onInputChange }>
<option value="request-body">Request body</option>
<option value="basic">Basic auth</option>
<option value="query">Query parameters</option>
</select>
</Col>
}
</Row>
</Row>
}
{
( flow === APPLICATION || flow === IMPLICIT || flow === ACCESS_CODE || ( flow === PASSWORD && this.state.passwordType!== "basic") ) &&
( !isAuthorized || isAuthorized && this.state.clientId) && <Row>
<label htmlFor="client_id">client_id:</label>
{
isAuthorized ? <code> ****** </code>
: <Col tablet={10} desktop={10}>
<input id="client_id"
type="text"
required={ flow === PASSWORD }
value={ this.state.clientId }
data-name="clientId"
onChange={ this.onInputChange }/>
</Col>
}
</Row>
}
{
( flow === APPLICATION || flow === ACCESS_CODE || ( flow === PASSWORD && this.state.passwordType!== "basic") ) && <Row>
<label htmlFor="client_secret">client_secret:</label>
{
isAuthorized ? <code> ****** </code>
: <Col tablet={10} desktop={10}>
<input id="client_secret"
value={ this.state.clientSecret }
type="text"
data-name="clientSecret"
onChange={ this.onInputChange }/>
</Col>
}
</Row>
}
{
!isAuthorized && scopes && scopes.size ? <div className="scopes">
<h2>Scopes:</h2>
{ scopes.map((description, name) => {
return (
<Row key={ name }>
<div className="checkbox">
<Input data-value={ name }
id={`${name}-checkbox-${this.state.name}`}
disabled={ isAuthorized }
type="checkbox"
onChange={ this.onScopeChange }/>
<label htmlFor={`${name}-checkbox-${this.state.name}`}>
<span className="item"></span>
<div className="text">
<p className="name">{name}</p>
<p className="description">{description}</p>
</div>
</label>
</div>
</Row>
)
}).toArray()
}
</div> : null
}
{
errors.valueSeq().map( (error, key) => {
return <AuthError error={ error }
key={ key }/>
} )
}
<div className="auth-btn-wrapper">
{ isValid &&
( isAuthorized ? <Button className="btn modal-btn auth authorize" onClick={ this.logout }>Logout</Button>
: <Button className="btn modal-btn auth authorize" onClick={ this.authorize }>Authorize</Button>
)
}
</div>
</div>
)
}
}

View File

@@ -0,0 +1,24 @@
import React, { Component, PropTypes } from "react"
export default class Clear extends Component {
onClick =() => {
let { specActions, path, method } = this.props
specActions.clearResponse( path, method )
specActions.clearRequest( path, method )
}
render(){
return (
<button className="btn btn-clear opblock-control__btn" onClick={ this.onClick }>
Clear
</button>
)
}
static propTypes = {
specActions: PropTypes.object.isRequired,
path: PropTypes.string.isRequired,
method: PropTypes.string.isRequired,
}
}

View File

@@ -0,0 +1,45 @@
import React, { PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"
import { fromJS } from "immutable"
const noop = ()=>{}
export default class ContentType extends React.Component {
static propTypes = {
contentTypes: PropTypes.oneOfType([ImPropTypes.list, ImPropTypes.set]),
value: PropTypes.string,
onChange: PropTypes.func,
className: PropTypes.string
}
static defaultProps = {
onChange: noop,
value: null,
contentTypes: fromJS(["application/json"]),
}
componentDidMount() {
// Needed to populate the form, initially
this.props.onChange(this.props.contentTypes.first())
}
onChangeWrapper = e => this.props.onChange(e.target.value)
render() {
let { contentTypes, className, value } = this.props
if ( !contentTypes || !contentTypes.size )
return null
return (
<div className={ "content-type-wrapper " + ( className || "" ) }>
<select className="content-type" value={value} onChange={this.onChangeWrapper} >
{ contentTypes.map( (val) => {
return <option key={ val } value={ val }>{ val }</option>
}).toArray()}
</select>
</div>
)
}
}

View File

@@ -0,0 +1,28 @@
import React, { PropTypes } from "react"
import curlify from "core/curlify"
export default class Curl extends React.Component {
static propTypes = {
request: PropTypes.object.isRequired
}
handleFocus(e) {
e.target.select()
document.execCommand("copy")
}
render() {
let { request } = this.props
let curl = curlify(request)
return (
<div>
<h4>Curl</h4>
<div className="copy-paste">
<textarea onFocus={this.handleFocus} className="curl" style={{ whiteSpace: "normal" }} value={curl}></textarea>
</div>
</div>
)
}
}

View File

@@ -0,0 +1,51 @@
import React, { PropTypes } from "react"
import Collapse from "react-collapse"
import { presets } from "react-motion"
import ObjectInspector from "react-object-inspector"
import Perf from "react-addons-perf"
export default class Debug extends React.Component {
constructor() {
super()
this.state = {
jsonDumpOpen: false
}
this.toggleJsonDump = (e) => {
e.preventDefault()
this.setState({jsonDumpOpen: !this.state.jsonDumpOpen})
}
window.Perf = Perf
}
plusOrMinus(bool) {
return bool ? "-" : "+"
}
render() {
let { getState } = this.props
window.props = this.props
return (
<div className="info">
<h3><a onClick={this.toggleJsonDump}> {this.plusOrMinus(this.state.jsonDumpOpen)} App </a></h3>
<Collapse isOpened={this.state.jsonDumpOpen} springConfig={presets.noWobble}>
<ObjectInspector data={getState().toJS() || {}} name="state" initialExpandedPaths={["state"]}/>
</Collapse>
</div>
)
}
}
Debug.propTypes = {
getState: PropTypes.func.isRequired
}

View File

@@ -0,0 +1,133 @@
import React, { PropTypes } from "react"
import { List } from "immutable"
import Collapse from "react-collapse"
export default class Errors extends React.Component {
static propTypes = {
editorActions: PropTypes.object,
errSelectors: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired
}
render() {
let { editorActions, errSelectors, layoutSelectors, layoutActions } = this.props
if(editorActions && editorActions.jumpToLine) {
var jumpToLine = editorActions.jumpToLine
}
let errors = errSelectors.allErrors()
// all thrown errors, plus error-level everything else
let allErrorsToDisplay = errors.filter(err => err.get("type") === "thrown" ? true :err.get("level") === "error")
if(!allErrorsToDisplay || allErrorsToDisplay.count() < 1) {
return null
}
let isVisible = layoutSelectors.isShown(["errorPane"], true)
let toggleVisibility = () => layoutActions.show(["errorPane"], !isVisible)
let sortedJSErrors = allErrorsToDisplay.sortBy(err => err.get("line"))
return (
<pre className="errors-wrapper">
<hgroup className="error">
<h4 className="errors__title">Errors</h4>
<button className="btn errors__clear-btn" onClick={ toggleVisibility }>{ isVisible ? "Hide" : "Show" }</button>
</hgroup>
<Collapse isOpened={ isVisible } animated >
<div className="errors">
{ sortedJSErrors.map((err, i) => {
let type = err.get("type")
if(type === "thrown" || type === "auth") {
return <ThrownErrorItem key={ i } error={ err.get("error") || err } jumpToLine={jumpToLine} />
}
if(type === "spec") {
return <SpecErrorItem key={ i } error={ err } jumpToLine={jumpToLine} />
}
}) }
</div>
</Collapse>
</pre>
)
}
}
const ThrownErrorItem = ( { error, jumpToLine } ) => {
if(!error) {
return null
}
let errorLine = error.get("line")
return (
<div className="error-wrapper">
{ !error ? null :
<div>
<h4>{ (error.get("source") && error.get("level")) ?
toTitleCase(error.get("source")) + " " + error.get("level") : "" }
{ error.get("path") ? <small> at {error.get("path")}</small>: null }</h4>
<span style={{ whiteSpace: "pre-line", "maxWidth": "100%" }}>
{ error.get("message") }
</span>
<div>
{ errorLine && jumpToLine ? <a onClick={jumpToLine.bind(null, errorLine)}>Jump to line { errorLine }</a> : null }
</div>
</div>
}
</div>
)
}
const SpecErrorItem = ( { error, jumpToLine } ) => {
let locationMessage = null
if(error.get("path")) {
if(List.isList(error.get("path"))) {
locationMessage = <small>at { error.get("path").join(".") }</small>
} else {
locationMessage = <small>at { error.get("path") }</small>
}
} else if(error.get("line") && !jumpToLine) {
locationMessage = <small>on line { error.get("line") }</small>
}
return (
<div className="error-wrapper">
{ !error ? null :
<div>
<h4>{ toTitleCase(error.get("source")) + " " + error.get("level") }&nbsp;{ locationMessage }</h4>
<span style={{ whiteSpace: "pre-line"}}>{ error.get("message") }</span>
<div style={{ "text-decoration": "underline", "cursor": "pointer" }}>
{ jumpToLine ? (
<a onClick={jumpToLine.bind(null, error.get("line"))}>Jump to line { error.get("line") }</a>
) : null }
</div>
</div>
}
</div>
)
}
function toTitleCase(str) {
return str
.split(" ")
.map(substr => substr[0].toUpperCase() + substr.slice(1))
.join(" ")
}
ThrownErrorItem.propTypes = {
error: PropTypes.object.isRequired,
jumpToLine: PropTypes.func
}
ThrownErrorItem.defaultProps = {
jumpToLine: null
}
SpecErrorItem.propTypes = {
error: PropTypes.object.isRequired,
jumpToLine: PropTypes.func
}

View File

@@ -0,0 +1,37 @@
import React, { Component, PropTypes } from "react"
export default class Execute extends Component {
static propTypes = {
specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
operation: PropTypes.object.isRequired,
path: PropTypes.string.isRequired,
getComponent: PropTypes.func.isRequired,
method: PropTypes.string.isRequired,
onExecute: PropTypes.func
}
onClick=()=>{
let { specSelectors, specActions, operation, path, method } = this.props
specActions.validateParams( [path, method] )
if ( specSelectors.validateBeforeExecute([path, method]) ) {
if(this.props.onExecute) {
this.props.onExecute()
}
specActions.execute( { operation, path, method } )
}
}
onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue([this.props.path, this.props.method], val)
render(){
return (
<button className="btn execute opblock-control__btn" onClick={ this.onClick }>
Execute
</button>
)
}
}

View File

@@ -0,0 +1,9 @@
import React from "react"
export default class Footer extends React.Component {
render() {
return (
<div className="footer"></div>
)
}
}

View File

@@ -0,0 +1,46 @@
import React, { PropTypes } from "react"
import Im from "immutable"
export default class Headers extends React.Component {
static propTypes = {
headers: PropTypes.object.isRequired
};
render() {
let { headers } = this.props
if ( !headers || !headers.size )
return null
return (
<div className="headers-wrapper">
<h4 className="headers__title">Headers:</h4>
<table className="headers">
<thead>
<tr className="header-row">
<th className="header-col">Name</th>
<th className="header-col">Description</th>
<th className="header-col">Type</th>
</tr>
</thead>
<tbody>
{
headers.entrySeq().map( ([ key, header ]) => {
if(!Im.Map.isMap(header)) {
return null
}
return (<tr key={ key }>
<td className="header-col">{ key }</td>
<td className="header-col">{ header.get( "description" ) }</td>
<td className="header-col">{ header.get( "type" ) }</td>
</tr>)
}).toArray()
}
</tbody>
</table>
</div>
)
}
}

View File

@@ -0,0 +1,24 @@
import React, { Component, PropTypes } from "react"
import { highlight } from "core/utils"
export default class HighlightCode extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
className: PropTypes.string
}
componentDidMount() {
highlight(this.refs.el)
}
componentDidUpdate() {
highlight(this.refs.el)
}
render () {
let { value, className } = this.props
className = className || ""
return <pre ref="el" className={className + " microlight"}>{ value }</pre>
}
}

View File

@@ -0,0 +1,128 @@
import React, { PropTypes } from "react"
import { fromJS } from "immutable"
import ImPropTypes from "react-immutable-proptypes"
class Path extends React.Component {
static propTypes = {
host: PropTypes.string,
basePath: PropTypes.string
}
render() {
let { host, basePath } = this.props
return (
<pre className="base-url">
[ Base url: {host}{basePath}]
</pre>
)
}
}
class Contact extends React.Component {
static propTypes = {
data: PropTypes.object
}
render(){
let { data } = this.props
let name = data.get("name") || "the developer"
let url = data.get("url")
let email = data.get("email")
return (
<div>
{ url && <div><a href={ url } target="_blank">{ name } - Website</a></div> }
{ email &&
<a href={`mailto:${email}`}>
{ url ? `Send email to ${name}` : `Contact ${name}`}
</a>
}
</div>
)
}
}
class License extends React.Component {
static propTypes = {
license: PropTypes.object
}
render(){
let { license } = this.props
let name = license.get("name") || "License"
let url = license.get("url")
return (
<div>
{
url ? <a target="_blank" href={ url }>{ name }</a>
: <span>{ name }</span>
}
</div>
)
}
}
export default class Info extends React.Component {
static propTypes = {
info: PropTypes.object,
url: PropTypes.string,
host: PropTypes.string,
basePath: PropTypes.string,
externalDocs: ImPropTypes.map,
getComponent: PropTypes.func.isRequired,
}
render() {
let { info, url, host, basePath, getComponent, externalDocs } = this.props
let version = info.get("version")
let description = info.get("description")
let title = info.get("title")
let termsOfService = info.get("termsOfService")
let contact = info.get("contact")
let license = info.get("license")
const { url:externalDocsUrl, description:externalDocsDescription } = (externalDocs || fromJS({})).toJS()
const Markdown = getComponent("Markdown")
return (
<div className="info">
<hgroup className="main">
<h2 className="title" >{ title }
{ version && <small><pre className="version"> { version } </pre></small> }
</h2>
{ host || basePath ? <Path host={ host } basePath={ basePath } /> : null }
{ url && <a target="_blank" href={ url }><span className="url"> { url } </span></a> }
</hgroup>
<div className="description">
<Markdown source={ description } />
</div>
{
termsOfService && <div>
<a target="_blank" href={ termsOfService }>Terms of service</a>
</div>
}
{ contact && contact.size ? <Contact data={ contact } /> : null }
{ license && license.size ? <License license={ license } /> : null }
{ externalDocsUrl ?
<a target="_blank" href={externalDocsUrl}>{externalDocsDescription || externalDocsUrl}</a>
: null }
</div>
)
}
}
Info.propTypes = {
title: PropTypes.any,
description: PropTypes.any,
version: PropTypes.any,
url: PropTypes.string
}

View File

@@ -0,0 +1,249 @@
import React, { PropTypes } from "react"
import OriCollapse from "react-collapse"
function xclass(...args) {
return args.filter(a => !!a).join(" ").trim()
}
export class Container extends React.Component {
render() {
let { fullscreen, full, ...rest } = this.props
// Normal element
if(fullscreen)
return <section {...rest}/>
let containerClass = "swagger-container" + (full ? "-full" : "")
return (
<section {...rest} className={xclass(rest.className, containerClass)}/>
)
}
}
Container.propTypes = {
fullscreen: PropTypes.bool,
full: PropTypes.bool,
className: PropTypes.string
}
const DEVICES = {
"mobile": "",
"tablet": "-tablet",
"desktop": "-desktop",
"large": "-hd"
}
export class Col extends React.Component {
render() {
const {
hide,
keepContents,
/* we don't want these in the `rest` object that passes to the final component,
since React now complains. So we extract them */
/* eslint-disable no-unused-vars */
mobile,
tablet,
desktop,
large,
/* eslint-enable no-unused-vars */
...rest
} = this.props
if(hide && !keepContents)
return <span/>
let classesAr = []
for (let device in DEVICES) {
let deviceClass = DEVICES[device]
if(device in this.props) {
let val = this.props[device]
if(val < 1) {
classesAr.push("none" + deviceClass)
continue
}
classesAr.push("block" + deviceClass)
classesAr.push("col-" + val + deviceClass)
}
}
let classes = xclass(rest.className, ...classesAr)
return (
<section {...rest} style={{display: hide ? "none": null}} className={classes}/>
)
}
}
Col.propTypes = {
hide: PropTypes.bool,
keepContents: PropTypes.bool,
mobile: PropTypes.number,
tablet: PropTypes.number,
desktop: PropTypes.number,
large: PropTypes.number,
className: PropTypes.string
}
export class Row extends React.Component {
render() {
return <div {...this.props} className={xclass(this.props.className, "wrapper")} />
}
}
Row.propTypes = {
className: PropTypes.string
}
export class Button extends React.Component {
static propTypes = {
className: PropTypes.string
}
static defaultProps = {
className: ""
}
render() {
return <button {...this.props} className={xclass(this.props.className, "button")} />
}
}
export const TextArea = (props) => <textarea {...props} />
export const Input = (props) => <input {...props} />
export class Select extends React.Component {
static propTypes = {
allowedValues: PropTypes.array,
value: PropTypes.any,
onChange: PropTypes.func,
multiple: PropTypes.bool,
allowEmptyValue: PropTypes.bool
}
static defaultProps = {
multiple: false,
allowEmptyValue: true
}
constructor(props, context) {
super(props, context)
let value
if (props.value !== undefined) {
value = props.value
} else {
value = props.multiple ? [""] : ""
}
this.state = { value: value }
}
onChange = (e) => {
let { onChange, multiple } = this.props
let options = [].slice.call(e.target.options)
let value
if (multiple) {
value = options.filter(function (option) {
return option.selected
})
.map(function (option){
return option.value
})
} else {
value = e.target.value
}
this.setState({value: value})
onChange && onChange(value)
}
render(){
let { allowedValues, multiple, allowEmptyValue } = this.props
let value = this.state.value.toJS ? this.state.value.toJS() : this.state.value
return (
<select multiple={ multiple } value={ value } onChange={ this.onChange } >
{ allowEmptyValue ? <option value="">--</option> : null }
{
allowedValues.map(function (item, key) {
return <option key={ key } value={ String(item) }>{ item }</option>
})
}
</select>
)
}
}
export class Link extends React.Component {
render() {
return <a {...this.props} className={xclass(this.props.className, "link")}/>
}
}
Link.propTypes = {
className: PropTypes.string
}
const NoMargin = ({children}) => <div style={{height: "auto", border: "none", margin: 0, padding: 0}}> {children} </div>
NoMargin.propTypes = {
children: PropTypes.node
}
export class Collapse extends React.Component {
static propTypes = {
isOpened: PropTypes.bool,
children: PropTypes.node.isRequired,
animated: PropTypes.bool
}
static defaultProps = {
isOpened: false,
animated: false
}
renderNotAnimated() {
if(!this.props.isOpened)
return <noscript/>
return (
<NoMargin>
{this.props.children}
</NoMargin>
)
}
render() {
let { animated, isOpened, children } = this.props
if(!animated)
return this.renderNotAnimated()
children = isOpened ? children : null
return (
<OriCollapse isOpened={isOpened}>
<NoMargin>
{children}
</NoMargin>
</OriCollapse>
)
}
}

View File

@@ -0,0 +1,80 @@
import React, { PropTypes } from "react"
export default class BaseLayout extends React.Component {
static propTypes = {
errSelectors: PropTypes.object.isRequired,
errActions: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired
}
render() {
let { specSelectors, specActions, getComponent } = this.props
let info = specSelectors.info()
let url = specSelectors.url()
let basePath = specSelectors.basePath()
let host = specSelectors.host()
let securityDefinitions = specSelectors.securityDefinitions()
let externalDocs = specSelectors.externalDocs()
let schemes = specSelectors.schemes()
let Info = getComponent("info")
let Operations = getComponent("operations", true)
let Models = getComponent("models", true)
let AuthorizeBtn = getComponent("authorizeBtn", true)
let Row = getComponent("Row")
let Col = getComponent("Col")
let Errors = getComponent("errors", true)
const Schemes = getComponent("schemes")
const isSpecEmpty = !specSelectors.specStr()
if(isSpecEmpty) {
return <h4>No spec provided.</h4>
}
return (
<div className='swagger-ui'>
<div>
<Errors/>
<Row className="information-container">
<Col mobile={12}>
{ info.count() ? (
<Info info={ info } url={ url } host={ host } basePath={ basePath } externalDocs={externalDocs} getComponent={getComponent}/>
) : null }
</Col>
</Row>
{ schemes && schemes.size || securityDefinitions ? (
<div className="scheme-container">
<Col className="schemes wrapper" mobile={12}>
{ schemes && schemes.size ? (
<Schemes schemes={ schemes } specActions={ specActions } />
) : null }
{ securityDefinitions ? (
<AuthorizeBtn />
) : null }
</Col>
</div>
) : null }
<Row>
<Col mobile={12} desktop={12} >
<Operations/>
</Col>
</Row>
<Row>
<Col mobile={12} desktop={12} >
<Models/>
</Col>
</Row>
</div>
</div>
)
}
}

View File

@@ -0,0 +1,72 @@
import React, { PropTypes } from "react"
export default class XPane extends React.Component {
render() {
let { getComponent, specSelectors, specActions, layoutSelectors, layoutActions } = this.props
let info = specSelectors.info()
let url = specSelectors.url()
let showEditor = layoutSelectors.isShown("editor")
let Info = getComponent("info")
let Operations = getComponent("operations", true)
let Overview = getComponent("overview", true)
let Editor = getComponent("editor", true)
let Footer = getComponent("footer", true)
let Header = getComponent("header", true)
let Container = getComponent("Container")
let Row = getComponent("Row")
let Col = getComponent("Col")
let Button = getComponent("Button")
let showEditorAction = ()=> layoutActions.show("editor", !showEditor)
return (
<Container fullscreen>
<Header/>
{
info && info.size ? <Info version={info.get("version")}
description={info.get("description")}
title={info.get("title")}
url={url}/>
: null
}
<Button onClick={showEditorAction}>{showEditor ? "Hide" : "Show"} Editor</Button>
<Button onClick={specActions.formatIntoYaml}>Format contents</Button>
<Row>
<Col desktop={3} >
<Overview/>
</Col>
<Col hide={!showEditor} keepContents={true} desktop={5} >
<Editor/>
</Col>
<Col desktop={showEditor ? 4 : 9} >
<Operations/>
</Col>
</Row>
<Footer></Footer>
</Container>
)
}
}
XPane.propTypes = {
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired
}

View File

@@ -0,0 +1,93 @@
import React, { PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"
const Headers = ( { headers } )=>{
return (
<div>
<h5>Response headers</h5>
<pre>{headers}</pre>
</div>)
}
Headers.propTypes = {
headers: PropTypes.array.isRequired
}
export default class LiveResponse extends React.Component {
static propTypes = {
response: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired
}
render() {
const { request, response, getComponent } = this.props
const body = response.get("text")
const status = response.get("status")
const url = response.get("url")
const headers = response.get("headers").toJS()
const notDocumented = response.get("notDocumented")
const isError = response.get("error")
const headersKeys = Object.keys(headers)
const contentType = headers["content-type"]
const Curl = getComponent("curl")
const ResponseBody = getComponent("responseBody")
const returnObject = headersKeys.map(key => {
return <span className="headerline" key={key}> {key}: {headers[key]} </span>
})
return (
<div>
{ request && <Curl request={ request }/> }
<h4>Server response</h4>
<table className="responses-table">
<thead>
<tr className="responses-header">
<td className="col col_header response-col_status">Code</td>
<td className="col col_header response-col_description">Details</td>
</tr>
</thead>
<tbody>
<tr className="response">
<td className="col response-col_status">
{ status }
{
!notDocumented ? null :
<div className="response-undocumented">
<i> Undocumented </i>
</div>
}
</td>
<td className="col response-col_description">
{
!isError ? null : <span>
{`${response.get("name")}: ${response.get("message")}`}
</span>
}
{
!body || isError ? null
: <ResponseBody content={ body }
contentType={ contentType }
url={ url }
headers={ headers }
getComponent={ getComponent }/>
}
{
!headers ? null : <Headers headers={ returnObject }/>
}
</td>
</tr>
</tbody>
</table>
</div>
)
}
static propTypes = {
getComponent: PropTypes.func.isRequired,
request: ImPropTypes.map,
response: ImPropTypes.map
}
}

View File

@@ -0,0 +1,58 @@
import React, { PropTypes } from "react"
export default class ModelExample extends React.Component {
static propTypes = {
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
schema: PropTypes.object.isRequired,
example: PropTypes.any.isRequired,
isExecute: PropTypes.bool
}
constructor(props, context) {
super(props, context)
this.state = {
activeTab: "example"
}
}
activeTab =( e ) => {
let { target : { dataset : { name } } } = e
this.setState({
activeTab: name
})
}
render() {
let { getComponent, specSelectors, schema, example, isExecute } = this.props
const Model = getComponent("model")
return <div>
<ul className="tab">
<li className={ "tabitem" + ( isExecute || this.state.activeTab === "example" ? " active" : "") }>
<a className="tablinks" data-name="example" onClick={ this.activeTab }>Example Value</a>
</li>
<li className={ "tabitem" + ( !isExecute && this.state.activeTab === "model" ? " active" : "") }>
<a className={ "tablinks" + ( isExecute ? " inactive" : "" )} data-name="model" onClick={ this.activeTab }>Model</a>
</li>
</ul>
<div>
{
(isExecute || this.state.activeTab === "example") && example
}
{
!isExecute && this.state.activeTab === "model" && <Model schema={ schema }
getComponent={ getComponent }
specSelectors={ specSelectors }
expandDepth={ 1 } />
}
</div>
</div>
}
}

View File

@@ -0,0 +1,307 @@
import React, { Component, PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"
import { List } from "immutable"
const braceOpen = "{"
const braceClose = "}"
const propStyle = { color: "#999", fontStyle: "italic" }
const EnumModel = ({ value }) => {
let collapsedContent = <span>Array [ { value.count() } ]</span>
return <span className="prop-enum">
Enum:<br />
<Collapse collapsedContent={ collapsedContent }>
[ { value.join(", ") } ]
</Collapse>
</span>
}
EnumModel.propTypes = {
value: ImPropTypes.iterable
}
class ObjectModel extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
isRef: PropTypes.bool,
expandDepth: PropTypes.number,
depth: PropTypes.number
}
render(){
let { schema, name, isRef, getComponent, depth, ...props } = this.props
let { expandDepth } = this.props
const JumpToPath = getComponent("JumpToPath", true)
let description = schema.get("description")
let properties = schema.get("properties")
let additionalProperties = schema.get("additionalProperties")
let title = schema.get("title") || name
let required = schema.get("required")
const JumpToPathSection = ({ name }) => <span className="model-jump-to-path"><JumpToPath path={`definitions.${name}`} /></span>
let collapsedContent = (<span>
<span>{ braceOpen }</span>...<span>{ braceClose }</span>
{
isRef ? <JumpToPathSection name={ name }/> : ""
}
</span>)
return <span className="model">
{
title && <span className="model-title">
{ isRef && schema.get("$$ref") && <span className="model-hint">{ schema.get("$$ref") }</span> }
<span className="model-title__text">{ title }</span>
</span>
}
<Collapse collapsed={ depth > expandDepth } collapsedContent={ collapsedContent }>
<span className="brace-open object">{ braceOpen }</span>
{
!isRef ? null : <JumpToPathSection name={ name }/>
}
<span className="inner-object">
{
<table className="model" style={{ marginLeft: "2em" }}><tbody>
{
!description ? null : <tr style={{ color: "#999", fontStyle: "italic" }}>
<td>description:</td>
<td>{ description }</td>
</tr>
}
{
!(properties && properties.size) ? null : properties.entrySeq().map(
([key, value]) => {
let isRequired = List.isList(required) && required.contains(key)
let propertyStyle = { verticalAlign: "top", paddingRight: "0.2em" }
if ( isRequired ) {
propertyStyle.fontWeight = "bold"
}
return (<tr key={key}>
<td style={ propertyStyle }>{ key }:</td>
<td style={{ verticalAlign: "top" }}>
<Model key={ `object-${name}-${key}_${value}` } { ...props }
required={ isRequired }
getComponent={ getComponent }
schema={ value }
depth={ depth + 1 } />
</td>
</tr>)
}).toArray()
}
{
!additionalProperties || !additionalProperties.size ? null
: <tr>
<td>{ "< * >:" }</td>
<td>
<Model { ...props } required={ false }
getComponent={ getComponent }
schema={ additionalProperties }
depth={ depth + 1 } />
</td>
</tr>
}
</tbody></table>
}
</span>
<span className="brace-close">{ braceClose }</span>
</Collapse>
</span>
}
}
class Primitive extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
required: PropTypes.bool
}
render(){
let { schema, required } = this.props
if(!schema || !schema.get) {
// don't render if schema isn't correctly formed
return <div></div>
}
let type = schema.get("type")
let format = schema.get("format")
let xml = schema.get("xml")
let enumArray = schema.get("enum")
let properties = schema.filter( ( v, key) => ["enum", "type", "format", "$$ref"].indexOf(key) === -1 )
let style = required ? { fontWeight: "bold" } : {}
return <span className="prop">
<span className="prop-type" style={ style }>{ type }</span> { required && <span style={{ color: "red" }}>*</span>}
{ format && <span className="prop-format">(${format})</span>}
{
properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }>
<br />{ key !== "description" && key + ": " }{ String(v) }</span>)
: null
}
{
xml && xml.size ? (<span><br /><span style={ propStyle }>xml:</span>
{
xml.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }><br/>&nbsp;&nbsp;&nbsp;{key}: { String(v) }</span>).toArray()
}
</span>): null
}
{
enumArray && <EnumModel value={ enumArray } />
}
</span>
}
}
class ArrayModel extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
required: PropTypes.bool,
expandDepth: PropTypes.number,
depth: PropTypes.number
}
render(){
let { required, schema, depth, expandDepth } = this.props
let items = schema.get("items")
let properties = schema.filter( ( v, key) => ["type", "items", "$$ref"].indexOf(key) === -1 )
return <span className="model">
<span className="model-title">
<span className="model-title__text">{ schema.get("title") }</span>
</span>
<Collapse collapsed={ depth > expandDepth } collapsedContent="[...]">
[
<span><Model { ...this.props } schema={ items } required={ false }/></span>
]
{
properties.size ? <span>
{ properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={propStyle}>
<br />{ `${key}:`}{ String(v) }</span>)
}<br /></span>
: null
}
</Collapse>
{ required && <span style={{ color: "red" }}>*</span>}
</span>
}
}
class Model extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
isRef: PropTypes.bool,
required: PropTypes.bool,
expandDepth: PropTypes.number,
depth: PropTypes.number
}
getModelName =( ref )=> {
if ( ref.indexOf("#/definitions/") !== -1 ) {
return ref.replace(/^.*#\/definitions\//, "")
}
}
getRefSchema =( model )=> {
let { specSelectors } = this.props
return specSelectors.findDefinition(model)
}
render () {
let { schema, required, name, isRef } = this.props
let $$ref = schema && schema.get("$$ref")
let modelName = $$ref && this.getModelName( $$ref )
let modelSchema, type
if ( schema && (schema.get("type") || schema.get("properties")) ) {
modelSchema = schema
} else if ( $$ref ) {
modelSchema = this.getRefSchema( modelName )
}
type = modelSchema && modelSchema.get("type")
if ( !type && modelSchema && modelSchema.get("properties") ) {
type = "object"
}
switch(type) {
case "object":
return <ObjectModel className="object" { ...this.props } schema={ modelSchema }
name={ modelName || name }
isRef={ isRef!== undefined ? isRef : !!$$ref }/>
case "array":
return <ArrayModel className="array" { ...this.props } schema={ modelSchema } required={ required } />
case "string":
case "number":
case "integer":
case "boolean":
default:
return <Primitive schema={ modelSchema } required={ required }/>
}
}
}
export default class ModelComponent extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
name: PropTypes.string,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
expandDepth: PropTypes.number
}
render(){
return <div className="model-box">
<Model { ...this.props } depth={ 1 } expandDepth={ this.props.expandDepth || 0 }/>
</div>
}
}
class Collapse extends Component {
static propTypes = {
collapsedContent: PropTypes.any,
collapsed: PropTypes.bool,
children: PropTypes.any
}
static defaultProps = {
collapsedContent: "{...}",
collapsed: true,
}
constructor(props, context) {
super(props, context)
let { collapsed, collapsedContent } = this.props
this.state = {
collapsed: collapsed !== undefined ? collapsed : Collapse.defaultProps.collapsed,
collapsedContent: collapsedContent || Collapse.defaultProps.collapsedContent
}
}
toggleCollapsed=()=>{
this.setState({
collapsed: !this.state.collapsed
})
}
render () {
return (<span>
<span onClick={ this.toggleCollapsed } style={{ "cursor": "pointer" }}>
<span className={ "model-toggle" + ( this.state.collapsed ? " collapsed" : "" ) }></span>
</span>
{ this.state.collapsed ? this.state.collapsedContent : this.props.children }
</span>)
}
}

View File

@@ -0,0 +1,46 @@
import React, { Component, PropTypes } from "react"
export default class Models extends Component {
static propTypes = {
getComponent: PropTypes.func,
specSelectors: PropTypes.object,
layoutSelectors: PropTypes.object,
layoutActions: PropTypes.object,
getConfigs: PropTypes.func.isRequired
}
render(){
let { specSelectors, getComponent, layoutSelectors, layoutActions, getConfigs } = this.props
let definitions = specSelectors.definitions()
let { docExpansion } = getConfigs()
let showModels = layoutSelectors.isShown("models", docExpansion === "full" || docExpansion === "list" )
const Model = getComponent("model")
const Collapse = getComponent("Collapse")
if (!definitions.size) return null
return <section className={ showModels ? "models is-open" : "models"}>
<h4 onClick={() => layoutActions.show("models", !showModels)}>
<span>Models</span>
<svg width="20" height="20">
<use xlinkHref="#large-arrow" />
</svg>
</h4>
<Collapse isOpened={showModels} animated>
{
definitions.entrySeq().map( ( [ name, model ])=>{
return <div className="model-container" key={ `models-section-${name}` }>
<Model name={ name }
schema={ model }
isRef={ true }
getComponent={ getComponent }
specSelectors={ specSelectors }/>
</div>
}).toArray()
}
</Collapse>
</section>
}
}

View File

@@ -0,0 +1,104 @@
import React, { PropTypes } from "react"
export default class OnlineValidatorBadge extends React.Component {
static propTypes = {
getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired
}
constructor(props, context) {
super(props, context)
let { specSelectors, getConfigs } = props
let { validatorUrl } = getConfigs()
this.state = {
url: specSelectors.url(),
validatorUrl: validatorUrl === undefined ? "https://online.swagger.io/validator" : validatorUrl
}
}
componentWillReceiveProps(nextProps) {
let { specSelectors, getConfigs } = nextProps
let { validatorUrl } = getConfigs()
this.setState({
url: specSelectors.url(),
validatorUrl: validatorUrl === undefined ? "https://online.swagger.io/validator" : validatorUrl
})
}
render() {
let { getConfigs } = this.props
let { spec } = getConfigs()
if ( typeof spec === "object" && Object.keys(spec).length) return null
if (!this.state.url || !this.state.validatorUrl || this.state.url.indexOf("localhost") >= 0
|| this.state.url.indexOf("127.0.0.1") >= 0) {
return null
}
return (<span style={{ float: "right"}}>
<a target="_blank" href={`${ this.state.validatorUrl }/debug?url=${ this.state.url }`}>
<ValidatorImage src={`${ this.state.validatorUrl }?url=${ this.state.url }`} alt="Online validator badge"/>
</a>
</span>)
}
}
class ValidatorImage extends React.Component {
static propTypes = {
src: PropTypes.string,
alt: PropTypes.string
}
constructor(props) {
super(props)
this.state = {
loaded: false,
error: false
}
}
componentDidMount() {
const img = new Image()
img.onload = () => {
this.setState({
loaded: true
})
}
img.onerror = () => {
this.setState({
error: true
})
}
img.src = this.props.src
}
componentWillReceiveProps(nextProps) {
if (nextProps.src !== this.props.src) {
const img = new Image()
img.onload = () => {
this.setState({
loaded: true
})
}
img.onerror = () => {
this.setState({
error: true
})
}
img.src = nextProps.src
}
}
render() {
if (this.state.error) {
return <img alt={"Error"} />
} else if (!this.state.loaded) {
return <img alt= {"Loading..."} />
}
return <img src={this.props.src} alt={this.props.alt} />
}
}

View File

@@ -0,0 +1,266 @@
import React, { PropTypes } from "react"
import shallowCompare from "react-addons-shallow-compare"
import { getList } from "core/utils"
import * as CustomPropTypes from "core/proptypes"
//import "less/opblock"
export default class Operation extends React.Component {
static propTypes = {
path: PropTypes.string.isRequired,
method: PropTypes.string.isRequired,
operation: PropTypes.object.isRequired,
showSummary: PropTypes.bool,
isShownKey: CustomPropTypes.arrayOrString.isRequired,
jumpToKey: CustomPropTypes.arrayOrString.isRequired,
allowTryItOut: PropTypes.bool,
displayOperationId: PropTypes.bool,
response: PropTypes.object,
request: PropTypes.object,
getComponent: PropTypes.func.isRequired,
authActions: PropTypes.object,
authSelectors: PropTypes.object,
specActions: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired,
getConfigs: PropTypes.func.isRequired
}
static defaultProps = {
showSummary: true,
response: null,
allowTryItOut: true,
displayOperationId: false,
}
constructor(props, context) {
super(props, context)
this.state = {
tryItOutEnabled: false
}
}
componentWillReceiveProps(nextProps) {
const defaultContentType = "application/json"
let { specActions, path, method, operation } = nextProps
let producesValue = operation.get("produces_value")
let produces = operation.get("produces")
let consumes = operation.get("consumes")
let consumesValue = operation.get("consumes_value")
if(nextProps.response !== this.props.response) {
this.setState({ executeInProgress: false })
}
if (producesValue === undefined) {
producesValue = produces && produces.size ? produces.first() : defaultContentType
specActions.changeProducesValue([path, method], producesValue)
}
if (consumesValue === undefined) {
consumesValue = consumes && consumes.size ? consumes.first() : defaultContentType
specActions.changeConsumesValue([path, method], consumesValue)
}
}
shouldComponentUpdate(props, state) {
return shallowCompare(this, props, state)
}
toggleShown =() => {
let { layoutActions, isShownKey } = this.props
layoutActions.show(isShownKey, !this.isShown())
}
isShown =() => {
let { layoutSelectors, isShownKey, getConfigs } = this.props
let { docExpansion } = getConfigs()
return layoutSelectors.isShown(isShownKey, docExpansion === "full" ) // Here is where we set the default
}
onTryoutClick =() => {
this.setState({tryItOutEnabled: !this.state.tryItOutEnabled})
}
onCancelClick =() => {
let { specActions, path, method } = this.props
this.setState({tryItOutEnabled: !this.state.tryItOutEnabled})
specActions.clearValidateParams([path, method])
}
onExecute = () => {
this.setState({ executeInProgress: true })
}
render() {
let {
isShownKey,
jumpToKey,
path,
method,
operation,
showSummary,
response,
request,
allowTryItOut,
displayOperationId,
fn,
getComponent,
specActions,
specSelectors,
authActions,
authSelectors
} = this.props
let summary = operation.get("summary")
let description = operation.get("description")
let deprecated = operation.get("deprecated")
let externalDocs = operation.get("externalDocs")
let responses = operation.get("responses")
let security = operation.get("security") || specSelectors.security()
let produces = operation.get("produces")
let schemes = operation.get("schemes")
let parameters = getList(operation, ["parameters"])
let operationId = operation.get("__originalOperationId")
const Responses = getComponent("responses")
const Parameters = getComponent( "parameters" )
const Execute = getComponent( "execute" )
const Clear = getComponent( "clear" )
const AuthorizeOperationBtn = getComponent( "authorizeOperationBtn" )
const JumpToPath = getComponent("JumpToPath", true)
const Collapse = getComponent( "Collapse" )
const Markdown = getComponent( "Markdown" )
const Schemes = getComponent( "schemes" )
// Merge in Live Response
if(response && response.size > 0) {
let notDocumented = !responses.get(String(response.get("status")))
response = response.set("notDocumented", notDocumented)
}
let { tryItOutEnabled } = this.state
let shown = this.isShown()
let onChangeKey = [ path, method ] // Used to add values to _this_ operation ( indexed by path and method )
return (
<div className={deprecated ? "opblock opblock-deprecated" : shown ? `opblock opblock-${method} is-open` : `opblock opblock-${method}`} id={isShownKey} >
<div className={`opblock-summary opblock-summary-${method}`} onClick={this.toggleShown} >
<span className="opblock-summary-method">{method.toUpperCase()}</span>
<span className={ deprecated ? "opblock-summary-path__deprecated" : "opblock-summary-path" } >
<span>{path}</span>
<JumpToPath path={jumpToKey} />
</span>
{ !showSummary ? null :
<div className="opblock-summary-description">
{ summary }
</div>
}
{ displayOperationId && operationId ? <span className="opblock-summary-operation-id">{operationId}</span> : null }
{
(!security || !security.count()) ? null :
<AuthorizeOperationBtn authActions={ authActions }
security={ security }
authSelectors={ authSelectors }/>
}
</div>
<Collapse isOpened={shown} animated>
<div className="opblock-body">
{ deprecated && <h4 className="opblock-title_normal"> Warning: Deprecated</h4>}
{ description &&
<div className="opblock-description-wrapper">
<div className="opblock-description">
<Markdown source={ description } />
</div>
</div>
}
{
externalDocs && externalDocs.get("url") ?
<div className="opblock-external-docs-wrapper">
<h4 className="opblock-title_normal">Find more details</h4>
<div className="opblock-external-docs">
<span className="opblock-external-docs__description">{ externalDocs.get("description") }</span>
<a className="opblock-external-docs__link" href={ externalDocs.get("url") }>{ externalDocs.get("url") }</a>
</div>
</div> : null
}
<Parameters
parameters={parameters}
onChangeKey={onChangeKey}
onTryoutClick = { this.onTryoutClick }
onCancelClick = { this.onCancelClick }
tryItOutEnabled = { tryItOutEnabled }
allowTryItOut={allowTryItOut}
fn={fn}
getComponent={ getComponent }
specActions={ specActions }
specSelectors={ specSelectors }
pathMethod={ [path, method] }
/>
{!tryItOutEnabled || !allowTryItOut ? null : schemes && schemes.size ? <div className="opblock-schemes">
<Schemes schemes={ schemes }
path={ path }
method={ method }
specActions={ specActions }/>
</div> : null
}
<div className={(!tryItOutEnabled || !response || !allowTryItOut) ? "execute-wrapper" : "btn-group"}>
{ !tryItOutEnabled || !allowTryItOut ? null :
<Execute
getComponent={getComponent}
operation={ operation }
specActions={ specActions }
specSelectors={ specSelectors }
path={ path }
method={ method }
onExecute={ this.onExecute } />
}
{ (!tryItOutEnabled || !response || !allowTryItOut) ? null :
<Clear
onClick={ this.onClearClick }
specActions={ specActions }
path={ path }
method={ method }/>
}
</div>
{this.state.executeInProgress ? <div className="loading-container"><div className="loading"></div></div> : null}
{ !responses ? null :
<Responses
responses={ responses }
request={ request }
tryItOutResponse={ response }
getComponent={ getComponent }
specSelectors={ specSelectors }
specActions={ specActions }
produces={ produces }
producesValue={ operation.get("produces_value") }
pathMethod={ [path, method] }
fn={fn} />
}
</div>
</Collapse>
</div>
)
}
}

View File

@@ -0,0 +1,126 @@
import React, { PropTypes } from "react"
export default class Operations extends React.Component {
static propTypes = {
specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
authSelectors: PropTypes.object.isRequired,
getConfigs: PropTypes.func.isRequired
};
render() {
let {
specSelectors,
specActions,
getComponent,
layoutSelectors,
layoutActions,
authActions,
authSelectors,
getConfigs,
fn
} = this.props
let taggedOps = specSelectors.taggedOperations()
const Operation = getComponent("operation")
const Collapse = getComponent("Collapse")
let showSummary = layoutSelectors.showSummary()
let { docExpansion, displayOperationId } = getConfigs()
return (
<div>
{
taggedOps.map( (tagObj, tag) => {
let operations = tagObj.get("operations")
let tagDescription = tagObj.getIn(["tagDetails", "description"], null)
let isShownKey = ["operations-tag", tag]
let showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list")
return (
<div className={showTag ? "opblock-tag-section is-open" : "opblock-tag-section"} key={"operation-" + tag}>
<h4 onClick={() => layoutActions.show(isShownKey, !showTag)} className={!tagDescription ? "opblock-tag no-desc" : "opblock-tag" }>
<span>{tag}</span>
{ !tagDescription ? null :
<small>
{ tagDescription }
</small>
}
<button className="expand-operation" title="Expand operation" onClick={() => layoutActions.show(isShownKey, !showTag)}>
<svg className="arrow" width="20" height="20">
<use xlinkHref={showTag ? "#large-arrow-down" : "#large-arrow"} />
</svg>
</button>
</h4>
<Collapse isOpened={showTag}>
{
operations.map( op => {
const isShownKey = ["operations", op.get("id"), tag]
const path = op.get("path", "")
const method = op.get("method", "")
const jumpToKey = `paths.${path}.${method}`
const allowTryItOut = specSelectors.allowTryItOutFor(op.get("path"), op.get("method"))
const response = specSelectors.responseFor(op.get("path"), op.get("method"))
const request = specSelectors.requestFor(op.get("path"), op.get("method"))
return <Operation
{...op.toObject()}
isShownKey={isShownKey}
jumpToKey={jumpToKey}
showSummary={showSummary}
key={isShownKey}
response={ response }
request={ request }
allowTryItOut={allowTryItOut}
displayOperationId={displayOperationId}
specActions={ specActions }
specSelectors={ specSelectors }
layoutActions={ layoutActions }
layoutSelectors={ layoutSelectors }
authActions={ authActions }
authSelectors={ authSelectors }
getComponent={ getComponent }
fn={fn}
getConfigs={ getConfigs }
/>
}).toArray()
}
</Collapse>
</div>
)
}).toArray()
}
{ taggedOps.size < 1 ? <h3> No operations defined in spec! </h3> : null }
</div>
)
}
}
Operations.propTypes = {
layoutActions: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
fn: PropTypes.object.isRequired
}

View File

@@ -0,0 +1,118 @@
import React, { PropTypes } from "react"
import { Link } from "core/components/layout-utils"
export default class Overview extends React.Component {
constructor(...args) {
super(...args)
this.setTagShown = this._setTagShown.bind(this)
}
_setTagShown(showTagId, shown) {
this.props.layoutActions.show(showTagId, shown)
}
showOp(key, shown) {
let { layoutActions } = this.props
layoutActions.show(key, shown)
}
render() {
let { specSelectors, layoutSelectors, layoutActions, getComponent } = this.props
let taggedOps = specSelectors.taggedOperations()
const Collapse = getComponent("Collapse")
return (
<div>
<h4 className="overview-title">Overview</h4>
{
taggedOps.map( (tagObj, tag) => {
let operations = tagObj.get("operations")
let showTagId = ["overview-tags", tag]
let showTag = layoutSelectors.isShown(showTagId, true)
let toggleShow = ()=> layoutActions.show(showTagId, !showTag)
return (
<div key={"overview-"+tag}>
<h4 onClick={toggleShow} className="link overview-tag"> {showTag ? "-" : "+"}{tag}</h4>
<Collapse isOpened={showTag} animated>
{
operations.map( op => {
let { path, method, id } = op.toObject() // toObject is shallow
let showOpIdPrefix = "operations"
let showOpId = id
let shown = layoutSelectors.isShown([showOpIdPrefix, showOpId])
return <OperationLink key={id}
path={path}
method={method}
id={path + "-" + method}
shown={shown}
showOpId={showOpId}
showOpIdPrefix={showOpIdPrefix}
href={`#operation-${showOpId}`}
onClick={layoutActions.show} />
}).toArray()
}
</Collapse>
</div>
)
}).toArray()
}
{ taggedOps.size < 1 && <h3> No operations defined in spec! </h3> }
</div>
)
}
}
Overview.propTypes = {
layoutSelectors: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired
}
export class OperationLink extends React.Component {
constructor(props) {
super(props)
this.onClick = this._onClick.bind(this)
}
_onClick() {
let { showOpId, showOpIdPrefix, onClick, shown } = this.props
onClick([showOpIdPrefix, showOpId], !shown)
}
render() {
let { id, method, shown, href } = this.props
return (
<Link href={ href } style={{fontWeight: shown ? "bold" : "normal"}} onClick={this.onClick} className="block opblock-link">
<div>
<small className={`bold-label-${method}`}>{method.toUpperCase()}</small>
<span className="bold-label" >{id}</span>
</div>
</Link>
)
}
}
OperationLink.propTypes = {
href: PropTypes.string,
onClick: PropTypes.func,
id: PropTypes.string.isRequired,
method: PropTypes.string.isRequired,
shown: PropTypes.bool.isRequired,
showOpId: PropTypes.string.isRequired,
showOpIdPrefix: PropTypes.string.isRequired
}

View File

@@ -0,0 +1,142 @@
import React, { Component, PropTypes } from "react"
import shallowCompare from "react-addons-shallow-compare"
import { fromJS, List } from "immutable"
import { getSampleSchema } from "core/utils"
const NOOP = Function.prototype
export default class ParamBody extends Component {
static propTypes = {
param: PropTypes.object,
onChange: PropTypes.func,
onChangeConsumes: PropTypes.func,
consumes: PropTypes.object,
consumesValue: PropTypes.string,
fn: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
isExecute: PropTypes.bool,
specSelectors: PropTypes.object.isRequired,
pathMethod: PropTypes.array.isRequired
};
static defaultProp = {
consumes: fromJS(["application/json"]),
param: fromJS({}),
onChange: NOOP,
onChangeConsumes: NOOP,
};
constructor(props, context) {
super(props, context)
this.state = {
isEditBox: false,
value: ""
}
}
componentDidMount() {
this.updateValues.call(this, this.props)
}
shouldComponentUpdate(props, state) {
return shallowCompare(this, props, state)
}
componentWillReceiveProps(nextProps) {
this.updateValues.call(this, nextProps)
}
updateValues = (props) => {
let { specSelectors, pathMethod, param, isExecute, consumesValue="" } = props
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name")) : {}
let isXml = /xml/i.test(consumesValue)
let paramValue = isXml ? parameter.get("value_xml") : parameter.get("value")
if ( paramValue !== undefined ) {
let val = !paramValue && !isXml ? "{}" : paramValue
this.setState({ value: val })
this.onChange(val, {isXml: isXml, isEditBox: isExecute})
} else {
if (isXml) {
this.onChange(this.sample("xml"), {isXml: isXml, isEditBox: isExecute})
} else {
this.onChange(this.sample(), {isEditBox: isExecute})
}
}
}
sample = (xml) => {
let { param, fn:{inferSchema} } = this.props
let schema = inferSchema(param.toJS())
return getSampleSchema(schema, xml)
}
onChange = (value, { isEditBox, isXml }) => {
this.setState({value, isEditBox})
this._onChange(value, isXml)
}
_onChange = (val, isXml) => { (this.props.onChange || NOOP)(this.props.param, val, isXml) }
handleOnChange = e => {
let {consumesValue} = this.props
this.onChange(e.target.value.trim(), {isXml: /xml/i.test(consumesValue)})
}
toggleIsEditBox = () => this.setState( state => ({isEditBox: !state.isEditBox}))
render() {
let {
onChangeConsumes,
param,
isExecute,
specSelectors,
pathMethod,
getComponent,
} = this.props
const Button = getComponent("Button")
const TextArea = getComponent("TextArea")
const HighlightCode = getComponent("highlightCode")
const ContentType = getComponent("contentType")
// for domains where specSelectors not passed
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name")) : param
let errors = parameter.get("errors", List())
let consumesValue = specSelectors.contentTypeValues(pathMethod).get("requestContentType")
let consumes = this.props.consumes && this.props.consumes.size ? this.props.consumes : ParamBody.defaultProp.consumes
let { value, isEditBox } = this.state
return (
<div className="body-param">
{
isEditBox && isExecute
? <TextArea className={ "body-param__text" + ( errors.count() ? " invalid" : "")} 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>
}
<label htmlFor="">
<span>Parameter content type</span>
<ContentType value={ consumesValue } contentTypes={ consumes } onChange={onChangeConsumes} className="body-param-content-type" />
</label>
</div>
</div>
)
}
}

View File

@@ -0,0 +1,132 @@
import React, { Component, PropTypes } from "react"
import win from "core/window"
export default class ParameterRow extends Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
param: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
fn: PropTypes.object.isRequired,
isExecute: PropTypes.bool,
onChangeConsumes: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
pathMethod: PropTypes.array.isRequired
}
constructor(props, context) {
super(props, context)
let { specSelectors, pathMethod, param } = props
let defaultValue = param.get("default")
let parameter = specSelectors.getParameter(pathMethod, param.get("name"))
let value = parameter ? parameter.get("value") : ""
if ( defaultValue !== undefined && value === undefined ) {
this.onChangeWrapper(defaultValue)
}
}
componentWillReceiveProps(props) {
let { specSelectors, pathMethod, param } = props
let example = param.get("example")
let defaultValue = param.get("default")
let parameter = specSelectors.getParameter(pathMethod, param.get("name"))
let paramValue = parameter ? parameter.get("value") : undefined
let enumValue = parameter ? parameter.get("enum") : undefined
let value
if ( paramValue !== undefined ) {
value = paramValue
} else if ( example !== undefined ) {
value = example
} else if ( defaultValue !== undefined) {
value = defaultValue
} else if ( param.get("required") && enumValue && enumValue.size ) {
value = enumValue.first()
}
if ( value !== undefined ) {
this.onChangeWrapper(value)
}
}
onChangeWrapper = (value) => {
let { onChange, param } = this.props
return onChange(param, value)
}
render() {
let {param, onChange, getComponent, isExecute, fn, onChangeConsumes, specSelectors, pathMethod} = this.props
// const onChangeWrapper = (value) => onChange(param, value)
const JsonSchemaForm = getComponent("JsonSchemaForm")
const ParamBody = getComponent("ParamBody")
let inType = param.get("in")
let bodyParam = inType !== "body" ? null
: <ParamBody getComponent={getComponent}
fn={fn}
param={param}
consumes={ specSelectors.operationConsumes(pathMethod) }
consumesValue={ specSelectors.contentTypeValues(pathMethod).get("requestContentType") }
onChange={onChange}
onChangeConsumes={onChangeConsumes}
isExecute={ isExecute }
specSelectors={ specSelectors }
pathMethod={ pathMethod }
/>
const ModelExample = getComponent("modelExample")
const Markdown = getComponent("Markdown")
let schema = param.get("schema")
let isFormData = inType === "formData"
let isFormDataSupported = "FormData" in win
let required = param.get("required")
let itemType = param.getIn(["items", "type"])
let parameter = specSelectors.getParameter(pathMethod, param.get("name"))
let value = parameter ? parameter.get("value") : ""
return (
<tr>
<td className="col parameters-col_name">
<div className={required ? "parameter__name required" : "parameter__name"}>
{ param.get("name") }
{ !required ? null : <span style={{color: "red"}}>&nbsp;*</span> }
</div>
<div className="parаmeter__type">{ param.get("type") } { itemType && `[${itemType}]` }</div>
<div className="parameter__in">({ param.get("in") })</div>
</td>
<td className="col parameters-col_description">
<Markdown source={ param.get("description") }/>
{(isFormData && !isFormDataSupported) && <div>Error: your browser does not support FormData</div>}
{ bodyParam || !isExecute ? null
: <JsonSchemaForm fn={fn}
getComponent={getComponent}
value={ value }
required={ required }
description={param.get("description") ? `${param.get("name")} - ${param.get("description")}` : `${param.get("name")}`}
onChange={ this.onChangeWrapper }
schema={ param }/>
}
{
bodyParam && schema ? <ModelExample getComponent={ getComponent }
isExecute={ isExecute }
specSelectors={ specSelectors }
schema={ schema }
example={ bodyParam }/>
: null
}
</td>
</tr>
)
}
}

View File

@@ -0,0 +1,109 @@
import React, { Component, PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"
import Im from "immutable"
// More readable, just iterate over maps, only
const eachMap = (iterable, fn) => iterable.valueSeq().filter(Im.Map.isMap).map(fn)
export default class Parameters extends Component {
static propTypes = {
parameters: ImPropTypes.list.isRequired,
specActions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired,
tryItOutEnabled: PropTypes.bool,
allowTryItOut: PropTypes.bool,
onTryoutClick: PropTypes.func,
onCancelClick: PropTypes.func,
onChangeKey: PropTypes.array,
pathMethod: PropTypes.array.isRequired
}
static defaultProps = {
onTryoutClick: Function.prototype,
onCancelClick: Function.prototype,
tryItOutEnabled: false,
allowTryItOut: true,
onChangeKey: [],
}
onChange = ( param, value, isXml ) => {
let {
specActions: { changeParam },
onChangeKey,
} = this.props
changeParam( onChangeKey, param.get("name"), value, isXml)
}
onChangeConsumesWrapper = ( val ) => {
let {
specActions: { changeConsumesValue },
onChangeKey
} = this.props
changeConsumesValue(onChangeKey, val)
}
render(){
let {
onTryoutClick,
onCancelClick,
parameters,
allowTryItOut,
tryItOutEnabled,
fn,
getComponent,
specSelectors,
pathMethod
} = this.props
const ParameterRow = getComponent("parameterRow")
const TryItOutButton = getComponent("TryItOutButton")
const isExecute = tryItOutEnabled && allowTryItOut
return (
<div className="opblock-section">
<div className="opblock-section-header">
<h4 className="opblock-title">Parameters</h4>
{ allowTryItOut ? (
<TryItOutButton enabled={ tryItOutEnabled } onCancelClick={ onCancelClick } onTryoutClick={ onTryoutClick } />
) : null }
</div>
{ !parameters.count() ? <div className="opblock-description-wrapper"><p>No parameters</p></div> :
<div className="table-container">
<table className="parameters">
<thead>
<tr>
<th className="col col_header parameters-col_name">Name</th>
<th className="col col_header parameters-col_description">Description</th>
</tr>
</thead>
<tbody>
{
eachMap(parameters, (parameter) => (
<ParameterRow fn={ fn }
getComponent={ getComponent }
param={ parameter }
key={ parameter.get( "name" ) }
onChange={ this.onChange }
onChangeConsumes={this.onChangeConsumesWrapper}
specSelectors={ specSelectors }
pathMethod={ pathMethod }
isExecute={ isExecute }/>
)).toArray()
}
</tbody>
</table>
</div>
}
</div>
)
}
}

View File

@@ -0,0 +1,6 @@
# Providers
Providers are generic bridges to third-party components. They provide two benefits:
1. ability for plugins to override third-party components, because providers are loaded through `getComponent`
2. allows us to avoid painting ourselves into a corner with a third-party component

View File

@@ -0,0 +1,17 @@
import React, { PropTypes } from "react"
import Remarkable from "react-remarkable"
import sanitize from "sanitize-html"
function Markdown({ source }) {
const sanitized = sanitize(source)
return <Remarkable
options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}}
source={sanitized}
></Remarkable>
}
Markdown.propTypes = {
source: PropTypes.string.isRequired
}
export default Markdown

View File

@@ -0,0 +1,95 @@
import React, { PropTypes } from "react"
import { formatXml } from "core/utils"
import lowerCase from "lodash/lowerCase"
export default class ResponseBody extends React.Component {
static propTypes = {
content: PropTypes.any.isRequired,
contentType: PropTypes.string.isRequired,
getComponent: PropTypes.func.isRequired,
headers: PropTypes.object,
url: PropTypes.string
}
render() {
let { content, contentType, url, headers={}, getComponent } = this.props
const HighlightCode = getComponent("highlightCode")
let body, bodyEl
url = url || ""
// JSON
if (/json/i.test(contentType)) {
try {
body = JSON.stringify(JSON.parse(content), null, " ")
} catch (error) {
body = "can't parse JSON. Raw result:\n\n" + content
}
bodyEl = <HighlightCode value={ body } />
// XML
} else if (/xml/i.test(contentType)) {
body = formatXml(content)
bodyEl = <HighlightCode value={ body } />
// HTML or Plain Text
} else if (lowerCase(contentType) === "text/html" || /text\/plain/.test(contentType)) {
bodyEl = <HighlightCode value={ content } />
// Image
} else if (/^image\//i.test(contentType)) {
bodyEl = <img src={ url } />
// Audio
} else if (/^audio\//i.test(contentType)) {
bodyEl = <pre><audio controls><source src={ url } type={ contentType } /></audio></pre>
// Download
} else if (
/^application\/octet-stream/i.test(contentType) ||
headers["Content-Disposition"] && (/attachment/i).test(headers["Content-Disposition"]) ||
headers["content-disposition"] && (/attachment/i).test(headers["content-disposition"]) ||
headers["Content-Description"] && (/File Transfer/i).test(headers["Content-Description"]) ||
headers["content-description"] && (/File Transfer/i).test(headers["content-description"])) {
let contentLength = headers["content-length"] || headers["Content-Length"]
if ( !(+contentLength) ) return null
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
if (!isSafari && "Blob" in window) {
let type = contentType || "text/html"
let blob = (content instanceof Blob) ? content : new Blob([content], {type: type})
let href = window.URL.createObjectURL(blob)
let fileName = url.substr(url.lastIndexOf("/") + 1)
let download = [type, fileName, href].join(":")
// Use filename from response header
let disposition = headers["content-disposition"] || headers["Content-Disposition"]
if (typeof disposition !== "undefined") {
let responseFilename = /filename=([^;]*);?/i.exec(disposition)
if (responseFilename !== null && responseFilename.length > 1) {
download = responseFilename[1]
}
}
bodyEl = <div><a href={ href } download={ download }>{ "Download file" }</a></div>
} else {
bodyEl = <pre>Download headers detected but your browser does not support downloading binary via XHR (Blob).</pre>
}
// Anything else (CORS)
} else if (typeof content === "string") {
bodyEl = <HighlightCode value={ content } />
} else {
bodyEl = <div>Unknown response type</div>
}
return ( !bodyEl ? null : <div>
<h5>Response body</h5>
{ bodyEl }
</div>
)
}
}

View File

@@ -0,0 +1,99 @@
import React, { PropTypes } from "react"
import { fromJS } from "immutable"
import { getSampleSchema } from "core/utils"
const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => {
if ( examples && examples.size ) {
return examples.entrySeq().map( ([ key, example ]) => {
let exampleValue
try {
exampleValue = example && example.toJS ? example.toJS() : example
exampleValue = JSON.stringify(exampleValue)
}
catch(e) {
exampleValue = String(example)
}
return (<div key={ key }>
<h5>{ key }</h5>
<HighlightCode className="example" value={ exampleValue } />
</div>)
}).toArray()
}
if ( sampleResponse ) { return <div>
<HighlightCode className="example" value={ sampleResponse } />
</div>
}
return null
}
export default class Response extends React.Component {
static propTypes = {
code: PropTypes.string.isRequired,
response: PropTypes.object,
className: PropTypes.string,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired,
contentType: PropTypes.string
}
static defaultProps = {
response: fromJS({}),
};
render() {
let {
code,
response,
className,
fn,
getComponent,
specSelectors,
contentType
} = this.props
let { inferSchema } = fn
let schema = inferSchema(response.toJS())
let headers = response.get("headers")
let examples = response.get("examples")
const Headers = getComponent("headers")
const HighlightCode = getComponent("highlightCode")
const ModelExample = getComponent("modelExample")
const Markdown = getComponent( "Markdown" )
let sampleResponse = schema ? getSampleSchema(schema, contentType, { includeReadOnly: true }) : null
let example = getExampleComponent( sampleResponse, examples, HighlightCode )
return (
<tr className={ "response " + ( className || "") }>
<td className="col response-col_status">
{ code }
</td>
<td className="col response-col_description">
<div className="response-col_description__inner">
<Markdown source={ response.get( "description" ) } />
</div>
{ example ? (
<ModelExample
getComponent={ getComponent }
specSelectors={ specSelectors }
schema={ fromJS(schema) }
example={ example }/>
) : null}
{ headers ? (
<Headers headers={ headers }/>
) : null}
</td>
</tr>
)
}
}

View File

@@ -0,0 +1,93 @@
import React, { PropTypes } from "react"
import { fromJS } from "immutable"
import { defaultStatusCode } from "core/utils"
export default class Responses extends React.Component {
static propTypes = {
request: PropTypes.object,
tryItOutResponse: PropTypes.object,
responses: PropTypes.object.isRequired,
produces: PropTypes.object,
producesValue: PropTypes.any,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
pathMethod: PropTypes.array.isRequired,
fn: PropTypes.object.isRequired
}
static defaultProps = {
request: null,
tryItOutResponse: null,
produces: fromJS(["application/json"])
}
onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue(this.props.pathMethod, val)
render() {
let { responses, request, tryItOutResponse, getComponent, specSelectors, fn, producesValue } = this.props
let defaultCode = defaultStatusCode( responses )
const ContentType = getComponent( "contentType" )
const LiveResponse = getComponent( "liveResponse" )
const Response = getComponent( "response" )
let produces = this.props.produces && this.props.produces.size ? this.props.produces : Responses.defaultProps.produces
return (
<div className="responses-wrapper">
<div className="opblock-section-header">
<h4>Responses</h4>
<label>
<span>Response content type</span>
<ContentType value={producesValue}
onChange={this.onChangeProducesWrapper}
contentTypes={produces}
className="execute-content-type"/>
</label>
</div>
<div className="responses-inner">
{
!tryItOutResponse ? null
: <div>
<LiveResponse request={ request }
response={ tryItOutResponse }
getComponent={ getComponent } />
<h4>Responses</h4>
</div>
}
<table className="responses-table">
<thead>
<tr className="responses-header">
<td className="col col_header response-col_status">Code</td>
<td className="col col_header response-col_description">Description</td>
</tr>
</thead>
<tbody>
{
responses.entrySeq().map( ([code, response]) => {
let className = tryItOutResponse && tryItOutResponse.get("status") == code ? "response_current" : ""
return (
<Response key={ code }
isDefault={defaultCode === code}
fn={fn}
className={ className }
code={ code }
response={ response }
specSelectors={ specSelectors }
contentType={ producesValue }
getComponent={ getComponent }/>
)
}).toArray()
}
</tbody>
</table>
</div>
</div>
)
}
}

View File

@@ -0,0 +1,43 @@
import React, { PropTypes } from "react"
export default class Schemes extends React.Component {
static propTypes = {
specActions: PropTypes.object.isRequired,
schemes: PropTypes.object.isRequired,
path: PropTypes.string,
method: PropTypes.string
}
componentWillMount() {
let { schemes } = this.props
//fire 'change' event to set default 'value' of select
this.setScheme(schemes.first())
}
onChange =( e ) => {
this.setScheme( e.target.value )
}
setScheme =( value ) => {
let { path, method, specActions } = this.props
specActions.setScheme( value, path, method )
}
render() {
let { schemes } = this.props
return (
<label htmlFor="schemes">
<span className="schemes-title">Schemes</span>
<select onChange={ this.onChange }>
{ schemes.valueSeq().map(
( scheme ) => <option value={ scheme } key={ scheme }>{ scheme }</option>
).toArray()}
</select>
</label>
)
}
}

View File

@@ -0,0 +1,29 @@
import React, { PropTypes } from "react"
export default class TryItOutButton extends React.Component {
static propTypes = {
onTryoutClick: PropTypes.func,
onCancelClick: PropTypes.func,
enabled: PropTypes.bool, // Try it out is enabled, ie: the user has access to the form
};
static defaultProps = {
onTryoutClick: Function.prototype,
onCancelClick: Function.prototype,
enabled: false,
};
render() {
const { onTryoutClick, onCancelClick, enabled } = this.props
return (
<div className="try-out">
{
enabled ? <button className="btn try-out__btn cancel" onClick={ onTryoutClick }>Cancel</button>
: <button className="btn try-out__btn" onClick={ onCancelClick }>Try it out </button>
}
</div>
)
}
}

View File

@@ -0,0 +1,34 @@
export default function curl( request ){
let curlified = []
let type = ""
let headers = request.get("headers")
curlified.push( "curl" )
curlified.push( "-X", request.get("method") )
curlified.push( `"${request.get("url")}"`)
if ( headers && headers.size ) {
for( let p of request.get("headers").entries() ){
let [ h,v ] = p
type = v
curlified.push( "-H " )
curlified.push( `"${h}: ${v}"` )
}
}
if ( request.get("body") ){
if(type === "multipart/form-data" && request.get("method") === "POST") {
let formDataBody = request.get("body").split("&")
for(var data in formDataBody) {
curlified.push( "-F" )
curlified.push(formDataBody[data])
}
} else {
curlified.push( "-d" )
curlified.push( JSON.stringify( request.get("body") ).replace(/\\n/g, "") )
}
}
return curlified.join( " " )
}

View File

@@ -0,0 +1,128 @@
import deepExtend from "deep-extend"
import System from "core/system"
import win from "core/window"
import ApisPreset from "core/presets/apis"
import * as AllPlugins from "core/plugins/all"
import { parseSeach, filterConfigs } from "core/utils"
const CONFIGS = [ "url", "spec", "validatorUrl", "onComplete", "onFailure", "authorizations", "docExpansion",
"apisSorter", "operationsSorter", "supportedSubmitMethods", "dom_id", "defaultModelRendering", "oauth2RedirectUrl",
"showRequestHeaders", "custom", "modelPropertyMacro", "parameterMacro", "displayOperationId" ]
// eslint-disable-next-line no-undef
const { GIT_DIRTY, GIT_COMMIT, PACKAGE_VERSION } = buildInfo
module.exports = function SwaggerUI(opts) {
win.versions = win.versions || {}
win.versions.swaggerUi = `${PACKAGE_VERSION}/${GIT_COMMIT || "unknown"}${GIT_DIRTY ? "-dirty" : ""}`
const defaults = {
// Some general settings, that we floated to the top
dom_id: null,
spec: {},
url: "",
layout: "BaseLayout",
docExpansion: "list",
validatorUrl: "https://online.swagger.io/validator",
configs: {},
custom: {},
displayOperationId: false,
// 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.
presets: [
],
// Plugins; ( loaded after presets )
plugins: [
],
// Inline Plugin
fn: { },
components: { },
state: { },
// Override some core configs... at your own risk
store: { },
}
const constructorConfig = deepExtend({}, defaults, opts)
const storeConfigs = deepExtend({}, constructorConfig.store, {
system: {
configs: constructorConfig.configs
},
plugins: constructorConfig.presets,
state: {
layout: {
layout: constructorConfig.layout
},
spec: {
spec: "",
url: constructorConfig.url
}
}
})
let inlinePlugin = ()=> {
return {
fn: constructorConfig.fn,
components: constructorConfig.components,
state: constructorConfig.state,
}
}
var store = new System(storeConfigs)
store.register([constructorConfig.plugins, inlinePlugin])
var system = store.getSystem()
let queryConfig = parseSeach()
system.initOAuth = system.authActions.configureAuth
const downloadSpec = (fetchedConfig) => {
if(typeof constructorConfig !== "object") {
return system
}
let localConfig = system.specSelectors.getLocalConfig ? system.specSelectors.getLocalConfig() : {}
let mergedConfig = deepExtend({}, localConfig, constructorConfig, fetchedConfig || {}, queryConfig)
store.setConfigs(filterConfigs(mergedConfig, CONFIGS))
if (fetchedConfig !== null) {
if (!queryConfig.url && typeof mergedConfig.spec === "object" && Object.keys(mergedConfig.spec).length) {
system.specActions.updateUrl("")
system.specActions.updateLoadingStatus("success")
system.specActions.updateSpec(JSON.stringify(mergedConfig.spec))
} else if (system.specActions.download && mergedConfig.url) {
system.specActions.updateUrl(mergedConfig.url)
system.specActions.download(mergedConfig.url)
}
}
if(mergedConfig.dom_id) {
system.render(mergedConfig.dom_id, "App")
} else {
console.error("Skipped rendering: no `dom_id` was specified")
}
return system
}
let configUrl = queryConfig.config || constructorConfig.configUrl
if (!configUrl || !system.specActions.getConfigByUrl || system.specActions.getConfigByUrl && !system.specActions.getConfigByUrl(configUrl, downloadSpec)) {
return downloadSpec()
}
}
// Add presets
module.exports.presets = {
apis: ApisPreset,
}
// All Plugins
module.exports.plugins = AllPlugins

View File

@@ -0,0 +1,185 @@
import React, { PropTypes, Component } from "react"
import shallowCompare from "react-addons-shallow-compare"
import { List, fromJS } from "immutable"
//import "less/json-schema-form"
const noop = ()=> {}
const JsonSchemaPropShape = {
getComponent: PropTypes.func.isRequired,
value: PropTypes.any,
onChange: PropTypes.func,
keyName: PropTypes.any,
fn: PropTypes.object.isRequired,
schema: PropTypes.object,
required: PropTypes.bool,
description: PropTypes.any
}
const JsonSchemaDefaultProps = {
value: "",
onChange: noop,
schema: {},
keyName: "",
required: false
}
export class JsonSchemaForm extends Component {
static propTypes = JsonSchemaPropShape
static defaultProps = JsonSchemaDefaultProps
render() {
let { schema, value, onChange, getComponent, fn } = this.props
if(schema.toJS)
schema = schema.toJS()
let { type, format="" } = schema
let Comp = getComponent(`JsonSchema_${type}_${format}`) || getComponent(`JsonSchema_${type}`) || getComponent("JsonSchema_string")
return <Comp { ...this.props } fn={fn} getComponent={getComponent} value={value} onChange={onChange} schema={schema}/>
}
}
export class JsonSchema_string extends Component {
static propTypes = JsonSchemaPropShape
static defaultProps = JsonSchemaDefaultProps
onChange = (e) => {
const value = this.props.schema["type"] === "file" ? e.target.files[0] : e.target.value
this.props.onChange(value, this.props.keyName)
}
onEnumChange = (val) => this.props.onChange(val)
render() {
let { getComponent, value, schema, required, description } = this.props
let enumValue = schema["enum"]
let errors = schema.errors || []
if ( enumValue ) {
const Select = getComponent("Select")
return (<Select allowedValues={ enumValue }
value={ value }
allowEmptyValue={ !required }
onChange={ this.onEnumChange }/>)
}
const isDisabled = schema["in"] === "formData" && !("FormData" in window)
const Input = getComponent("Input")
if (schema["type"] === "file") {
return <Input type="file" className={ errors.length ? "invalid" : ""} onChange={ this.onChange } disabled={isDisabled}/>
}
else {
return <Input type={ schema.format === "password" ? "password" : "text" } className={ errors.length ? "invalid" : ""} value={value} placeholder={description} onChange={ this.onChange } disabled={isDisabled}/>
}
}
}
export class JsonSchema_array extends Component {
static propTypes = JsonSchemaPropShape
static defaultProps = JsonSchemaDefaultProps
constructor(props, context) {
super(props, context)
this.state = {value: props.value}
}
componentWillReceiveProps(props) {
if(props.value !== this.state.value)
this.setState({value: props.value})
}
shouldComponentUpdate(props, state) {
return shallowCompare(this, props, state)
}
onChange = () => this.props.onChange(this.state.value)
onItemChange = (itemVal, i) => {
this.setState(state => ({
value: state.value.set(i, itemVal)
}), this.onChange)
}
removeItem = (i) => {
this.setState(state => ({
value: state.value.remove(i)
}), this.onChange)
}
addItem = () => {
this.setState(state => {
state.value = state.value || List()
return {
value: state.value.push("")
}
}, this.onChange)
}
onEnumChange = (value) => {
this.setState(() => ({
value: value
}), this.onChange)
}
render() {
let { getComponent, required, schema, fn } = this.props
let itemSchema = fn.inferSchema(schema.items)
const JsonSchemaForm = getComponent("JsonSchemaForm")
const Button = getComponent("Button")
let enumValue = itemSchema["enum"]
let value = this.state.value
if ( enumValue ) {
const Select = getComponent("Select")
return (<Select multiple={ true }
value={ value }
allowedValues={ enumValue }
allowEmptyValue={ !required }
onChange={ this.onEnumChange }/>)
}
let errors = schema.errors || []
return (
<div>
{ !value || value.count() < 1 ?
(errors.length ? <span style={{ color: "red", fortWeight: "bold" }}>{ errors[0] }</span> : null) :
value.map( (item,i) => {
let schema = Object.assign({}, itemSchema)
if ( errors.length ) {
let err = errors.filter((err) => err.index === i)
if (err.length) schema.errors = [ err[0].error + i ]
}
return (
<div key={i} className="json-schema-form-item">
<JsonSchemaForm fn={fn} getComponent={getComponent} value={item} onChange={(val) => this.onItemChange(val, i)} schema={schema} />
<Button className="json-schema-form-item-remove" onClick={()=> this.removeItem(i)} > - </Button>
</div>
)
}).toArray()
}
<Button className="json-schema-form-item-add" onClick={this.addItem}> Add item </Button>
</div>
)
}
}
export class JsonSchema_boolean extends Component {
static propTypes = JsonSchemaPropShape
static defaultProps = JsonSchemaDefaultProps
onEnumChange = (val) => this.props.onChange(val)
render() {
let { getComponent, required, value } = this.props
const Select = getComponent("Select")
return (<Select value={ String(value) }
allowedValues={ fromJS(["true", "false"]) }
allowEmptyValue={ !required }
onChange={ this.onEnumChange }/>)
}
}

View File

@@ -0,0 +1,80 @@
import win from "core/window"
import { btoa } from "core/utils"
export default function authorize ( { auth, authActions, errActions, configs, authConfigs={} } ) {
let { schema, scopes, name, clientId } = auth
let flow = schema.get("flow")
let query = []
switch (flow) {
case "password":
authActions.authorizePassword(auth)
return
case "application":
authActions.authorizeApplication(auth)
return
case "accessCode":
query.push("response_type=code")
break
case "implicit":
query.push("response_type=token")
break
}
if (typeof clientId === "string") {
query.push("client_id=" + encodeURIComponent(clientId))
}
let redirectUrl = configs.oauth2RedirectUrl
// todo move to parser
if (typeof redirectUrl === "undefined") {
errActions.newAuthErr( {
authId: name,
source: "validation",
level: "error",
message: "oauth2RedirectUri configuration is not passed. Oauth2 authorization cannot be performed."
})
return
}
query.push("redirect_uri=" + encodeURIComponent(redirectUrl))
if (Array.isArray(scopes) && 0 < scopes.length) {
let scopeSeparator = authConfigs.scopeSeparator || " "
query.push("scope=" + encodeURIComponent(scopes.join(scopeSeparator)))
}
let state = btoa(new Date())
query.push("state=" + encodeURIComponent(state))
if (typeof authConfigs.realm !== "undefined") {
query.push("realm=" + encodeURIComponent(authConfigs.realm))
}
let { additionalQueryStringParams } = authConfigs
for (let key in additionalQueryStringParams) {
if (typeof additionalQueryStringParams[key] !== "undefined") {
query.push([key, additionalQueryStringParams[key]].map(encodeURIComponent).join("="))
}
}
let url = [schema.get("authorizationUrl"), query.join("&")].join("?")
// pass action authorizeOauth2 and authentication data through window
// to authorize with oauth2
win.swaggerUIRedirectOauth2 = {
auth: auth,
state: state,
redirectUrl: redirectUrl,
callback: flow === "implicit" ? authActions.preAuthorizeImplicit : authActions.authorizeAccessCode,
errCb: errActions.newAuthErr
}
win.open(url)
}

View File

@@ -0,0 +1,67 @@
import get from "lodash/get"
export function transformPathToArray(property, jsSpec) {
if(property.slice(0,9) === "instance.") {
var str = property.slice(9)
} else { // eslint-disable-next-line no-redeclare
var str = property
}
var pathArr = []
str
.split(".")
.map(item => {
// "key[0]" becomes ["key", "0"]
if(item.includes("[")) {
let index = parseInt(item.match(/\[(.*)\]/)[1])
let keyName = item.slice(0, item.indexOf("["))
return [keyName, index.toString()]
} else {
return item
}
})
.reduce(function(a, b) {
// flatten!
return a.concat(b)
}, [])
.concat([""]) // add an empty item into the array, so we don't get stuck with something in our buffer below
.reduce((buffer, curr) => {
let obj = pathArr.length ? get(jsSpec, pathArr) : jsSpec
if(get(obj, makeAccessArray(buffer, curr))) {
if(buffer.length) {
pathArr.push(buffer)
}
if(curr.length) {
pathArr.push(curr)
}
return ""
} else {
// attach key to buffer
return `${buffer}${buffer.length ? "." : ""}${curr}`
}
}, "")
if(typeof get(jsSpec, pathArr) !== "undefined") {
return pathArr
} else {
// if our path is not correct (there is no value at the path),
// return null
return null
}
}
function makeAccessArray(buffer, curr) {
let arr = []
if(buffer.length) {
arr.push(buffer)
}
if(curr.length) {
arr.push(curr)
}
return arr
}

View File

@@ -0,0 +1,17 @@
import { pascalCaseFilename } from "core/utils"
const request = require.context(".", true, /\.jsx?$/)
request.keys().forEach( function( key ){
if( key === "./index.js" ) {
return
}
// if( key.slice(2).indexOf("/") > -1) {
// // skip files in subdirs
// return
// }
let mod = request(key)
module.exports[pascalCaseFilename(key)] = mod.default ? mod.default : mod
})

View File

@@ -0,0 +1,282 @@
import YAML from "yaml-js"
import isArray from "lodash/isArray"
import lodashFind from "lodash/find"
import { memoize } from "core/utils"
let cachedCompose = memoize(YAML.compose) // TODO: build a custom cache based on content
var MAP_TAG = "tag:yaml.org,2002:map"
var SEQ_TAG = "tag:yaml.org,2002:seq"
export function getLineNumberForPath(yaml, path) {
// Type check
if (typeof yaml !== "string") {
throw new TypeError("yaml should be a string")
}
if (!isArray(path)) {
throw new TypeError("path should be an array of strings")
}
var i = 0
let ast = cachedCompose(yaml)
// simply walks the tree using current path recursively to the point that
// path is empty
return find(ast, path)
function find(current, path, last) {
if(!current) {
// something has gone quite wrong
// return the last start_mark as a best-effort
if(last && last.start_mark)
return last.start_mark.line
return 0
}
if (path.length && current.tag === MAP_TAG) {
for (i = 0; i < current.value.length; i++) {
var pair = current.value[i]
var key = pair[0]
var value = pair[1]
if (key.value === path[0]) {
return find(value, path.slice(1), current)
}
if (key.value === path[0].replace(/\[.*/, "")) {
// access the array at the index in the path (example: grab the 2 in "tags[2]")
var index = parseInt(path[0].match(/\[(.*)\]/)[1])
if(value.value.length === 1 && index !== 0 && !!index) {
var nextVal = lodashFind(value.value[0], { value: index.toString() })
} else { // eslint-disable-next-line no-redeclare
var nextVal = value.value[index]
}
return find(nextVal, path.slice(1), value.value)
}
}
}
if (path.length && current.tag === SEQ_TAG) {
var item = current.value[path[0]]
if (item && item.tag) {
return find(item, path.slice(1), current.value)
}
}
if (current.tag === MAP_TAG && !Array.isArray(last)) {
return current.start_mark.line
} else {
return current.start_mark.line + 1
}
}
}
/**
* Get a position object with given
* @param {string} yaml
* YAML or JSON string
* @param {array} path
* an array of stings that constructs a
* JSON Path similiar to JSON Pointers(RFC 6901). The difference is, each
* component of path is an item of the array intead of beinf seperated with
* slash(/) in a string
*/
export function positionRangeForPath(yaml, path) {
// Type check
if (typeof yaml !== "string") {
throw new TypeError("yaml should be a string")
}
if (!isArray(path)) {
throw new TypeError("path should be an array of strings")
}
var invalidRange = {
start: {line: -1, column: -1},
end: {line: -1, column: -1}
}
var i = 0
let ast = cachedCompose(yaml)
// simply walks the tree using current path recursively to the point that
// path is empty.
return find(ast)
function find(current) {
if (current.tag === MAP_TAG) {
for (i = 0; i < current.value.length; i++) {
var pair = current.value[i]
var key = pair[0]
var value = pair[1]
if (key.value === path[0]) {
path.shift()
return find(value)
}
}
}
if (current.tag === SEQ_TAG) {
var item = current.value[path[0]]
if (item && item.tag) {
path.shift()
return find(item)
}
}
// if path is still not empty we were not able to find the node
if (path.length) {
return invalidRange
}
return {
/* jshint camelcase: false */
start: {
line: current.start_mark.line,
column: current.start_mark.column
},
end: {
line: current.end_mark.line,
column: current.end_mark.column
}
}
}
}
/**
* Get a JSON Path for position object in the spec
* @param {string} yaml
* YAML or JSON string
* @param {object} position
* position in the YAML or JSON string with `line` and `column` properties.
* `line` and `column` number are zero indexed
*/
export function pathForPosition(yaml, position) {
// Type check
if (typeof yaml !== "string") {
throw new TypeError("yaml should be a string")
}
if (typeof position !== "object" || typeof position.line !== "number" ||
typeof position.column !== "number") {
throw new TypeError("position should be an object with line and column" +
" properties")
}
try {
var ast = cachedCompose(yaml)
} catch (e) {
console.error("Error composing AST", e)
console.error(`Problem area:\n`, yaml.split("\n").slice(position.line - 5, position.line + 5).join("\n"))
return null
}
var path = []
return find(ast)
/**
* recursive find function that finds the node matching the position
* @param {object} current - AST object to serach into
*/
function find(current) {
// algorythm:
// is current a promitive?
// // finish recursion without modifying the path
// is current a hash?
// // find a key or value that position is in their range
// // if key is in range, terminate recursion with exisiting path
// // if a value is in range push the corresponding key to the path
// // andcontinue recursion
// is current an array
// // find the item that position is in it"s range and push the index
// // of the item to the path and continue recursion with that item.
var i = 0
if (!current || [MAP_TAG, SEQ_TAG].indexOf(current.tag) === -1) {
return path
}
if (current.tag === MAP_TAG) {
for (i = 0; i < current.value.length; i++) {
var pair = current.value[i]
var key = pair[0]
var value = pair[1]
if (isInRange(key)) {
return path
} else if (isInRange(value)) {
path.push(key.value)
return find(value)
}
}
}
if (current.tag === SEQ_TAG) {
for (i = 0; i < current.value.length; i++) {
var item = current.value[i]
if (isInRange(item)) {
path.push(i.toString())
return find(item)
}
}
}
return path
/**
* Determines if position is in node"s range
* @param {object} node - AST node
* @return {Boolean} true if position is in node"s range
*/
function isInRange(node) {
/* jshint camelcase: false */
// if node is in a single line
if (node.start_mark.line === node.end_mark.line) {
return (position.line === node.start_mark.line) &&
(node.start_mark.column <= position.column) &&
(node.end_mark.column >= position.column)
}
// if position is in the same line as start_mark
if (position.line === node.start_mark.line) {
return position.column >= node.start_mark.column
}
// if position is in the same line as end_mark
if (position.line === node.end_mark.line) {
return position.column <= node.end_mark.column
}
// if position is between start and end lines return true, otherwise
// return false.
return (node.start_mark.line < position.line) &&
(node.end_mark.line > position.line)
}
}
}
// utility fns
export let pathForPositionAsync = promisifySyncFn(pathForPosition)
export let positionRangeForPathAsync = promisifySyncFn(positionRangeForPath)
export let getLineNumberForPathAsync = promisifySyncFn(getLineNumberForPath)
function promisifySyncFn(fn) {
return function(...args) {
return new Promise((resolve) => resolve(fn(...args)))
}
}

View File

@@ -0,0 +1,9 @@
import * as AST from "./ast"
import JumpToPath from "./jump-to-path"
export default function() {
return {
fn: { AST },
components: { JumpToPath }
}
}

View File

@@ -0,0 +1,9 @@
import React from "react"
// Nothing by default- component can be overriden by another plugin.
export default class JumpToPath extends React.Component {
render() {
return null
}
}

View File

@@ -0,0 +1,192 @@
import win from "core/window"
import { btoa, buildFormData } from "core/utils"
export const SHOW_AUTH_POPUP = "show_popup"
export const AUTHORIZE = "authorize"
export const LOGOUT = "logout"
export const PRE_AUTHORIZE_OAUTH2 = "pre_authorize_oauth2"
export const AUTHORIZE_OAUTH2 = "authorize_oauth2"
export const VALIDATE = "validate"
export const CONFIGURE_AUTH = "configure_auth"
const scopeSeparator = " "
export function showDefinitions(payload) {
return {
type: SHOW_AUTH_POPUP,
payload: payload
}
}
export function authorize(payload) {
return {
type: AUTHORIZE,
payload: payload
}
}
export function logout(payload) {
return {
type: LOGOUT,
payload: payload
}
}
export const preAuthorizeImplicit = (payload) => ( { authActions, errActions } ) => {
let { auth , token, isValid } = payload
let { schema, name } = auth
let flow = schema.get("flow")
// remove oauth2 property from window after redirect from authentication
delete win.swaggerUIRedirectOauth2
if ( flow !== "accessCode" && !isValid ) {
errActions.newAuthErr( {
authId: name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
})
}
if ( token.error ) {
errActions.newAuthErr({
authId: name,
source: "auth",
level: "error",
message: JSON.stringify(token)
})
return
}
authActions.authorizeOauth2({ auth, token })
}
export function authorizeOauth2(payload) {
return {
type: AUTHORIZE_OAUTH2,
payload: payload
}
}
export const authorizePassword = ( auth ) => ( { authActions } ) => {
let { schema, name, username, password, passwordType, clientId, clientSecret } = auth
let form = {
grant_type: "password",
scopes: encodeURIComponent(auth.scopes.join(scopeSeparator))
}
let query = {}
let headers = {}
if ( passwordType === "basic") {
headers.Authorization = "Basic " + btoa(username + ":" + password)
} else {
Object.assign(form, {username}, {password})
if ( passwordType === "query") {
if ( clientId ) {
query.client_id = clientId
}
if ( clientSecret ) {
query.client_secret = clientSecret
}
} else {
headers.Authorization = "Basic " + btoa(clientId + ":" + clientSecret)
}
}
return authActions.authorizeRequest({ body: buildFormData(form), url: schema.get("tokenUrl"), name, headers, query, auth})
}
export const authorizeApplication = ( auth ) => ( { authActions } ) => {
let { schema, scopes, name, clientId, clientSecret } = auth
let headers = {
Authorization: "Basic " + btoa(clientId + ":" + clientSecret)
}
let form = {
grant_type: "client_credentials",
scope: scopes.join(scopeSeparator)
}
return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth, headers })
}
export const authorizeAccessCode = ( { auth, redirectUrl } ) => ( { authActions } ) => {
let { schema, name, clientId, clientSecret } = auth
let form = {
grant_type: "authorization_code",
code: auth.code,
client_id: clientId,
client_secret: clientSecret,
redirect_uri: redirectUrl
}
return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth})
}
export const authorizeRequest = ( data ) => ( { fn, authActions, errActions, authSelectors } ) => {
let { body, query={}, headers={}, name, url, auth } = data
let { additionalQueryStringParams } = authSelectors.getConfigs() || {}
let fetchUrl = url
for (let key in additionalQueryStringParams) {
url += "&" + key + "=" + encodeURIComponent(additionalQueryStringParams[key])
}
let _headers = Object.assign({
"Accept":"application/json, text/plain, */*",
"Access-Control-Allow-Origin": "*",
"Content-Type": "application/x-www-form-urlencoded"
}, headers)
fn.fetch({
url: fetchUrl,
method: "post",
headers: _headers,
query: query,
body: body
})
.then(function (response) {
let token = JSON.parse(response.data)
let error = token && ( token.error || "" )
let parseError = token && ( token.parseError || "" )
if ( !response.ok ) {
errActions.newAuthErr( {
authId: name,
level: "error",
source: "auth",
message: response.statusText
} )
return
}
if ( error || parseError ) {
errActions.newAuthErr({
authId: name,
level: "error",
source: "auth",
message: JSON.stringify(token)
})
return
}
authActions.authorizeOauth2({ auth, token})
})
.catch(e => {
let err = new Error(e)
errActions.newAuthErr( {
authId: name,
level: "error",
source: "auth",
message: err.message
} )
})
}
export function configureAuth(payload) {
return {
type: CONFIGURE_AUTH,
payload: payload
}
}

View File

@@ -0,0 +1,19 @@
import reducers from "./reducers"
import * as actions from "./actions"
import * as selectors from "./selectors"
import * as specWrapActionReplacements from "./spec-wrap-actions"
export default function() {
return {
statePlugins: {
auth: {
reducers,
actions,
selectors
},
spec: {
wrapActions: specWrapActionReplacements
}
}
}
}

View File

@@ -0,0 +1,66 @@
import { fromJS, Map } from "immutable"
import { btoa } from "core/utils"
import {
SHOW_AUTH_POPUP,
AUTHORIZE,
AUTHORIZE_OAUTH2,
LOGOUT,
CONFIGURE_AUTH
} from "./actions"
export default {
[SHOW_AUTH_POPUP]: (state, { payload } ) =>{
return state.set( "showDefinitions", payload )
},
[AUTHORIZE]: (state, { payload } ) =>{
let securities = fromJS(payload)
let map = state.get("authorized") || Map()
// refactor withMutations
securities.entrySeq().forEach( ([ key, security ]) => {
let type = security.getIn(["schema", "type"])
if ( type === "apiKey" ) {
map = map.set(key, security)
} else if ( type === "basic" ) {
let username = security.getIn(["value", "username"])
let password = security.getIn(["value", "password"])
map = map.setIn([key, "value"], {
username: username,
header: "Basic " + btoa(username + ":" + password)
})
map = map.setIn([key, "schema"], security.get("schema"))
}
})
return state.set( "authorized", map )
},
[AUTHORIZE_OAUTH2]: (state, { payload } ) =>{
let { auth, token } = payload
let parsedAuth
auth.token = token
parsedAuth = fromJS(auth)
return state.setIn( [ "authorized", parsedAuth.get("name") ], parsedAuth )
},
[LOGOUT]: (state, { payload } ) =>{
let result = state.get("authorized").withMutations((authorized) => {
payload.forEach((auth) => {
authorized.delete(auth)
})
})
return state.set("authorized", result)
},
[CONFIGURE_AUTH]: (state, { payload } ) =>{
return state.set("configs", payload)
}
}

View File

@@ -0,0 +1,86 @@
import { createSelector } from "reselect"
import { List, Map } from "immutable"
const state = state => state
export const shownDefinitions = createSelector(
state,
auth => auth.get( "showDefinitions" )
)
export const definitionsToAuthorize = createSelector(
state,
() =>( { specSelectors } ) => {
let definitions = specSelectors.securityDefinitions()
let list = List()
//todo refactor
definitions.entrySeq().forEach( ([ key, val ]) => {
let map = Map()
map = map.set(key, val)
list = list.push(map)
})
return list
}
)
export const getDefinitionsByNames = ( state, securities ) =>( { specSelectors } ) => {
let securityDefinitions = specSelectors.securityDefinitions()
let result = List()
securities.valueSeq().forEach( (names) => {
let map = Map()
names.entrySeq().forEach( ([name, scopes]) => {
let definition = securityDefinitions.get(name)
let allowedScopes
if ( definition.get("type") === "oauth2" && scopes.size ) {
allowedScopes = definition.get("scopes")
allowedScopes.keySeq().forEach( (key) => {
if ( !scopes.contains(key) ) {
allowedScopes = allowedScopes.delete(key)
}
})
definition = definition.set("allowedScopes", allowedScopes)
}
map = map.set(name, definition)
})
result = result.push(map)
})
return result
}
export const authorized = createSelector(
state,
auth => auth.get("authorized") || Map()
)
export const isAuthorized = ( state, securities ) =>( { authSelectors } ) => {
let authorized = authSelectors.authorized()
if(!List.isList(securities)) {
return null
}
return !!securities.toJS().filter( ( security ) => {
let isAuthorized = true
return Object.keys(security).map((key) => {
return !isAuthorized || !!authorized.get(key)
}).indexOf(false) === -1
}).length
}
export const getConfigs = createSelector(
state,
auth => auth.get( "configs" )
)

View File

@@ -0,0 +1,10 @@
// Add security to the final `execute` call ( via `extras` )
export const execute = ( oriAction, { authSelectors, specSelectors }) => ({ path, method, operation, extras }) => {
let securities = {
authorized: authSelectors.authorized() && authSelectors.authorized().toJS(),
definitions: specSelectors.securityDefinitions() && specSelectors.securityDefinitions().toJS(),
specSecurity: specSelectors.security() && specSelectors.security().toJS()
}
return oriAction({ path, method, operation, securities, ...extras })
}

View File

@@ -0,0 +1,70 @@
/* global Promise */
import { createSelector } from "reselect"
import { Map } from "immutable"
export default function downloadUrlPlugin (toolbox) {
let { fn } = toolbox
const actions = {
download: (url)=> ({ errActions, specSelectors, specActions }) => {
let { fetch } = fn
url = url || specSelectors.url()
specActions.updateLoadingStatus("loading")
fetch({
url,
loadSpec: true,
credentials: "same-origin",
headers: {
"Accept": "application/json,*/*"
}
}).then(next,next)
function next(res) {
if(res instanceof Error || res.status >= 400) {
specActions.updateLoadingStatus("failed")
return errActions.newThrownErr( new Error(res.statusText + " " + url) )
}
specActions.updateLoadingStatus("success")
specActions.updateSpec(res.text)
specActions.updateUrl(url)
}
},
updateLoadingStatus: (status) => {
let enums = [null, "loading", "failed", "success", "failedConfig"]
if(enums.indexOf(status) === -1) {
console.error(`Error: ${status} is not one of ${JSON.stringify(enums)}`)
}
return {
type: "spec_update_loading_status",
payload: status
}
}
}
let reducers = {
"spec_update_loading_status": (state, action) => {
return (typeof action.payload === "string")
? state.set("loadingStatus", action.payload)
: state
}
}
let selectors = {
loadingStatus: createSelector(
state => {
return state || Map()
},
spec => spec.get("loadingStatus") || null
)
}
return {
statePlugins: {
spec: { actions, reducers, selectors }
}
}
}

View File

@@ -0,0 +1,43 @@
import serializeError from "serialize-error"
export const NEW_THROWN_ERR = "err_new_thrown_err"
export const NEW_THROWN_ERR_BATCH = "err_new_thrown_err_batch"
export const NEW_SPEC_ERR = "err_new_spec_err"
export const NEW_AUTH_ERR = "err_new_auth_err"
export const CLEAR = "err_clear"
export function newThrownErr(err, action) {
return {
type: NEW_THROWN_ERR,
payload: { action, error: serializeError(err) }
}
}
export function newThrownErrBatch(errors) {
return {
type: NEW_THROWN_ERR_BATCH,
payload: errors
}
}
export function newSpecErr(err) {
return {
type: NEW_SPEC_ERR,
payload: err
}
}
export function newAuthErr(err) {
return {
type: NEW_AUTH_ERR,
payload: err
}
}
export function clear(filter = {}) {
// filter looks like: {type: 'spec'}, {source: 'parser'}
return {
type: CLEAR,
payload: filter
}
}

View File

@@ -0,0 +1,31 @@
# Error transformers
Error transformers provide a standard interface for making generated error messages more useful to end users.
### Inputs & outputs
Each transformer's `transform` function is given an Immutable List of Immutable Maps as its first argument.
It is expected that each `transform` function returns a List of similarly-formed Maps.
These errors originate from the Redux error actions that add errors to state. Errors are transformed before being passed into the reducer.
It's important that all the keys present in each error (specifically, `line`, `level`, `message`, `source`, and `type`) are present when the transformer is finished.
##### Deleting an error
If you want to delete an error completely, you can overwrite it with `null`. The null value will be filtered out of the transformed error array before the errors are returned.
å
### Example transformer
This transformer will increase all your line numbers by 10.
```
export function transform(errors) {
return errors.map(err => {
err.line += 10
return err
})
}
```

View File

@@ -0,0 +1,56 @@
import reduce from "lodash/reduce"
let request = require.context("./transformers/", true, /\.js$/)
let errorTransformers = []
request.keys().forEach( function( key ){
if( key === "./hook.js" ) {
return
}
if( !key.match(/js$/) ) {
return
}
if( key.slice(2).indexOf("/") > -1) {
// skip files in subdirs
return
}
errorTransformers.push({
name: toTitleCase(key).replace(".js", "").replace("./", ""),
transform: request(key).transform
})
})
export default function transformErrors (errors, system) {
let inputs = {
jsSpec: system.specSelectors.specJson().toJS()
}
let transformedErrors = reduce(errorTransformers, (result, transformer) => {
try {
let newlyTransformedErrors = transformer.transform(result, inputs)
return newlyTransformedErrors.filter(err => !!err) // filter removed errors
} catch(e) {
console.error("Transformer error:", e)
return result
}
}, errors)
return transformedErrors
.filter(err => !!err) // filter removed errors
.map(err => {
if(!err.get("line") && err.get("path")) {
// TODO: re-resolve line number if we've transformed it away
}
return err
})
}
function toTitleCase(str) {
return str
.split("-")
.map(substr => substr[0].toUpperCase() + substr.slice(1))
.join("")
}

View File

@@ -0,0 +1,29 @@
export function transform(errors) {
// JSONSchema refers to the current object being validated
// as 'instance'. This isn't helpful to users, so we remove it.
return errors
.map(err => {
let seekStr = "is not of a type(s)"
let i = err.get("message").indexOf(seekStr)
if(i > -1) {
let types = err.get("message").slice(i + seekStr.length).split(",")
return err.set("message", err.get("message").slice(0, i) + makeNewMessage(types))
} else {
return err
}
})
}
function makeNewMessage(types) {
return types.reduce((p, c, i, arr) => {
if(i === arr.length - 1 && arr.length > 1) {
return p + "or " + c
} else if(arr[i+1] && arr.length > 2) {
return p + c + ", "
} else if(arr[i+1]) {
return p + c + " "
} else {
return p + c
}
}, "should be a")
}

View File

@@ -0,0 +1,59 @@
import get from "lodash/get"
import { fromJS } from "immutable"
export function transform(errors, { jsSpec }) {
// LOOK HERE THIS TRANSFORMER IS CURRENTLY DISABLED 😃
// TODO: finish implementing, fix flattening problem
/* eslint-disable no-unreachable */
return errors
// JSONSchema gives us very little to go on
let searchStr = "is not exactly one from <#/definitions/parameter>,<#/definitions/jsonReference>"
return errors
.map(err => {
let message = err.get("message")
let isParameterOneOfError = message.indexOf(searchStr) > -1
if(isParameterOneOfError) {
// try to find what's wrong
return createTailoredParameterError(err, jsSpec)
} else {
return err
}
})
.flatten(true) // shallow Immutable flatten
}
const VALID_IN_VALUES = ["path", "query", "header", "body", "formData"]
const VALID_COLLECTIONFORMAT_VALUES = ["csv", "ssv", "tsv", "pipes", "multi"]
function createTailoredParameterError(err, jsSpec) {
let newErrs = []
let parameter = get(jsSpec, err.get("path"))
// find addressable cases
if(parameter.in && VALID_IN_VALUES.indexOf(parameter.in) === -1) {
let message = `Wrong value for the "in" keyword. Expected one of: ${VALID_IN_VALUES.join(", ")}.`
newErrs.push({
message,
path: err.get("path") + ".in",
type: "spec",
source: "schema",
level: "error"
})
}
if(parameter.collectionFormat && VALID_COLLECTIONFORMAT_VALUES.indexOf(parameter.collectionFormat) === -1) {
let message = `Wrong value for the "collectionFormat" keyword. Expected one of: ${VALID_COLLECTIONFORMAT_VALUES.join(", ")}.`
newErrs.push({
message,
path: err.get("path") + ".collectionFormat",
type: "spec",
source: "schema",
level: "error"
})
}
return newErrs.length ? fromJS(newErrs) : err // fall back to making no changes
}

View File

@@ -0,0 +1,10 @@
export function transform(errors) {
return errors
.map(err => {
return err.set("message", removeSubstring(err.get("message"), "instance."))
})
}
function removeSubstring(str, substr) {
return str.replace(new RegExp(substr, "g"), "")
}

View File

@@ -0,0 +1,15 @@
import makeReducers from "./reducers"
import * as actions from "./actions"
import * as selectors from "./selectors"
export default function(system) {
return {
statePlugins: {
err: {
reducers: makeReducers(system),
actions,
selectors
}
}
}
}

View File

@@ -0,0 +1,68 @@
import {
NEW_THROWN_ERR,
NEW_THROWN_ERR_BATCH,
NEW_SPEC_ERR,
NEW_AUTH_ERR,
CLEAR
} from "./actions"
import reject from "lodash/reject"
import Im, { fromJS, List } from "immutable"
import transformErrors from "./error-transformers/hook"
let DEFAULT_ERROR_STRUCTURE = {
// defaults
line: 0,
level: "error",
message: "Unknown error"
}
export default function(system) {
return {
[NEW_THROWN_ERR]: (state, { payload }) => {
let error = Object.assign(DEFAULT_ERROR_STRUCTURE, payload, {type: "thrown"})
return state
.update("errors", errors => (errors || List()).push( fromJS( error )) )
.update("errors", errors => transformErrors(errors, system.getSystem()))
},
[NEW_THROWN_ERR_BATCH]: (state, { payload }) => {
payload = payload.map(err => {
return fromJS(Object.assign(DEFAULT_ERROR_STRUCTURE, err, { type: "thrown" }))
})
return state
.update("errors", errors => (errors || List()).concat( fromJS( payload )) )
.update("errors", errors => transformErrors(errors, system.getSystem()))
},
[NEW_SPEC_ERR]: (state, { payload }) => {
let error = fromJS(payload)
error = error.set("type", "spec")
return state
.update("errors", errors => (errors || List()).push( fromJS(error)).sortBy(err => err.get("line")) )
.update("errors", errors => transformErrors(errors, system.getSystem()))
},
[NEW_AUTH_ERR]: (state, { payload }) => {
let error = fromJS(Object.assign({}, payload))
error = error.set("type", "auth")
return state
.update("errors", errors => (errors || List()).push( fromJS(error)) )
.update("errors", errors => transformErrors(errors, system.getSystem()))
},
[CLEAR]: (state, { payload }) => {
if(!payload) {
return
}
// TODO: Rework, to use immutable only, no need for lodash
let newErrors = Im.fromJS(reject((state.get("errors") || List()).toJS(), payload))
return state.merge({
errors: newErrors
})
}
}
}

View File

@@ -0,0 +1,15 @@
import { List } from "immutable"
import { createSelector } from "reselect"
const state = state => state
export const allErrors = createSelector(
state,
err => err.get("errors", List())
)
export const lastError = createSelector(
allErrors,
all => all.last()
)

Some files were not shown because too many files have changed in this diff Show More