@@ -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" | |||||
} | |||||
] | |||||
] | |||||
] | |||||
} |
@@ -0,0 +1,5 @@ | |||||
node_modules | |||||
.idea | |||||
.deps_check | |||||
.DS_Store | |||||
npm-debug.log |
@@ -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. |
@@ -0,0 +1,111 @@ | |||||
# 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. | |||||
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.0 | 2017-03-17 | 2.0 | [tag v3.0.0](https://github.com/swagger-api/swagger-ui/tree/v3.0.0) | | |||||
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 | |||||
##### Prerequisites | |||||
- Node 6.x | |||||
- NPM 3.x | |||||
If you just want to see your specs, open `public/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`. | |||||
##### Browser support | |||||
Swagger UI works in the latest versions of Chrome, Safari, Firefox, Edge and IE11. | |||||
## 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. |
@@ -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; | |||||
}; |
@@ -0,0 +1,92 @@ | |||||
<!DOCTYPE html> | |||||
<!-- this is for the dev server --> | |||||
<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"> | |||||
<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> | |||||
<!-- don't be alarmed, these don't match what's in dist, because webpack-dev-server serves them in memory. --> | |||||
<script src="/dist/SwaggerUIBundle.js"> </script> | |||||
<script src="/dist/SwaggerUIStandalonePreset.js"> </script> | |||||
<script> | |||||
window.onload = function() { | |||||
// Build a system | |||||
const ui = SwaggerUIBundle({ | |||||
url: "http://petstore.swagger.io/v2/swagger.json", | |||||
dom_id: '#swagger-ui', | |||||
presets: [ | |||||
SwaggerUIBundle.presets.apis, | |||||
// yay ES6 modules ↘ | |||||
Array.isArray(SwaggerUIStandalonePreset) ? SwaggerUIStandalonePreset : SwaggerUIStandalonePreset.default | |||||
], | |||||
plugins: [ | |||||
SwaggerUIBundle.plugins.DownloadUrl | |||||
], | |||||
layout: "StandaloneLayout" | |||||
}) | |||||
window.ui = ui | |||||
} | |||||
</script> | |||||
</body> | |||||
</html> |
@@ -0,0 +1 @@ | |||||
{"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;AA6OA;;;;;;AAoIA;AAm7FA;AAwtCA;AAg0IA;;;;;AAkxBA;AAo8IA;AA41GA;AA23FA;AAmqFA;AA0nFA;AA49CA;AAwhDA;AAkrCA;AAumFA;AAmnHA;;;;;;;;;;;;;;AAqjHA;AAyoIA;AAkuJA;AAilHA;AA4kGA;AAwkEA;AAs3DA;AAovDA;AAotBA;AAoqGA;;;;;;AAueA;AAimGA;AA44EA;;;;;AAoGA;AA2qFA;AAo2CA;AAgvDA;AA8tCA;AAoiEA;AA69FA;;;;;;;;;AA20BA;AA2zIA;AAm4DA","sourceRoot":""} |
@@ -0,0 +1 @@ | |||||
{"version":3,"file":"swagger-ui-standalone-preset.js","sources":["webpack:///swagger-ui-standalone-preset.js"],"mappings":"AAAA;;;;;AAwSA;AAyiGA;AAqwFA;;;;;;AA4eA;AAkvFA;AAu+CA;AAo+CA;AAgrCA;AAgyEA","sourceRoot":""} |
@@ -0,0 +1 @@ | |||||
{"version":3,"file":"swagger-ui.css","sources":[],"mappings":"","sourceRoot":""} |
@@ -0,0 +1 @@ | |||||
{"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;AAooGA;AAw0HA;AAijGA;AA6lCA;AA29BA;AAmwCA;AAw4BA","sourceRoot":""} |
@@ -0,0 +1,145 @@ | |||||
var path = require('path') | |||||
var webpack = require('webpack') | |||||
var ExtractTextPlugin = require('extract-text-webpack-plugin') | |||||
var deepExtend = require('deep-extend') | |||||
var autoprefixer = require('autoprefixer') | |||||
var loadersByExtension = require('./build-tools/loadersByExtension') | |||||
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) | |||||
}, | |||||
})) | |||||
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 | |||||
} |
@@ -0,0 +1,124 @@ | |||||
{ | |||||
"name": "swagger-ui", | |||||
"version": "3.0.0", | |||||
"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": { | |||||
"start": "http-server -i -a 0.0.0.0 -p 3001", | |||||
"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 watch 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 dist/", | |||||
"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", | |||||
"just-test-in-node": "mocha --recursive --compilers js:babel-core/register test/core" | |||||
}, | |||||
"dependencies": { | |||||
"brace": "0.7.0", | |||||
"btoa": "^1.1.2", | |||||
"debounce": "1.0.0", | |||||
"deep-extend": "0.4.1", | |||||
"expect": "1.20.2", | |||||
"getbase": "^2.8.2", | |||||
"immutable": "^3.x.x", | |||||
"js-yaml": "^3.5.5", | |||||
"jsonschema": "^1.1.0", | |||||
"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": "0.14.8", | |||||
"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", | |||||
"serialize-error": "2.0.0", | |||||
"swagger-client": "^3.0.1", | |||||
"whatwg-fetch": "0.11.1", | |||||
"worker-loader": "^0.7.1", | |||||
"xml": "1.0.1", | |||||
"yaml-js": "^0.1.3", | |||||
"yaml-worker": "^2.1.0" | |||||
}, | |||||
"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", | |||||
"extract-text-webpack-plugin": "0.8.2", | |||||
"file-loader": "0.8.4", | |||||
"html-webpack-plugin": "^2.28.0", | |||||
"imports-loader": "0.6.5", | |||||
"json-loader": "0.5.3", | |||||
"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", | |||||
"rimraf": "^2.6.0", | |||||
"sass-loader": "^6.0.2", | |||||
"shallowequal": "0.2.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" | |||||
} | |||||
} |
@@ -0,0 +1,93 @@ | |||||
<!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="../dist/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="../dist/swagger-ui-bundle.js"> </script> | |||||
<script src="../dist/swagger-ui-standalone-preset.js"> </script> | |||||
<script> | |||||
window.onload = function() { | |||||
// Build a system | |||||
const ui = SwaggerUIBundle({ | |||||
url: "http://petstore.swagger.io/v2/swagger.json", | |||||
dom_id: '#swagger-ui', | |||||
presets: [ | |||||
SwaggerUIBundle.presets.apis, | |||||
// yay ES6 modules ↘ | |||||
Array.isArray(SwaggerUIStandalonePreset) ? SwaggerUIStandalonePreset : SwaggerUIStandalonePreset.default | |||||
], | |||||
plugins: [ | |||||
SwaggerUIBundle.plugins.DownloadUrl | |||||
], | |||||
layout: "StandaloneLayout" | |||||
}) | |||||
window.ui = ui | |||||
} | |||||
</script> | |||||
</body> | |||||
</html> |
@@ -0,0 +1,83 @@ | |||||
<!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; | |||||
qp = (window.location.hash || location.search).substring(1); | |||||
qp = qp ? JSON.parse('{"' + qp.replace(/&/g, '","').replace(/=/g, '":"') + '"}', | |||||
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; | |||||
createForm(oauth2.auth, qp).submit(); | |||||
} else { | |||||
oauth2.errCb({ | |||||
authId: oauth2.auth.name, | |||||
source: "auth", | |||||
level: "error", | |||||
message: "Authorization failed: no accessCode came from the server" | |||||
}); | |||||
window.close(); | |||||
} | |||||
} else { | |||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid}); | |||||
window.close(); | |||||
} | |||||
} | |||||
function createForm(auth, qp) { | |||||
var form = document.createElement("form"); | |||||
var schema = auth.schema; | |||||
var action = schema.get("tokenUrl"); | |||||
var name, input; | |||||
var fields = { | |||||
code: qp.code, | |||||
"redirect_uri": location.protocol + "//" + location.host + location.pathname, | |||||
"grant_type": "authorization_code", | |||||
"client_secret": auth.clientSecret, | |||||
"client_id": auth.clientId | |||||
} | |||||
for ( name in fields ) { | |||||
input = document.createElement("input"); | |||||
input.name = name; | |||||
input.value = fields[name]; | |||||
input.type = "hidden"; | |||||
form.appendChild(input); | |||||
} | |||||
form.method = "POST"; | |||||
form.action = action; | |||||
document.body.appendChild(form); | |||||
return form; | |||||
} | |||||
</script> |
@@ -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" | |||||
}) |
@@ -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 = { | |||||
} |
@@ -0,0 +1,82 @@ | |||||
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 options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}} | |||||
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> | |||||
<Col> | |||||
{ | |||||
value || <Input type="text" onChange={ this.onChange }/> | |||||
} | |||||
</Col> | |||||
</Row> | |||||
{ | |||||
errors.valueSeq().map( (error, key) => { | |||||
return <AuthError error={ error } | |||||
key={ key }/> | |||||
} ) | |||||
} | |||||
</div> | |||||
) | |||||
} | |||||
} |
@@ -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, | |||||
} | |||||
} |
@@ -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, errActions} = 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, | |||||
} | |||||
} |
@@ -0,0 +1,34 @@ | |||||
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) | |||||
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 | |||||
} | |||||
} |
@@ -0,0 +1,138 @@ | |||||
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, specSelectors } = this.props | |||||
const ApiKeyAuth = getComponent("apiKeyAuth") | |||||
const BasicAuth = getComponent("basicAuth") | |||||
const Oauth2 = getComponent("oauth2", true) | |||||
const Button = getComponent("Button") | |||||
const JumpToPath = getComponent("JumpToPath", true) | |||||
let specStr = specSelectors.specStr() | |||||
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 | |||||
} | |||||
} |
@@ -0,0 +1,97 @@ | |||||
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 options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}} | |||||
source={ schema.get("description") } /> | |||||
</Row> | |||||
<Row> | |||||
<Col tablet={2} desktop={2}>username:</Col> | |||||
<Col tablet={10} desktop={10}> | |||||
{ | |||||
username || <Input type="text" required="required" name="username" onChange={ this.onChange }/> | |||||
} | |||||
</Col> | |||||
</Row> | |||||
{ | |||||
!username && <Row> | |||||
<Col tablet={2} desktop={2}>password:</Col> | |||||
<Col tablet={10} desktop={10}><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 | |||||
} | |||||
} |
@@ -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> | |||||
) | |||||
} | |||||
} |
@@ -0,0 +1,218 @@ | |||||
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, | |||||
configs: 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 | |||||
} | |||||
constructor(props, context) { | |||||
super(props, context) | |||||
let { name, schema, authorized } = this.props | |||||
let auth = authorized && authorized.get(name) | |||||
let username = auth && auth.get("username") || "" | |||||
let clientId = auth && auth.get("clientId") || "" | |||||
let clientSecret = auth && auth.get("clientSecret") || "" | |||||
let passwordType = auth && auth.get("passwordType") || "none" | |||||
this.state = { | |||||
name: name, | |||||
schema: schema, | |||||
scopes: [], | |||||
clientId: clientId, | |||||
clientSecret: clientSecret, | |||||
username: username, | |||||
password: "", | |||||
passwordType: passwordType | |||||
} | |||||
} | |||||
authorize =() => { | |||||
let { authActions, errActions, getConfigs } = this.props | |||||
let configs = getConfigs() | |||||
errActions.clear({authId: name,type: "auth", source: "auth"}) | |||||
oauth2Authorize(this.state, authActions, errActions, configs) | |||||
} | |||||
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> | |||||
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}} | |||||
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 && ( !isAuthorized || isAuthorized && this.state.username) && <Row> | |||||
<Col tablet={2} desktop={2}>username:</Col> | |||||
<Col tablet={10} desktop={10}> | |||||
{ | |||||
isAuthorized ? <span>{ this.state.username }</span> | |||||
: <input type="text" data-name="username" onChange={ this.onInputChange }/> | |||||
} | |||||
</Col> | |||||
</Row> | |||||
} | |||||
{ | |||||
flow === PASSWORD && !isAuthorized && <Row> | |||||
<Col tablet={2} desktop={2}>password:</Col> | |||||
<Col tablet={10} desktop={10}> | |||||
<input type="password" data-name="password" onChange={ this.onInputChange }/> | |||||
</Col> | |||||
</Row> | |||||
} | |||||
{ | |||||
flow === PASSWORD && <Row> | |||||
<Col tablet={2} desktop={2}>type:</Col> | |||||
<Col tablet={10} desktop={10}> | |||||
{ | |||||
isAuthorized ? <span>{ this.state.passwordType }</span> | |||||
: <select data-name="passwordType" onChange={ this.onInputChange }> | |||||
<option value="none">None or other</option> | |||||
<option value="basic">Basic auth</option> | |||||
<option value="request">Request body</option> | |||||
</select> | |||||
} | |||||
</Col> | |||||
</Row> | |||||
} | |||||
{ | |||||
( flow === IMPLICIT || flow === ACCESS_CODE || ( flow === PASSWORD && this.state.passwordType!== "none") ) && | |||||
( !isAuthorized || isAuthorized && this.state.clientId) && <Row> | |||||
<label htmlFor="client_id">client_id:</label> | |||||
<Col tablet={10} desktop={10}> | |||||
{ | |||||
isAuthorized ? <span>{ this.state.clientId }</span> | |||||
: <input id="client_id" type="text" required={ flow === PASSWORD } data-name="clientId" | |||||
onChange={ this.onInputChange }/> | |||||
} | |||||
</Col> | |||||
</Row> | |||||
} | |||||
{ | |||||
( flow === ACCESS_CODE || ( flow === PASSWORD && this.state.passwordType!== "none") ) && <Row> | |||||
<label htmlFor="client_secret">client_secret:</label> | |||||
<Col tablet={10} desktop={10}> | |||||
{ | |||||
isAuthorized ? <span>{ this.state.clientSecret }</span> | |||||
: <input id="client_secret" type="text" data-name="clientSecret" | |||||
onChange={ this.onInputChange }/> | |||||
} | |||||
</Col> | |||||
</Row> | |||||
} | |||||
{ | |||||
!isAuthorized && flow !== PASSWORD && 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`} | |||||
disabled={ isAuthorized } | |||||
type="checkbox" | |||||
onChange={ this.onScopeChange }/> | |||||
<label htmlFor={`${name}-checkbox`}> | |||||
<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 && flow !== APPLICATION && | |||||
( 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> | |||||
) | |||||
} | |||||
} |
@@ -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, | |||||
} | |||||
} |
@@ -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> | |||||
) | |||||
} | |||||
} |
@@ -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" }} defaultValue={curl}></textarea> | |||||
</div> | |||||
</div> | |||||
) | |||||
} | |||||
} |
@@ -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 | |||||
} | |||||
@@ -0,0 +1,113 @@ | |||||
import React, { PropTypes } from "react" | |||||
import Im, { List } from "immutable" | |||||
import Collapse from "react-collapse" | |||||
import sortBy from "lodash/sortBy" | |||||
export default class Errors extends React.Component { | |||||
static propTypes = { | |||||
jumpToLine: PropTypes.func, | |||||
errSelectors: PropTypes.object.isRequired, | |||||
layoutSelectors: PropTypes.object.isRequired, | |||||
layoutActions: PropTypes.object.isRequired | |||||
} | |||||
render() { | |||||
let { jumpToLine, errSelectors, layoutSelectors, layoutActions } = this.props | |||||
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) => { | |||||
if(err.get("type") === "thrown") { | |||||
return <ThrownErrorItem key={ i } error={ err.get("error") || err } jumpToLine={jumpToLine} /> | |||||
} | |||||
if(err.get("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 ? <a onClick={jumpToLine.bind(null, errorLine)}>Jump to line { errorLine }</a> : null } | |||||
</div> | |||||
</div> | |||||
} | |||||
</div> | |||||
) | |||||
} | |||||
const SpecErrorItem = ( { error, jumpToLine } ) => { | |||||
return ( | |||||
<div className="error-wrapper"> | |||||
{ !error ? null : | |||||
<div> | |||||
<h4>{ toTitleCase(error.get("source")) + " " + error.get("level") }{ error.get("path") ? <small> at {List.isList(error.get("path")) ? error.get("path").join(".") : error.get("path")}</small>: null }</h4> | |||||
<span style={{ whiteSpace: "pre-line"}}>{ error.get("message") }</span> | |||||
<div> | |||||
{ 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 | |||||
} | |||||
SpecErrorItem.propTypes = { | |||||
error: PropTypes.object.isRequired, | |||||
jumpToLine: PropTypes.func | |||||
} |
@@ -0,0 +1,41 @@ | |||||
import React, { Component, PropTypes } from "react" | |||||
import { fromJS } from "immutable" | |||||
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(){ | |||||
let { getComponent, operation, specActions, path, method } = this.props | |||||
const ContentType = getComponent( "contentType" ) | |||||
return ( | |||||
<button className="btn execute opblock-control__btn" onClick={ this.onClick }> | |||||
Execute | |||||
</button> | |||||
) | |||||
} | |||||
} |
@@ -0,0 +1,9 @@ | |||||
import React from "react" | |||||
export default class Footer extends React.Component { | |||||
render() { | |||||
return ( | |||||
<div className="footer"></div> | |||||
) | |||||
} | |||||
} |
@@ -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> | |||||
) | |||||
} | |||||
} |
@@ -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> | |||||
} | |||||
} |
@@ -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 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 href={ url }><span className="url"> { url } </span></a> } | |||||
</hgroup> | |||||
<div className="description"> | |||||
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}} source={ description } /> | |||||
</div> | |||||
{ | |||||
termsOfService && <div> | |||||
<a 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 | |||||
} |
@@ -0,0 +1,252 @@ | |||||
import React, { PropTypes } from "react" | |||||
import OriCollapse from "react-collapse" | |||||
import _Markdown from "react-remarkable" | |||||
const noop = () => {} | |||||
function xclass(...args) { | |||||
return args.filter(a => !!a).join(" ").trim() | |||||
} | |||||
export const Markdown = _Markdown | |||||
export class Container extends React.Component { | |||||
render() { | |||||
let { fullscreen, full, ...rest } = this.props | |||||
// Normal element | |||||
if(fullscreen) | |||||
return <section {...rest}/> | |||||
let containerClass = "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, | |||||
mobile, /* we don't want these in the final component, since React now complains. So we extract them */ | |||||
tablet, | |||||
desktop, | |||||
large, | |||||
...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, "clear", ...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.object, | |||||
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> | |||||
) | |||||
} | |||||
} |
@@ -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 | |||||
} | |||||
@@ -0,0 +1,91 @@ | |||||
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() { | |||||
let { request, response, getComponent } = this.props | |||||
const Curl = getComponent("curl") | |||||
let body = response.get("text") | |||||
let status = response.get("status") | |||||
let url = response.get("url") | |||||
let originalHeaders = response.get("headers") | |||||
let headers = originalHeaders && originalHeaders.toJS() | |||||
let headersKeys = Object.keys(headers) | |||||
let returnObject = headersKeys.map(key => { | |||||
return <span className="headerline" key={key}> {key}: {headers[key]} </span> | |||||
}) | |||||
let notDocumented = response.get("notDocumented") | |||||
let ResponseBody = getComponent("responseBody") | |||||
let contentType = headers && headers["content-type"] | |||||
let isError = response.get("error") | |||||
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 | |||||
} | |||||
} |
@@ -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> | |||||
} | |||||
} |
@@ -0,0 +1,300 @@ | |||||
import React, { Component, PropTypes } from "react" | |||||
import ImPropTypes from "react-immutable-proptypes" | |||||
import isObject from "lodash/isObject" | |||||
import { List } from "immutable" | |||||
const braceOpen = "{" | |||||
const braceClose = "}" | |||||
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 description = schema.get("description") | |||||
let properties = schema.filter( ( v, key) => ["enum", "type", "format", "$$ref"].indexOf(key) === -1 ) | |||||
let style = required ? { fontWeight: "bold" } : {} | |||||
let propStyle = { color: "#999", fontStyle: "italic" } | |||||
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/> {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") | |||||
return <span> | |||||
<Collapse collapsed={ depth > expandDepth } collapsedContent="[...]"> | |||||
[ | |||||
<span><Model { ...this.props } schema={ items } required={ false }/></span> | |||||
] | |||||
</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(){ | |||||
let { name, schema } = this.props | |||||
let title = schema.get("title") || name | |||||
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>) | |||||
} | |||||
} |
@@ -0,0 +1,42 @@ | |||||
import React, { Component, PropTypes } from "react" | |||||
export default class Models extends Component { | |||||
static propTypes = { | |||||
getComponent: PropTypes.func, | |||||
specSelectors: PropTypes.object | |||||
} | |||||
render(){ | |||||
let { specSelectors, getComponent, layoutSelectors, layoutActions } = this.props | |||||
let definitions = specSelectors.definitions() | |||||
let showModels = layoutSelectors.isShown('models', true) | |||||
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> | |||||
} | |||||
} |
@@ -0,0 +1,40 @@ | |||||
import React from "react" | |||||
export default class OnlineValidatorBadge extends React.Component { | |||||
constructor(props, context) { | |||||
super(props, context) | |||||
let { specSelectors, getConfigs } = props | |||||
let { validatorUrl } = getConfigs() | |||||
this.state = { | |||||
url: specSelectors.url(), | |||||
validatorUrl: validatorUrl | |||||
} | |||||
} | |||||
componentWillReceiveProps(nextProps) { | |||||
let { specSelectors, getConfigs } = nextProps | |||||
let { validatorUrl } = getConfigs() | |||||
this.setState({ | |||||
url: specSelectors.url(), | |||||
validatorUrl: validatorUrl | |||||
}) | |||||
} | |||||
render() { | |||||
let { getConfigs } = this.props | |||||
let { spec } = getConfigs() | |||||
if ( typeof spec === "object" && Object.keys(spec).length) return null | |||||
if (!this.state.url) { | |||||
return null | |||||
} | |||||
return (<span style={{ float: "right"}}> | |||||
<a target="_blank" href={`${ this.state.validatorUrl }/debug?url=${ this.state.url }`}> | |||||
<img alt="Online validator badge" src={`${ this.state.validatorUrl }?url=${ this.state.url }`} /> | |||||
</a> | |||||
</span>) | |||||
} | |||||
} |
@@ -0,0 +1,258 @@ | |||||
import React, { PropTypes } from "react" | |||||
import { Map, fromJS } from "immutable" | |||||
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, | |||||
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 | |||||
} | |||||
static defaultProps = { | |||||
showSummary: true, | |||||
response: null, | |||||
allowTryItOut: true, | |||||
} | |||||
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 } = this.props | |||||
return layoutSelectors.isShown(isShownKey, false ) // 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, | |||||
fn, | |||||
getComponent, | |||||
specActions, | |||||
specSelectors, | |||||
authActions, | |||||
authSelectors, | |||||
layoutSelectors, | |||||
layoutActions, | |||||
} = 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"]) | |||||
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> | |||||
} | |||||
{ | |||||
(!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 options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}} 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 ? <Schemes schemes={ schemes } | |||||
path={ path } | |||||
method={ method } | |||||
specActions={ specActions }/> | |||||
: 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> | |||||
) | |||||
} | |||||
} |
@@ -0,0 +1,133 @@ | |||||
import React, { PropTypes } from "react" | |||||
import {presets} from "react-motion" | |||||
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, | |||||
}; | |||||
static defaultProps = { | |||||
}; | |||||
render() { | |||||
let { | |||||
specSelectors, | |||||
specActions, | |||||
getComponent, | |||||
layoutSelectors, | |||||
layoutActions, | |||||
authActions, | |||||
authSelectors, | |||||
fn | |||||
} = this.props | |||||
let taggedOps = specSelectors.taggedOperations() | |||||
const Operation = getComponent("operation") | |||||
const Collapse = getComponent("Collapse") | |||||
const Schemes = getComponent("schemes") | |||||
let showSummary = layoutSelectors.showSummary() | |||||
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, true) | |||||
return ( | |||||
<div className={showTag ? "opblock-tag-section is-open" : "opblock-tag-section"} key={"operation-" + tag}> | |||||
<h4 className={!tagDescription ? "opblock-tag no-desc" : "opblock-tag" }> | |||||
<span onClick={() => layoutActions.show(isShownKey, !showTag)}>{tag}</span> | |||||
{ !tagDescription ? null : | |||||
<small onClick={() => layoutActions.show(isShownKey, !showTag)} > | |||||
{ tagDescription } | |||||
</small> | |||||
} | |||||
<button className="expand-methods" title="Expand all methods"> | |||||
<svg className="expand" width="20" height="20"> | |||||
<use xlinkHref="#expand" /> | |||||
</svg> | |||||
</button> | |||||
<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} | |||||
specActions={ specActions } | |||||
specSelectors={ specSelectors } | |||||
layoutActions={ layoutActions } | |||||
layoutSelectors={ layoutSelectors } | |||||
authActions={ authActions } | |||||
authSelectors={ authSelectors } | |||||
getComponent={ getComponent } | |||||
fn={fn} | |||||
/> | |||||
}).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 | |||||
} |
@@ -0,0 +1,119 @@ | |||||
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 tagDetails = tagObj.get("tagDetails") | |||||
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, operation, 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 | |||||
} |
@@ -0,0 +1,141 @@ | |||||
import React, { Component, PropTypes } from "react" | |||||
import shallowCompare from "react-addons-shallow-compare" | |||||
import { Set, 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="", onChangeConsumes } = 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 ) { | |||||
this.setState({ value: paramValue }) | |||||
this.onChange(paramValue, {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> | |||||
) | |||||
} | |||||
} |
@@ -0,0 +1,119 @@ | |||||
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 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) | |||||
} | |||||
} | |||||
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"}}> *</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 options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}} | |||||
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> | |||||
) | |||||
} | |||||
} |
@@ -0,0 +1,109 @@ | |||||
import React, { Component, PropTypes } from "react" | |||||
import ImPropTypes from "react-immutable-proptypes" | |||||
import Im, { fromJS } 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, k) => ( | |||||
<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> | |||||
) | |||||
} | |||||
} |
@@ -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> | |||||
) | |||||
} | |||||
} |
@@ -0,0 +1,91 @@ | |||||
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 ]) => { | |||||
return (<div key={ key }> | |||||
<h5>{ key }</h5> | |||||
<HighlightCode className="example" value={ example } /> | |||||
</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 options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}} source={ response.get( "description" ) } /> | |||||
</div> | |||||
{ example ? ( | |||||
<ModelExample | |||||
getComponent={ getComponent } | |||||
specSelectors={ specSelectors } | |||||
schema={ fromJS(schema) } | |||||
example={ example }/> | |||||
) : null} | |||||
{ headers ? ( | |||||
<Headers headers={ headers }/> | |||||
) : null} | |||||
</td> | |||||
</tr> | |||||
) | |||||
} | |||||
} |
@@ -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> | |||||
) | |||||
} | |||||
} |
@@ -0,0 +1,45 @@ | |||||
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 ) => { | |||||
let { path, method, specActions } = this.props | |||||
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>Schemes</span> | |||||
<select onChange={ this.onChange }> | |||||
{ schemes.valueSeq().map( | |||||
( scheme ) => <option value={ scheme } key={ scheme }>{ scheme }</option> | |||||
).toArray()} | |||||
</select> | |||||
</label> | |||||
) | |||||
} | |||||
} |
@@ -0,0 +1,27 @@ | |||||
import React, { PropTypes } from "react" | |||||
export default class TryItOutButton extends React.Component { | |||||
static propTypes = { | |||||
onTryoutClick: PropTypes.func, | |||||
enabled: PropTypes.bool, // Try it out is enabled, ie: the user has access to the form | |||||
}; | |||||
static defaultProps = { | |||||
onTryoutClick: 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> | |||||
) | |||||
} | |||||
} |
@@ -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( " " ) | |||||
} |
@@ -0,0 +1,119 @@ | |||||
import deepExtend from "deep-extend" | |||||
import System from "core/system" | |||||
import ApisPreset from "core/presets/apis" | |||||
import * as AllPlugins from "core/plugins/all" | |||||
import { filterConfigs } from "plugins/configs" | |||||
module.exports = function SwaggerUI(opts) { | |||||
const defaults = { | |||||
// Some general settings, that we floated to the top | |||||
dom_id: null, | |||||
spec: {}, | |||||
url: "", | |||||
layout: "Layout", | |||||
configs: { | |||||
validatorUrl: "https://online.swagger.io/validator" | |||||
}, | |||||
// 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 config = deepExtend({}, defaults, opts) | |||||
const storeConfigs = deepExtend({}, config.store, { | |||||
system: { | |||||
configs: config.configs | |||||
}, | |||||
plugins: config.presets, | |||||
state: { | |||||
layout: { | |||||
layout: config.layout | |||||
}, | |||||
spec: { | |||||
spec: "", | |||||
url: config.url | |||||
} | |||||
} | |||||
}) | |||||
let inlinePlugin = ()=> { | |||||
return { | |||||
fn: config.fn, | |||||
components: config.components, | |||||
state: config.state, | |||||
} | |||||
} | |||||
var store = new System(storeConfigs) | |||||
store.register([config.plugins, inlinePlugin]) | |||||
var system = store.getSystem() | |||||
const downloadSpec = (configs) => { | |||||
if(typeof config !== "object") { | |||||
return system | |||||
} | |||||
let localConfig = system.specSelectors.getLocalConfig ? system.specSelectors.getLocalConfig() : {} | |||||
let mergedConfig = deepExtend({}, config, configs, localConfig) | |||||
store.setConfigs(filterConfigs(mergedConfig)) | |||||
if(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(mergedConfig.url) { | |||||
system.specActions.updateUrl(mergedConfig.url) | |||||
system.specActions.download(mergedConfig.url) | |||||
} | |||||
if(mergedConfig.dom_id) | |||||
system.render(mergedConfig.dom_id, "App") | |||||
return system | |||||
} | |||||
if (system.specActions.getConfigByUrl && !system.specActions.getConfigByUrl(downloadSpec)) { | |||||
return downloadSpec(config) | |||||
} | |||||
if (system.specActions.download && config.url) { | |||||
system.specActions.download(config.url) | |||||
} | |||||
if(config.spec && typeof config.spec === "string") | |||||
system.specActions.updateSpec(config.spec) | |||||
if(config.dom_id) { | |||||
system.render(config.dom_id, "App") | |||||
} else { | |||||
console.error("Skipped rendering: no `dom_id` was specified") | |||||
} | |||||
return system | |||||
} | |||||
// Add presets | |||||
module.exports.presets = { | |||||
apis: ApisPreset, | |||||
} | |||||
// All Plugins | |||||
module.exports.plugins = AllPlugins |
@@ -0,0 +1,187 @@ | |||||
import React, { PropTypes, Component } from "react" | |||||
import { arrayify } from "core/utils" | |||||
import shallowCompare from "react-addons-shallow-compare" | |||||
import { List, fromJS } from "immutable" | |||||
import assign from "object-assign" | |||||
//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, fn, 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="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(state => ({ | |||||
value: value | |||||
}), this.onChange) | |||||
} | |||||
render() { | |||||
let { getComponent, onChange, 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) | |||||
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 }/>) | |||||
} | |||||
} |
@@ -0,0 +1,47 @@ | |||||
import win from "core/window" | |||||
export default function authorize ( auth, authActions, errActions, configs ) { | |||||
let { schema, scopes, name, clientId } = auth | |||||
let redirectUrl = configs.oauth2RedirectUrl | |||||
let scopeSeparator = " " | |||||
let state = name | |||||
let flow = schema.get("flow") | |||||
let url | |||||
if (flow === "password") { | |||||
authActions.authorizePassword(auth) | |||||
return | |||||
} | |||||
// todo move to parser | |||||
if ( !redirectUrl ) { | |||||
errActions.newAuthErr( { | |||||
authId: name, | |||||
source: "validation", | |||||
level: "error", | |||||
message: "oauth2RedirectUri configuration is not passed. Oauth2 authorization cannot be performed." | |||||
}) | |||||
return | |||||
} | |||||
if (flow === "implicit" || flow === "accessCode") { | |||||
url = schema.get("authorizationUrl") + "?response_type=" + (flow === "implicit" ? "token" : "code") | |||||
} | |||||
url += "&redirect_uri=" + encodeURIComponent(redirectUrl) | |||||
+ "&scope=" + encodeURIComponent(scopes.join(scopeSeparator)) | |||||
+ "&state=" + encodeURIComponent(state) | |||||
+ "&client_id=" + encodeURIComponent(clientId) | |||||
// pass action authorizeOauth2 and authentication data through window | |||||
// to authorize with oauth2 | |||||
win.swaggerUIRedirectOauth2 = { | |||||
auth: auth, | |||||
state: state, | |||||
callback: authActions.preAuthorizeOauth2, | |||||
errCb: errActions.newAuthErr | |||||
} | |||||
win.open(url) | |||||
} |
@@ -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, i, arr) => { | |||||
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 | |||||
} |
@@ -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 | |||||
}) |
@@ -0,0 +1,36 @@ | |||||
import React, { PropTypes } from "react" | |||||
export default function (system) { | |||||
return { | |||||
components: { | |||||
NoHostWarning, | |||||
}, | |||||
statePlugins: { | |||||
spec: { | |||||
selectors: { | |||||
allowTryItOutFor, | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
// This is a quick style. How do we improve this? | |||||
const style = { | |||||
backgroundColor: "#e7f0f7", | |||||
padding: "1rem", | |||||
borderRadius: "3px", | |||||
} | |||||
function NoHostWarning() { | |||||
return ( | |||||
<div style={style}>Note: The interactive forms are disabled, as no `host` property was found in the specification. Please see: <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object" target="_blank">OAI 2.0/#swagger-object</a></div> | |||||
) | |||||
} | |||||
// Only allow if, there is a host field | |||||
function allowTryItOutFor(state) { | |||||
return ({specSelectors}) => { | |||||
return specSelectors.hasHost(state) | |||||
} | |||||
} |
@@ -0,0 +1,284 @@ | |||||
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(function(resolve, reject) { | |||||
resolve(fn(...args)) | |||||
}) | |||||
} | |||||
} |
@@ -0,0 +1,9 @@ | |||||
import * as AST from "./ast" | |||||
import JumpToPath from "./jump-to-path" | |||||
export default function() { | |||||
return { | |||||
fn: { AST }, | |||||
components: { JumpToPath } | |||||
} | |||||
} |
@@ -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 | |||||
} | |||||
} |
@@ -0,0 +1,118 @@ | |||||
import win from "core/window" | |||||
import btoa from "btoa" | |||||
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 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 preAuthorizeOauth2 = (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 ) => ( { fn, authActions, errActions } ) => { | |||||
let { schema, name, username, password, passwordType, clientId, clientSecret } = auth | |||||
let req = { | |||||
url: schema.get("tokenUrl"), | |||||
method: "post", | |||||
headers: { | |||||
"content-type": "application/x-www-form-urlencoded" | |||||
}, | |||||
query: { | |||||
grant_type: "password", | |||||
username, | |||||
password | |||||
} | |||||
} | |||||
if ( passwordType === "basic") { | |||||
req.headers.authorization = "Basic " + btoa(clientId + ":" + clientSecret) | |||||
} else if ( passwordType === "request") { | |||||
req.query = Object.assign(req.query, { client_id: clientId, client_secret: clientSecret }) | |||||
} | |||||
return fn.fetch(req) | |||||
.then(( 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(err => { errActions.newAuthErr( err ) }) | |||||
} |
@@ -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 | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,63 @@ | |||||
import { fromJS, Map } from "immutable" | |||||
import btoa from "btoa" | |||||
import { | |||||
SHOW_AUTH_POPUP, | |||||
AUTHORIZE, | |||||
PRE_AUTHORIZE_OAUTH2, | |||||
AUTHORIZE_OAUTH2, | |||||
LOGOUT | |||||
} 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"]) | |||||
let name = security.get("name") | |||||
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) | |||||
} | |||||
} |
@@ -0,0 +1,78 @@ | |||||
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, | |||||
auth =>( { 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() | |||||
let isAuth = false | |||||
return !!securities.toJS().filter( ( security ) => { | |||||
let isAuthorized = true | |||||
return Object.keys(security).map((key) => { | |||||
return !isAuthorized || !!authorized.get(key) | |||||
}).indexOf(false) === -1 | |||||
}).length | |||||
} |
@@ -0,0 +1,13 @@ | |||||
import { Map } from "immutable" | |||||
// 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 }) | |||||
} | |||||
@@ -0,0 +1,67 @@ | |||||
/* global Promise */ | |||||
import { createSelector } from "reselect" | |||||
import { Map } from "immutable" | |||||
export default function downloadUrlPlugin (toolbox) { | |||||
let { fn, Im } = toolbox | |||||
const actions = { | |||||
download: (url)=> ({ errActions, specSelectors, specActions }) => { | |||||
let { fetch } = fn | |||||
url = url || specSelectors.url() | |||||
specActions.updateLoadingStatus("loading") | |||||
fetch(url, { | |||||
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"] | |||||
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 } | |||||
} | |||||
} | |||||
} |
@@ -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, action) { | |||||
return { | |||||
type: NEW_SPEC_ERR, | |||||
payload: err | |||||
} | |||||
} | |||||
export function newAuthErr(err, action) { | |||||
return { | |||||
type: NEW_AUTH_ERR, | |||||
payload: err | |||||
} | |||||
} | |||||
export function clear(filter = {}) { | |||||
// filter looks like: {type: 'spec'}, {source: 'parser'} | |||||
return { | |||||
type: CLEAR, | |||||
payload: filter | |||||
} | |||||
} |
@@ -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 | |||||
}) | |||||
} | |||||
``` |
@@ -0,0 +1,57 @@ | |||||
import concat from "lodash/concat" | |||||
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("") | |||||
} |
@@ -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") | |||||
} |
@@ -0,0 +1,60 @@ | |||||
import get from "lodash/get" | |||||
import last from "lodash/get" | |||||
import { fromJS, List } 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 | |||||
} |
@@ -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"), "") | |||||
} |
@@ -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 | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -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 | |||||
}) | |||||
} | |||||
} | |||||
} |
@@ -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() | |||||
) | |||||
@@ -0,0 +1,45 @@ | |||||
import { normalizeArray } from "core/utils" | |||||
export const UPDATE_LAYOUT = "layout_update_layout" | |||||
export const UPDATE_MODE = "layout_update_mode" | |||||
export const SHOW = "layout_show" | |||||
// export const ONLY_SHOW = "layout_only_show" | |||||
export function updateLayout(layout) { | |||||
return { | |||||
type: UPDATE_LAYOUT, | |||||
payload: layout | |||||
} | |||||
} | |||||
export function show(thing, shown=true) { | |||||
thing = normalizeArray(thing) | |||||
return { | |||||
type: SHOW, | |||||
payload: {thing, shown} | |||||
} | |||||
} | |||||
// Simple string key-store, used for | |||||
export function changeMode(thing, mode="") { | |||||
thing = normalizeArray(thing) | |||||
return { | |||||
type: UPDATE_MODE, | |||||
payload: {thing, mode} | |||||
} | |||||
} | |||||
// export function onlyShow(thing, shown=true) { | |||||
// thing = normalizeArray(thing) | |||||
// if(thing.length < 2) | |||||
// throw new Error("layoutActions.onlyShow only works, when `thing` is an array with length > 1") | |||||
// return { | |||||
// type: ONLY_SHOW, | |||||
// payload: {thing, shown} | |||||
// } | |||||
// } | |||||
@@ -0,0 +1,15 @@ | |||||
import reducers from "./reducers" | |||||
import * as actions from "./actions" | |||||
import * as selectors from "./selectors" | |||||
export default function() { | |||||
return { | |||||
statePlugins: { | |||||
layout: { | |||||
reducers, | |||||
actions, | |||||
selectors | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,24 @@ | |||||
import { | |||||
UPDATE_LAYOUT, | |||||
UPDATE_MODE, | |||||
SHOW | |||||
} from "./actions" | |||||
export default { | |||||
[UPDATE_LAYOUT]: (state, action) => state.set("layout", action.payload), | |||||
[SHOW]: (state, action) => { | |||||
let thing = action.payload.thing | |||||
let shown = action.payload.shown | |||||
return state.setIn(["shown"].concat(thing), shown) | |||||
}, | |||||
[UPDATE_MODE]: (state, action) => { | |||||
let thing = action.payload.thing | |||||
let mode = action.payload.mode | |||||
return state.setIn(["modes"].concat(thing), (mode || "") + "") | |||||
} | |||||
} | |||||
@@ -0,0 +1,22 @@ | |||||
import { createSelector } from "reselect" | |||||
import { normalizeArray } from "core/utils" | |||||
const state = state => state | |||||
export const current = state => state.get("layout") | |||||
export const isShown = (state, thing, def) => { | |||||
thing = normalizeArray(thing) | |||||
return Boolean(state.getIn(["shown", ...thing], def)) | |||||
} | |||||
export const whatMode = (state, thing, def="") => { | |||||
thing = normalizeArray(thing) | |||||
return state.getIn(["modes", ...thing], def) | |||||
} | |||||
export const showSummary = createSelector( | |||||
state, | |||||
state => !isShown(state, "editor") | |||||
) | |||||
@@ -0,0 +1,28 @@ | |||||
export default function ({configs}) { | |||||
const levels = { | |||||
"debug": 0, | |||||
"info": 1, | |||||
"log": 2, | |||||
"warn": 3, | |||||
"error": 4 | |||||
} | |||||
const getLevel = (level) => levels[level] || -1 | |||||
let { logLevel } = configs | |||||
let logLevelInt = getLevel(logLevel) | |||||
function log(level, ...args) { | |||||
if(getLevel(level) >= logLevelInt) | |||||
// eslint-disable-next-line no-console | |||||
console[level](...args) | |||||
} | |||||
log.warn = log.bind(null, "warn") | |||||
log.error = log.bind(null, "error") | |||||
log.info = log.bind(null, "info") | |||||
log.debug = log.bind(null, "debug") | |||||
return { rootInjects: { log } } | |||||
} |
@@ -0,0 +1,223 @@ | |||||
import { objectify, isFunc, normalizeArray } from "core/utils" | |||||
import XML from "xml" | |||||
import memoizee from "memoizee" | |||||
const primitives = { | |||||
"string": () => "string", | |||||
"string_email": () => "user@example.com", | |||||
"string_date-time": () => new Date().toISOString(), | |||||
"number": () => 0, | |||||
"number_float": () => 0.0, | |||||
"integer": () => 0, | |||||
"boolean": () => true | |||||
} | |||||
const primitive = (schema) => { | |||||
schema = objectify(schema) | |||||
let { type, format } = schema | |||||
let fn = primitives[`${type}_${format}`] || primitives[type] | |||||
if(isFunc(fn)) | |||||
return fn(schema) | |||||
return "Unknown Type: " + schema.type | |||||
} | |||||
export const sampleFromSchema = (schema, config={}) => { | |||||
let { type, example, properties, additionalProperties, items } = objectify(schema) | |||||
let { includeReadOnly } = config | |||||
if(example !== undefined) | |||||
return example | |||||
if(!type) { | |||||
if(properties) { | |||||
type = "object" | |||||
} else if(items) { | |||||
type = "array" | |||||
} else { | |||||
return | |||||
} | |||||
} | |||||
if(type === "object") { | |||||
let props = objectify(properties) | |||||
let obj = {} | |||||
for (var name in props) { | |||||
if ( !props[name].readOnly || includeReadOnly ) { | |||||
obj[name] = sampleFromSchema(props[name]) | |||||
} | |||||
} | |||||
if ( additionalProperties === true ) { | |||||
obj.additionalProp1 = {} | |||||
} else if ( additionalProperties ) { | |||||
let additionalProps = objectify(additionalProperties) | |||||
let additionalPropVal = sampleFromSchema(additionalProps) | |||||
for (let i = 1; i < 4; i++) { | |||||
obj["additionalProp" + i] = additionalPropVal | |||||
} | |||||
} | |||||
return obj | |||||
} | |||||
if(type === "array") { | |||||
return [ sampleFromSchema(items) ] | |||||
} | |||||
if(schema["enum"]) { | |||||
if(schema["default"]) | |||||
return schema["default"] | |||||
return normalizeArray(schema["enum"])[0] | |||||
} | |||||
return primitive(schema) | |||||
} | |||||
export const inferSchema = (thing) => { | |||||
if(thing.schema) | |||||
thing = thing.schema | |||||
if(thing.properties) { | |||||
thing.type = "object" | |||||
} | |||||
return thing // Hopefully this will have something schema like in it... `type` for example | |||||
} | |||||
export const sampleXmlFromSchema = (schema, config={}) => { | |||||
let objectifySchema = objectify(schema) | |||||
let { type, properties, additionalProperties, items, example } = objectifySchema | |||||
let { includeReadOnly } = config | |||||
let defaultValue = objectifySchema.default | |||||
let res = {} | |||||
let _attr = {} | |||||
let { xml } = schema | |||||
let { name, prefix, namespace } = xml | |||||
let enumValue = objectifySchema.enum | |||||
let displayName, value | |||||
if(!type) { | |||||
if(properties || additionalProperties) { | |||||
type = "object" | |||||
} else if(items) { | |||||
type = "array" | |||||
} else { | |||||
return | |||||
} | |||||
} | |||||
name = name || "notagname" | |||||
// add prefix to name if exists | |||||
displayName = (prefix ? prefix + ":" : "") + name | |||||
if ( namespace ) { | |||||
//add prefix to namespace if exists | |||||
let namespacePrefix = prefix ? ( "xmlns:" + prefix ) : "xmlns" | |||||
_attr[namespacePrefix] = namespace | |||||
} | |||||
if (type === "array") { | |||||
if (items) { | |||||
items.xml = items.xml || xml || {} | |||||
items.xml.name = items.xml.name || xml.name | |||||
if (xml.wrapped) { | |||||
res[displayName] = [] | |||||
if (Array.isArray(defaultValue)) { | |||||
defaultValue.forEach((v)=>{ | |||||
items.default = v | |||||
res[displayName].push(sampleXmlFromSchema(items, config)) | |||||
}) | |||||
} else { | |||||
res[displayName] = [sampleXmlFromSchema(items, config)] | |||||
} | |||||
if (_attr) { | |||||
res[displayName].push({_attr: _attr}) | |||||
} | |||||
return res | |||||
} | |||||
let _res = [] | |||||
if (Array.isArray(defaultValue)) { | |||||
defaultValue.forEach((v)=>{ | |||||
items.default = v | |||||
_res.push(sampleXmlFromSchema(items, config)) | |||||
}) | |||||
return _res | |||||
} | |||||
return sampleXmlFromSchema(items, config) | |||||
} | |||||
} | |||||
if (type === "object") { | |||||
let props = objectify(properties) | |||||
res[displayName] = [] | |||||
example = example || {} | |||||
for (let propName in props) { | |||||
if ( !props[propName].readOnly || includeReadOnly ) { | |||||
props[propName].xml = props[propName].xml || {} | |||||
if (props[propName].xml.attribute) { | |||||
let enumAttrVal = Array.isArray(props[propName].enum) && props[propName].enum[0] | |||||
let attrExample = props[propName].example | |||||
let attrDefault = props[propName].default | |||||
_attr[props[propName].xml.name || propName] = attrExample!== undefined && attrExample | |||||
|| example[propName] !== undefined && example[propName] || attrDefault !== undefined && attrDefault | |||||
|| enumAttrVal || primitive(props[propName]) | |||||
} else { | |||||
props[propName].xml.name = props[propName].xml.name || propName | |||||
props[propName].example = props[propName].example !== undefined ? props[propName].example : example[propName] | |||||
res[displayName].push(sampleXmlFromSchema(props[propName])) | |||||
} | |||||
} | |||||
} | |||||
if (additionalProperties === true) { | |||||
res[displayName].push({additionalProp: "Anything can be here"}) | |||||
} else if (additionalProperties) { | |||||
res[displayName].push({additionalProp: primitive(additionalProperties)}) | |||||
} | |||||
if (_attr) { | |||||
res[displayName].push({_attr: _attr}) | |||||
} | |||||
return res | |||||
} | |||||
if (example !== undefined) { | |||||
value = example | |||||
} else if (defaultValue !== undefined) { | |||||
//display example if exists | |||||
value = defaultValue | |||||
} else if (Array.isArray(enumValue)) { | |||||
//display enum first value | |||||
value = enumValue[0] | |||||
} else { | |||||
//set default value | |||||
value = primitive(schema) | |||||
} | |||||
res[displayName] = _attr ? [{_attr: _attr}, value] : value | |||||
return res | |||||
} | |||||
export function createXMLExample(schema, config) { | |||||
let json = sampleXmlFromSchema(schema, config) | |||||
if (!json) { return } | |||||
return XML(json, { declaration: true, indent: "\t" }) | |||||
} | |||||
export const memoizedCreateXMLExample = memoizee(createXMLExample) | |||||
export const memoizedSampleFromSchema = memoizee(sampleFromSchema) |
@@ -0,0 +1,5 @@ | |||||
import * as fn from "./fn" | |||||
export default function () { | |||||
return { fn } | |||||
} |
@@ -0,0 +1,234 @@ | |||||
import YAML from "js-yaml" | |||||
import serializeError from "serialize-error" | |||||
// Actions conform to FSA (flux-standard-actions) | |||||
// {type: string,payload: Any|Error, meta: obj, error: bool} | |||||
export const UPDATE_SPEC = "spec_update_spec" | |||||
export const UPDATE_URL = "spec_update_url" | |||||
export const UPDATE_JSON = "spec_update_json" | |||||
export const UPDATE_PARAM = "spec_update_param" | |||||
export const VALIDATE_PARAMS = "spec_validate_param" | |||||
export const SET_RESPONSE = "spec_set_response" | |||||
export const SET_REQUEST = "spec_set_request" | |||||
export const LOG_REQUEST = "spec_log_request" | |||||
export const CLEAR_RESPONSE = "spec_clear_response" | |||||
export const CLEAR_REQUEST = "spec_clear_request" | |||||
export const ClEAR_VALIDATE_PARAMS = "spec_clear_validate_param" | |||||
export const UPDATE_OPERATION_VALUE = "spec_update_operation_value" | |||||
export const UPDATE_RESOLVED = "spec_update_resolved" | |||||
export const SET_SCHEME = "set_scheme" | |||||
export function updateSpec(spec) { | |||||
if(spec instanceof Error) { | |||||
return {type: UPDATE_SPEC, error: true, payload: spec} | |||||
} | |||||
if(typeof spec === "string") { | |||||
return { | |||||
type: UPDATE_SPEC, | |||||
payload: spec.replace(/\t/g, " ") || "" | |||||
} | |||||
} | |||||
return { | |||||
type: UPDATE_SPEC, | |||||
payload: "" | |||||
} | |||||
} | |||||
export function updateResolved(spec) { | |||||
return { | |||||
type: UPDATE_RESOLVED, | |||||
payload: spec | |||||
} | |||||
} | |||||
export function updateUrl(url) { | |||||
return {type: UPDATE_URL, payload: url} | |||||
} | |||||
export function updateJsonSpec(json) { | |||||
if(!json || typeof json !== "object") { | |||||
throw new Error("updateJson must only accept a simple JSON object") | |||||
} | |||||
return {type: UPDATE_JSON, payload: json} | |||||
} | |||||
export const parseToJson = (str) => ({specActions, specSelectors, errActions}) => { | |||||
let { specStr } = specSelectors | |||||
let json = null | |||||
try { | |||||
str = str || specStr() | |||||
errActions.clear({ source: "parser" }) | |||||
json = YAML.safeLoad(str) | |||||
} catch(e) { | |||||
// TODO: push error to state | |||||
console.error(e) | |||||
return errActions.newSpecErr({ | |||||
source: "parser", | |||||
level: "error", | |||||
message: e.reason, | |||||
line: e.mark && e.mark.line ? e.mark.line + 1 : undefined | |||||
}) | |||||
} | |||||
return specActions.updateJsonSpec(json) | |||||
} | |||||
export const resolveSpec = (json, url) => ({specActions, specSelectors, errActions, fn: { fetch, resolve, AST }}) => { | |||||
if(typeof(json) === "undefined") { | |||||
json = specSelectors.specJson() | |||||
} | |||||
if(typeof(url) === "undefined") { | |||||
url = specSelectors.url() | |||||
} | |||||
let { getLineNumberForPath } = AST | |||||
let specStr = specSelectors.specStr() | |||||
return resolve({fetch, spec: json, baseDoc: url}) | |||||
.then( ({spec, errors}) => { | |||||
errActions.clear({ | |||||
type: "thrown" | |||||
}) | |||||
if(errors.length > 0) { | |||||
let preparedErrors = errors | |||||
.map(err => { | |||||
console.error(err) | |||||
err.line = err.fullPath ? getLineNumberForPath(specStr, err.fullPath) : null | |||||
err.path = err.fullPath ? err.fullPath.join(".") : null | |||||
err.level = "error" | |||||
err.type = "thrown" | |||||
err.source = "resolver" | |||||
Object.defineProperty(err, "message", { enumerable: true, value: err.message }) | |||||
return err | |||||
}) | |||||
errActions.newThrownErrBatch(preparedErrors) | |||||
} | |||||
return specActions.updateResolved(spec) | |||||
}) | |||||
} | |||||
export const formatIntoYaml = () => ({specActions, specSelectors}) => { | |||||
let { specStr } = specSelectors | |||||
let { updateSpec } = specActions | |||||
try { | |||||
let yaml = YAML.safeDump(YAML.safeLoad(specStr()), {indent: 2}) | |||||
updateSpec(yaml) | |||||
} catch(e) { | |||||
updateSpec(e) | |||||
} | |||||
} | |||||
export function changeParam( path, paramName, value, isXml ){ | |||||
return { | |||||
type: UPDATE_PARAM, | |||||
payload:{ path, value, paramName, isXml } | |||||
} | |||||
} | |||||
export function validateParams( payload ){ | |||||
return { | |||||
type: VALIDATE_PARAMS, | |||||
payload:{ pathMethod: payload } | |||||
} | |||||
} | |||||
export function clearValidateParams( payload ){ | |||||
return { | |||||
type: ClEAR_VALIDATE_PARAMS, | |||||
payload:{ pathMethod: payload } | |||||
} | |||||
} | |||||
export function changeConsumesValue(path, value) { | |||||
return { | |||||
type: UPDATE_OPERATION_VALUE, | |||||
payload:{ path, value, key: "consumes_value" } | |||||
} | |||||
} | |||||
export function changeProducesValue(path, value) { | |||||
return { | |||||
type: UPDATE_OPERATION_VALUE, | |||||
payload:{ path, value, key: "produces_value" } | |||||
} | |||||
} | |||||
export const setResponse = ( path, method, res ) => { | |||||
return { | |||||
payload: { path, method, res }, | |||||
type: SET_RESPONSE | |||||
} | |||||
} | |||||
export const setRequest = ( path, method, req ) => { | |||||
return { | |||||
payload: { path, method, req }, | |||||
type: SET_REQUEST | |||||
} | |||||
} | |||||
// This is for debugging, remove this comment if you depend on this action | |||||
export const logRequest = (req) => { | |||||
return { | |||||
payload: req, | |||||
type: LOG_REQUEST | |||||
} | |||||
} | |||||
// Actually fire the request via fn.execute | |||||
// (For debugging) and ease of testing | |||||
export const executeRequest = (req) => ({fn, specActions, errActions}) => { | |||||
let { pathName, method } = req | |||||
let parsedRequest = Object.assign({}, req) | |||||
if ( pathName && method ) { | |||||
parsedRequest.operationId = method.toLowerCase() + "-" + pathName | |||||
} | |||||
parsedRequest = fn.buildRequest(parsedRequest) | |||||
specActions.setRequest(req.pathName, req.method, parsedRequest) | |||||
return fn.execute(req) | |||||
.then( fn.serializeRes ) | |||||
.then( res => specActions.setResponse(req.pathName, req.method, res)) | |||||
.catch( err => specActions.setResponse(req.pathName, req.method, { error: true, err: serializeError(err) } ) ) | |||||
} | |||||
// I'm using extras as a way to inject properties into the final, `execute` method - It's not great. Anyone have a better idea? @ponelat | |||||
export const execute = ( { path, method, ...extras }={} ) => (system) => { | |||||
let { fn:{fetch}, specSelectors, specActions } = system | |||||
let spec = specSelectors.spec().toJS() | |||||
let scheme = specSelectors.operationScheme(path, method) | |||||
let { requestContentType, responseContentType } = specSelectors.contentTypeValues([path, method]).toJS() | |||||
let isXml = /xml/i.test(requestContentType) | |||||
let parameters = specSelectors.parameterValues([path, method], isXml).toJS() | |||||
return specActions.executeRequest({fetch, spec, pathName: path, method, parameters, requestContentType, scheme, responseContentType, ...extras }) | |||||
} | |||||
export function clearResponse (path, method) { | |||||
return { | |||||
type: CLEAR_RESPONSE, | |||||
payload:{ path, method } | |||||
} | |||||
} | |||||
export function clearRequest (path, method) { | |||||
return { | |||||
type: CLEAR_REQUEST, | |||||
payload:{ path, method } | |||||
} | |||||
} | |||||
export function setScheme (scheme, path, method) { | |||||
return { | |||||
type: SET_SCHEME, | |||||
payload: { scheme, path, method } | |||||
} | |||||
} |
@@ -0,0 +1,17 @@ | |||||
import reducers from "./reducers" | |||||
import * as actions from "./actions" | |||||
import * as selectors from "./selectors" | |||||
import * as wrapActions from "./wrap-actions" | |||||
export default function() { | |||||
return { | |||||
statePlugins: { | |||||
spec: { | |||||
wrapActions, | |||||
reducers, | |||||
actions, | |||||
selectors | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,123 @@ | |||||
import { fromJS } from "immutable" | |||||
import { fromJSOrdered, validateParam } from "core/utils" | |||||
import win from "../../window" | |||||
import { | |||||
UPDATE_SPEC, | |||||
UPDATE_URL, | |||||
UPDATE_JSON, | |||||
UPDATE_PARAM, | |||||
VALIDATE_PARAMS, | |||||
SET_RESPONSE, | |||||
SET_REQUEST, | |||||
UPDATE_RESOLVED, | |||||
UPDATE_OPERATION_VALUE, | |||||
CLEAR_RESPONSE, | |||||
CLEAR_REQUEST, | |||||
ClEAR_VALIDATE_PARAMS, | |||||
SET_SCHEME | |||||
} from "./actions" | |||||
export default { | |||||
[UPDATE_SPEC]: (state, action) => { | |||||
return (typeof action.payload === "string") | |||||
? state.set("spec", action.payload) | |||||
: state | |||||
}, | |||||
[UPDATE_URL]: (state, action) => { | |||||
return state.set("url", action.payload+"") | |||||
}, | |||||
[UPDATE_JSON]: (state, action) => { | |||||
return state.set("json", fromJSOrdered(action.payload)) | |||||
}, | |||||
[UPDATE_RESOLVED]: (state, action) => { | |||||
return state.setIn(["resolved"], fromJSOrdered(action.payload)) | |||||
}, | |||||
[UPDATE_PARAM]: ( state, {payload} ) => { | |||||
let { path, paramName, value, isXml } = payload | |||||
return state.updateIn( [ "resolved", "paths", ...path, "parameters" ], fromJS([]), parameters => { | |||||
let index = parameters.findIndex( p => p.get( "name" ) === paramName ) | |||||
if (!(value instanceof win.File)) { | |||||
value = fromJSOrdered( value ) | |||||
} | |||||
return parameters.setIn( [ index, isXml ? "value_xml" : "value" ], value) | |||||
}) | |||||
}, | |||||
[VALIDATE_PARAMS]: ( state, { payload: { pathMethod } } ) => { | |||||
let operation = state.getIn( [ "resolved", "paths", ...pathMethod ] ) | |||||
let parameters = operation.get("parameters") | |||||
let isXml = /xml/i.test(operation.get("consumes_value")) | |||||
return state.updateIn( [ "resolved", "paths", ...pathMethod, "parameters" ], fromJS([]), parameters => { | |||||
return parameters.withMutations( parameters => { | |||||
for ( let i = 0, len = parameters.count(); i < len; i++ ) { | |||||
let errors = validateParam(parameters.get(i), isXml) | |||||
parameters.setIn([i, "errors"], fromJS(errors)) | |||||
} | |||||
}) | |||||
}) | |||||
}, | |||||
[ClEAR_VALIDATE_PARAMS]: ( state, { payload: { pathMethod } } ) => { | |||||
let operation = state.getIn( [ "resolved", "paths", ...pathMethod ] ) | |||||
let parameters = operation.get("parameters") | |||||
return state.updateIn( [ "resolved", "paths", ...pathMethod, "parameters" ], fromJS([]), parameters => { | |||||
return parameters.withMutations( parameters => { | |||||
for ( let i = 0, len = parameters.count(); i < len; i++ ) { | |||||
parameters.setIn([i, "errors"], fromJS({})) | |||||
} | |||||
}) | |||||
}) | |||||
}, | |||||
[SET_RESPONSE]: (state, { payload: { res, path, method } } ) =>{ | |||||
let result | |||||
if ( res.error ) { | |||||
result = Object.assign({error: true}, res.err) | |||||
} else { | |||||
result = res | |||||
} | |||||
let newState = state.setIn( [ "responses", path, method ], fromJSOrdered(result) ) | |||||
// ImmutableJS messes up Blob. Needs to reset its value. | |||||
if (res.data instanceof win.Blob) { | |||||
newState = newState.setIn( [ "responses", path, method, "text" ], res.data) | |||||
} | |||||
return newState | |||||
}, | |||||
[SET_REQUEST]: (state, { payload: { req, path, method } } ) =>{ | |||||
return state.setIn( [ "requests", path, method ], fromJSOrdered(req)) | |||||
}, | |||||
[UPDATE_OPERATION_VALUE]: (state, { payload: { path, value, key } }) => { | |||||
return state.setIn(["resolved", "paths", ...path, key], fromJS(value)) | |||||
}, | |||||
[CLEAR_RESPONSE]: (state, { payload: { path, method } } ) =>{ | |||||
return state.deleteIn( [ "responses", path, method ]) | |||||
}, | |||||
[CLEAR_REQUEST]: (state, { payload: { path, method } } ) =>{ | |||||
return state.deleteIn( [ "requests", path, method ]) | |||||
}, | |||||
[SET_SCHEME]: (state, { payload: { scheme, path, method } } ) =>{ | |||||
if ( path && method ) { | |||||
return state.setIn( [ "scheme", path, method ], scheme) | |||||
} | |||||
if (!path && !method) { | |||||
return state.setIn( [ "scheme", "_defaultScheme" ], scheme) | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,318 @@ | |||||
import { createSelector } from "reselect" | |||||
import { fromJS, Set, Map, List } from "immutable" | |||||
const DEFAULT_TAG = "default" | |||||
const OPERATION_METHODS = ["get", "put", "post", "delete", "options", "head", "patch"] | |||||
const state = state => { | |||||
return state || Map() | |||||
} | |||||
export const lastError = createSelector( | |||||
state, | |||||
spec => spec.get("lastError") | |||||
) | |||||
export const url = createSelector( | |||||
state, | |||||
spec => spec.get("url") | |||||
) | |||||
export const specStr = createSelector( | |||||
state, | |||||
spec => spec.get("spec") || "" | |||||
) | |||||
export const specSource = createSelector( | |||||
state, | |||||
spec => spec.get("specSource") || "not-editor" | |||||
) | |||||
export const specJson = createSelector( | |||||
state, | |||||
spec => spec.get("json", Map()) | |||||
) | |||||
export const specResolved = createSelector( | |||||
state, | |||||
spec => spec.get("resolved", Map()) | |||||
) | |||||
// Default Spec ( as an object ) | |||||
export const spec = state => { | |||||
let res = specResolved(state) | |||||
if(res.count() < 1) | |||||
res = specJson(state) | |||||
return res | |||||
} | |||||
export const info = createSelector( | |||||
spec, | |||||
spec => returnSelfOrNewMap(spec && spec.get("info")) | |||||
) | |||||
export const externalDocs = createSelector( | |||||
spec, | |||||
spec => returnSelfOrNewMap(spec && spec.get("externalDocs")) | |||||
) | |||||
export const version = createSelector( | |||||
info, | |||||
info => info && info.get("version") | |||||
) | |||||
export const semver = createSelector( | |||||
version, | |||||
version => /v?([0-9]*)\.([0-9]*)\.([0-9]*)/i.exec(version).slice(1) | |||||
) | |||||
export const paths = createSelector( | |||||
spec, | |||||
spec => spec.get("paths") | |||||
) | |||||
export const operations = createSelector( | |||||
paths, | |||||
paths => { | |||||
if(!paths || paths.size < 1) | |||||
return List() | |||||
let list = List() | |||||
if(!paths || !paths.forEach) { | |||||
return List() | |||||
} | |||||
paths.forEach((path, pathName) => { | |||||
if(!path || !path.forEach) { | |||||
return {} | |||||
} | |||||
path.forEach((operation, method) => { | |||||
if(OPERATION_METHODS.indexOf(method) === -1) { | |||||
return | |||||
} | |||||
list = list.push(fromJS({ | |||||
path: pathName, | |||||
method, | |||||
operation, | |||||
id: `${method}-${pathName}` | |||||
})) | |||||
}) | |||||
}) | |||||
return list | |||||
} | |||||
) | |||||
export const consumes = createSelector( | |||||
spec, | |||||
spec => Set(spec.get("consumes")) | |||||
) | |||||
export const produces = createSelector( | |||||
spec, | |||||
spec => Set(spec.get("produces")) | |||||
) | |||||
export const security = createSelector( | |||||
spec, | |||||
spec => spec.get("security", List()) | |||||
) | |||||
export const securityDefinitions = createSelector( | |||||
spec, | |||||
spec => spec.get("securityDefinitions") | |||||
) | |||||
export const findDefinition = ( state, name ) => { | |||||
return specResolved(state).getIn(["definitions", name], null) | |||||
} | |||||
export const definitions = createSelector( | |||||
spec, | |||||
spec => spec.get("definitions") || Map() | |||||
) | |||||
export const basePath = createSelector( | |||||
spec, | |||||
spec => spec.get("basePath") | |||||
) | |||||
export const host = createSelector( | |||||
spec, | |||||
spec => spec.get("host") | |||||
) | |||||
export const schemes = createSelector( | |||||
spec, | |||||
spec => spec.get("schemes", Map()) | |||||
) | |||||
export const operationsWithRootInherited = createSelector( | |||||
operations, | |||||
consumes, | |||||
produces, | |||||
(operations, consumes, produces) => { | |||||
return operations.map( ops => ops.update("operation", op => { | |||||
if(op) { | |||||
if(!Map.isMap(op)) { return } | |||||
return op.withMutations( op => { | |||||
if ( !op.get("consumes") ) { | |||||
op.update("consumes", a => Set(a).merge(consumes)) | |||||
} | |||||
if ( !op.get("produces") ) { | |||||
op.update("produces", a => Set(a).merge(produces)) | |||||
} | |||||
return op | |||||
}) | |||||
} else { | |||||
// return something with Immutable methods | |||||
return Map() | |||||
} | |||||
})) | |||||
} | |||||
) | |||||
export const tags = createSelector( | |||||
spec, | |||||
json => json.get("tags", List()) | |||||
) | |||||
export const tagDetails = (state, tag) => { | |||||
let currentTags = tags(state) || List() | |||||
return currentTags.filter(Map.isMap).find(t => t.get("name") === tag, Map()) | |||||
} | |||||
export const operationsWithTags = createSelector( | |||||
operationsWithRootInherited, | |||||
operations => { | |||||
return operations.reduce( (taggedMap, op) => { | |||||
let tags = Set(op.getIn(["operation","tags"])) | |||||
if(tags.count() < 1) | |||||
return taggedMap.update(DEFAULT_TAG, List(), ar => ar.push(op)) | |||||
return tags.reduce( (res, tag) => res.update(tag, List(), (ar) => ar.push(op)), taggedMap ) | |||||
}, Map()) | |||||
} | |||||
) | |||||
export const taggedOperations = createSelector( | |||||
state, | |||||
operationsWithTags, | |||||
(state, tagMap) => { | |||||
return tagMap.map((ops, tag) => Map({tagDetails: tagDetails(state, tag), operations: ops})) | |||||
} | |||||
) | |||||
export const responses = createSelector( | |||||
state, | |||||
state => state.get( "responses", Map() ) | |||||
) | |||||
export const requests = createSelector( | |||||
state, | |||||
state => state.get( "requests", Map() ) | |||||
) | |||||
export const responseFor = (state, path, method) => { | |||||
return responses(state).getIn([path, method], null) | |||||
} | |||||
export const requestFor = (state, path, method) => { | |||||
return requests(state).getIn([path, method], null) | |||||
} | |||||
export const allowTryItOutFor = (state, path, method ) => { | |||||
// This is just a hook for now. | |||||
return true | |||||
} | |||||
// Get the parameter value by parameter name | |||||
export function getParameter(state, pathMethod, name) { | |||||
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([])) | |||||
return params.filter( (p) => { | |||||
return Map.isMap(p) && p.get("name") === name | |||||
}).first() | |||||
} | |||||
export const hasHost = createSelector( | |||||
spec, | |||||
spec => { | |||||
const host = spec.get("host") | |||||
return typeof host === "string" && host.length > 0 && host[0] !== "/" | |||||
} | |||||
) | |||||
// Get the parameter values, that the user filled out | |||||
export function parameterValues(state, pathMethod, isXml) { | |||||
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([])) | |||||
return params.reduce( (hash, p) => { | |||||
let value = isXml && p.get("in") === "body" ? p.get("value_xml") : p.get("value") | |||||
return hash.set(p.get("name"), value) | |||||
}, fromJS({})) | |||||
} | |||||
// True if any parameter includes `in: ?` | |||||
export function parametersIncludeIn(parameters, inValue="") { | |||||
if(List.isList(parameters)) { | |||||
return parameters.some( p => Map.isMap(p) && p.get("in") === inValue ) | |||||
} | |||||
} | |||||
// True if any parameter includes `type: ?` | |||||
export function parametersIncludeType(parameters, typeValue="") { | |||||
if(List.isList(parameters)) { | |||||
return parameters.some( p => Map.isMap(p) && p.get("type") === typeValue ) | |||||
} | |||||
} | |||||
// Get the consumes/produces value that the user selected | |||||
export function contentTypeValues(state, pathMethod) { | |||||
let op = spec(state).getIn(["paths", ...pathMethod], fromJS({})) | |||||
const parameters = op.get("parameters") || new List() | |||||
const requestContentType = ( | |||||
parametersIncludeType(parameters, "file") ? "multipart/form-data" | |||||
: parametersIncludeIn(parameters, "formData") ? "application/x-www-form-urlencoded" | |||||
: op.get("consumes_value") | |||||
) | |||||
return fromJS({ | |||||
requestContentType, | |||||
responseContentType: op.get("produces_value") | |||||
}) | |||||
} | |||||
// Get the consumes/produces by path | |||||
export function operationConsumes(state, pathMethod) { | |||||
return spec(state).getIn(["paths", ...pathMethod, "consumes"], fromJS({})) | |||||
} | |||||
export const operationScheme = ( state, path, method ) => { | |||||
return state.getIn(["scheme", path, method]) || state.getIn(["scheme", "_defaultScheme"]) || "http" | |||||
} | |||||
export const canExecuteScheme = ( state, path, method ) => { | |||||
return ["http", "https"].indexOf(operationScheme(state, path, method)) > -1 | |||||
} | |||||
export const validateBeforeExecute = ( state, pathMethod ) => { | |||||
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([])) | |||||
let isValid = true | |||||
params.forEach( (p) => { | |||||
let errors = p.get("errors") | |||||
if ( errors && errors.count() ) { | |||||
isValid = false | |||||
} | |||||
}) | |||||
return isValid | |||||
} | |||||
function returnSelfOrNewMap(obj) { | |||||
// returns obj if obj is an Immutable map, else returns a new Map | |||||
return Map.isMap(obj) ? obj : new Map() | |||||
} |
@@ -0,0 +1,15 @@ | |||||
export const updateSpec = (ori, {specActions}) => (...args) => { | |||||
ori(...args) | |||||
specActions.parseToJson(...args) | |||||
} | |||||
export const updateJsonSpec = (ori, {specActions}) => (...args) => { | |||||
ori(...args) | |||||
specActions.resolveSpec(...args) | |||||
} | |||||
// Log the request ( just for debugging, shouldn't affect prod ) | |||||
export const executeRequest = (ori, { specActions }) => (req) => { | |||||
specActions.logRequest(req) | |||||
return ori(req) | |||||
} |
@@ -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 | |||||
}) |
@@ -0,0 +1,81 @@ | |||||
import React, { PropTypes } from "react" | |||||
import SplitPane from "react-split-pane" | |||||
import "./split-pane-mode.less" | |||||
const MODE_KEY = ["split-pane-mode"] | |||||
const MODE_LEFT = "left" | |||||
const MODE_RIGHT = "right" | |||||
const MODE_BOTH = "both" // or anything other than left/right | |||||
export default class SplitPaneMode extends React.Component { | |||||
static propTypes = { | |||||
threshold: PropTypes.number, | |||||
children: PropTypes.array, | |||||
layoutSelectors: PropTypes.object.isRequired, | |||||
layoutActions: PropTypes.object.isRequired, | |||||
}; | |||||
static defaultProps = { | |||||
threshold: 100, // in pixels | |||||
children: [], | |||||
}; | |||||
onDragFinished = () => { | |||||
let { threshold, layoutActions } = this.props | |||||
let { position, draggedSize } = this.refs.splitPane.state | |||||
this.draggedSize = draggedSize | |||||
let nearLeftEdge = position <= threshold | |||||
let nearRightEdge = draggedSize <= threshold | |||||
layoutActions | |||||
.changeMode(MODE_KEY, ( | |||||
nearLeftEdge | |||||
? MODE_RIGHT : nearRightEdge | |||||
? MODE_LEFT : MODE_BOTH | |||||
)) | |||||
} | |||||
sizeFromMode = (mode, defaultSize) => { | |||||
if(mode === MODE_LEFT) { | |||||
this.draggedSize = null | |||||
return "0px" | |||||
} else if (mode === MODE_RIGHT) { | |||||
this.draggedSize = null | |||||
return "100%" | |||||
} | |||||
// mode === "both" | |||||
return this.draggedSize || defaultSize | |||||
} | |||||
render() { | |||||
let { children, layoutSelectors } = this.props | |||||
const mode = layoutSelectors.whatMode(MODE_KEY) | |||||
const left = mode === MODE_RIGHT ? <noscript/> : children[0] | |||||
const right = mode === MODE_LEFT ? <noscript/> : children[1] | |||||
const size = this.sizeFromMode(mode, '50%') | |||||
return ( | |||||
<SplitPane | |||||
disabledClass={''} | |||||
ref={'splitPane'} | |||||
split='vertical' | |||||
defaultSize={'50%'} | |||||
primary="second" | |||||
minSize={0} | |||||
size={size} | |||||
onDragFinished={this.onDragFinished} | |||||
allowResize={mode !== MODE_LEFT && mode !== MODE_RIGHT } | |||||
resizerStyle={{"flex": "0 0 auto", "position": "relative"}} | |||||
> | |||||
{ left } | |||||
{ right } | |||||
</SplitPane> | |||||
) | |||||
} | |||||
} |
@@ -0,0 +1,5 @@ | |||||
.swagger-ui { | |||||
.Resizer.vertical.disabled { | |||||
display: none; | |||||
} | |||||
} |