Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
003ec46ef5 | ||
|
|
9c8955edd4 | ||
|
|
411884f714 | ||
|
|
7c8e3f4444 | ||
|
|
93d27ff392 | ||
|
|
003a2e7584 | ||
|
|
aefcfcd5d0 | ||
|
|
1f9fb0e68d | ||
|
|
14ef0cc379 | ||
|
|
08cfc77fde | ||
|
|
b8cf080e6c | ||
|
|
245428e7cd | ||
|
|
624a81201f | ||
|
|
b0be261ef8 | ||
|
|
6c5a370ef7 | ||
|
|
fb7f12551f | ||
|
|
49e0466641 | ||
|
|
ad57f5a549 | ||
|
|
ebf94dfccf | ||
|
|
6ba93ad43f | ||
|
|
54153dd929 | ||
|
|
83232dc2d8 | ||
|
|
ad43965d16 | ||
|
|
861cc65cff | ||
|
|
4900b11079 | ||
|
|
b13b05e416 | ||
|
|
feef20dd67 | ||
|
|
4fd7f1d099 | ||
|
|
8777d8b9ff | ||
|
|
22036b1fbf | ||
|
|
043e491f94 | ||
|
|
1fb56c6f96 | ||
|
|
e6722d87aa | ||
|
|
6f7a4c3097 | ||
|
|
71d7c1a5ab | ||
|
|
e41067b4c1 | ||
|
|
558d054986 | ||
|
|
d1779dc47b | ||
|
|
ba1603a7e2 | ||
|
|
ecf688171f | ||
|
|
54ed39d69f | ||
|
|
48a65983c1 | ||
|
|
1f565b4847 | ||
|
|
ba59db2449 | ||
|
|
1013d16d9e | ||
|
|
f0c5f1fe33 | ||
|
|
f589cb99af | ||
|
|
3d7a00c0d0 | ||
|
|
1f44231df9 | ||
|
|
f2a8ed07e0 | ||
|
|
7fd229fe72 | ||
|
|
d9c60d7d25 | ||
|
|
9653258bc1 | ||
|
|
1e1b7f6063 | ||
|
|
2f236982f8 | ||
|
|
4599033b5a | ||
|
|
6110a7da12 | ||
|
|
10ee5c46c0 | ||
|
|
2ab6fcc49b | ||
|
|
179699277d | ||
|
|
3f647875fd | ||
|
|
22ccdfff10 | ||
|
|
efc1194e4a | ||
|
|
bb84da4e1d | ||
|
|
e531c40356 | ||
|
|
f29202fed8 | ||
|
|
c28213d457 | ||
|
|
286c0e0f72 | ||
|
|
7af7121e64 | ||
|
|
ec4890299c | ||
|
|
31ccaf3ea3 | ||
|
|
253573200a | ||
|
|
024b7db81e | ||
|
|
5b1dbec9b0 | ||
|
|
64f99356a5 | ||
|
|
b3e80cc500 | ||
|
|
53ea2003c1 | ||
|
|
a037c7205b | ||
|
|
63d5f164c6 | ||
|
|
2e816e0440 | ||
|
|
0818882a90 | ||
|
|
0751022b77 | ||
|
|
fc070408f6 | ||
|
|
a47da98b54 | ||
|
|
12a9fbc0ca | ||
|
|
bd41b736a8 | ||
|
|
a8f204181e | ||
|
|
67d472acc7 | ||
|
|
aebcfe8e5d | ||
|
|
71c0742735 | ||
|
|
8e4f94b33f | ||
|
|
a9ea10da2c | ||
|
|
8f3535b5ff | ||
|
|
0ca0b8120f | ||
|
|
603c436427 | ||
|
|
c601f9b0ca | ||
|
|
c742df9573 | ||
|
|
ecfda531de | ||
|
|
b749ea737d | ||
|
|
248aeca0db | ||
|
|
adf03e49ae | ||
|
|
9bad35ed85 | ||
|
|
ccc3b109a7 | ||
|
|
56caeec8c8 | ||
|
|
9d48c4751a | ||
|
|
1646b270f8 | ||
|
|
03f80dcc70 | ||
|
|
f982eb4894 | ||
|
|
10b4f5d8a7 | ||
|
|
9c2f9d2059 | ||
|
|
7469705bef | ||
|
|
57db23ed1f | ||
|
|
135fc34077 | ||
|
|
ca0b248df2 | ||
|
|
27156bb79d | ||
|
|
b03bf90664 | ||
|
|
a9ebdb8a7a | ||
|
|
68c689e491 | ||
|
|
150ebf3bc0 | ||
|
|
e07b105b29 | ||
|
|
022b660b8c | ||
|
|
8c4b511227 | ||
|
|
a2f8d289a0 | ||
|
|
2dd25abbdd | ||
|
|
c1ae4c133d | ||
|
|
85c83931c4 | ||
|
|
b5c713a72b | ||
|
|
ca7f7ea78b |
@@ -9,6 +9,10 @@
|
||||
[
|
||||
"module-alias",
|
||||
[
|
||||
{
|
||||
"expose": "root",
|
||||
"src": "."
|
||||
},
|
||||
{
|
||||
"expose": "components",
|
||||
"src": "src/core/components"
|
||||
@@ -17,6 +21,10 @@
|
||||
"expose": "core",
|
||||
"src": "src/core"
|
||||
},
|
||||
{
|
||||
"expose": "plugins",
|
||||
"src": "src/plugins"
|
||||
},
|
||||
{
|
||||
"expose": "img",
|
||||
"src": "src/img"
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/.git
|
||||
/.github
|
||||
/dev-helpers
|
||||
/docs
|
||||
/src
|
||||
/swagger-ui-dist-package
|
||||
/test
|
||||
@@ -18,7 +18,7 @@ or anything that violates the specifications.
|
||||
<!--- Provide a general summary of the issue in the title above -->
|
||||
|
||||
<!---
|
||||
If you aren't sure what Swagger-UI version, see this guide: https://github.com/swagger-api/swagger-ui/blob/master/docs/version-detection.md
|
||||
If you aren't sure what Swagger-UI version, see this guide: https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/version-detection.md
|
||||
--->
|
||||
|
||||
|
||||
|
||||
@@ -24,19 +24,32 @@
|
||||
|
||||
|
||||
|
||||
### Types of changes
|
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
- [ ] No code changes (changes to documentation, CI, metadata, etc)
|
||||
- [ ] Dependency changes (any modification to dependencies in `package.json`)
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||
|
||||
### Checklist:
|
||||
## Checklist
|
||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
- [ ] My code follows the code style of this project.
|
||||
- [ ] My change requires a change to the documentation.
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
- [ ] I have added tests to cover my changes.
|
||||
|
||||
### My PR contains...
|
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
- [ ] No code changes (`src/` is unmodified: changes to documentation, CI, metadata, etc.)
|
||||
- [ ] Dependency changes (any modification to dependencies in `package.json`)
|
||||
- [ ] Bug fixes (non-breaking change which fixes an issue)
|
||||
- [ ] Improvements (misc. changes to existing features)
|
||||
- [ ] Features (non-breaking change which adds functionality)
|
||||
|
||||
### My changes...
|
||||
- [ ] are breaking changes to a public API (config options, System API, major UI change, etc).
|
||||
- [ ] are breaking changes to a private API (Redux, component props, utility functions, etc.).
|
||||
- [ ] are breaking changes to a developer API (npm script behavior changes, new dev system dependencies, etc).
|
||||
- [ ] are not breaking changes.
|
||||
|
||||
### Documentation
|
||||
- [ ] My changes do not require a change to the project documentation.
|
||||
- [ ] My changes require a change to the project documentation.
|
||||
- [ ] If yes to above: I have updated the documentation accordingly.
|
||||
|
||||
### Automated tests
|
||||
- [ ] My changes can not or do not need to be tested.
|
||||
- [ ] My changes can and should be tested by unit and/or integration tests.
|
||||
- [ ] If yes to above: I have added tests to cover my changes.
|
||||
- [ ] If yes to above: I have taken care to cover edge cases in my tests.
|
||||
- [ ] All new and existing tests passed.
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
!package.json
|
||||
!dist/swagger-ui.js
|
||||
!dist/swagger-ui.js.map
|
||||
!dist/swagger-ui-standalone-preset.js
|
||||
!dist/swagger-ui-standalone-preset.js.map
|
||||
!dist/swagger-ui.css
|
||||
!dist/swagger-ui.css.map
|
||||
!dist/oauth2-redirect.html
|
||||
|
||||
@@ -34,7 +34,7 @@ deploy:
|
||||
node: '6.9'
|
||||
after_success:
|
||||
- if [ $DOCKER_HUB_USERNAME ]; then
|
||||
docker login --email=$DOCKER_HUB_EMAIL --username=$DOCKER_HUB_USERNAME --password=$DOCKER_HUB_PASSWORD;
|
||||
docker login --username=$DOCKER_HUB_USERNAME --password=$DOCKER_HUB_PASSWORD;
|
||||
|
||||
if [ ! -z "$TRAVIS_TAG" ]; then
|
||||
DOCKER_IMAGE_TAG=$TRAVIS_TAG;
|
||||
|
||||
@@ -52,3 +52,7 @@ _Adapted from [How to Write a Git Commit Message](https://chris.beams.io/posts/g
|
||||
- **Don't** include any changes to files in the `dist/` directory - we update those files only during releases.
|
||||
- **Don't** mention maintainers in your original PR body - we probably would've seen it anyway, so it just increases the noise in our inboxes. Do feel free to ping maintainers if a week has passed and you've heard nothing from us.
|
||||
- **Don't** open PRs for custom functionality that only serves a small subset of our users - custom functionality should be implemented outside of our codebase, via a plugin.
|
||||
|
||||
#### Merging pull requests
|
||||
- **Do** use GitHub's `Squash and merge` strategy.
|
||||
- **Do** follow the [Conventional Commits](https://conventionalcommits.org) standard format for your squash commit.
|
||||
|
||||
@@ -5,6 +5,7 @@ MAINTAINER fehguy
|
||||
ENV VERSION "v2.2.10"
|
||||
ENV FOLDER "swagger-ui-2.2.10"
|
||||
ENV API_URL "http://petstore.swagger.io/v2/swagger.json"
|
||||
ENV API_URLS ""
|
||||
ENV API_KEY "**None**"
|
||||
ENV OAUTH_CLIENT_ID "**None**"
|
||||
ENV OAUTH_CLIENT_SECRET "**None**"
|
||||
@@ -12,7 +13,7 @@ ENV OAUTH_REALM "**None**"
|
||||
ENV OAUTH_APP_NAME "**None**"
|
||||
ENV OAUTH_ADDITIONAL_PARAMS "**None**"
|
||||
ENV SWAGGER_JSON "/app/swagger.json"
|
||||
ENV PORT 80
|
||||
ENV PORT 8080
|
||||
ENV BASE_URL ""
|
||||
|
||||
RUN apk add --update nginx
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright 2017 SmartBear Software
|
||||
Copyright 2018 SmartBear Software
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -22,7 +22,7 @@ The OpenAPI Specification has undergone 5 revisions since initial creation in 20
|
||||
|
||||
Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes
|
||||
------------------ | ------------ | -------------------------- | -----
|
||||
3.7.0 | 2017-12-15 | 2.0, 3.0 | [tag v3.7.0](https://github.com/swagger-api/swagger-ui/tree/v3.7.0)
|
||||
3.13.1 | 2018-03-23 | 2.0, 3.0 | [tag v3.13.1](https://github.com/swagger-api/swagger-ui/tree/v3.13.1)
|
||||
3.0.21 | 2017-07-26 | 2.0 | [tag v3.0.21](https://github.com/swagger-api/swagger-ui/tree/v3.0.21)
|
||||
2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10)
|
||||
2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5)
|
||||
|
||||
@@ -45,11 +45,18 @@
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "["+qp.error+"]: " +
|
||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||
}
|
||||
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: "Authorization failed: no accessCode received from the server"
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -45,11 +45,18 @@
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "["+qp.error+"]: " +
|
||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||
}
|
||||
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: "Authorization failed: no accessCode received from the server"
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
set -e
|
||||
|
||||
BASE_URL=${BASE_URL:-/}
|
||||
NGINX_ROOT=/usr/share/nginx/html
|
||||
INDEX_FILE=$NGINX_ROOT/index.html
|
||||
|
||||
@@ -22,13 +23,7 @@ replace_or_delete_in_index () {
|
||||
}
|
||||
|
||||
if [ "${BASE_URL}" ]; then
|
||||
NGINX_WITH_BASE_URL="${NGINX_ROOT}${BASE_URL}"
|
||||
|
||||
mkdir -p ${NGINX_WITH_BASE_URL}
|
||||
mv ${NGINX_ROOT}/*.* ${NGINX_WITH_BASE_URL}/
|
||||
|
||||
INDEX_FILE=$NGINX_WITH_BASE_URL/index.html
|
||||
NGINX_ROOT=$NGINX_WITH_BASE_URL
|
||||
sed -i "s|location .* {|location $BASE_URL {|g" /etc/nginx/nginx.conf
|
||||
fi
|
||||
|
||||
replace_in_index myApiKeyXXXX123456789 $API_KEY
|
||||
@@ -41,7 +36,7 @@ if [ "$OAUTH_ADDITIONAL_PARAMS" != "**None**" ]; then
|
||||
fi
|
||||
|
||||
if [[ -f $SWAGGER_JSON ]]; then
|
||||
cp $SWAGGER_JSON $NGINX_ROOT
|
||||
cp -s $SWAGGER_JSON $NGINX_ROOT
|
||||
REL_PATH="./$(basename $SWAGGER_JSON)"
|
||||
sed -i "s|http://petstore.swagger.io/v2/swagger.json|$REL_PATH|g" $INDEX_FILE
|
||||
sed -i "s|http://example.com/api|$REL_PATH|g" $INDEX_FILE
|
||||
@@ -58,4 +53,14 @@ if [[ -n "$VALIDATOR_URL" ]]; then
|
||||
unset TMP_VU
|
||||
fi
|
||||
|
||||
# replace `url` with `urls` option if API_URLS is set
|
||||
if [[ -n "$API_URLS" ]]; then
|
||||
sed -i "s|url: .*,|urls: $API_URLS,|g" $INDEX_FILE
|
||||
fi
|
||||
|
||||
# replace the PORT that nginx listens on if PORT is supplied
|
||||
if [[ -n "${PORT}" ]]; then
|
||||
sed -i "s|8080|${PORT}|g" /etc/nginx/nginx.conf
|
||||
fi
|
||||
|
||||
exec nginx -g 'daemon off;'
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
#### Usage
|
||||
- [Installation](docs/usage/installation.md)
|
||||
- [Configuration](docs/usage/configuration.md)
|
||||
- [CORS](docs/usage/cors.md)
|
||||
- [OAuth2](docs/usage/oauth2.md)
|
||||
- [Deep Linking](docs/usage/deep-linking.md)
|
||||
- [Limitations](docs/usage/limitations.md)
|
||||
- [Version detection](docs/usage/version-detection.md)
|
||||
- [Installation](usage/installation.md)
|
||||
- [Configuration](usage/configuration.md)
|
||||
- [CORS](usage/cors.md)
|
||||
- [OAuth2](usage/oauth2.md)
|
||||
- [Deep Linking](usage/deep-linking.md)
|
||||
- [Limitations](usage/limitations.md)
|
||||
- [Version detection](usage/version-detection.md)
|
||||
|
||||
#### Customization
|
||||
- [Overview](docs/customization/overview.md)
|
||||
- [Plugin API](docs/customization/plugin-api.md)
|
||||
- [Custom layout](docs/customization/custom-layout.md)
|
||||
- [Overview](customization/overview.md)
|
||||
- [Plugin API](customization/plugin-api.md)
|
||||
- [Custom layout](customization/custom-layout.md)
|
||||
|
||||
#### Development
|
||||
- [Setting up](docs/development/setting-up.md)
|
||||
- [Scripts](docs/development/scripts.md)
|
||||
- [Setting up](development/setting-up.md)
|
||||
- [Scripts](development/scripts.md)
|
||||
|
||||
@@ -20,16 +20,16 @@ class OperationsLayout extends React.Component {
|
||||
|
||||
const Operations = getComponent("Operations", true)
|
||||
|
||||
return {
|
||||
return (
|
||||
<div>
|
||||
<Operations />
|
||||
</div>
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the plugin that provides our layout component
|
||||
const OperationsLayoutPlugin = function() {
|
||||
const OperationsLayoutPlugin = () => {
|
||||
return {
|
||||
components: {
|
||||
OperationsLayout: OperationsLayout
|
||||
@@ -62,19 +62,19 @@ class AugmentingLayout extends React.Component {
|
||||
|
||||
const BaseLayout = getComponent("BaseLayout", true)
|
||||
|
||||
return {
|
||||
return (
|
||||
<div>
|
||||
<div className="myCustomHeader">
|
||||
<h1>I have a custom header above Swagger-UI!</h1>
|
||||
</div>
|
||||
<BaseLayout />
|
||||
</div>
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the plugin that provides our layout component
|
||||
const AugmentingLayoutPlugin = function() {
|
||||
const AugmentingLayoutPlugin = () => {
|
||||
return {
|
||||
components: {
|
||||
AugmentingLayout: AugmentingLayout
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Plug points
|
||||
|
||||
Swagger-UI exposes most of its internal logic through the plugin system.
|
||||
|
||||
Often, it is beneficial to override the core internals to achieve custom behavior.
|
||||
|
||||
### Note: Semantic Versioning
|
||||
|
||||
Swagger-UI's internal APIs are _not_ part of our public contract, which means that they can change without the major version changing.
|
||||
|
||||
If your custom plugins wrap, extend, override, or consume any internal core APIs, we recommend specifying a specific minor version of Swagger-UI to use in your application, because they will _not_ change between patch versions.
|
||||
|
||||
If you're installing Swagger-UI via NPM, for example, you can do this by using a tilde:
|
||||
|
||||
```js
|
||||
{
|
||||
"dependencies": {
|
||||
"swagger-ui": "~3.11.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `fn.opsFilter`
|
||||
|
||||
When using the `filter` option, tag names will be filtered by the user-provided value. If you'd like to customize this behavior, you can override the default `opsFilter` function.
|
||||
|
||||
For example, you can implement a multiple-phrase filter:
|
||||
|
||||
```js
|
||||
const MultiplePhraseFilterPlugin = function() {
|
||||
return {
|
||||
fn: {
|
||||
opsFilter: (taggedOps, phrase) => {
|
||||
const phrases = phrase.split(", ")
|
||||
|
||||
return taggedOps.filter((val, key) => {
|
||||
return phrases.some(item => key.indexOf(item) > -1)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -2,14 +2,30 @@
|
||||
|
||||
A plugin is a function that returns an object - more specifically, the object may contain functions and components that augment and modify Swagger-UI's functionality.
|
||||
|
||||
### Note: Semantic Versioning
|
||||
|
||||
Swagger-UI's internal APIs are _not_ part of our public contract, which means that they can change without the major version changing.
|
||||
|
||||
If your custom plugins wrap, extend, override, or consume any internal core APIs, we recommend specifying a specific minor version of Swagger-UI to use in your application, because they will _not_ change between patch versions.
|
||||
|
||||
If you're installing Swagger-UI via NPM, for example, you can do this by using a tilde:
|
||||
|
||||
```js
|
||||
{
|
||||
"dependencies": {
|
||||
"swagger-ui": "~3.11.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Format
|
||||
|
||||
A plugin return value may contain any of these keys, where `myStateKey` is a name for a piece of state:
|
||||
A plugin return value may contain any of these keys, where `stateKey` is a name for a piece of state:
|
||||
|
||||
```javascript
|
||||
{
|
||||
statePlugins: {
|
||||
myStateKey: {
|
||||
[stateKey]: {
|
||||
actions,
|
||||
reducers,
|
||||
selectors,
|
||||
@@ -19,7 +35,9 @@ A plugin return value may contain any of these keys, where `myStateKey` is a nam
|
||||
},
|
||||
components: {},
|
||||
wrapComponents: {},
|
||||
fn: {}
|
||||
rootInjects: {},
|
||||
afterLoad: (system) => {},
|
||||
fn: {},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -332,7 +350,7 @@ const MyWrapComponentPlugin = function(system) {
|
||||
if(props.number > 10) {
|
||||
return <div>
|
||||
<h3>Warning! Big number ahead.</h3>
|
||||
<OriginalComponent {...props} />
|
||||
<Original {...props} />
|
||||
</div>
|
||||
} else {
|
||||
return <Original {...props} />
|
||||
@@ -351,7 +369,7 @@ const MyWrapComponentPlugin = function(system) {
|
||||
if(props.number > 10) {
|
||||
return <div>
|
||||
<h3>Warning! Big number ahead.</h3>
|
||||
<OriginalComponent {...props} />
|
||||
<Original {...props} />
|
||||
</div>
|
||||
} else {
|
||||
return <Original {...props} />
|
||||
@@ -363,7 +381,55 @@ const MyWrapComponentPlugin = function(system) {
|
||||
}
|
||||
```
|
||||
|
||||
##### `rootInjects`
|
||||
|
||||
The `rootInjects` interface allows you to inject values at the top level of the system.
|
||||
|
||||
This interface takes an object, which will be merged in with the top-level system object at runtime.
|
||||
|
||||
```js
|
||||
const MyRootInjectsPlugin = function(system) {
|
||||
return {
|
||||
rootInjects: {
|
||||
myConstant: 123,
|
||||
myMethod: (...params) => console.log(...params)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### `afterLoad`
|
||||
|
||||
The `afterLoad` plugin method allows you to get a reference to the system after your plugin has been registered.
|
||||
|
||||
This interface is used in the core code to attach methods that are driven by bound selectors or actions. You can also use it to execute logic that requires your plugin to already be ready, for example fetching initial data from a remote endpoint and passing it to an action your plugin creates.
|
||||
|
||||
The plugin context, which is bound to `this`, is undocumented, but below is an example of how to attach a bound action as a top-level method:
|
||||
|
||||
```javascript
|
||||
const MyMethodProvidingPlugin = function() {
|
||||
return {
|
||||
afterLoad(system) {
|
||||
// at this point in time, your actions have been bound into the system
|
||||
// so you can do things with them
|
||||
this.rootInjects = this.rootInjects || {}
|
||||
this.rootInjects.myMethod = system.exampleActions.updateFavoriteColor
|
||||
},
|
||||
statePlugins: {
|
||||
example: {
|
||||
actions: {
|
||||
updateFavoriteColor: (str) => {
|
||||
return {
|
||||
type: "EXAMPLE_SET_FAV_COLOR",
|
||||
payload: str
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### fn
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 390 KiB After Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 297 KiB After Width: | Height: | Size: 126 KiB |
@@ -59,6 +59,7 @@ Parameter Name | Description
|
||||
`operationsSorter` | `Function=(a => a)`. Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically), 'method' (sort by HTTP method) or a function (see Array.prototype.sort() to know how sort function works). Default is the order returned by the server unchanged.
|
||||
`showExtensions` | `Boolean=false`. Controls the display of vendor extension (`x-`) fields and values for Operations, Parameters, and Schema.
|
||||
`tagsSorter` | `Function=(a => a)`. Apply a sort to the tag list of each API. It can be 'alpha' (sort by paths alphanumerically) or a function (see [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) to learn how to write a sort function). Two tag name strings are passed to the sorter for each pass. Default is the order determined by Swagger-UI.
|
||||
`onComplete` | `Function=NOOP`. Provides a mechanism to be notified when Swagger-UI has finished rendering a newly provided definition.
|
||||
|
||||
##### Network
|
||||
|
||||
@@ -68,6 +69,7 @@ Parameter Name | Description
|
||||
`requestInterceptor` | `Function=(a => a)`. MUST be a function. Function to intercept remote definition, Try-It-Out, and OAuth2 requests. Accepts one argument requestInterceptor(request) and must return the potentially modified request.
|
||||
`responseInterceptor` |`Function=(a => a)`. MUST be a function. Function to intercept remote definition, Try-It-Out, and OAuth2 responses. Accepts one argument responseInterceptor(response) and must return the potentially modified response.
|
||||
`showMutatedRequest` | `Boolean=true`. If set to `true`, uses the mutated request returned from a requestInterceptor to produce the curl command in the UI, otherwise the request before the requestInterceptor was applied is used.
|
||||
`supportedSubmitMethods` | `Array=["get", "put", "post", "delete", "options", "head", "patch", "trace"]`. List of HTTP methods that have the Try it out feature enabled. An empty array disables Try it out for all operations. This does not filter the operations from the display.
|
||||
`validatorUrl` | `String="https://online.swagger.io/validator" OR null`. By default, Swagger-UI attempts to validate specs against swagger.io's online validator. You can use this parameter to set a different validator URL, for example for locally deployed validators ([Validator Badge](https://github.com/swagger-api/validator-badge)). Setting it to `null` will disable validation.
|
||||
|
||||
##### Macros
|
||||
@@ -76,3 +78,11 @@ Parameter Name | Description
|
||||
--- | ---
|
||||
`modelPropertyMacro` | `Function`. Function to set default values to each property in model. Accepts one argument modelPropertyMacro(property), property is immutable
|
||||
`parameterMacro` | `Function`. Function to set default value to parameters. Accepts two arguments parameterMacro(operation, parameter). Operation and parameter are objects passed for context, both remain immutable
|
||||
|
||||
### Instance methods
|
||||
|
||||
Method Name | Description
|
||||
--- | ---
|
||||
`initOAuth` | `(configObj) => void`. Provide Swagger-UI with information about your OAuth server - see the OAuth2 documentation for more information.
|
||||
`preauthorizeBasic` | `(authDefinitionKey, username, password) => action`. Programmatically set values for a Basic authorization scheme.
|
||||
`preauthorizeApiKey` | `(authDefinitionKey, apiKeyValue) => action`. Programmatically set values for an API key authorization scheme.
|
||||
|
||||
@@ -27,7 +27,7 @@ The module's contents mirrors the `dist` folder you see in the Git repository. T
|
||||
|
||||
```javascript
|
||||
const express = require('express')
|
||||
const pathToSwaggerUi = require('swagger-ui').absolutePath()
|
||||
const pathToSwaggerUi = require('swagger-ui-dist').absolutePath()
|
||||
|
||||
const app = express()
|
||||
|
||||
@@ -37,7 +37,7 @@ app.listen(3000)
|
||||
```
|
||||
|
||||
The module also exports `SwaggerUIBundle` and `SwaggerUIStandalonePreset`, so
|
||||
if you're in a JavaScript project that can't handle a tranditional npm module,
|
||||
if you're in a JavaScript project that can't handle a traditional npm module,
|
||||
you could do something like this:
|
||||
|
||||
```js
|
||||
@@ -83,7 +83,7 @@ This will serve Swagger UI at `/swagger` instead of `/`.
|
||||
|
||||
### unpkg
|
||||
|
||||
You can embed Swagger-UI's code directly in your HTML by using unkpg's interface:
|
||||
You can embed Swagger-UI's code directly in your HTML by using unpkg's interface:
|
||||
|
||||
```html
|
||||
<script src="//unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# OAuth2 configuration
|
||||
You can configure OAuth2 authorization by calling `initOAuth` method with passed configs under the instance of `SwaggerUIBundle`
|
||||
default `client_id` and `client_secret`, `realm`, an application name `appName`, `scopeSeparator`, `additionalQueryStringParams`,
|
||||
`useBasicAuthenticationWithAccessCodeGrant`.
|
||||
You can configure OAuth2 authorization by calling the `initOAuth` method.
|
||||
|
||||
Config Name | Description
|
||||
--- | ---
|
||||
@@ -11,7 +9,7 @@ realm | realm query parameter (for oauth1) added to `authorizationUrl` and `toke
|
||||
appName | application name, displayed in authorization popup. MUST be a string
|
||||
scopeSeparator | scope separator for passing scopes, encoded before calling, default value is a space (encoded value `%20`). MUST be a string
|
||||
additionalQueryStringParams | Additional query parameters added to `authorizationUrl` and `tokenUrl`. MUST be an object
|
||||
useBasicAuthenticationWithAccessCodeGrant | Only activated for the `accessCode` flow. During the `authorization_code` request to the `tokenUrl`, pass the [Client Password](https://tools.ietf.org/html/rfc6749#section-2.3.1) using the HTTP Basic Authentication scheme (`Authorization` header with `Basic base64encoded[client_id:client_secret]`). The default is `false`
|
||||
useBasicAuthenticationWithAccessCodeGrant | Only activated for the `accessCode` flow. During the `authorization_code` request to the `tokenUrl`, pass the [Client Password](https://tools.ietf.org/html/rfc6749#section-2.3.1) using the HTTP Basic Authentication scheme (`Authorization` header with `Basic base64encode(client_id + client_secret)`). The default is `false`
|
||||
|
||||
```javascript
|
||||
const ui = SwaggerUI({...})
|
||||
|
||||
@@ -15,10 +15,11 @@ http {
|
||||
server {
|
||||
listen 8080;
|
||||
server_name localhost;
|
||||
index index.html index.htm;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
alias /usr/share/nginx/html/;
|
||||
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "swagger-ui",
|
||||
"version": "3.7.0",
|
||||
"version": "3.13.1",
|
||||
"main": "dist/swagger-ui.js",
|
||||
"repository": "git@github.com:swagger-api/swagger-ui.git",
|
||||
"contributors": [
|
||||
@@ -37,7 +37,8 @@
|
||||
"test-e2e": "sleep 3 && nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json",
|
||||
"e2e-initial-render": "nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json --group initial-render",
|
||||
"mock-api": "json-server --watch test/e2e/db.json --port 3204",
|
||||
"e2e": "npm-run-all --parallel -r hot-server mock-api test-e2e"
|
||||
"hot-e2e-server": "webpack-dev-server --content-base test/e2e/helpers --host 0.0.0.0 --config webpack-hot-dev-server.config.js --inline --hot --progress",
|
||||
"e2e": "npm-run-all --parallel -r hot-e2e-server mock-api test-e2e"
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^2.0.2",
|
||||
@@ -53,7 +54,7 @@
|
||||
"ieee754": "^1.1.8",
|
||||
"immutable": "^3.x.x",
|
||||
"js-yaml": "^3.5.5",
|
||||
"lodash": "4.17.2",
|
||||
"lodash": "4.17.5",
|
||||
"matcher": "^0.1.2",
|
||||
"memoizee": "0.4.1",
|
||||
"promise-worker": "^1.1.1",
|
||||
@@ -63,10 +64,12 @@
|
||||
"react-addons-shallow-compare": "0.14.8",
|
||||
"react-addons-test-utils": "^15.6.2",
|
||||
"react-collapse": "^4.0.3",
|
||||
"react-debounce-input": "^3.2.0",
|
||||
"react-dom": "^15.6.2",
|
||||
"react-height": "^2.0.0",
|
||||
"react-hot-loader": "1.3.1",
|
||||
"react-immutable-proptypes": "2.1.0",
|
||||
"react-immutable-pure-component": "^1.1.1",
|
||||
"react-markdown": "^2.5.0",
|
||||
"react-motion": "^0.5.2",
|
||||
"react-object-inspector": "0.2.1",
|
||||
@@ -81,14 +84,14 @@
|
||||
"scroll-to-element": "^2.0.0",
|
||||
"serialize-error": "2.0.0",
|
||||
"shallowequal": "0.2.2",
|
||||
"swagger-client": "^3.4.2",
|
||||
"swagger-client": "^3.5.2",
|
||||
"url-parse": "^1.1.8",
|
||||
"whatwg-fetch": "0.11.1",
|
||||
"worker-loader": "^0.7.1",
|
||||
"xml": "1.0.1",
|
||||
"xml-but-prettier": "^1.0.1",
|
||||
"yaml-js": "0.2.0",
|
||||
"zenscroll": "4.0.0"
|
||||
"zenscroll": "4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "7.1.1",
|
||||
@@ -142,6 +145,7 @@
|
||||
"standard": "^10.0.2",
|
||||
"standard-loader": "^6.0.1",
|
||||
"style-loader": "0.18.2",
|
||||
"tachyons-sass": "^4.9.2",
|
||||
"url-loader": "0.5.9",
|
||||
"webpack": "^2.6.1",
|
||||
"webpack-bundle-size-analyzer": "^2.5.0"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { Component } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
|
||||
const propStyle = { color: "#999", fontStyle: "italic" }
|
||||
|
||||
@@ -12,7 +13,7 @@ export default class ArrayModel extends Component {
|
||||
name: PropTypes.string,
|
||||
required: PropTypes.bool,
|
||||
expandDepth: PropTypes.number,
|
||||
specPath: PropTypes.array.isRequired,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
depth: PropTypes.number
|
||||
}
|
||||
|
||||
@@ -45,10 +46,20 @@ export default class ArrayModel extends Component {
|
||||
properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <Property key={`${key}-${v}`} propKey={ key } propVal={ v } propStyle={ propStyle } />) : null
|
||||
}
|
||||
{
|
||||
!description ? null :
|
||||
!description ? (properties.size ? <div className="markdown"></div> : null) :
|
||||
<Markdown source={ description } />
|
||||
}
|
||||
<span><Model { ...this.props } getConfigs={ getConfigs } specPath={[...specPath, "items"]} name={null} schema={ items } required={ false } depth={ depth + 1 } /></span>
|
||||
<span>
|
||||
<Model
|
||||
{ ...this.props }
|
||||
getConfigs={ getConfigs }
|
||||
specPath={specPath.push("items")}
|
||||
name={null}
|
||||
schema={ items }
|
||||
required={ false }
|
||||
depth={ depth + 1 }
|
||||
/>
|
||||
</span>
|
||||
]
|
||||
</ModelCollapse>
|
||||
</span>
|
||||
|
||||
@@ -20,7 +20,9 @@ export default class AuthorizeOperationBtn extends React.Component {
|
||||
let { isAuthorized } = this.props
|
||||
|
||||
return (
|
||||
<button className={isAuthorized ? "authorization__btn locked" : "authorization__btn unlocked"} onClick={ this.onClick }>
|
||||
<button className={isAuthorized ? "authorization__btn locked" : "authorization__btn unlocked"}
|
||||
aria-label={isAuthorized ? "authorization button locked" : "authorization button unlocked"}
|
||||
onClick={this.onClick}>
|
||||
<svg width="20" height="20">
|
||||
<use href={ isAuthorized ? "#locked" : "#unlocked" } xlinkHref={ isAuthorized ? "#locked" : "#unlocked" } />
|
||||
</svg>
|
||||
|
||||
@@ -81,11 +81,11 @@ export default class Auths extends React.Component {
|
||||
}).toArray()
|
||||
}
|
||||
<div className="auth-btn-wrapper">
|
||||
<Button className="btn modal-btn auth btn-done" onClick={ this.close }>Done</Button>
|
||||
{
|
||||
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>
|
||||
}
|
||||
<Button className="btn modal-btn auth btn-done" onClick={ this.close }>Close</Button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
|
||||
@@ -39,6 +39,13 @@ export default class Oauth2 extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
close = (e) => {
|
||||
e.preventDefault()
|
||||
let { authActions } = this.props
|
||||
|
||||
authActions.showDefinitions(false)
|
||||
}
|
||||
|
||||
authorize =() => {
|
||||
let { authActions, errActions, getConfigs, authSelectors } = this.props
|
||||
let configs = getConfigs()
|
||||
@@ -231,6 +238,7 @@ export default class Oauth2 extends React.Component {
|
||||
: <Button className="btn modal-btn auth authorize" onClick={ this.authorize }>Authorize</Button>
|
||||
)
|
||||
}
|
||||
<Button className="btn modal-btn auth btn-done" onClick={ this.close }>Close</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import ImPropTypes from "react-immutable-proptypes"
|
||||
import { sanitizeUrl } from "core/utils"
|
||||
|
||||
|
||||
class Path extends React.Component {
|
||||
export class InfoBasePath extends React.Component {
|
||||
static propTypes = {
|
||||
host: PropTypes.string,
|
||||
basePath: PropTypes.string
|
||||
@@ -68,6 +68,17 @@ class License extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export class InfoUrl extends React.PureComponent {
|
||||
static propTypes = {
|
||||
url: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
render() {
|
||||
const { url } = this.props
|
||||
return <a target="_blank" href={ sanitizeUrl(url) }><span className="url"> { url } </span></a>
|
||||
}
|
||||
}
|
||||
|
||||
export default class Info extends React.Component {
|
||||
static propTypes = {
|
||||
info: PropTypes.object,
|
||||
@@ -90,6 +101,8 @@ export default class Info extends React.Component {
|
||||
|
||||
const Markdown = getComponent("Markdown")
|
||||
const VersionStamp = getComponent("VersionStamp")
|
||||
const InfoUrl = getComponent("InfoUrl")
|
||||
const InfoBasePath = getComponent("InfoBasePath")
|
||||
|
||||
return (
|
||||
<div className="info">
|
||||
@@ -97,8 +110,8 @@ export default class Info extends React.Component {
|
||||
<h2 className="title" >{ title }
|
||||
{ version && <VersionStamp version={version}></VersionStamp> }
|
||||
</h2>
|
||||
{ host || basePath ? <Path host={ host } basePath={ basePath } /> : null }
|
||||
{ url && <a target="_blank" href={ sanitizeUrl(url) }><span className="url"> { url } </span></a> }
|
||||
{ host || basePath ? <InfoBasePath host={ host } basePath={ basePath } /> : null }
|
||||
{ url && <InfoUrl url={url} /> }
|
||||
</hgroup>
|
||||
|
||||
<div className="description">
|
||||
|
||||
@@ -57,6 +57,9 @@ export class Col extends React.Component {
|
||||
let classesAr = []
|
||||
|
||||
for (let device in DEVICES) {
|
||||
if (!DEVICES.hasOwnProperty(device)) {
|
||||
continue
|
||||
}
|
||||
let deviceClass = DEVICES[device]
|
||||
if(device in this.props) {
|
||||
let val = this.props[device]
|
||||
|
||||
@@ -53,7 +53,7 @@ export default class LiveResponse extends React.Component {
|
||||
|
||||
const curlRequest = showMutatedRequest ? specSelectors.mutatedRequestFor(path, method) : specSelectors.requestFor(path, method)
|
||||
const status = response.get("status")
|
||||
const url = response.get("url")
|
||||
const url = curlRequest.get("url")
|
||||
const headers = response.get("headers").toJS()
|
||||
const notDocumented = response.get("notDocumented")
|
||||
const isError = response.get("error")
|
||||
|
||||
@@ -8,13 +8,17 @@ export default class ModelCollapse extends Component {
|
||||
children: PropTypes.any,
|
||||
title: PropTypes.element,
|
||||
modelName: PropTypes.string,
|
||||
onToggle: PropTypes.func.isRequired
|
||||
classes: PropTypes.string,
|
||||
onToggle: PropTypes.func,
|
||||
hideSelfOnExpand: PropTypes.bool,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
collapsedContent: "{...}",
|
||||
expanded: false,
|
||||
title: null
|
||||
title: null,
|
||||
onToggle: () => {},
|
||||
hideSelfOnExpand: false
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
@@ -28,17 +32,23 @@ export default class ModelCollapse extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps){
|
||||
componentDidMount() {
|
||||
const { hideSelfOnExpand, expanded, modelName } = this.props
|
||||
if(hideSelfOnExpand && expanded) {
|
||||
// We just mounted pre-expanded, and we won't be going back..
|
||||
// So let's give our parent an `onToggle` call..
|
||||
// Since otherwise it will never be called.
|
||||
this.props.onToggle(modelName, expanded)
|
||||
}
|
||||
}
|
||||
|
||||
if(this.props.expanded!= nextProps.expanded){
|
||||
componentWillReceiveProps(nextProps){
|
||||
if(this.props.expanded !== nextProps.expanded){
|
||||
this.setState({expanded: nextProps.expanded})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
toggleCollapsed=()=>{
|
||||
|
||||
|
||||
if(this.props.onToggle){
|
||||
this.props.onToggle(this.props.modelName,!this.state.expanded)
|
||||
}
|
||||
@@ -49,9 +59,18 @@ export default class ModelCollapse extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const {title} = this.props
|
||||
const { title, classes } = this.props
|
||||
|
||||
if(this.state.expanded ) {
|
||||
if(this.props.hideSelfOnExpand) {
|
||||
return <span className={classes || ""}>
|
||||
{this.props.children}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
<span className={classes || ""}>
|
||||
{ title && <span onClick={this.toggleCollapsed} style={{ "cursor": "pointer" }}>{title}</span> }
|
||||
<span onClick={ this.toggleCollapsed } style={{ "cursor": "pointer" }}>
|
||||
<span className={ "model-toggle" + ( this.state.expanded ? "" : " collapsed" ) }></span>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
|
||||
export default class ModelExample extends React.Component {
|
||||
static propTypes = {
|
||||
@@ -9,7 +10,7 @@ export default class ModelExample extends React.Component {
|
||||
example: PropTypes.any.isRequired,
|
||||
isExecute: PropTypes.bool,
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
specPath: PropTypes.array.isRequired,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React, { PureComponent } from "react"
|
||||
import React from "react"
|
||||
import ImmutablePureComponent from "react-immutable-pure-component"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import PropTypes from "prop-types"
|
||||
|
||||
export default class Model extends PureComponent {
|
||||
export default class Model extends ImmutablePureComponent {
|
||||
static propTypes = {
|
||||
schema: ImPropTypes.orderedMap.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
@@ -13,7 +14,7 @@ export default class Model extends PureComponent {
|
||||
required: PropTypes.bool,
|
||||
expandDepth: PropTypes.number,
|
||||
depth: PropTypes.number,
|
||||
specPath: PropTypes.array.isRequired,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
}
|
||||
|
||||
getModelName =( ref )=> {
|
||||
@@ -48,6 +49,17 @@ export default class Model extends PureComponent {
|
||||
schema = this.getRefSchema( name )
|
||||
}
|
||||
|
||||
if(!schema) {
|
||||
return <span className="model model-title">
|
||||
<span className="model-title__text">{ name }</span>
|
||||
<img src={require("core/../img/rolling-load.svg")} height={"20px"} width={"20px"} style={{
|
||||
marginLeft: "1em",
|
||||
position: "relative",
|
||||
bottom: "0px"
|
||||
}} />
|
||||
</span>
|
||||
}
|
||||
|
||||
const deprecated = specSelectors.isOAS3() && schema.get("deprecated")
|
||||
isRef = isRef !== undefined ? isRef : !!$$ref
|
||||
type = schema && schema.get("type") || type
|
||||
|
||||
@@ -1,15 +1,34 @@
|
||||
import React, { Component } from "react"
|
||||
import Im from "immutable"
|
||||
import PropTypes from "prop-types"
|
||||
|
||||
export default class Models extends Component {
|
||||
static propTypes = {
|
||||
getComponent: PropTypes.func,
|
||||
specSelectors: PropTypes.object,
|
||||
specActions: PropTypes.object.isRequired,
|
||||
layoutSelectors: PropTypes.object,
|
||||
layoutActions: PropTypes.object,
|
||||
getConfigs: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
getSchemaBasePath = () => {
|
||||
const isOAS3 = this.props.specSelectors.isOAS3()
|
||||
return isOAS3 ? ["components", "schemas"] : ["definitions"]
|
||||
}
|
||||
|
||||
getCollapsedContent = () => {
|
||||
return " "
|
||||
}
|
||||
|
||||
handleToggle = (name, isExpanded) => {
|
||||
const { layoutActions } = this.props
|
||||
layoutActions.show(["models", name], isExpanded)
|
||||
if(isExpanded) {
|
||||
this.props.specActions.requestResolvedSubtree([...this.getSchemaBasePath(), name])
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
let { specSelectors, getComponent, layoutSelectors, layoutActions, getConfigs } = this.props
|
||||
let definitions = specSelectors.definitions()
|
||||
@@ -17,10 +36,11 @@ export default class Models extends Component {
|
||||
if (!definitions.size || defaultModelsExpandDepth < 0) return null
|
||||
|
||||
let showModels = layoutSelectors.isShown("models", defaultModelsExpandDepth > 0 && docExpansion !== "none")
|
||||
const specPathBase = specSelectors.isOAS3() ? ["components", "schemas"] : ["definitions"]
|
||||
const specPathBase = this.getSchemaBasePath()
|
||||
|
||||
const ModelWrapper = getComponent("ModelWrapper")
|
||||
const Collapse = getComponent("Collapse")
|
||||
const Collapse = getComponent("Collapse")
|
||||
const ModelCollapse = getComponent("ModelCollapse")
|
||||
|
||||
return <section className={ showModels ? "models is-open" : "models"}>
|
||||
<h4 onClick={() => layoutActions.show("models", !showModels)}>
|
||||
@@ -31,18 +51,40 @@ export default class Models extends Component {
|
||||
</h4>
|
||||
<Collapse isOpened={showModels}>
|
||||
{
|
||||
definitions.entrySeq().map( ( [ name, model ])=>{
|
||||
definitions.entrySeq().map( ( [ name ])=>{
|
||||
|
||||
const schema = specSelectors.specResolvedSubtree([...specPathBase, name])
|
||||
|
||||
if(layoutSelectors.isShown(["models", name], false) && schema === undefined) {
|
||||
// Firing an action in a container render is not great,
|
||||
// but it works for now.
|
||||
this.props.specActions.requestResolvedSubtree([...this.getSchemaBasePath(), name])
|
||||
}
|
||||
|
||||
const content = <ModelWrapper name={ name }
|
||||
expandDepth={ defaultModelsExpandDepth }
|
||||
schema={ schema || Im.Map() }
|
||||
specPath={Im.List([...specPathBase, name])}
|
||||
getComponent={ getComponent }
|
||||
specSelectors={ specSelectors }
|
||||
getConfigs = {getConfigs}
|
||||
layoutSelectors = {layoutSelectors}
|
||||
layoutActions = {layoutActions}/>
|
||||
|
||||
const title = <span className="model-box">
|
||||
<span className="model model-title">{name}</span>
|
||||
</span>
|
||||
|
||||
return <div id={ `model-${name}` } className="model-container" key={ `models-section-${name}` }>
|
||||
<ModelWrapper name={ name }
|
||||
expandDepth={ defaultModelsExpandDepth }
|
||||
schema={ model }
|
||||
specPath={[...specPathBase, name]}
|
||||
getComponent={ getComponent }
|
||||
specSelectors={ specSelectors }
|
||||
getConfigs = {getConfigs}
|
||||
layoutSelectors = {layoutSelectors}
|
||||
layoutActions = {layoutActions}/>
|
||||
<ModelCollapse
|
||||
classes="model-box"
|
||||
collapsedContent={this.getCollapsedContent(name)}
|
||||
onToggle={this.handleToggle}
|
||||
title={title}
|
||||
modelName={name}
|
||||
hideSelfOnExpand={true}
|
||||
expanded={defaultModelsExpandDepth > 1}
|
||||
>{content}</ModelCollapse>
|
||||
</div>
|
||||
}).toArray()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { Component, } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { List } from "immutable"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
|
||||
const braceOpen = "{"
|
||||
const braceClose = "}"
|
||||
@@ -11,13 +12,13 @@ export default class ObjectModel extends Component {
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
expanded: PropTypes.bool,
|
||||
onToggle: PropTypes.func.isRequired,
|
||||
onToggle: PropTypes.func,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
name: PropTypes.string,
|
||||
isRef: PropTypes.bool,
|
||||
expandDepth: PropTypes.number,
|
||||
depth: PropTypes.number,
|
||||
specPath: PropTypes.object.isRequired
|
||||
specPath: ImPropTypes.list.isRequired
|
||||
}
|
||||
|
||||
render(){
|
||||
@@ -102,7 +103,7 @@ export default class ObjectModel extends Component {
|
||||
<Model key={ `object-${name}-${key}_${value}` } { ...otherProps }
|
||||
required={ isRequired }
|
||||
getComponent={ getComponent }
|
||||
specPath={[...specPath, "properties", key]}
|
||||
specPath={specPath.push("properties", key)}
|
||||
getConfigs={ getConfigs }
|
||||
schema={ value }
|
||||
depth={ depth + 1 } />
|
||||
@@ -141,7 +142,7 @@ export default class ObjectModel extends Component {
|
||||
<td>
|
||||
<Model { ...otherProps } required={ false }
|
||||
getComponent={ getComponent }
|
||||
specPath={[...specPath, "additionalProperties"]}
|
||||
specPath={specPath.push("additionalProperties")}
|
||||
getConfigs={ getConfigs }
|
||||
schema={ additionalProperties }
|
||||
depth={ depth + 1 } />
|
||||
@@ -156,7 +157,7 @@ export default class ObjectModel extends Component {
|
||||
{anyOf.map((schema, k) => {
|
||||
return <div key={k}><Model { ...otherProps } required={ false }
|
||||
getComponent={ getComponent }
|
||||
specPath={[...specPath, "anyOf", k]}
|
||||
specPath={specPath.push("anyOf", k)}
|
||||
getConfigs={ getConfigs }
|
||||
schema={ schema }
|
||||
depth={ depth + 1 } /></div>
|
||||
@@ -172,7 +173,7 @@ export default class ObjectModel extends Component {
|
||||
{oneOf.map((schema, k) => {
|
||||
return <div key={k}><Model { ...otherProps } required={ false }
|
||||
getComponent={ getComponent }
|
||||
specPath={[...specPath, "oneOf", k]}
|
||||
specPath={specPath.push("oneOf", k)}
|
||||
getConfigs={ getConfigs }
|
||||
schema={ schema }
|
||||
depth={ depth + 1 } /></div>
|
||||
@@ -189,7 +190,7 @@ export default class ObjectModel extends Component {
|
||||
<Model { ...otherProps }
|
||||
required={ false }
|
||||
getComponent={ getComponent }
|
||||
specPath={[...specPath, "not"]}
|
||||
specPath={specPath.push("not")}
|
||||
getConfigs={ getConfigs }
|
||||
schema={ not }
|
||||
depth={ depth + 1 } />
|
||||
|
||||
@@ -101,7 +101,7 @@ class ValidatorImage extends React.Component {
|
||||
if (this.state.error) {
|
||||
return <img alt={"Error"} />
|
||||
} else if (!this.state.loaded) {
|
||||
return <img alt= {"Loading..."} />
|
||||
return null
|
||||
}
|
||||
return <img src={this.props.src} alt={this.props.alt} />
|
||||
}
|
||||
|
||||
@@ -2,12 +2,15 @@ import React, { PureComponent } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { getList } from "core/utils"
|
||||
import { getExtensions, sanitizeUrl } from "core/utils"
|
||||
import { Iterable } from "immutable"
|
||||
import { Iterable, List } from "immutable"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
|
||||
|
||||
export default class Operation extends PureComponent {
|
||||
static propTypes = {
|
||||
specPath: PropTypes.array.isRequired,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
operation: PropTypes.instanceOf(Iterable).isRequired,
|
||||
summary: PropTypes.string,
|
||||
response: PropTypes.instanceOf(Iterable),
|
||||
request: PropTypes.instanceOf(Iterable),
|
||||
|
||||
@@ -33,7 +36,8 @@ export default class Operation extends PureComponent {
|
||||
operation: null,
|
||||
response: null,
|
||||
request: null,
|
||||
specPath: []
|
||||
specPath: List(),
|
||||
summary: ""
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -58,6 +62,8 @@ export default class Operation extends PureComponent {
|
||||
let operationProps = this.props.operation
|
||||
|
||||
let {
|
||||
summary,
|
||||
deprecated,
|
||||
isShown,
|
||||
isAuthorized,
|
||||
path,
|
||||
@@ -75,14 +81,13 @@ export default class Operation extends PureComponent {
|
||||
} = operationProps.toJS()
|
||||
|
||||
let {
|
||||
summary,
|
||||
summary: resolvedSummary,
|
||||
description,
|
||||
deprecated,
|
||||
externalDocs,
|
||||
schemes
|
||||
} = op.operation
|
||||
} = op
|
||||
|
||||
let operation = operationProps.getIn(["op", "operation"])
|
||||
let operation = operationProps.getIn(["op"])
|
||||
let security = operationProps.get("security")
|
||||
let responses = operation.get("responses")
|
||||
let produces = operation.get("produces")
|
||||
@@ -131,7 +136,7 @@ export default class Operation extends PureComponent {
|
||||
|
||||
{ !showSummary ? null :
|
||||
<div className="opblock-summary-description">
|
||||
{ summary }
|
||||
{ resolvedSummary || summary }
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -151,6 +156,9 @@ export default class Operation extends PureComponent {
|
||||
|
||||
<Collapse isOpened={isShown}>
|
||||
<div className="opblock-body">
|
||||
{ (operation && operation.size) || operation === null ? null :
|
||||
<img height={"32px"} width={"32px"} src={require("core/../img/rolling-load.svg")} className="opblock-loading-animation" />
|
||||
}
|
||||
{ deprecated && <h4 className="opblock-title_normal"> Warning: Deprecated</h4>}
|
||||
{ description &&
|
||||
<div className="opblock-description-wrapper">
|
||||
@@ -172,23 +180,25 @@ export default class Operation extends PureComponent {
|
||||
</div> : null
|
||||
}
|
||||
|
||||
<Parameters
|
||||
parameters={parameters}
|
||||
specPath={[...specPath, "parameters"]}
|
||||
operation={operation}
|
||||
onChangeKey={onChangeKey}
|
||||
onTryoutClick = { onTryoutClick }
|
||||
onCancelClick = { onCancelClick }
|
||||
tryItOutEnabled = { tryItOutEnabled }
|
||||
allowTryItOut={allowTryItOut}
|
||||
{ !operation || !operation.size ? null :
|
||||
<Parameters
|
||||
parameters={parameters}
|
||||
specPath={specPath.push("parameters")}
|
||||
operation={operation}
|
||||
onChangeKey={onChangeKey}
|
||||
onTryoutClick = { onTryoutClick }
|
||||
onCancelClick = { onCancelClick }
|
||||
tryItOutEnabled = { tryItOutEnabled }
|
||||
allowTryItOut={allowTryItOut}
|
||||
|
||||
fn={fn}
|
||||
getComponent={ getComponent }
|
||||
specActions={ specActions }
|
||||
specSelectors={ specSelectors }
|
||||
pathMethod={ [path, method] }
|
||||
getConfigs={ getConfigs }
|
||||
/>
|
||||
fn={fn}
|
||||
getComponent={ getComponent }
|
||||
specActions={ specActions }
|
||||
specSelectors={ specSelectors }
|
||||
pathMethod={ [path, method] }
|
||||
getConfigs={ getConfigs }
|
||||
/>
|
||||
}
|
||||
|
||||
{ !tryItOutEnabled ? null :
|
||||
<OperationServers
|
||||
@@ -247,8 +257,8 @@ export default class Operation extends PureComponent {
|
||||
oas3Actions={oas3Actions}
|
||||
specActions={ specActions }
|
||||
produces={ produces }
|
||||
producesValue={ operation.get("produces_value") }
|
||||
specPath={[...specPath, "responses"]}
|
||||
producesValue={ specSelectors.currentProducesFor([path, method]) }
|
||||
specPath={specPath.push("responses")}
|
||||
path={ path }
|
||||
method={ method }
|
||||
displayRequestDuration={ displayRequestDuration }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import Im from "immutable"
|
||||
import { createDeepLinkPath, sanitizeUrl } from "core/utils"
|
||||
|
||||
const SWAGGER2_OPERATION_METHODS = [
|
||||
@@ -20,7 +21,8 @@ export default class Operations extends React.Component {
|
||||
layoutActions: PropTypes.object.isRequired,
|
||||
authActions: PropTypes.object.isRequired,
|
||||
authSelectors: PropTypes.object.isRequired,
|
||||
getConfigs: PropTypes.func.isRequired
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
fn: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -29,7 +31,8 @@ export default class Operations extends React.Component {
|
||||
getComponent,
|
||||
layoutSelectors,
|
||||
layoutActions,
|
||||
getConfigs
|
||||
getConfigs,
|
||||
fn
|
||||
} = this.props
|
||||
|
||||
let taggedOps = specSelectors.taggedOperations()
|
||||
@@ -51,9 +54,7 @@ export default class Operations extends React.Component {
|
||||
|
||||
if (filter) {
|
||||
if (filter !== true) {
|
||||
taggedOps = taggedOps.filter((tagObj, tag) => {
|
||||
return tag.indexOf(filter) !== -1
|
||||
})
|
||||
taggedOps = fn.opsFilter(taggedOps, filter)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +86,7 @@ export default class Operations extends React.Component {
|
||||
isShown={showTag}
|
||||
path={tag}
|
||||
text={tag} />
|
||||
{ !tagDescription ? null :
|
||||
{ !tagDescription ? <small></small> :
|
||||
<small>
|
||||
<Markdown source={tagDescription} />
|
||||
</small>
|
||||
@@ -107,7 +108,7 @@ export default class Operations extends React.Component {
|
||||
}
|
||||
</div>
|
||||
|
||||
<button className="expand-operation" title="Expand operation" onClick={() => layoutActions.show(isShownKey, !showTag)}>
|
||||
<button className="expand-operation" title={showTag ? "Collapse operation": "Expand operation"} onClick={() => layoutActions.show(isShownKey, !showTag)}>
|
||||
<svg className="arrow" width="20" height="20">
|
||||
<use href={showTag ? "#large-arrow-down" : "#large-arrow"} xlinkHref={showTag ? "#large-arrow-down" : "#large-arrow"} />
|
||||
</svg>
|
||||
@@ -119,7 +120,7 @@ export default class Operations extends React.Component {
|
||||
operations.map( op => {
|
||||
const path = op.get("path")
|
||||
const method = op.get("method")
|
||||
const specPath = ["paths", path, method]
|
||||
const specPath = Im.List(["paths", path, method])
|
||||
|
||||
|
||||
// FIXME: (someday) this logic should probably be in a selector,
|
||||
|
||||
@@ -47,7 +47,7 @@ export default class ParamBody extends PureComponent {
|
||||
|
||||
updateValues = (props) => {
|
||||
let { specSelectors, pathMethod, param, isExecute, consumesValue="" } = props
|
||||
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name"), param.get("in")) : fromJS({})
|
||||
let parameter = specSelectors ? specSelectors.parameterWithMeta(pathMethod, param.get("name"), param.get("in")) : fromJS({})
|
||||
let isXml = /xml/i.test(consumesValue)
|
||||
let isJson = /json/i.test(consumesValue)
|
||||
let paramValue = isXml ? parameter.get("value_xml") : parameter.get("value")
|
||||
@@ -107,7 +107,7 @@ export default class ParamBody extends PureComponent {
|
||||
const HighlightCode = getComponent("highlightCode")
|
||||
const ContentType = getComponent("contentType")
|
||||
// for domains where specSelectors not passed
|
||||
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name"), param.get("in")) : param
|
||||
let parameter = specSelectors ? specSelectors.parameterWithMeta(pathMethod, param.get("name"), param.get("in")) : param
|
||||
let errors = parameter.get("errors", List())
|
||||
let consumesValue = specSelectors.contentTypeValues(pathMethod).get("requestContentType")
|
||||
let consumes = this.props.consumes && this.props.consumes.size ? this.props.consumes : ParamBody.defaultProp.consumes
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { Component } from "react"
|
||||
import { Map } from "immutable"
|
||||
import PropTypes from "prop-types"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import win from "core/window"
|
||||
import { getExtensions } from "core/utils"
|
||||
|
||||
@@ -15,7 +16,7 @@ export default class ParameterRow extends Component {
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
pathMethod: PropTypes.array.isRequired,
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
specPath: PropTypes.array.isRequired,
|
||||
specPath: ImPropTypes.list.isRequired
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
@@ -23,11 +24,18 @@ export default class ParameterRow extends Component {
|
||||
|
||||
let { specSelectors, pathMethod, param } = props
|
||||
let defaultValue = param.get("default")
|
||||
let parameter = specSelectors.getParameter(pathMethod, param.get("name"), param.get("in"))
|
||||
let xExampleValue = param.get("x-example")
|
||||
let parameter = specSelectors.parameterWithMeta(pathMethod, param.get("name"), param.get("in"))
|
||||
let value = parameter ? parameter.get("value") : ""
|
||||
if ( defaultValue !== undefined && value === undefined ) {
|
||||
this.onChangeWrapper(defaultValue)
|
||||
|
||||
if( param.get("in") !== "body" ) {
|
||||
if ( xExampleValue !== undefined && value === undefined && specSelectors.isSwagger2() ) {
|
||||
this.onChangeWrapper(xExampleValue)
|
||||
} else if ( defaultValue !== undefined && value === undefined ) {
|
||||
this.onChangeWrapper(defaultValue)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props) {
|
||||
@@ -36,7 +44,7 @@ export default class ParameterRow extends Component {
|
||||
|
||||
let example = param.get("example")
|
||||
let defaultValue = param.get("default")
|
||||
let parameter = specSelectors.getParameter(pathMethod, param.get("name"), param.get("in"))
|
||||
let parameter = specSelectors.parameterWithMeta(pathMethod, param.get("name"), param.get("in"))
|
||||
let enumValue
|
||||
|
||||
if(isOAS3()) {
|
||||
@@ -97,16 +105,44 @@ export default class ParameterRow extends Component {
|
||||
const Markdown = getComponent("Markdown")
|
||||
const ParameterExt = getComponent("ParameterExt")
|
||||
|
||||
let paramWithMeta = specSelectors.parameterWithMeta(pathMethod, param.get("name"), param.get("in"))
|
||||
|
||||
let schema = param.get("schema")
|
||||
let type = isOAS3 && isOAS3() ? param.getIn(["schema", "type"]) : param.get("type")
|
||||
let isFormData = inType === "formData"
|
||||
let isFormDataSupported = "FormData" in win
|
||||
let required = param.get("required")
|
||||
let itemType = param.getIn(isOAS3 && isOAS3() ? ["schema", "items", "type"] : ["items", "type"])
|
||||
let parameter = specSelectors.getParameter(pathMethod, param.get("name"), param.get("in"))
|
||||
let value = parameter ? parameter.get("value") : ""
|
||||
let value = paramWithMeta ? paramWithMeta.get("value") : ""
|
||||
let extensions = getExtensions(param)
|
||||
|
||||
let paramItems // undefined
|
||||
let paramItemsEnum // undefined
|
||||
let isDisplayParamItemsEnum = false
|
||||
if ( param !== undefined ) {
|
||||
paramItems = param.get("items")
|
||||
}
|
||||
if ( paramItems !== undefined ) {
|
||||
paramItemsEnum = param.get("items").get("enum")
|
||||
}
|
||||
if ( paramItemsEnum !== undefined ) {
|
||||
if (paramItemsEnum.size > 0) {
|
||||
isDisplayParamItemsEnum = true
|
||||
}
|
||||
}
|
||||
|
||||
// Default and Example Value for readonly doc
|
||||
let paramDefaultValue // undefined
|
||||
let paramExample // undefined
|
||||
if ( param !== undefined ) {
|
||||
paramDefaultValue = param.get("default")
|
||||
paramExample = param.get("example")
|
||||
}
|
||||
|
||||
if (isDisplayParamItemsEnum) { // if we have an array, default value is in "items"
|
||||
paramDefaultValue = paramItems.get("default")
|
||||
}
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td className="col parameters-col_name">
|
||||
@@ -124,6 +160,25 @@ export default class ParameterRow extends Component {
|
||||
|
||||
<td className="col parameters-col_description">
|
||||
<Markdown source={ param.get("description") }/>
|
||||
|
||||
{ (bodyParam || !isExecute) && isDisplayParamItemsEnum ?
|
||||
<Markdown source={
|
||||
"<i>Available values</i>: " + paramItemsEnum.map(function(item) {
|
||||
return item
|
||||
}).toArray().join(", ")}/>
|
||||
: null
|
||||
}
|
||||
|
||||
{ (bodyParam || !isExecute) && paramDefaultValue !== undefined ?
|
||||
<Markdown source={"<i>Default value</i>: " + paramDefaultValue}/>
|
||||
: null
|
||||
}
|
||||
|
||||
{ (bodyParam || !isExecute) && paramExample !== undefined ?
|
||||
<Markdown source={"<i>Example</i>: " + paramExample}/>
|
||||
: null
|
||||
}
|
||||
|
||||
{(isFormData && !isFormDataSupported) && <div>Error: your browser does not support FormData</div>}
|
||||
|
||||
{ bodyParam || !isExecute ? null
|
||||
@@ -133,13 +188,14 @@ export default class ParameterRow extends Component {
|
||||
required={ required }
|
||||
description={param.get("description") ? `${param.get("name")} - ${param.get("description")}` : `${param.get("name")}`}
|
||||
onChange={ this.onChangeWrapper }
|
||||
errors={ param.get("errors") }
|
||||
schema={ isOAS3 && isOAS3() ? param.get("schema") : param }/>
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
bodyParam && schema ? <ModelExample getComponent={ getComponent }
|
||||
specPath={[...specPath, "schema"]}
|
||||
specPath={specPath.push("schema")}
|
||||
getConfigs={ getConfigs }
|
||||
isExecute={ isExecute }
|
||||
specSelectors={ specSelectors }
|
||||
|
||||
@@ -21,7 +21,7 @@ export default class Parameters extends Component {
|
||||
onChangeKey: PropTypes.array,
|
||||
pathMethod: PropTypes.array.isRequired,
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
specPath: PropTypes.array.isRequired,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
}
|
||||
|
||||
|
||||
@@ -98,10 +98,10 @@ export default class Parameters extends Component {
|
||||
eachMap(parameters, (parameter, i) => (
|
||||
<ParameterRow
|
||||
fn={ fn }
|
||||
specPath={[...specPath, i]}
|
||||
specPath={specPath.push(i.toString())}
|
||||
getComponent={ getComponent }
|
||||
getConfigs={ getConfigs }
|
||||
param={ parameter }
|
||||
param={ specSelectors.parameterWithMeta(pathMethod, parameter.get("name"), parameter.get("in")) }
|
||||
key={ `${parameter.get( "in" )}.${parameter.get("name")}` }
|
||||
onChange={ this.onChange }
|
||||
onChangeConsumes={this.onChangeConsumesWrapper}
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { Component } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { getExtensions } from "core/utils"
|
||||
|
||||
const propStyle = { color: "#999", fontStyle: "italic" }
|
||||
const propStyle = { color: "#6b6b6b", fontStyle: "italic" }
|
||||
|
||||
export default class Primitive extends Component {
|
||||
static propTypes = {
|
||||
|
||||
@@ -3,7 +3,17 @@ import PropTypes from "prop-types"
|
||||
import Remarkable from "remarkable"
|
||||
import sanitize from "sanitize-html"
|
||||
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const isPlainText = (str) => /^[A-Z\s0-9!?\.]+$/gi.test(str)
|
||||
|
||||
function Markdown({ source }) {
|
||||
if(isPlainText(source)) {
|
||||
// If the source text is not Markdown,
|
||||
// let's save some time and just render it.
|
||||
return <div className="markdown">
|
||||
{source}
|
||||
</div>
|
||||
}
|
||||
const html = new Remarkable({
|
||||
html: true,
|
||||
typographer: true,
|
||||
@@ -29,11 +39,14 @@ Markdown.propTypes = {
|
||||
export default Markdown
|
||||
|
||||
const sanitizeOptions = {
|
||||
allowedTags: sanitize.defaults.allowedTags.concat([ "h1", "h2", "img" ]),
|
||||
allowedTags: sanitize.defaults.allowedTags.concat([ "h1", "h2", "img", "span" ]),
|
||||
allowedAttributes: {
|
||||
...sanitize.defaults.allowedAttributes,
|
||||
"img": sanitize.defaults.allowedAttributes.img.concat(["title"])
|
||||
"img": sanitize.defaults.allowedAttributes.img.concat(["title"]),
|
||||
"td": [ "colspan" ],
|
||||
"*": [ "class" ]
|
||||
},
|
||||
allowedSchemesByTag: { img: [ "http", "https", "data" ] },
|
||||
textFilter: function(text) {
|
||||
return text.replace(/"/g, "\"")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import formatXml from "xml-but-prettier"
|
||||
import lowerCase from "lodash/lowerCase"
|
||||
import { extractFileNameFromContentDispositionHeader } from "core/utils"
|
||||
import win from "core/window"
|
||||
|
||||
export default class ResponseBody extends React.Component {
|
||||
|
||||
@@ -19,8 +21,45 @@ export default class ResponseBody extends React.Component {
|
||||
let body, bodyEl
|
||||
url = url || ""
|
||||
|
||||
// JSON
|
||||
if (/json/i.test(contentType)) {
|
||||
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"]))) {
|
||||
// Download
|
||||
|
||||
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,
|
||||
// First check if filename is quoted (e.g. contains space), if no, fallback to not quoted check
|
||||
let disposition = headers["content-disposition"] || headers["Content-Disposition"]
|
||||
if (typeof disposition !== "undefined") {
|
||||
let responseFilename = extractFileNameFromContentDispositionHeader(disposition)
|
||||
if (responseFilename !== null) {
|
||||
download = responseFilename
|
||||
}
|
||||
}
|
||||
|
||||
if(win.navigator && win.navigator.msSaveOrOpenBlob) {
|
||||
bodyEl = <div><a href={ href } onClick={() => win.navigator.msSaveOrOpenBlob(blob, download)}>{ "Download file" }</a></div>
|
||||
} else {
|
||||
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 (/json/i.test(contentType)) {
|
||||
// JSON
|
||||
try {
|
||||
body = JSON.stringify(JSON.parse(content), null, " ")
|
||||
} catch (error) {
|
||||
@@ -52,39 +91,6 @@ export default class ResponseBody extends React.Component {
|
||||
// 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"]))) {
|
||||
|
||||
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 if ( content.size > 0 ) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import cx from "classnames"
|
||||
import { fromJS, Seq, Iterable } from "immutable"
|
||||
import { fromJS, Seq, Iterable, List } from "immutable"
|
||||
import { getSampleSchema, fromJSOrdered } from "core/utils"
|
||||
|
||||
const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => {
|
||||
@@ -47,7 +48,7 @@ export default class Response extends React.Component {
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
specPath: PropTypes.array.isRequired,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
fn: PropTypes.object.isRequired,
|
||||
contentType: PropTypes.string,
|
||||
controlsAcceptHeader: PropTypes.bool,
|
||||
@@ -99,7 +100,7 @@ export default class Response extends React.Component {
|
||||
var schema, specPathWithPossibleSchema
|
||||
|
||||
if(isOAS3()) {
|
||||
const schemaPath = ["content", this.state.responseContentType, "schema"]
|
||||
const schemaPath = List(["content", this.state.responseContentType, "schema"])
|
||||
const oas3SchemaForContentType = response.getIn(schemaPath)
|
||||
sampleResponse = oas3SchemaForContentType ? getSampleSchema(oas3SchemaForContentType.toJS(), this.state.responseContentType, {
|
||||
includeReadOnly: true
|
||||
@@ -108,7 +109,7 @@ export default class Response extends React.Component {
|
||||
specPathWithPossibleSchema = oas3SchemaForContentType ? schemaPath : specPath
|
||||
} else {
|
||||
schema = inferSchema(response.toJS()) // TODO: don't convert back and forth. Lets just stick with immutable for inferSchema
|
||||
specPathWithPossibleSchema = response.has("schema") ? [...specPath, "schema"] : specPath
|
||||
specPathWithPossibleSchema = response.has("schema") ? specPath.push("schema") : specPath
|
||||
sampleResponse = schema ? getSampleSchema(schema, contentType, {
|
||||
includeReadOnly: true,
|
||||
includeWriteOnly: true // writeOnly has no filtering effect in swagger 2.0
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { fromJS, Iterable } from "immutable"
|
||||
import PropTypes from "prop-types"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import { defaultStatusCode, getAcceptControllingResponse } from "core/utils"
|
||||
|
||||
export default class Responses extends React.Component {
|
||||
@@ -17,7 +18,7 @@ export default class Responses extends React.Component {
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
specActions: PropTypes.object.isRequired,
|
||||
oas3Actions: PropTypes.object.isRequired,
|
||||
specPath: PropTypes.array.isRequired,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
fn: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
@@ -120,7 +121,7 @@ export default class Responses extends React.Component {
|
||||
let className = tryItOutResponse && tryItOutResponse.get("status") == code ? "response_current" : ""
|
||||
return (
|
||||
<Response key={ code }
|
||||
specPath={[...specPath, code]}
|
||||
specPath={specPath.push(code)}
|
||||
isDefault={defaultCode === code}
|
||||
fn={fn}
|
||||
className={ className }
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { PureComponent } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import { helpers } from "swagger-client"
|
||||
import { Iterable, fromJS } from "immutable"
|
||||
import { Iterable, fromJS, Map } from "immutable"
|
||||
|
||||
const { opId } = helpers
|
||||
|
||||
@@ -31,8 +32,7 @@ export default class OperationContainer extends PureComponent {
|
||||
request: PropTypes.instanceOf(Iterable),
|
||||
security: PropTypes.instanceOf(Iterable),
|
||||
isDeepLinkingEnabled: PropTypes.bool.isRequired,
|
||||
specPath: PropTypes.array.isRequired,
|
||||
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
authActions: PropTypes.object,
|
||||
oas3Actions: PropTypes.object,
|
||||
@@ -56,13 +56,13 @@ export default class OperationContainer extends PureComponent {
|
||||
|
||||
mapStateToProps(nextState, props) {
|
||||
const { op, layoutSelectors, getConfigs } = props
|
||||
const { docExpansion, deepLinking, displayOperationId, displayRequestDuration } = getConfigs()
|
||||
const { docExpansion, deepLinking, displayOperationId, displayRequestDuration, supportedSubmitMethods } = getConfigs()
|
||||
const showSummary = layoutSelectors.showSummary()
|
||||
const operationId = op.getIn(["operation", "operationId"]) || op.getIn(["operation", "__originalOperationId"]) || opId(op.get("operation"), props.path, props.method) || op.get("id")
|
||||
const isShownKey = ["operations", props.tag, operationId]
|
||||
const isDeepLinkingEnabled = deepLinking && deepLinking !== "false"
|
||||
const allowTryItOut = typeof props.allowTryItOut === "undefined" ?
|
||||
props.specSelectors.allowTryItOutFor(props.path, props.method) : props.allowTryItOut
|
||||
const allowTryItOut = supportedSubmitMethods.indexOf(props.method) >= 0 && (typeof props.allowTryItOut === "undefined" ?
|
||||
props.specSelectors.allowTryItOutFor(props.path, props.method) : props.allowTryItOut)
|
||||
const security = op.getIn(["operation", "security"]) || props.specSelectors.security()
|
||||
|
||||
return {
|
||||
@@ -81,32 +81,34 @@ export default class OperationContainer extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const defaultContentType = "application/json"
|
||||
let { specActions, path, method, op } = nextProps
|
||||
let operation = op.get("operation")
|
||||
let producesValue = operation.get("produces_value")
|
||||
let produces = operation.get("produces")
|
||||
let consumes = operation.get("consumes")
|
||||
let consumesValue = operation.get("consumes_value")
|
||||
componentDidMount() {
|
||||
const { isShown } = this.props
|
||||
const resolvedSubtree = this.getResolvedSubtree()
|
||||
|
||||
if(nextProps.response !== this.props.response) {
|
||||
if(isShown && resolvedSubtree === undefined) {
|
||||
this.requestResolvedSubtree()
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const { response, isShown } = nextProps
|
||||
const resolvedSubtree = this.getResolvedSubtree()
|
||||
|
||||
if(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)
|
||||
if(isShown && resolvedSubtree === undefined) {
|
||||
this.requestResolvedSubtree()
|
||||
}
|
||||
}
|
||||
|
||||
toggleShown =() => {
|
||||
let { layoutActions, tag, operationId, isShown } = this.props
|
||||
if(!isShown) {
|
||||
// transitioning from collapsed to expanded
|
||||
this.requestResolvedSubtree()
|
||||
}
|
||||
layoutActions.show(["operations", tag, operationId], !isShown)
|
||||
}
|
||||
|
||||
@@ -124,9 +126,40 @@ export default class OperationContainer extends PureComponent {
|
||||
this.setState({ executeInProgress: true })
|
||||
}
|
||||
|
||||
getResolvedSubtree = () => {
|
||||
const {
|
||||
specSelectors,
|
||||
path,
|
||||
method,
|
||||
specPath
|
||||
} = this.props
|
||||
|
||||
if(specPath) {
|
||||
return specSelectors.specResolvedSubtree(specPath.toJS())
|
||||
}
|
||||
|
||||
return specSelectors.specResolvedSubtree(["paths", path, method])
|
||||
}
|
||||
|
||||
requestResolvedSubtree = () => {
|
||||
const {
|
||||
specActions,
|
||||
path,
|
||||
method,
|
||||
specPath
|
||||
} = this.props
|
||||
|
||||
|
||||
if(specPath) {
|
||||
return specActions.requestResolvedSubtree(specPath.toJS())
|
||||
}
|
||||
|
||||
return specActions.requestResolvedSubtree(["paths", path, method])
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
op,
|
||||
op: unresolvedOp,
|
||||
tag,
|
||||
path,
|
||||
method,
|
||||
@@ -158,10 +191,14 @@ export default class OperationContainer extends PureComponent {
|
||||
|
||||
const Operation = getComponent( "operation" )
|
||||
|
||||
const resolvedSubtree = this.getResolvedSubtree() || Map()
|
||||
|
||||
const operationProps = fromJS({
|
||||
op,
|
||||
op: resolvedSubtree,
|
||||
tag,
|
||||
path,
|
||||
summary: unresolvedOp.getIn(["operation", "summary"]) || "",
|
||||
deprecated: resolvedSubtree.get("deprecated") || unresolvedOp.getIn(["operation", "deprecated"]) || false,
|
||||
method,
|
||||
security,
|
||||
isAuthorized,
|
||||
|
||||
@@ -49,6 +49,16 @@ module.exports = function SwaggerUI(opts) {
|
||||
defaultModelExpandDepth: 1,
|
||||
defaultModelsExpandDepth: 1,
|
||||
showExtensions: false,
|
||||
supportedSubmitMethods: [
|
||||
"get",
|
||||
"put",
|
||||
"post",
|
||||
"delete",
|
||||
"options",
|
||||
"head",
|
||||
"patch",
|
||||
"trace"
|
||||
],
|
||||
|
||||
// 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.
|
||||
@@ -92,6 +102,20 @@ module.exports = function SwaggerUI(opts) {
|
||||
}, constructorConfig.initialState)
|
||||
}
|
||||
|
||||
if(constructorConfig.initialState) {
|
||||
// if the user sets a key as `undefined`, that signals to us that we
|
||||
// should delete the key entirely.
|
||||
// known usage: Swagger-Editor validate plugin tests
|
||||
for (var key in constructorConfig.initialState) {
|
||||
if(
|
||||
constructorConfig.initialState.hasOwnProperty(key)
|
||||
&& constructorConfig.initialState[key] === undefined
|
||||
) {
|
||||
delete storeConfigs.state[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let inlinePlugin = ()=> {
|
||||
return {
|
||||
fn: constructorConfig.fn,
|
||||
@@ -105,8 +129,6 @@ module.exports = function SwaggerUI(opts) {
|
||||
|
||||
var system = store.getSystem()
|
||||
|
||||
system.initOAuth = system.authActions.configureAuth
|
||||
|
||||
const downloadSpec = (fetchedConfig) => {
|
||||
if(typeof constructorConfig !== "object") {
|
||||
return system
|
||||
@@ -138,6 +160,9 @@ module.exports = function SwaggerUI(opts) {
|
||||
} else if(mergedConfig.dom_id) {
|
||||
let domNode = document.querySelector(mergedConfig.dom_id)
|
||||
system.render(domNode, "App")
|
||||
} else if(mergedConfig.dom_id === null || mergedConfig.domNode === null) {
|
||||
// do nothing
|
||||
// this is useful for testing that does not need to do any rendering
|
||||
} else {
|
||||
console.error("Skipped rendering: no `dom_id` or `domNode` was specified")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { PureComponent, Component } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { List, fromJS } from "immutable"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import DebounceInput from "react-debounce-input"
|
||||
//import "less/json-schema-form"
|
||||
|
||||
const noop = ()=> {}
|
||||
@@ -11,6 +13,7 @@ const JsonSchemaPropShape = {
|
||||
keyName: PropTypes.any,
|
||||
fn: PropTypes.object.isRequired,
|
||||
schema: PropTypes.object,
|
||||
errors: ImPropTypes.list,
|
||||
required: PropTypes.bool,
|
||||
description: PropTypes.any
|
||||
}
|
||||
@@ -20,7 +23,8 @@ const JsonSchemaDefaultProps = {
|
||||
onChange: noop,
|
||||
schema: {},
|
||||
keyName: "",
|
||||
required: false
|
||||
required: false,
|
||||
errors: List()
|
||||
}
|
||||
|
||||
export class JsonSchemaForm extends Component {
|
||||
@@ -29,7 +33,7 @@ export class JsonSchemaForm extends Component {
|
||||
static defaultProps = JsonSchemaDefaultProps
|
||||
|
||||
render() {
|
||||
let { schema, value, onChange, getComponent, fn } = this.props
|
||||
let { schema, errors, value, onChange, getComponent, fn } = this.props
|
||||
|
||||
if(schema.toJS)
|
||||
schema = schema.toJS()
|
||||
@@ -37,7 +41,7 @@ export class JsonSchemaForm extends Component {
|
||||
let { type, format="" } = schema
|
||||
|
||||
let Comp = (format ? getComponent(`JsonSchema_${type}_${format}`) : getComponent(`JsonSchema_${type}`)) || getComponent("JsonSchema_string")
|
||||
return <Comp { ...this.props } fn={fn} getComponent={getComponent} value={value} onChange={onChange} schema={schema}/>
|
||||
return <Comp { ...this.props } errors={errors} fn={fn} getComponent={getComponent} value={value} onChange={onChange} schema={schema}/>
|
||||
}
|
||||
|
||||
}
|
||||
@@ -51,14 +55,15 @@ export class JsonSchema_string extends Component {
|
||||
}
|
||||
onEnumChange = (val) => this.props.onChange(val)
|
||||
render() {
|
||||
let { getComponent, value, schema, required, description } = this.props
|
||||
let { getComponent, value, schema, errors, required, description } = this.props
|
||||
let enumValue = schema["enum"]
|
||||
let errors = schema.errors || []
|
||||
|
||||
errors = errors.toJS ? errors.toJS() : []
|
||||
|
||||
if ( enumValue ) {
|
||||
const Select = getComponent("Select")
|
||||
return (<Select className={ errors.length ? "invalid" : ""}
|
||||
title={ errors.length ? errors : ""}
|
||||
title={ errors.length ? errors : ""}
|
||||
allowedValues={ enumValue }
|
||||
value={ value }
|
||||
allowEmptyValue={ !required }
|
||||
@@ -70,15 +75,18 @@ export class JsonSchema_string extends Component {
|
||||
if (schema["type"] === "file") {
|
||||
return (<Input type="file"
|
||||
className={ errors.length ? "invalid" : ""}
|
||||
title={ errors.length ? errors : ""}
|
||||
title={ errors.length ? errors : ""}
|
||||
onChange={ this.onChange }
|
||||
disabled={isDisabled}/>)
|
||||
}
|
||||
else {
|
||||
return (<Input type={ schema.format === "password" ? "password" : "text" }
|
||||
return (<DebounceInput
|
||||
type={ schema.format === "password" ? "password" : "text" }
|
||||
className={ errors.length ? "invalid" : ""}
|
||||
title={ errors.length ? errors : ""}
|
||||
title={ errors.length ? errors : ""}
|
||||
value={value}
|
||||
minLength={0}
|
||||
debounceTimeout={350}
|
||||
placeholder={description}
|
||||
onChange={ this.onChange }
|
||||
disabled={isDisabled}/>)
|
||||
@@ -131,9 +139,10 @@ export class JsonSchema_array extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
let { getComponent, required, schema, fn } = this.props
|
||||
let { getComponent, required, schema, errors, fn } = this.props
|
||||
|
||||
errors = errors.toJS ? errors.toJS() : []
|
||||
|
||||
let errors = schema.errors || []
|
||||
let itemSchema = fn.inferSchema(schema.items)
|
||||
|
||||
const JsonSchemaForm = getComponent("JsonSchemaForm")
|
||||
@@ -145,7 +154,7 @@ export class JsonSchema_array extends PureComponent {
|
||||
if ( enumValue ) {
|
||||
const Select = getComponent("Select")
|
||||
return (<Select className={ errors.length ? "invalid" : ""}
|
||||
title={ errors.length ? errors : ""}
|
||||
title={ errors.length ? errors : ""}
|
||||
multiple={ true }
|
||||
value={ value }
|
||||
allowedValues={ enumValue }
|
||||
@@ -160,7 +169,7 @@ export class JsonSchema_array extends PureComponent {
|
||||
let schema = Object.assign({}, itemSchema)
|
||||
if ( errors.length ) {
|
||||
let err = errors.filter((err) => err.index === i)
|
||||
if (err.length) schema.errors = [ err[0].error + i ]
|
||||
if (err.length) errors = [ err[0].error + i ]
|
||||
}
|
||||
return (
|
||||
<div key={i} className="json-schema-form-item">
|
||||
@@ -182,12 +191,13 @@ export class JsonSchema_boolean extends Component {
|
||||
|
||||
onEnumChange = (val) => this.props.onChange(val)
|
||||
render() {
|
||||
let { getComponent, value, schema } = this.props
|
||||
let errors = schema.errors || []
|
||||
let { getComponent, value, errors, schema } = this.props
|
||||
errors = errors.toJS ? errors.toJS() : []
|
||||
|
||||
const Select = getComponent("Select")
|
||||
|
||||
return (<Select className={ errors.length ? "invalid" : ""}
|
||||
title={ errors.length ? errors : ""}
|
||||
title={ errors.length ? errors : ""}
|
||||
value={ String(value) }
|
||||
allowedValues={ fromJS(schema.enum || ["true", "false"]) }
|
||||
allowEmptyValue={ !this.props.required }
|
||||
|
||||
@@ -5,6 +5,12 @@ import * as specWrapActionReplacements from "./spec-wrap-actions"
|
||||
|
||||
export default function() {
|
||||
return {
|
||||
afterLoad(system) {
|
||||
this.rootInjects = this.rootInjects || {}
|
||||
this.rootInjects.initOAuth = system.authActions.configureAuth
|
||||
this.rootInjects.preauthorizeApiKey = preauthorizeApiKey.bind(null, system)
|
||||
this.rootInjects.preauthorizeBasic = preauthorizeBasic.bind(null, system)
|
||||
},
|
||||
statePlugins: {
|
||||
auth: {
|
||||
reducers,
|
||||
@@ -17,3 +23,50 @@ export default function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function preauthorizeBasic(system, key, username, password) {
|
||||
const {
|
||||
authActions: { authorize },
|
||||
specSelectors: { specJson, isOAS3 }
|
||||
} = system
|
||||
|
||||
const definitionBase = isOAS3() ? ["components", "securitySchemes"] : ["securityDefinitions"]
|
||||
|
||||
const schema = specJson().getIn([...definitionBase, key])
|
||||
|
||||
if(!schema) {
|
||||
return null
|
||||
}
|
||||
|
||||
return authorize({
|
||||
[key]: {
|
||||
value: {
|
||||
username,
|
||||
password,
|
||||
},
|
||||
schema: schema.toJS()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function preauthorizeApiKey(system, key, value) {
|
||||
const {
|
||||
authActions: { authorize },
|
||||
specSelectors: { specJson, isOAS3 }
|
||||
} = system
|
||||
|
||||
const definitionBase = isOAS3() ? ["components", "securitySchemes"] : ["securityDefinitions"]
|
||||
|
||||
const schema = specJson().getIn([...definitionBase, key])
|
||||
|
||||
if(!schema) {
|
||||
return null
|
||||
}
|
||||
|
||||
return authorize({
|
||||
[key]: {
|
||||
value,
|
||||
schema: schema.toJS()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export default {
|
||||
let { auth, token } = payload
|
||||
let parsedAuth
|
||||
|
||||
auth.token = token
|
||||
auth.token = Object.assign({}, token)
|
||||
parsedAuth = fromJS(auth)
|
||||
|
||||
return state.setIn( [ "authorized", parsedAuth.get("name") ], parsedAuth )
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import YAML from "js-yaml"
|
||||
import yamlConfig from "../../../swagger-config.yaml"
|
||||
import yamlConfig from "root/swagger-config.yaml"
|
||||
import * as actions from "./actions"
|
||||
import * as selectors from "./selectors"
|
||||
import reducers from "./reducers"
|
||||
@@ -4,7 +4,7 @@ import { escapeDeepLinkPath } from "core/utils"
|
||||
let hasHashBeenParsed = false //TODO this forces code to only run once which may prevent scrolling if page not refreshed
|
||||
|
||||
|
||||
export const updateResolved = (ori, { layoutActions, getConfigs }) => (...args) => {
|
||||
export const updateJsonSpec = (ori, { layoutActions, getConfigs }) => (...args) => {
|
||||
ori(...args)
|
||||
|
||||
const isDeepLinkingEnabled = getConfigs().deepLinking
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { createSelector } from "reselect"
|
||||
import { Map } from "immutable"
|
||||
import win from "../window"
|
||||
|
||||
export default function downloadUrlPlugin (toolbox) {
|
||||
let { fn } = toolbox
|
||||
@@ -12,6 +13,7 @@ export default function downloadUrlPlugin (toolbox) {
|
||||
const config = getConfigs()
|
||||
url = url || specSelectors.url()
|
||||
specActions.updateLoadingStatus("loading")
|
||||
errActions.clear({source: "fetch"})
|
||||
fetch({
|
||||
url,
|
||||
loadSpec: true,
|
||||
@@ -26,11 +28,48 @@ export default function downloadUrlPlugin (toolbox) {
|
||||
function next(res) {
|
||||
if(res instanceof Error || res.status >= 400) {
|
||||
specActions.updateLoadingStatus("failed")
|
||||
return errActions.newThrownErr( new Error(res.statusText + " " + url) )
|
||||
errActions.newThrownErr(Object.assign( new Error((res.message || res.statusText) + " " + url), {source: "fetch"}))
|
||||
// Check if the failure was possibly due to CORS or mixed content
|
||||
if (!res.status && res instanceof Error) checkPossibleFailReasons()
|
||||
return
|
||||
}
|
||||
specActions.updateLoadingStatus("success")
|
||||
specActions.updateSpec(res.text)
|
||||
specActions.updateUrl(url)
|
||||
if(specSelectors.url() !== url) {
|
||||
specActions.updateUrl(url)
|
||||
}
|
||||
}
|
||||
|
||||
function checkPossibleFailReasons() {
|
||||
try {
|
||||
let specUrl
|
||||
|
||||
if("URL" in win ) {
|
||||
specUrl = new URL(url)
|
||||
} else {
|
||||
// legacy browser, use <a href> to parse the URL
|
||||
specUrl = document.createElement("a")
|
||||
specUrl.href = url
|
||||
}
|
||||
|
||||
if(specUrl.protocol !== "https:" && win.location.protocol === "https:") {
|
||||
const error = Object.assign(
|
||||
new Error(`Possible mixed-content issue? The page was loaded over https:// but a ${specUrl.protocol}// URL was specified. Check that you are not attempting to load mixed content.`),
|
||||
{source: "fetch"}
|
||||
)
|
||||
errActions.newThrownErr(error)
|
||||
return
|
||||
}
|
||||
if(specUrl.origin !== win.location.origin) {
|
||||
const error = Object.assign(
|
||||
new Error(`Possible cross-origin (CORS) issue? The URL origin (${specUrl.origin}) does not match the page (${win.location.origin}). Check the server returns the correct 'Access-Control-Allow-*' headers.`),
|
||||
{source: "fetch"}
|
||||
)
|
||||
errActions.newThrownErr(error)
|
||||
}
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
@@ -6,11 +6,12 @@ export const NEW_SPEC_ERR = "err_new_spec_err"
|
||||
export const NEW_SPEC_ERR_BATCH = "err_new_spec_err_batch"
|
||||
export const NEW_AUTH_ERR = "err_new_auth_err"
|
||||
export const CLEAR = "err_clear"
|
||||
export const CLEAR_BY = "err_clear_by"
|
||||
|
||||
export function newThrownErr(err, action) {
|
||||
export function newThrownErr(err) {
|
||||
return {
|
||||
type: NEW_THROWN_ERR,
|
||||
payload: { action, error: serializeError(err) }
|
||||
payload: serializeError(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,3 +50,11 @@ export function clear(filter = {}) {
|
||||
payload: filter
|
||||
}
|
||||
}
|
||||
|
||||
export function clearBy(filter = () => true) {
|
||||
// filter is a function
|
||||
return {
|
||||
type: CLEAR_BY,
|
||||
payload: filter
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,13 @@
|
||||
import reduce from "lodash/reduce"
|
||||
let request = require.context("./transformers/", true, /\.js$/)
|
||||
let errorTransformers = []
|
||||
import * as NotOfType from "./transformers/not-of-type"
|
||||
import * as ParameterOneOf from "./transformers/parameter-oneof"
|
||||
import * as StripInstance from "./transformers/strip-instance"
|
||||
|
||||
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
|
||||
})
|
||||
})
|
||||
const errorTransformers = [
|
||||
NotOfType,
|
||||
ParameterOneOf,
|
||||
StripInstance
|
||||
]
|
||||
|
||||
export default function transformErrors (errors, system) {
|
||||
let inputs = {
|
||||
@@ -47,10 +34,3 @@ export default function transformErrors (errors, system) {
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function toTitleCase(str) {
|
||||
return str
|
||||
.split("-")
|
||||
.map(substr => substr[0].toUpperCase() + substr.slice(1))
|
||||
.join("")
|
||||
}
|
||||
|
||||
@@ -4,12 +4,11 @@ import {
|
||||
NEW_SPEC_ERR,
|
||||
NEW_SPEC_ERR_BATCH,
|
||||
NEW_AUTH_ERR,
|
||||
CLEAR
|
||||
CLEAR,
|
||||
CLEAR_BY,
|
||||
} from "./actions"
|
||||
|
||||
import reject from "lodash/reject"
|
||||
|
||||
import Im, { fromJS, List } from "immutable"
|
||||
import { fromJS, List } from "immutable"
|
||||
|
||||
import transformErrors from "./error-transformers/hook"
|
||||
|
||||
@@ -65,11 +64,34 @@ export default function(system) {
|
||||
},
|
||||
|
||||
[CLEAR]: (state, { payload }) => {
|
||||
if(!payload) {
|
||||
return
|
||||
if(!payload || !state.get("errors")) {
|
||||
return state
|
||||
}
|
||||
// TODO: Rework, to use immutable only, no need for lodash
|
||||
let newErrors = Im.fromJS(reject((state.get("errors") || List()).toJS(), payload))
|
||||
|
||||
let newErrors = state.get("errors")
|
||||
.filter(err => {
|
||||
return err.keySeq().every(k => {
|
||||
const errValue = err.get(k)
|
||||
const filterValue = payload[k]
|
||||
|
||||
if(!filterValue) return true
|
||||
|
||||
return errValue !== filterValue
|
||||
})
|
||||
})
|
||||
return state.merge({
|
||||
errors: newErrors
|
||||
})
|
||||
},
|
||||
|
||||
[CLEAR_BY]: (state, { payload }) => {
|
||||
if(!payload || typeof payload !== "function") {
|
||||
return state
|
||||
}
|
||||
let newErrors = state.get("errors")
|
||||
.filter(err => {
|
||||
return payload(err)
|
||||
})
|
||||
return state.merge({
|
||||
errors: newErrors
|
||||
})
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import opsFilter from "./opsFilter"
|
||||
|
||||
export default function() {
|
||||
return {
|
||||
fn: {
|
||||
opsFilter
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function(taggedOps, phrase) {
|
||||
return taggedOps.filter((tagObj, tag) => tag.indexOf(phrase) !== -1)
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import ImPropTypes from "react-immutable-proptypes"
|
||||
import { fromJS } from "immutable"
|
||||
|
||||
const Callbacks = (props) => {
|
||||
let { callbacks, getComponent } = props
|
||||
let { callbacks, getComponent, specPath } = props
|
||||
// const Markdown = getComponent("Markdown")
|
||||
const OperationContainer = getComponent("OperationContainer", true)
|
||||
|
||||
@@ -28,6 +28,7 @@ const Callbacks = (props) => {
|
||||
tag={""}
|
||||
method={method}
|
||||
path={pathItemName}
|
||||
specPath={specPath.push(callbackName, pathItemName, method)}
|
||||
allowTryItOut={false}
|
||||
/>
|
||||
}) }
|
||||
@@ -42,8 +43,8 @@ const Callbacks = (props) => {
|
||||
|
||||
Callbacks.propTypes = {
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
callbacks: ImPropTypes.iterable.isRequired
|
||||
|
||||
callbacks: ImPropTypes.iterable.isRequired,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
}
|
||||
|
||||
export default Callbacks
|
||||
|
||||
@@ -27,6 +27,7 @@ export default class RequestBodyEditor extends PureComponent {
|
||||
|
||||
this.state = {
|
||||
isEditBox: false,
|
||||
userDidModify: false,
|
||||
value: ""
|
||||
}
|
||||
}
|
||||
@@ -59,6 +60,11 @@ export default class RequestBodyEditor extends PureComponent {
|
||||
this.onChange(this.sample(explicitMediaType))
|
||||
}
|
||||
|
||||
resetValueToSample = (explicitMediaType) => {
|
||||
this.setState({ userDidModify: false })
|
||||
this.setValueToSample(explicitMediaType)
|
||||
}
|
||||
|
||||
sample = (explicitMediaType) => {
|
||||
let { requestBody, mediaType } = this.props
|
||||
let schema = requestBody.getIn(["content", explicitMediaType || mediaType, "schema"]).toJS()
|
||||
@@ -78,6 +84,7 @@ export default class RequestBodyEditor extends PureComponent {
|
||||
const isJson = /json/i.test(mediaType)
|
||||
const inputValue = isJson ? e.target.value.trim() : e.target.value
|
||||
|
||||
this.setState({ userDidModify: true })
|
||||
this.onChange(inputValue)
|
||||
}
|
||||
|
||||
@@ -87,13 +94,14 @@ export default class RequestBodyEditor extends PureComponent {
|
||||
let {
|
||||
isExecute,
|
||||
getComponent,
|
||||
mediaType,
|
||||
} = this.props
|
||||
|
||||
const Button = getComponent("Button")
|
||||
const TextArea = getComponent("TextArea")
|
||||
const HighlightCode = getComponent("highlightCode")
|
||||
|
||||
let { value, isEditBox } = this.state
|
||||
let { value, isEditBox, userDidModify } = this.state
|
||||
|
||||
return (
|
||||
<div className="body-param">
|
||||
@@ -104,14 +112,18 @@ export default class RequestBodyEditor extends PureComponent {
|
||||
value={ value }/>)
|
||||
}
|
||||
<div className="body-param-options">
|
||||
{
|
||||
!isExecute ? null
|
||||
: <div className="body-param-edit">
|
||||
<Button className={isEditBox ? "btn cancel body-param__example-edit" : "btn edit body-param__example-edit"}
|
||||
onClick={this.toggleIsEditBox}>{ isEditBox ? "Cancel" : "Edit"}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
<div className="body-param-edit">
|
||||
{
|
||||
!isExecute ? null
|
||||
: <Button className={isEditBox ? "btn cancel body-param__example-edit" : "btn edit body-param__example-edit"}
|
||||
onClick={this.toggleIsEditBox}>{ isEditBox ? "Cancel" : "Edit"}
|
||||
</Button>
|
||||
|
||||
}
|
||||
{ userDidModify &&
|
||||
<Button className="btn ml3" onClick={() => { this.resetValueToSample(mediaType) }}>Reset</Button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -38,7 +38,7 @@ const RequestBody = ({
|
||||
expandDepth={1}
|
||||
isExecute={isExecute}
|
||||
schema={mediaTypeValue.get("schema")}
|
||||
specPath={[...specPath, "content", contentType]}
|
||||
specPath={specPath.push("content", contentType)}
|
||||
example={<RequestBodyEditor
|
||||
requestBody={requestBody}
|
||||
onChange={onChange}
|
||||
|
||||
@@ -35,7 +35,11 @@ export default class Servers extends React.Component {
|
||||
if(this.props.currentServer !== nextProps.currentServer) {
|
||||
// Server has changed, we may need to set default values
|
||||
let currentServerDefinition = servers
|
||||
.find(v => v.get("url") === nextProps.currentServer) || OrderedMap()
|
||||
.find(v => v.get("url") === nextProps.currentServer)
|
||||
|
||||
if(!currentServerDefinition) {
|
||||
return this.setServer(servers.first().get("url"))
|
||||
}
|
||||
|
||||
let currentServerVariableDefs = currentServerDefinition.get("variables") || OrderedMap()
|
||||
|
||||
@@ -90,6 +94,7 @@ export default class Servers extends React.Component {
|
||||
getEffectiveServerValue
|
||||
} = this.props
|
||||
|
||||
|
||||
let currentServerDefinition = servers.find(v => v.get("url") === currentServer) || OrderedMap()
|
||||
|
||||
let currentServerVariableDefs = currentServerDefinition.get("variables") || OrderedMap()
|
||||
|
||||
@@ -20,7 +20,7 @@ const state = state => {
|
||||
return state || Map()
|
||||
}
|
||||
|
||||
const nullSelector = createSelector(() => null)
|
||||
const nullSelector = createSelector(() => null)
|
||||
|
||||
const OAS3NullSelector = onlyOAS3(nullSelector)
|
||||
|
||||
@@ -72,10 +72,10 @@ export const servers = onlyOAS3(createSelector(
|
||||
|
||||
export const isOAS3 = (ori, system) => () => {
|
||||
const spec = system.getSystem().specSelectors.specJson()
|
||||
return isOAS3Helper(spec)
|
||||
return isOAS3Helper(Map.isMap(spec) ? spec : Map())
|
||||
}
|
||||
|
||||
export const isSwagger2 = (ori, system) => () => {
|
||||
const spec = system.getSystem().specSelectors.specJson()
|
||||
return isSwagger2Helper(spec)
|
||||
return isSwagger2Helper(Map.isMap(spec) ? spec : Map())
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class Parameters extends Component {
|
||||
fn: PropTypes.object.isRequired,
|
||||
tryItOutEnabled: PropTypes.bool,
|
||||
allowTryItOut: PropTypes.bool,
|
||||
specPath: PropTypes.array.isRequired,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
onTryoutClick: PropTypes.func,
|
||||
onCancelClick: PropTypes.func,
|
||||
onChangeKey: PropTypes.array,
|
||||
@@ -107,7 +107,7 @@ class Parameters extends Component {
|
||||
const { isOAS3 } = specSelectors
|
||||
|
||||
const requestBody = operation.get("requestBody")
|
||||
const requestBodySpecPath = [...specPath.slice(0, -1), "requestBody"] // remove the "parameters" part
|
||||
const requestBodySpecPath = specPath.slice(0, -1).push("requestBody") // remove the "parameters" part
|
||||
|
||||
return (
|
||||
<div className="opblock-section">
|
||||
@@ -143,7 +143,7 @@ class Parameters extends Component {
|
||||
eachMap(parameters, (parameter, i) => (
|
||||
<ParameterRow fn={ fn }
|
||||
getComponent={ getComponent }
|
||||
specPath={[...specPath, i]}
|
||||
specPath={specPath.push(i)}
|
||||
getConfigs={ getConfigs }
|
||||
param={ parameter }
|
||||
key={ parameter.get( "name" ) }
|
||||
@@ -161,7 +161,10 @@ class Parameters extends Component {
|
||||
</div> : "" }
|
||||
|
||||
{this.state.callbackVisible ? <div className="callbacks-container opblock-description-wrapper">
|
||||
<Callbacks callbacks={Map(operation.get("callbacks"))} />
|
||||
<Callbacks
|
||||
callbacks={Map(operation.get("callbacks"))}
|
||||
specPath={specPath.slice(0, -1).push("callbacks")}
|
||||
/>
|
||||
</div> : "" }
|
||||
{
|
||||
isOAS3() && requestBody && this.state.parametersVisible &&
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
let engaged = false
|
||||
|
||||
export default function() {
|
||||
|
||||
return {
|
||||
statePlugins: {
|
||||
spec: {
|
||||
wrapActions: {
|
||||
updateSpec: (ori) => (...args) => {
|
||||
engaged = true
|
||||
return ori(...args)
|
||||
},
|
||||
updateJsonSpec: (ori, system) => (...args) => {
|
||||
const cb = system.getConfigs().onComplete
|
||||
if(engaged && typeof cb === "function") {
|
||||
// call `onComplete` on next tick, which allows React to
|
||||
// reconcile the DOM before we notify the user
|
||||
setTimeout(cb, 0)
|
||||
engaged = false
|
||||
}
|
||||
|
||||
return ori(...args)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,10 @@ export const sampleFromSchema = (schema, config={}) => {
|
||||
let { type, example, properties, additionalProperties, items } = objectify(schema)
|
||||
let { includeReadOnly, includeWriteOnly } = config
|
||||
|
||||
if(example && example.$$ref) {
|
||||
delete example.$$ref
|
||||
}
|
||||
|
||||
if(example !== undefined)
|
||||
return example
|
||||
|
||||
@@ -69,6 +73,14 @@ export const sampleFromSchema = (schema, config={}) => {
|
||||
}
|
||||
|
||||
if(type === "array") {
|
||||
if(Array.isArray(items.anyOf)) {
|
||||
return items.anyOf.map(i => sampleFromSchema(i, config))
|
||||
}
|
||||
|
||||
if(Array.isArray(items.oneOf)) {
|
||||
return items.oneOf.map(i => sampleFromSchema(i, config))
|
||||
}
|
||||
|
||||
return [ sampleFromSchema(items, config) ]
|
||||
}
|
||||
|
||||
@@ -181,6 +193,9 @@ export const sampleXmlFromSchema = (schema, config={}) => {
|
||||
example = example || {}
|
||||
|
||||
for (let propName in props) {
|
||||
if (!props.hasOwnProperty(propName)) {
|
||||
continue
|
||||
}
|
||||
if ( props[propName].readOnly && !includeReadOnly ) {
|
||||
continue
|
||||
}
|
||||
@@ -199,7 +214,9 @@ export const sampleXmlFromSchema = (schema, config={}) => {
|
||||
|| 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]
|
||||
if(props[propName].example === undefined && example[propName] !== undefined) {
|
||||
props[propName].example = example[propName]
|
||||
}
|
||||
let t = sampleXmlFromSchema(props[propName])
|
||||
if (Array.isArray(t)) {
|
||||
res[displayName] = res[displayName].concat(t)
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import YAML from "js-yaml"
|
||||
import { Map } from "immutable"
|
||||
import parseUrl from "url-parse"
|
||||
import serializeError from "serialize-error"
|
||||
import isString from "lodash/isString"
|
||||
import debounce from "lodash/debounce"
|
||||
import set from "lodash/set"
|
||||
import { isJSONObject } from "core/utils"
|
||||
|
||||
// Actions conform to FSA (flux-standard-actions)
|
||||
@@ -19,8 +22,9 @@ 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_OPERATION_META_VALUE = "spec_update_operation_meta_value"
|
||||
export const UPDATE_RESOLVED = "spec_update_resolved"
|
||||
export const UPDATE_RESOLVED_SUBTREE = "spec_update_resolved_subtree"
|
||||
export const SET_SCHEME = "set_scheme"
|
||||
|
||||
const toStr = (str) => isString(str) ? str : ""
|
||||
@@ -68,13 +72,20 @@ export const parseToJson = (str) => ({specActions, specSelectors, errActions}) =
|
||||
line: e.mark && e.mark.line ? e.mark.line + 1 : undefined
|
||||
})
|
||||
}
|
||||
if(json) {
|
||||
if(json && typeof json === "object") {
|
||||
return specActions.updateJsonSpec(json)
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
let hasWarnedAboutResolveSpecDeprecation = false
|
||||
|
||||
export const resolveSpec = (json, url) => ({specActions, specSelectors, errActions, fn: { fetch, resolve, AST }, getConfigs}) => {
|
||||
if(!hasWarnedAboutResolveSpecDeprecation) {
|
||||
console.warn(`specActions.resolveSpec is deprecated since v3.10.0 and will be removed in v4.0.0; use requestResolvedSubtree instead!`)
|
||||
hasWarnedAboutResolveSpecDeprecation = true
|
||||
}
|
||||
|
||||
const {
|
||||
modelPropertyMacro,
|
||||
parameterMacro,
|
||||
@@ -105,8 +116,7 @@ export const resolveSpec = (json, url) => ({specActions, specSelectors, errActio
|
||||
errActions.clear({
|
||||
type: "thrown"
|
||||
})
|
||||
|
||||
if(errors.length > 0) {
|
||||
if(Array.isArray(errors) && errors.length > 0) {
|
||||
let preparedErrors = errors
|
||||
.map(err => {
|
||||
console.error(err)
|
||||
@@ -125,6 +135,98 @@ export const resolveSpec = (json, url) => ({specActions, specSelectors, errActio
|
||||
})
|
||||
}
|
||||
|
||||
let requestBatch = []
|
||||
|
||||
const debResolveSubtrees = debounce(async () => {
|
||||
const system = requestBatch.system // Just a reference to the "latest" system
|
||||
|
||||
if(!system) {
|
||||
console.error("debResolveSubtrees: don't have a system to operate on, aborting.")
|
||||
return
|
||||
}
|
||||
const {
|
||||
errActions,
|
||||
errSelectors,
|
||||
fn: {
|
||||
resolveSubtree,
|
||||
AST: { getLineNumberForPath }
|
||||
},
|
||||
specSelectors,
|
||||
specActions,
|
||||
} = system
|
||||
|
||||
if(!resolveSubtree) {
|
||||
console.error("Error: Swagger-Client did not provide a `resolveSubtree` method, doing nothing.")
|
||||
return
|
||||
}
|
||||
|
||||
const specStr = specSelectors.specStr()
|
||||
|
||||
const {
|
||||
modelPropertyMacro,
|
||||
parameterMacro,
|
||||
requestInterceptor,
|
||||
responseInterceptor
|
||||
} = system.getConfigs()
|
||||
|
||||
try {
|
||||
var batchResult = await requestBatch.reduce(async (prev, path) => {
|
||||
const { resultMap, specWithCurrentSubtrees } = await prev
|
||||
const { errors, spec } = await resolveSubtree(specWithCurrentSubtrees, path, {
|
||||
baseDoc: specSelectors.url(),
|
||||
modelPropertyMacro,
|
||||
parameterMacro,
|
||||
requestInterceptor,
|
||||
responseInterceptor
|
||||
})
|
||||
|
||||
if(errSelectors.allErrors().size) {
|
||||
errActions.clear({
|
||||
type: "thrown"
|
||||
})
|
||||
}
|
||||
|
||||
if(Array.isArray(errors) && errors.length > 0) {
|
||||
let preparedErrors = errors
|
||||
.map(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)
|
||||
}
|
||||
|
||||
set(resultMap, path, spec)
|
||||
set(specWithCurrentSubtrees, path, spec)
|
||||
|
||||
return {
|
||||
resultMap,
|
||||
specWithCurrentSubtrees
|
||||
}
|
||||
}, Promise.resolve({
|
||||
resultMap: (specSelectors.specResolvedSubtree([]) || Map()).toJS(),
|
||||
specWithCurrentSubtrees: specSelectors.specJson().toJS()
|
||||
}))
|
||||
|
||||
delete requestBatch.system
|
||||
requestBatch = [] // Clear stack
|
||||
} catch(e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
specActions.updateResolvedSubtree([], batchResult.resultMap)
|
||||
}, 35)
|
||||
|
||||
export const requestResolvedSubtree = path => system => {
|
||||
requestBatch.push(path)
|
||||
requestBatch.system = system
|
||||
debResolveSubtrees()
|
||||
}
|
||||
|
||||
export function changeParam( path, paramName, paramIn, value, isXml ){
|
||||
return {
|
||||
type: UPDATE_PARAM,
|
||||
@@ -132,6 +234,23 @@ export function changeParam( path, paramName, paramIn, value, isXml ){
|
||||
}
|
||||
}
|
||||
|
||||
export const updateResolvedSubtree = (path, value) => {
|
||||
return {
|
||||
type: UPDATE_RESOLVED_SUBTREE,
|
||||
payload: { path, value }
|
||||
}
|
||||
}
|
||||
|
||||
export const invalidateResolvedSubtreeCache = () => {
|
||||
return {
|
||||
type: UPDATE_RESOLVED_SUBTREE,
|
||||
payload: {
|
||||
path: [],
|
||||
value: Map()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const validateParams = ( payload, isOAS3 ) =>{
|
||||
return {
|
||||
type: VALIDATE_PARAMS,
|
||||
@@ -151,14 +270,14 @@ export function clearValidateParams( payload ){
|
||||
|
||||
export function changeConsumesValue(path, value) {
|
||||
return {
|
||||
type: UPDATE_OPERATION_VALUE,
|
||||
type: UPDATE_OPERATION_META_VALUE,
|
||||
payload:{ path, value, key: "consumes_value" }
|
||||
}
|
||||
}
|
||||
|
||||
export function changeProducesValue(path, value) {
|
||||
return {
|
||||
type: UPDATE_OPERATION_VALUE,
|
||||
type: UPDATE_OPERATION_META_VALUE,
|
||||
payload:{ path, value, key: "produces_value" }
|
||||
}
|
||||
}
|
||||
@@ -252,6 +371,7 @@ export const executeRequest = (req) =>
|
||||
// track duration of request
|
||||
const startTime = Date.now()
|
||||
|
||||
|
||||
return fn.execute(req)
|
||||
.then( res => {
|
||||
res.duration = Date.now() - startTime
|
||||
@@ -268,13 +388,22 @@ export const executeRequest = (req) =>
|
||||
// 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 spec = specSelectors.specJsonWithResolvedSubtrees().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 })
|
||||
return specActions.executeRequest({
|
||||
...extras,
|
||||
fetch,
|
||||
spec,
|
||||
pathName: path,
|
||||
method, parameters,
|
||||
requestContentType,
|
||||
scheme,
|
||||
responseContentType
|
||||
})
|
||||
}
|
||||
|
||||
export function clearResponse (path, method) {
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { fromJS } from "immutable"
|
||||
import { fromJS, List } from "immutable"
|
||||
import { fromJSOrdered, validateParam } from "core/utils"
|
||||
import win from "../../window"
|
||||
|
||||
// selector-in-reducer is suboptimal, but `operationWithMeta` is more of a helper
|
||||
import {
|
||||
operationWithMeta
|
||||
} from "./selectors"
|
||||
|
||||
import {
|
||||
UPDATE_SPEC,
|
||||
UPDATE_URL,
|
||||
@@ -12,7 +17,8 @@ import {
|
||||
SET_REQUEST,
|
||||
SET_MUTATED_REQUEST,
|
||||
UPDATE_RESOLVED,
|
||||
UPDATE_OPERATION_VALUE,
|
||||
UPDATE_RESOLVED_SUBTREE,
|
||||
UPDATE_OPERATION_META_VALUE,
|
||||
CLEAR_RESPONSE,
|
||||
CLEAR_REQUEST,
|
||||
CLEAR_VALIDATE_PARAMS,
|
||||
@@ -39,38 +45,38 @@ export default {
|
||||
return state.setIn(["resolved"], fromJSOrdered(action.payload))
|
||||
},
|
||||
|
||||
[UPDATE_PARAM]: ( state, {payload} ) => {
|
||||
let { path, paramName, paramIn, value, isXml } = payload
|
||||
[UPDATE_RESOLVED_SUBTREE]: (state, action) => {
|
||||
const { value, path } = action.payload
|
||||
return state.setIn(["resolvedSubtrees", ...path], fromJSOrdered(value))
|
||||
},
|
||||
|
||||
return state.updateIn( [ "resolved", "paths", ...path, "parameters" ], fromJS([]), parameters => {
|
||||
const index = parameters.findIndex(p => p.get( "name" ) === paramName && p.get("in") === paramIn )
|
||||
if (!(value instanceof win.File)) {
|
||||
value = fromJSOrdered( value )
|
||||
}
|
||||
return parameters.setIn( [ index, isXml ? "value_xml" : "value" ], value)
|
||||
})
|
||||
[UPDATE_PARAM]: ( state, {payload} ) => {
|
||||
let { path: pathMethod, paramName, paramIn, value, isXml } = payload
|
||||
|
||||
const valueKey = isXml ? "value_xml" : "value"
|
||||
|
||||
return state.setIn(
|
||||
["meta", "paths", ...pathMethod, "parameters", `${paramName}.${paramIn}`, valueKey],
|
||||
value
|
||||
)
|
||||
},
|
||||
|
||||
[VALIDATE_PARAMS]: ( state, { payload: { pathMethod, isOAS3 } } ) => {
|
||||
let operation = state.getIn( [ "resolved", "paths", ...pathMethod ] )
|
||||
let isXml = /xml/i.test(operation.get("consumes_value"))
|
||||
let meta = state.getIn( [ "meta", "paths", ...pathMethod ], fromJS({}) )
|
||||
let isXml = /xml/i.test(meta.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, isOAS3)
|
||||
parameters.setIn([i, "errors"], fromJS(errors))
|
||||
}
|
||||
})
|
||||
const op = operationWithMeta(state, ...pathMethod)
|
||||
|
||||
return state.updateIn(["meta", "paths", ...pathMethod, "parameters"], fromJS({}), paramMeta => {
|
||||
return op.get("parameters", List()).reduce((res, param) => {
|
||||
const errors = validateParam(param, isXml, isOAS3)
|
||||
return res.setIn([`${param.get("name")}.${param.get("in")}`, "errors"], fromJS(errors))
|
||||
}, paramMeta)
|
||||
})
|
||||
},
|
||||
[CLEAR_VALIDATE_PARAMS]: ( state, { payload: { pathMethod } } ) => {
|
||||
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({}))
|
||||
}
|
||||
})
|
||||
return state.updateIn( [ "meta", "paths", ...pathMethod, "parameters" ], fromJS([]), parameters => {
|
||||
return parameters.map(param => param.set("errors", fromJS([])))
|
||||
})
|
||||
},
|
||||
|
||||
@@ -107,12 +113,17 @@ export default {
|
||||
return state.setIn( [ "mutatedRequests", path, method ], fromJSOrdered(req))
|
||||
},
|
||||
|
||||
[UPDATE_OPERATION_VALUE]: (state, { payload: { path, value, key } }) => {
|
||||
let operationPath = ["resolved", "paths", ...path]
|
||||
if(!state.getIn(operationPath)) {
|
||||
[UPDATE_OPERATION_META_VALUE]: (state, { payload: { path, value, key } }) => {
|
||||
// path is a pathMethod tuple... can't change the name now.
|
||||
let operationPath = ["paths", ...path]
|
||||
let metaPath = ["meta", "paths", ...path]
|
||||
|
||||
if(!state.getIn(["json", ...operationPath]) && !state.getIn(["resolved", ...operationPath])) {
|
||||
// do nothing if the operation does not exist
|
||||
return state
|
||||
}
|
||||
return state.setIn([...operationPath, key], fromJS(value))
|
||||
|
||||
return state.setIn([...metaPath, key], fromJS(value))
|
||||
},
|
||||
|
||||
[CLEAR_RESPONSE]: (state, { payload: { path, method } } ) =>{
|
||||
|
||||
@@ -4,6 +4,10 @@ import { fromJS, Set, Map, OrderedMap, List } from "immutable"
|
||||
|
||||
const DEFAULT_TAG = "default"
|
||||
|
||||
const OPERATION_METHODS = [
|
||||
"get", "put", "post", "delete", "options", "head", "patch", "trace"
|
||||
]
|
||||
|
||||
const state = state => {
|
||||
return state || Map()
|
||||
}
|
||||
@@ -38,9 +42,40 @@ export const specResolved = createSelector(
|
||||
spec => spec.get("resolved", Map())
|
||||
)
|
||||
|
||||
export const specResolvedSubtree = (state, path) => {
|
||||
return state.getIn(["resolvedSubtrees", ...path], undefined)
|
||||
}
|
||||
|
||||
const mergerFn = (oldVal, newVal) => {
|
||||
if(Map.isMap(oldVal) && Map.isMap(newVal)) {
|
||||
if(newVal.get("$$ref")) {
|
||||
// resolver artifacts indicated that this key was directly resolved
|
||||
// so we should drop the old value entirely
|
||||
return newVal
|
||||
}
|
||||
|
||||
return Map().mergeWith(
|
||||
mergerFn,
|
||||
oldVal,
|
||||
newVal
|
||||
)
|
||||
}
|
||||
|
||||
return newVal
|
||||
}
|
||||
|
||||
export const specJsonWithResolvedSubtrees = createSelector(
|
||||
state,
|
||||
spec => Map().mergeWith(
|
||||
mergerFn,
|
||||
spec.get("json"),
|
||||
spec.get("resolvedSubtrees")
|
||||
)
|
||||
)
|
||||
|
||||
// Default Spec ( as an object )
|
||||
export const spec = state => {
|
||||
let res = specResolved(state)
|
||||
let res = specJson(state)
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -95,6 +130,9 @@ export const operations = createSelector(
|
||||
return {}
|
||||
}
|
||||
path.forEach((operation, method) => {
|
||||
if(OPERATION_METHODS.indexOf(method) < 0) {
|
||||
return
|
||||
}
|
||||
list = list.push(fromJS({
|
||||
path: pathName,
|
||||
method,
|
||||
@@ -130,7 +168,9 @@ export const securityDefinitions = createSelector(
|
||||
|
||||
|
||||
export const findDefinition = ( state, name ) => {
|
||||
return specResolved(state).getIn(["definitions", name], null)
|
||||
const resolvedRes = state.getIn(["resolvedSubtrees", "definitions", name], null)
|
||||
const unresolvedRes = state.getIn(["json", "definitions", name], null)
|
||||
return resolvedRes || unresolvedRes || null
|
||||
}
|
||||
|
||||
export const definitions = createSelector(
|
||||
@@ -254,10 +294,40 @@ export const allowTryItOutFor = () => {
|
||||
return true
|
||||
}
|
||||
|
||||
export const operationWithMeta = (state, path, method) => {
|
||||
const op = specJsonWithResolvedSubtrees(state).getIn(["paths", path, method], Map())
|
||||
const meta = state.getIn(["meta", "paths", path, method], Map())
|
||||
|
||||
const mergedParams = op.get("parameters", List()).map((param) => {
|
||||
return Map().merge(
|
||||
param,
|
||||
meta.getIn(["parameters", `${param.get("name")}.${param.get("in")}`])
|
||||
)
|
||||
})
|
||||
|
||||
return Map()
|
||||
.merge(op, meta)
|
||||
.set("parameters", mergedParams)
|
||||
}
|
||||
|
||||
export const parameterWithMeta = (state, pathMethod, paramName, paramIn) => {
|
||||
const opParams = specJsonWithResolvedSubtrees(state).getIn(["paths", ...pathMethod, "parameters"], Map())
|
||||
const metaParams = state.getIn(["meta", "paths", ...pathMethod, "parameters"], Map())
|
||||
|
||||
const mergedParams = opParams.map((param) => {
|
||||
return Map().merge(
|
||||
param,
|
||||
metaParams.get(`${param.get("name")}.${param.get("in")}`)
|
||||
)
|
||||
})
|
||||
|
||||
return mergedParams.find(param => param.get("in") === paramIn && param.get("name") === paramName, Map())
|
||||
}
|
||||
|
||||
// Get the parameter value by parameter name
|
||||
export function getParameter(state, pathMethod, name, inType) {
|
||||
pathMethod = pathMethod || []
|
||||
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
|
||||
let params = state.getIn(["meta", "paths", ...pathMethod, "parameters"], fromJS([]))
|
||||
return params.find( (p) => {
|
||||
return Map.isMap(p) && p.get("name") === name && p.get("in") === inType
|
||||
}) || Map() // Always return a map
|
||||
@@ -274,8 +344,9 @@ export const hasHost = createSelector(
|
||||
// Get the parameter values, that the user filled out
|
||||
export function parameterValues(state, pathMethod, isXml) {
|
||||
pathMethod = pathMethod || []
|
||||
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
|
||||
return params.reduce( (hash, p) => {
|
||||
// let paramValues = state.getIn(["meta", "paths", ...pathMethod, "parameters"], fromJS([]))
|
||||
let paramValues = operationWithMeta(state, ...pathMethod).get("parameters", List())
|
||||
return paramValues.reduce( (hash, p) => {
|
||||
let value = isXml && p.get("in") === "body" ? p.get("value_xml") : p.get("value")
|
||||
return hash.set(`${p.get("in")}.${p.get("name")}`, value)
|
||||
}, fromJS({}))
|
||||
@@ -298,11 +369,14 @@ export function parametersIncludeType(parameters, typeValue="") {
|
||||
// Get the consumes/produces value that the user selected
|
||||
export function contentTypeValues(state, pathMethod) {
|
||||
pathMethod = pathMethod || []
|
||||
let op = spec(state).getIn(["paths", ...pathMethod], fromJS({}))
|
||||
let op = specJsonWithResolvedSubtrees(state).getIn(["paths", ...pathMethod], fromJS({}))
|
||||
let meta = state.getIn(["meta", "paths", ...pathMethod], fromJS({}))
|
||||
let producesValue = currentProducesFor(state, pathMethod)
|
||||
|
||||
const parameters = op.get("parameters") || new List()
|
||||
|
||||
const requestContentType = (
|
||||
op.get("consumes_value") ? op.get("consumes_value")
|
||||
meta.get("consumes_value") ? meta.get("consumes_value")
|
||||
: parametersIncludeType(parameters, "file") ? "multipart/form-data"
|
||||
: parametersIncludeType(parameters, "formData") ? "application/x-www-form-urlencoded"
|
||||
: undefined
|
||||
@@ -310,14 +384,32 @@ export function contentTypeValues(state, pathMethod) {
|
||||
|
||||
return fromJS({
|
||||
requestContentType,
|
||||
responseContentType: op.get("produces_value")
|
||||
responseContentType: producesValue
|
||||
})
|
||||
}
|
||||
|
||||
// Get the consumes/produces by path
|
||||
export function operationConsumes(state, pathMethod) {
|
||||
pathMethod = pathMethod || []
|
||||
return spec(state).getIn(["paths", ...pathMethod, "consumes"], fromJS({}))
|
||||
return specJsonWithResolvedSubtrees(state).getIn(["paths", ...pathMethod, "consumes"], fromJS({}))
|
||||
}
|
||||
|
||||
// Get the currently selected produces value for an operation
|
||||
export function currentProducesFor(state, pathMethod) {
|
||||
pathMethod = pathMethod || []
|
||||
|
||||
const operation = specJsonWithResolvedSubtrees(state).getIn([ "paths", ...pathMethod], null)
|
||||
|
||||
if(operation === null) {
|
||||
// return nothing if the operation does not exist
|
||||
return
|
||||
}
|
||||
|
||||
const currentProducesValue = state.getIn(["meta", "paths", ...pathMethod, "produces_value"], null)
|
||||
const firstProducesArrayItem = operation.getIn(["produces", 0], null)
|
||||
|
||||
return currentProducesValue || firstProducesArrayItem || "application/json"
|
||||
|
||||
}
|
||||
|
||||
export const operationScheme = ( state, path, method ) => {
|
||||
@@ -334,10 +426,10 @@ export const canExecuteScheme = ( state, path, method ) => {
|
||||
|
||||
export const validateBeforeExecute = ( state, pathMethod ) => {
|
||||
pathMethod = pathMethod || []
|
||||
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
|
||||
let paramValues = state.getIn(["meta", "paths", ...pathMethod, "parameters"], fromJS([]))
|
||||
let isValid = true
|
||||
|
||||
params.forEach( (p) => {
|
||||
paramValues.forEach( (p) => {
|
||||
let errors = p.get("errors")
|
||||
if ( errors && errors.count() ) {
|
||||
isValid = false
|
||||
|
||||
@@ -5,7 +5,7 @@ export const updateSpec = (ori, {specActions}) => (...args) => {
|
||||
|
||||
export const updateJsonSpec = (ori, {specActions}) => (...args) => {
|
||||
ori(...args)
|
||||
specActions.resolveSpec(...args)
|
||||
specActions.invalidateResolvedSubtreeCache()
|
||||
}
|
||||
|
||||
// Log the request ( just for debugging, shouldn't affect prod )
|
||||
@@ -16,4 +16,4 @@ export const executeRequest = (ori, { specActions }) => (req) => {
|
||||
|
||||
export const validateParams = (ori, { specSelectors }) => (req) => {
|
||||
return ori(req, specSelectors.isOAS3())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
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
|
||||
})
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as components from "./components"
|
||||
|
||||
import SplitPaneMode from "./components/split-pane-mode"
|
||||
export default function SplitPaneModePlugin() {
|
||||
return {
|
||||
// statePlugins: {
|
||||
@@ -9,6 +8,8 @@ export default function SplitPaneModePlugin() {
|
||||
// }
|
||||
// },
|
||||
|
||||
components,
|
||||
components: {
|
||||
SplitPaneMode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ module.exports = function({ configs }) {
|
||||
buildRequest: Swagger.buildRequest,
|
||||
execute: Swagger.execute,
|
||||
resolve: Swagger.resolve,
|
||||
resolveSubtree: Swagger.resolveSubtree,
|
||||
serializeRes: Swagger.serializeRes,
|
||||
opId: Swagger.helpers.opId
|
||||
}
|
||||
|
||||
@@ -10,8 +10,10 @@ import auth from "core/plugins/auth"
|
||||
import util from "core/plugins/util"
|
||||
import SplitPaneModePlugin from "core/plugins/split-pane-mode"
|
||||
import downloadUrlPlugin from "core/plugins/download-url"
|
||||
import configsPlugin from "plugins/configs"
|
||||
import configsPlugin from "core/plugins/configs"
|
||||
import deepLinkingPlugin from "core/plugins/deep-linking"
|
||||
import filter from "core/plugins/filter"
|
||||
import onComplete from "core/plugins/on-complete"
|
||||
|
||||
import OperationContainer from "core/containers/OperationContainer"
|
||||
|
||||
@@ -44,7 +46,10 @@ import Headers from "core/components/headers"
|
||||
import Errors from "core/components/errors"
|
||||
import ContentType from "core/components/content-type"
|
||||
import Overview from "core/components/overview"
|
||||
import Info from "core/components/info"
|
||||
import Info, {
|
||||
InfoUrl,
|
||||
InfoBasePath
|
||||
} from "core/components/info"
|
||||
import Footer from "core/components/footer"
|
||||
import ParamBody from "core/components/param-body"
|
||||
import Curl from "core/components/curl"
|
||||
@@ -123,7 +128,9 @@ export default function() {
|
||||
OperationExtRow,
|
||||
ParameterExt,
|
||||
OperationContainer,
|
||||
DeepLink
|
||||
DeepLink,
|
||||
InfoUrl,
|
||||
InfoBasePath
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +159,8 @@ export default function() {
|
||||
ast,
|
||||
SplitPaneModePlugin,
|
||||
downloadUrlPlugin,
|
||||
deepLinkingPlugin
|
||||
deepLinkingPlugin,
|
||||
filter,
|
||||
onComplete
|
||||
]
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ function createStoreWithMiddleware(rootReducer, initialState, getSystem) {
|
||||
// createLogger( {
|
||||
// stateTransformer: state => state && state.toJS()
|
||||
// } ),
|
||||
// errorLog(getSystem), Need to properly handle errors that occur during a render. Ie: let them be...
|
||||
systemThunkMiddleware( getSystem )
|
||||
]
|
||||
|
||||
@@ -68,6 +67,12 @@ export default class Store {
|
||||
if(rebuild) {
|
||||
this.buildSystem()
|
||||
}
|
||||
|
||||
const needAnotherRebuild = callAfterLoad.call(this.system, plugins, this.getSystem())
|
||||
|
||||
if(needAnotherRebuild) {
|
||||
this.buildSystem()
|
||||
}
|
||||
}
|
||||
|
||||
buildSystem(buildReducer=true) {
|
||||
@@ -170,7 +175,7 @@ export default class Store {
|
||||
if(!isFn(newAction)) {
|
||||
throw new TypeError("wrapActions needs to return a function that returns a new function (ie the wrapped action)")
|
||||
}
|
||||
return newAction
|
||||
return wrapWithTryCatch(newAction)
|
||||
}, action || Function.prototype)
|
||||
})
|
||||
}
|
||||
@@ -250,11 +255,11 @@ export default class Store {
|
||||
|
||||
return objMap(obj, (fn) => {
|
||||
return (...args) => {
|
||||
let res = fn.apply(null, [getNestedState(), ...args])
|
||||
let res = wrapWithTryCatch(fn).apply(null, [getNestedState(), ...args])
|
||||
|
||||
// If a selector returns a function, give it the system - for advanced usage
|
||||
if(typeof(res) === "function")
|
||||
res = res(getSystem())
|
||||
res = wrapWithTryCatch(res)(getSystem())
|
||||
|
||||
return res
|
||||
}
|
||||
@@ -320,6 +325,25 @@ function combinePlugins(plugins, toolbox) {
|
||||
return {}
|
||||
}
|
||||
|
||||
function callAfterLoad(plugins, system, { hasLoaded } = {}) {
|
||||
let calledSomething = hasLoaded
|
||||
if(isObject(plugins) && !isArray(plugins)) {
|
||||
if(typeof plugins.afterLoad === "function") {
|
||||
calledSomething = true
|
||||
wrapWithTryCatch(plugins.afterLoad).call(this, system)
|
||||
}
|
||||
}
|
||||
|
||||
if(isFunc(plugins))
|
||||
return callAfterLoad.call(this, plugins(system), system, { hasLoaded: calledSomething })
|
||||
|
||||
if(isArray(plugins)) {
|
||||
return plugins.map(plugin => callAfterLoad.call(this, plugin, system, { hasLoaded: calledSomething }))
|
||||
}
|
||||
|
||||
return calledSomething
|
||||
}
|
||||
|
||||
// Wraps deepExtend, to account for certain fields, being wrappers.
|
||||
// Ie: we need to convert some fields into arrays, and append to them.
|
||||
// Rather than overwrite
|
||||
@@ -411,14 +435,36 @@ function makeReducer(reducerObj) {
|
||||
if(!reducerObj)
|
||||
return state
|
||||
|
||||
let redFn = reducerObj[action.type]
|
||||
let redFn = (reducerObj[action.type])
|
||||
if(redFn) {
|
||||
return redFn(state, action)
|
||||
const res = wrapWithTryCatch(redFn)(state, action)
|
||||
// If the try/catch wrapper kicks in, we'll get null back...
|
||||
// in that case, we want to avoid making any changes to state
|
||||
return res === null ? state : res
|
||||
}
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
function wrapWithTryCatch(fn, {
|
||||
logErrors = true
|
||||
} = {}) {
|
||||
if(typeof fn !== "function") {
|
||||
return fn
|
||||
}
|
||||
|
||||
return function(...args) {
|
||||
try {
|
||||
return fn.call(this, ...args)
|
||||
} catch(e) {
|
||||
if(logErrors) {
|
||||
console.error(e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function configureStore(rootReducer, initialState, getSystem) {
|
||||
const store = createStoreWithMiddleware(rootReducer, initialState, getSystem)
|
||||
|
||||
|
||||
@@ -126,15 +126,6 @@ export function systemThunkMiddleware(getSystem) {
|
||||
}
|
||||
}
|
||||
|
||||
export const errorLog = getSystem => () => next => action => {
|
||||
try{
|
||||
next( action )
|
||||
}
|
||||
catch( e ) {
|
||||
getSystem().errActions.newThrownErr( e, action )
|
||||
}
|
||||
}
|
||||
|
||||
export function defaultStatusCode ( responses ) {
|
||||
let codes = responses.keySeq()
|
||||
return codes.contains(DEFAULT_REPONSE_KEY) ? DEFAULT_REPONSE_KEY : codes.filter( key => (key+"")[0] === "2").sort().first()
|
||||
@@ -351,6 +342,17 @@ export function mapToList(map, keyNames="key", collectedKeys=Im.Map()) {
|
||||
return list
|
||||
}
|
||||
|
||||
export function extractFileNameFromContentDispositionHeader(value){
|
||||
let responseFilename = /filename="([^;]*);?"/i.exec(value)
|
||||
if (responseFilename === null) {
|
||||
responseFilename = /filename=([^;]*);?/i.exec(value)
|
||||
}
|
||||
if (responseFilename !== null && responseFilename.length > 1) {
|
||||
return responseFilename[1]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// PascalCase, aka UpperCamelCase
|
||||
export function pascalCase(str) {
|
||||
return upperFirst(camelCase(str))
|
||||
@@ -442,7 +444,8 @@ export const validateDateTime = (val) => {
|
||||
}
|
||||
|
||||
export const validateGuid = (val) => {
|
||||
if (!/^[{(]?[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}[)}]?$/.test(val)) {
|
||||
val = val.toString().toLowerCase()
|
||||
if (!/^[{(]?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[)}]?$/.test(val)) {
|
||||
return "Value must be a Guid"
|
||||
}
|
||||
}
|
||||
@@ -556,7 +559,7 @@ export const validateParam = (param, isXml, isOAS3 = false) => {
|
||||
} else if ( type === "array" ) {
|
||||
let itemType
|
||||
|
||||
if ( !value.count() ) { return errors }
|
||||
if ( !listCheck || !value.count() ) { return errors }
|
||||
|
||||
itemType = paramDetails.getIn(["items", "type"])
|
||||
|
||||
@@ -616,14 +619,23 @@ export const parseSearch = () => {
|
||||
let params = search.substr(1).split("&")
|
||||
|
||||
for (let i in params) {
|
||||
if (!params.hasOwnProperty(i)) {
|
||||
continue
|
||||
}
|
||||
i = params[i].split("=")
|
||||
map[decodeURIComponent(i[0])] = decodeURIComponent(i[1])
|
||||
map[decodeURIComponent(i[0])] = (i[1] && decodeURIComponent(i[1])) || ""
|
||||
}
|
||||
}
|
||||
|
||||
return map
|
||||
}
|
||||
|
||||
export const serializeSearch = (searchMap) => {
|
||||
return Object.keys(searchMap).map(k => {
|
||||
return encodeURIComponent(k) + "=" + encodeURIComponent(searchMap[k])
|
||||
}).join("&")
|
||||
}
|
||||
|
||||
export const btoa = (str) => {
|
||||
let buffer
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 770 B After Width: | Height: | Size: 458 B |
@@ -0,0 +1 @@
|
||||
<svg width="200px" height="200px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="lds-rolling" style="background-image: none; background-position: initial initial; background-repeat: initial initial;"><circle cx="50" cy="50" fill="none" ng-attr-stroke="{{config.color}}" ng-attr-stroke-width="{{config.width}}" ng-attr-r="{{config.radius}}" ng-attr-stroke-dasharray="{{config.dasharray}}" stroke="#555555" stroke-width="10" r="35" stroke-dasharray="164.93361431346415 56.97787143782138"><animateTransform attributeName="transform" type="rotate" calcMode="linear" values="0 50 50;360 50 50" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite"></animateTransform></circle></svg>
|
||||
|
After Width: | Height: | Size: 734 B |
@@ -6,7 +6,7 @@ Plugins allow you to add
|
||||
- `statePlugins`
|
||||
- `selectors` - query the state
|
||||
- `reducers` - modify the state
|
||||
- `actions` - fire and forget, that will eventually be handled by a reducer. You *can* rely on the result of async actions. But in general its not reccomended
|
||||
- `actions` - fire and forget, that will eventually be handled by a reducer. You *can* rely on the result of async actions. But in general its not recommended
|
||||
- `wrapActions` - replace an action with a wrapped action (useful for hooking into existing `actions`)
|
||||
- `components` - React components
|
||||
- `fn` - commons functions
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
import { pascalCaseFilename } from "js/utils"
|
||||
import Configs from "./configs"
|
||||
import Topbar from "./topbar"
|
||||
|
||||
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
|
||||
})
|
||||
export default {
|
||||
Configs,
|
||||
Topbar
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 770 B After Width: | Height: | Size: 458 B |
@@ -3,6 +3,7 @@ import PropTypes from "prop-types"
|
||||
|
||||
//import "./topbar.less"
|
||||
import Logo from "./logo_small.png"
|
||||
import {parseSearch, serializeSearch} from "../../core/utils"
|
||||
|
||||
export default class Topbar extends React.Component {
|
||||
|
||||
@@ -41,6 +42,15 @@ export default class Topbar extends React.Component {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
setSearch = (spec) => {
|
||||
let search = parseSearch()
|
||||
search["urls.primaryName"] = spec.name
|
||||
const newUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}`
|
||||
if(window && window.history && window.history.pushState) {
|
||||
window.history.replaceState(null, "", `${newUrl}?${serializeSearch(search)}`)
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedUrl = (selectedUrl) => {
|
||||
const configs = this.props.getConfigs()
|
||||
const urls = configs.urls || []
|
||||
@@ -52,6 +62,7 @@ export default class Topbar extends React.Component {
|
||||
if(spec.url === selectedUrl)
|
||||
{
|
||||
this.setState({selectedIndex: i})
|
||||
this.setSearch(spec)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Promise global, Used ( at least ) by 'whatwg-fetch'. And required by IE 11
|
||||
|
||||
if(typeof Promise === "undefined") {
|
||||
import win from "core/window"
|
||||
|
||||
if(typeof win.Promise === "undefined") {
|
||||
require("core-js/fn/promise")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import StandaloneLayout from "./layout"
|
||||
import TopbarPlugin from "plugins/topbar"
|
||||
import ConfigsPlugin from "plugins/configs"
|
||||
import ConfigsPlugin from "corePlugins/configs"
|
||||
|
||||
// the Standalone preset
|
||||
|
||||
|
||||
@@ -16,17 +16,20 @@ export default class StandaloneLayout extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
let { getComponent, specSelectors } = this.props
|
||||
let { getComponent, specSelectors, errSelectors } = this.props
|
||||
|
||||
let Container = getComponent("Container")
|
||||
let Row = getComponent("Row")
|
||||
let Col = getComponent("Col")
|
||||
let Errors = getComponent("errors", true)
|
||||
|
||||
const Topbar = getComponent("Topbar", true)
|
||||
const BaseLayout = getComponent("BaseLayout", true)
|
||||
const OnlineValidatorBadge = getComponent("onlineValidatorBadge", true)
|
||||
|
||||
const loadingStatus = specSelectors.loadingStatus()
|
||||
const lastErr = errSelectors.lastError()
|
||||
const lastErrMsg = lastErr ? lastErr.get("message") : ""
|
||||
|
||||
return (
|
||||
|
||||
@@ -43,12 +46,16 @@ export default class StandaloneLayout extends React.Component {
|
||||
<div className="info">
|
||||
<div className="loading-container">
|
||||
<h4 className="title">Failed to load API definition.</h4>
|
||||
<Errors/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{ loadingStatus === "failedConfig" &&
|
||||
<div className="info" style={{ maxWidth: "880px", marginLeft: "auto", marginRight: "auto", textAlign: "center" }}>
|
||||
<h4 className="title">Failed to load remote configuration.</h4>
|
||||
<div className="loading-container">
|
||||
<h4 className="title">Failed to load remote configuration.</h4>
|
||||
<p>{lastErrMsg}</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{ !loadingStatus || loadingStatus === "success" && <BaseLayout /> }
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
.authorize
|
||||
{
|
||||
padding-right: 20px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,35 +64,12 @@
|
||||
|
||||
&.execute
|
||||
{
|
||||
animation: swagger-ui-pulse 2s infinite;
|
||||
will-change: transform;
|
||||
background-color: $btn-execute-background-color;
|
||||
background-color: $btn-execute-background-color-alt;
|
||||
color: $btn-execute-font-color;
|
||||
border-color: $btn-execute-border-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@keyframes swagger-ui-pulse
|
||||
{
|
||||
0%
|
||||
{
|
||||
color: $btn-execute-font-color;
|
||||
background: $btn-execute-background-color-alt;
|
||||
box-shadow: 0 0 0 0 rgba($btn-execute-background-color-alt, .8);
|
||||
}
|
||||
70%
|
||||
{
|
||||
box-shadow: 0 0 0 5px rgba($btn-execute-background-color-alt, 0);
|
||||
}
|
||||
100%
|
||||
{
|
||||
color: $btn-execute-font-color;
|
||||
background: $btn-execute-background-color-alt;
|
||||
box-shadow: 0 0 0 0 rgba($btn-execute-background-color-alt, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-group
|
||||
{
|
||||
display: flex;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
max-width: 1460px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.opblock-tag-section
|
||||
@@ -585,6 +586,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.opblock-body
|
||||
{
|
||||
.opblock-loading-animation
|
||||
{
|
||||
display: block;
|
||||
margin: 3em;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.opblock-body pre
|
||||
{
|
||||
@@ -657,6 +668,8 @@
|
||||
min-height: 1px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
.loading
|
||||
{
|
||||
|
||||