Compare commits

...
Author SHA1 Message Date
Kyle Shockey daea0715d7 fix: nerf property access to better support Editor test harness 2018-06-11 22:49:30 -07:00
Kyle Shockey cdd3663647 Revert "initial perf, which gets stripped out in production (#4131)"
This reverts commit 5ea2150ae7.
2018-06-08 21:31:32 -07:00
kyle f1cdb6a032 v3.17.0 (#4616)
* v3.17.0

* rebuild dist
2018-06-01 21:31:19 -07:00
kyle a03c12786a feat(rendering): gate rendering based on valid version identifiers (#4614)
* create VersionPragmaFilter component

* use VersionPragmaFilter in BaseLayout

* tighten version idenitifier constraints

* handle case where user specifies a valid `swagger` and `openapi` field

* add traceable class names for each message

* add tests

* linter fixes!

* UNRELATED CHANGE: remove travis short-circuit

* add bypass switch to VersionPragmaFilter
2018-06-01 16:04:27 -07:00
kyle a51bf1ea3e fix(tio-params): disable empty values for required+enum booleans (#4615) 2018-06-01 15:46:44 -07:00
kyle 690e274e07 fix(try-it-out): initially unset required booleans (#4613) 2018-06-01 13:30:42 -07:00
masacc a2fee7ed58 [FIX] nested url option replacement when $API_URLS contains url: .*, pattern (#4601) 2018-06-01 13:24:22 -07:00
Josh Ponelat 90157c1a40 Refactor deep-linking, in the process extracted out OperationsTag (#4349)
* add configsActions.loaded hook

* add OperationTag to hold Operations

* fix test for operations

* refactor deep-linking plugin
2018-06-01 13:19:44 -07:00
Josh Ponelat 5ea2150ae7 initial perf, which gets stripped out in production (#4131) 2018-05-30 13:44:19 -07:00
kyle d91e4e84b8 docs: README touchups (#4606)
* docs: README touchups

* Update README.md
2018-05-29 11:53:21 -07:00
kyle 798cc88968 linter fixes (#4595)
* v3.16.0

* rebuild dist

* rework travis early-success trigger

* Linter fixes
2018-05-26 01:14:09 -07:00
kyle 1214477117 travis updates (#4594)
* v3.16.0

* rebuild dist

* rework travis early-success trigger
2018-05-26 00:56:12 -07:00
kyle bacea7af8a v3.16.0 (#4593)
* v3.16.0

* rebuild dist
2018-05-26 00:36:08 -07:00
kyle 43304aa80a feat: OAS3 binary media type support (#4592)
* fix(validator-badge): resolve definition URLs against browser location

* use param as meta parameter if not found

* convert request body from Immutable if necessary

* show file upload for `format: binary` and `format: base64` jsonschema strings

* add `dispatchInitialValue` prop to JsonSchemaForm

* add optional subkey parameter to onChange

* add binary media type support to request body
2018-05-25 20:37:58 -07:00
kyle cdbd120589 build: exit early if only documentation is being updated (#4583) 2018-05-22 17:05:56 -07:00
Fritz 37001b38bb build(umd-module): prevent mangling (#4320)
* Prevent mangling of names in UMD distribution

This makes debugging easier in general.

* use `uglifyOptions`
2018-05-21 21:31:27 -07:00
kyle 32ab8b3ce8 fix(validator-badge): resolve definition URLs against browser location (#4580) 2018-05-21 21:03:56 -07:00
Rishabh Chakrabarti 6b3aba697e build(npm): add start script (#4572)
CHANGELOG:

1. Added `start` command to `package.json`
2. Assign server port to `3002`.

Ref : Github issue #4551
2018-05-21 20:44:32 -07:00
kyle aa22756641 V/3.15.0 (#4571)
* v3.15.0

* rebuild dist
2018-05-19 00:38:16 -07:00
kyle 16bf8fb3be fix(docker-releases): trim leading v from image names (#4567)
Fixes #4497.
2018-05-17 16:24:14 -07:00
kyle bf5acc1f40 fix(topbar): remove href from Swagger logo (#4566)
Fixes #4527.

- this `href` didn't serve any visible or known purpose
- it was added before prior to 3.0.0 and has not been modified since
  - see UX 8ab65d3b
- the `Link` was kept, since the anchor was needed to preserve the current layout.
2018-05-17 16:00:26 -07:00
kyle c1007a287b feature: OAS3 object parameter support (#4563)
* render suitable interface for `type: object` parameters

* validate OAS3 object parameters correctly

* display parameter validation errors

* remove irrelevant css classes

* rm comment

* fix failing tests

* add validateParam tests

* add enzyme tests for object parameter rendering

* run actual tests first
2018-05-16 22:48:44 -07:00
kyle c8480a827a fix: respect OAS3 parameter default values (#4561)
* add test cases

* refactor default setters into own function

* reach into `schema` for default value in OAS3

* remove exclusive test
2018-05-16 00:27:14 -07:00
kyle 55fdeeb810 Issue template tweaks (#4520)
* MOAR issue templates

* ...LESS issue templates

* minor issue template tweaks
2018-05-14 21:03:00 -07:00
kyle 9e3a801750 v3.14.2 (#4548)
* v3.14.2

* rebuild dist

* constrict `eslint-plugin-react` version

https://github.com/yannickcr/eslint-plugin-react/issues/1789
2018-05-12 00:05:49 -07:00
kyle 9155eb946e fix: always display locally-available title property as a model's name (#4542)
* fix: always display locally-available `title` property as a model's name
* fix failing unit test
* add e2e test case
2018-05-10 22:15:36 -07:00
maryscar 66e9bd735a fix: model property color contrast (#4540) 2018-05-10 16:19:59 -07:00
kyle ae849c31ea fix: remove outdated Safari check for Blob downloads (#4541) 2018-05-10 16:07:29 -07:00
Helder Sepulveda 1a95b9e9c9 New fixes for 1 vulnerable dependency (deep-extend) (#4507)
* fix: package.json to reduce vulnerabilities

The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/npm:lodash:20180130

Latest report for heldersepu/swagger-ui:
https://snyk.io/test/github/heldersepu/swagger-ui

* fix: package.json to reduce vulnerabilities

The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/npm:deep-extend:20180409

Latest report for heldersepu/swagger-ui:
https://snyk.io/test/github/heldersepu/swagger-ui
2018-05-07 23:00:00 -07:00
Brett Randall 073b1df730 Updated 'urls' (An array of API definition objects) description. (#4525)
* Updated 'urls' (An array of API definition objects) description.

Updated 'urls' (An array of API definition objects) description to show an actual object array containing two (2) urls, clarifying this configuration-option.

* urls is an array, not a string
2018-05-07 21:56:08 -07:00
kyle 11c586309f v3.14.1 (#4521)
* v3.14.1

* transform `const` & `let` to `var` in node_modules, for IE11

* rebuild dist
2018-05-04 19:25:20 -07:00
kyle a37d637061 meta: multiple issue templates (#4519)
* MOAR issue templates

* ...LESS issue templates
2018-05-04 12:58:08 -07:00
kyle 75747424cf improvement: sanitization via DOMPurify (#4513)
* swap `sanitize-html` for `dompurify`

* set up node enzyme tests with jsdom

dompurify, as the name suggests, needs a DOM or it won't work!

* reconcile tests and sanitizer settings

* remove obsolete sanitizeOptions

* add `jsdom` dependency
2018-05-04 10:06:44 -07:00
Helder Sepulveda 8055129dd2 Improve downloadable HighlightCode filename (#4508)
* Update highlight-code.jsx
* improve filename no more response.txt
* use new `fileName` prop for file names
* use template strings for `fileName` prop values
* fall back to old "response.txt" file name if none is provided
2018-05-03 17:33:21 -07:00
kyle 7049de6201 v3.14.0 (#4492)
* v3.14.0

* fix: simplify config fetch interceptor implementation

* add `loadRemoteConfig` flag to requests

* v3.14.0
2018-04-27 23:20:13 -07:00
kyle 8cef3adfe6 fix: path-item $ref operation metadata storage (#4489) 2018-04-27 10:29:33 -07:00
Helder Sepulveda cbff0251ae feat: option to show common query parameters (#4245)
* extend getExtensions

Add optional param to getExtensions that can retrieve more stuff

* Add getCommonExtensions

* Trim trailing spaces

* Remove unused parameter

* Move the format inline with the param type

* correction to UnitTest
2018-04-26 21:18:45 -07:00
kyle 62354568a9 feat: request/response interceptors for remote config fetch (#4484) 2018-04-26 21:04:55 -07:00
kyle d981f0f26e v3.13.6 (#4472) 2018-04-24 00:18:38 -07:00
kyle 44c3f09abe v3.13.5 (#4464)
* v3.13.5

* rebuild dist
2018-04-20 20:34:43 -07:00
kyle 0c50c7e99f enhancement: don't block scrolling if HighlightCode is not scrollable (#4463) 2018-04-20 16:34:22 -07:00
Helder Sepulveda 62ed4db110 fix: http auth component state retention issue (#4394)
* Set the value if set is available

* tests: add failing e2e test case

* use Object.assign to always ensure setState receives a new value object
2018-04-20 16:08:39 -07:00
r-ising 553acee07d fix: redundant Markdown required className prop warning (#4457) 2018-04-19 14:39:19 -07:00
kyle 932cc9838c fix: Callback component $$ref handling (#4454) 2018-04-18 18:05:15 -07:00
Helder Sepulveda d3c9ceceef Change the 5# to 4# (#4447)
The 5# do not show well on the wiki but 4# do and they were not used
2018-04-18 16:29:53 -07:00
kyle 5f855e56bb v3.13.4 (#4438)
* v3.13.4

* rebuild dist
2018-04-13 18:38:21 -07:00
Ville Skyttä 3f6e21e3b1 improve: HTTP auth display logic and messaging (#4430)
* http-auth: Include unsupported scheme name in error message

* http-auth: Treat authentication scheme case insensitively

The spec refers to RFC 7235 for scheme names, and the RFC says names
are matched case insensitively.

* linter fixes
2018-04-13 17:02:36 -07:00
Aleksey Ganzha 26d0ad7684 fix(regression): model rendering for Swagger 2.0 body parameters (#4424)
* Fix model rendering for parameters

* tests(e2e): add test confirming that Pet model is shown in operation
2018-04-13 16:57:32 -07:00
kyle 9a121ea445 fix: attempt to display Blob response bodies as text (#4437)
* fix: attempt to display Blob response bodies as text

... if nothing else works.

* correctly pass `prevProps.content` to updateParsedContent

* remove debugger statement

* caniuse `Blob` && `FileReader`? yes.

Blob: https://caniuse.com/#search=Blob
FileReader: https://caniuse.com/#search=filereader
2018-04-13 16:16:54 -07:00
kyle c55f22575a fix: make Schemes <select> a fully state-controlled input (#4436) 2018-04-13 13:52:01 -07:00
Giancarlo Klemm Camilo 2795518340 enhance: scrollable + downloadable HighlightCode (#4397)
* Auto hidding content that is longer than 600 characters long.

* Added basic downloading

Slightly broken

* Better downloading

now downloads file on button click

* Fix the angry linter

* Fix dist

* Removed collapsing, added scrolling.

* Code clean up.

* CSS fix.

* prevent HighlightCode from scrolling entire document

* center "Download" text in button

* `this.downloadJSON` -> `this.downloadText`

we're always saving as `.txt`, so JSON is a misnomer

* hide Download button behind option `downloadable` prop

* `file-saver` -> `js-file-download`
2018-04-12 18:18:49 -07:00
Chris Ormerod 2bf2167a18 docs: fix typo (#4431)
Operations is lowercase, without this fix the example doesn't work as a Copy/Paste
2018-04-12 16:53:10 -07:00
kyle dc7d7054bf fix: provide svg assets through React app instead of relying on HTML (#4429)
* fix: provide svg assets through React app instead of relying on HTML

* fix stylistic inconsistencies in HTML assets
2018-04-10 16:05:17 -07:00
Ville Skyttä 14473dd51d fix: misc. spelling errors (#4421) 2018-04-09 15:58:52 -07:00
94 changed files with 2147 additions and 675 deletions
+1
View File
@@ -6,6 +6,7 @@
],
"plugins": [
"transform-runtime",
"babel-plugin-transform-es2015-constants",
[
"module-alias",
[
+73
View File
@@ -0,0 +1,73 @@
---
name: Bug report
about: Report an issue you're experiencing
---
<!---
Thanks for filing a bug report! 😄
Before you submit, please read the following:
If you're here to report a security issue, please STOP writing an issue and
contact us at security@swagger.io instead!
Search open/closed issues before submitting!
Issues on GitHub are only related to problems of Swagger-UI itself. We'll try
to offer support here for your use case, but we can't offer help with projects
that use Swagger-UI indirectly, like Springfox or swagger-node.
Likewise, we can't accept bugs in the Swagger/OpenAPI specifications
themselves, or anything that violates the specifications.
-->
### Q&A (please complete the following information)
- OS: [e.g. macOS]
- Browser: [e.g. chrome, safari]
- Version: [e.g. 22]
- Method of installation: [e.g. npm, dist assets]
- Swagger-UI version: [e.g. 3.10.0]
- Swagger/OpenAPI version: [e.g. Swagger 2.0, OpenAPI 3.0]
### Content & configuration
<!--
Provide us with a way to see what you're seeing,
so that we can fix your issue.
-->
Example Swagger/OpenAPI definition:
```yaml
# your YAML here
```
Swagger-UI configuration options:
```js
SwaggerUI({
// your config options here
})
```
```
?yourQueryStringConfig
```
### Describe the bug you're encountering
<!-- A clear and concise description of what the bug is. -->
### To reproduce...
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
### Expected behavior
<!-- A clear and concise description of what you expected to happen. -->
### Screenshots
<!-- If applicable, add screenshots to help explain your problem. -->
### Additional context or thoughts
<!-- Add any other context about the problem here. -->
+50
View File
@@ -0,0 +1,50 @@
---
name: Feature request
about: Suggest an new feature or enhancement for this project
---
### Q&A (please complete the following information)
- OS: [e.g. macOS]
- Browser: [e.g. chrome, safari]
- Version: [e.g. 22]
- Method of installation: [e.g. npm, dist assets]
- Swagger-UI version: [e.g. 3.10.0]
- Swagger/OpenAPI version: [e.g. Swagger 2.0, OpenAPI 3.0]
### Content & configuration
Swagger/OpenAPI definition:
```yaml
# your YAML here
```
Swagger-UI configuration options:
```js
SwaggerUI({
// your config options here
})
```
```
?yourQueryStringConfig
```
### Is your feature request related to a problem?
<!--
Please provide a clear and concise description of what the problem is.
"I'm always frustrated when..."
-->
### Describe the solution you'd like
<!-- A clear and concise description of what you want to happen. -->
### Describe alternatives you've considered
<!--
A clear and concise description of any alternative solutions or features
you've considered.
-->
### Additional context
<!-- Add any other context or screenshots about the feature request here. -->
+46
View File
@@ -0,0 +1,46 @@
---
name: Support
about: Ask a question or request help with your implementation.
---
<!--
We can only offer support for Swagger-UI itself.
If you're having a problem with a library that uses Swagger-UI
(for example, Springfox or swagger-node), please open an issue
in that project's repository instead.
-->
### Q&A (please complete the following information)
- OS: [e.g. macOS]
- Browser: [e.g. chrome, safari]
- Version: [e.g. 22]
- Method of installation: [e.g. npm, dist assets]
- Swagger-UI version: [e.g. 3.10.0]
- Swagger/OpenAPI version: [e.g. Swagger 2.0, OpenAPI 3.0]
### Content & configuration
<!-- Provide us with a way to see what you're seeing, so that we can help. -->
Swagger/OpenAPI definition:
```yaml
# your YAML here
```
Swagger-UI configuration options:
```js
SwaggerUI({
// your config options here
})
```
```
?yourQueryStringConfig
```
### Screenshots
<!-- If applicable, add screenshots to help give context to your problem. -->
### How can we help?
<!-- Your question or problem goes here! -->
-70
View File
@@ -1,70 +0,0 @@
<!---
Thanks for filing an issue 😄 ! Before you submit, please read the following:
If you're here to report a security issue, please STOP writing an issue and contact us
at security@swagger.io instead!
Search open/closed issues before submitting since someone might have asked the same thing before!
Issues on GitHub are only related to problems of Swagger-UI itself. We'll try to offer support
here for your use case, but we can't offer help with projects that use Swagger-UI indirectly,
like Springfox or swagger-node.
Likewise, we can't accept features or bugs in the Swagger/OpenAPI specifications themselves,
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/usage/version-detection.md
--->
| Q | A
| ------------------------------- | -------
| Bug or feature request? |
| Which Swagger/OpenAPI version? |
| Which Swagger-UI version? |
| How did you install Swagger-UI? |
| Which browser & version? |
| Which operating system? |
### Demonstration API definition
<!--- If you're describing a bug, please provide an API definition that reproduces your problem -->
<!--- If you have link to a demo repo please link that! -->
<!--- If your spec is large, please put it into a Gist (https://gist.github.com) instead of pasting it here. -->
```yaml
your: "API definition goes here"
```
### Configuration (browser query string, constructor, config.yaml)
<!--- If describing a bug, tell us what your configuration looks like -->
```js
{
"your": { "constructorConfig": "here" }
}
```
`?yourQueryStringConfig=here`
### Expected Behavior
<!--- If you're describing a bug, tell us what should happen -->
<!--- If you're suggesting a change/improvement, tell us how it should work -->
### Current Behavior
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
### Possible Solution
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
<!--- or ideas how to implement the addition or change -->
### Context
<!--- How has this issue affected you? What are you trying to accomplish? -->
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
+1 -1
View File
@@ -37,7 +37,7 @@ after_success:
docker login --username=$DOCKER_HUB_USERNAME --password=$DOCKER_HUB_PASSWORD;
if [ ! -z "$TRAVIS_TAG" ]; then
DOCKER_IMAGE_TAG=$TRAVIS_TAG;
DOCKER_IMAGE_TAG=${TRAVIS_TAG#?};
else
DOCKER_IMAGE_TAG=unstable;
fi;
+9 -11
View File
@@ -2,27 +2,25 @@
[![NPM version](https://badge.fury.io/js/swagger-ui.svg)](http://badge.fury.io/js/swagger-ui)
## New!
**This is the new version of swagger-ui, 3.x. Want to learn more? Check out our [FAQ](http://swagger.io/new-ui-faq/).**
**👉🏼 Want to score an easy open-source contribution?** Check out our [Good first issue](https://github.com/swagger-api/swagger-ui/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+first+issue%22) label.
As a brand new version, written from the ground up, there are some known issues and unimplemented features. Check out the [Known Issues](#known-issues) section for more details.
**🕰️ Looking for the older version of Swagger UI?** Refer to the [*2.x* branch](https://github.com/swagger-api/swagger-ui/tree/2.x).
This repository publishes to two different NPM modules:
* [swagger-ui](https://www.npmjs.com/package/swagger-ui) is a traditional npm module intended for use in JavaScript web application projects that are capable of resolving dependencies (via Webpack, Browserify, etc).
* [swagger-ui-dist](https://www.npmjs.com/package/swagger-ui-dist) is a dependency-free module that includes everything you need to serve Swagger-UI in a server-side project, or a web project that can't resolve npm module dependencies.
* [swagger-ui](https://www.npmjs.com/package/swagger-ui) is a traditional npm module intended for use in single-page applications that are capable of resolving dependencies (via Webpack, Browserify, etc).
* [swagger-ui-dist](https://www.npmjs.com/package/swagger-ui-dist) is a dependency-free module that includes everything you need to serve Swagger-UI in a server-side project, or a single-page application that can't resolve npm module dependencies.
We strongly suggest that you use `swagger-ui` instead of `swagger-ui-dist` if you're building a single-page application, since `swagger-ui-dist` is significantly larger.
For the older version of swagger-ui, refer to the [*2.x branch*](https://github.com/swagger-api/swagger-ui/tree/2.x).
## Compatibility
The OpenAPI Specification has undergone 5 revisions since initial creation in 2010. Compatibility between swagger-ui and the OpenAPI Specification is as follows:
The OpenAPI Specification has undergone 5 revisions since initial creation in 2010. Compatibility between Swagger UI and the OpenAPI Specification is as follows:
Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes
------------------ | ------------ | -------------------------- | -----
3.13.3 | 2018-03-23 | 2.0, 3.0 | [tag v3.13.3](https://github.com/swagger-api/swagger-ui/tree/v3.13.3)
3.17.0 | 2018-05-26 | 2.0, 3.0 | [tag v3.17.0](https://github.com/swagger-api/swagger-ui/tree/v3.17.0)
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)
@@ -76,7 +74,7 @@ Please disclose any security-related issues or vulnerabilities by emailing [secu
## License
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.
+35 -67
View File
@@ -16,13 +16,16 @@
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body {
body
{
margin:0;
background: #fafafa;
}
@@ -30,75 +33,40 @@
</head>
<body>
<div id="swagger-ui"></div>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>
<script src="./swagger-ui-bundle.js"> </script>
<script src="./swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
window["SwaggerUIBundle"] = window["swagger-ui-bundle"]
window["SwaggerUIStandalonePreset"] = window["swagger-ui-standalone-preset"]
// Build a system
const ui = SwaggerUIBundle({
url: "http://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
</symbol>
window.ui = ui
<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
</symbol>
</defs>
</svg>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js"> </script>
<script src="./swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
window["SwaggerUIBundle"] = window["swagger-ui-bundle"]
window["SwaggerUIStandalonePreset"] = window["swagger-ui-standalone-preset"]
// Build a system
const ui = SwaggerUIBundle({
url: "http://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
ui.initOAuth({
clientId: "your-client-id",
clientSecret: "your-client-secret-if-required",
realm: "your-realms",
appName: "your-app-name",
scopeSeparator: " ",
additionalQueryStringParams: {}
})
}
</script>
ui.initOAuth({
clientId: "your-client-id",
clientSecret: "your-client-secret-if-required",
realm: "your-realms",
appName: "your-app-name",
scopeSeparator: " ",
additionalQueryStringParams: {}
})
}
</script>
</body>
</html>
+56 -90
View File
@@ -1,95 +1,61 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body
{
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js"> </script>
<script src="./swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
url: "http://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body {
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
</symbol>
</defs>
</svg>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js"> </script>
<script src="./swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
url: "http://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
}
</script>
</body>
</script>
</body>
</html>
+34 -23
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -2
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -55,7 +55,7 @@ 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
sed -i "s|^\(\s*\)url: .*,|\1urls: $API_URLS,|g" $INDEX_FILE
fi
# replace the PORT that nginx listens on if PORT is supplied
+1 -1
View File
@@ -18,7 +18,7 @@ class OperationsLayout extends React.Component {
getComponent
} = this.props
const Operations = getComponent("Operations", true)
const Operations = getComponent("operations", true)
return (
<div>
+10 -10
View File
@@ -68,7 +68,7 @@ There is no dependency management built into the plugin system, so if you create
### Interfaces
##### Actions
#### Actions
```javascript
const MyActionPlugin = () => {
@@ -102,7 +102,7 @@ This action creator function will be exposed to container components as `example
For more information about the concept of actions in Redux, see the [Redux Actions documentation](http://redux.js.org/docs/basics/Actions.html).
##### Reducers
#### Reducers
Reducers take a state (which is an [Immutable.js map](https://facebook.github.io/immutable-js/docs/#/Map)) and an action, and return a new state.
@@ -126,7 +126,7 @@ const MyReducerPlugin = function(system) {
}
```
##### Selectors
#### Selectors
Selectors reach into their namespace's state to retrieve or derive data from the state.
@@ -172,7 +172,7 @@ Once a selector has been defined, you can use it anywhere that you can get a sys
system.exampleSelectors.myFavoriteColor() // gets `favColor` in state for you
```
##### Components
#### Components
You can provide a map of components to be integrated into the system.
@@ -214,7 +214,7 @@ const NeverShowInfoPlugin = function(system) {
}
```
##### Wrap-Actions
#### Wrap-Actions
Wrap Actions allow you to override the behavior of an action in the system.
@@ -262,7 +262,7 @@ const MyWrapActionPlugin = function(system) {
}
```
##### Wrap-Selectors
#### Wrap-Selectors
Wrap Selectors allow you to override the behavior of a selector in the system.
@@ -307,7 +307,7 @@ const MyWrapSelectorsPlugin = function(system) {
}
```
##### Wrap-Components
#### Wrap-Components
Wrap Components allow you to override a component registered within the system.
@@ -381,7 +381,7 @@ const MyWrapComponentPlugin = function(system) {
}
```
##### `rootInjects`
#### `rootInjects`
The `rootInjects` interface allows you to inject values at the top level of the system.
@@ -398,7 +398,7 @@ const MyRootInjectsPlugin = function(system) {
}
```
##### `afterLoad`
#### `afterLoad`
The `afterLoad` plugin method allows you to get a reference to the system after your plugin has been registered.
@@ -431,7 +431,7 @@ const MyMethodProvidingPlugin = function() {
}
```
##### fn
#### fn
The fn interface allows you to add helper functions to the system for use elsewhere.
+2 -1
View File
@@ -30,7 +30,7 @@ Parameter Name | Description
`domNode` | `Element`, **REQUIRED** if `dom_id` is not provided. The HTML DOM element inside which SwaggerUi will put the user interface for swagger. Overrides `dom_id`.
`spec` | `Object={}`. A JS object describing the OpenAPI Specification. When used, the `url` parameter will not be parsed. This is useful for testing manually-generated specifications without hosting them.
`url` | `String`. The url pointing to API definition (normally `swagger.json` or `swagger.yaml`). Will be ignored if `urls` or `spec` is used.
`urls` | `String`. An array of API definition objects (`{url: "<url>", name: "<name>"}`) used by Topbar plugin. When used and Topbar plugin is enabled, the `url` parameter will not be parsed. Names and URLs must be unique among all items in this array, since they're used as identifiers.
`urls` | `Array`. An array of API definition objects (`[{url: "<url1>", name: "<name1>"},{url: "<url2>", name: "<name2>"}]`) used by Topbar plugin. When used and Topbar plugin is enabled, the `url` parameter will not be parsed. Names and URLs must be unique among all items in this array, since they're used as identifiers.
`urls.primaryName` | `String`. When using `urls`, you can use this subparameter. If the value matches the name of a spec provided in `urls`, that spec will be displayed when Swagger-UI loads, instead of defaulting to the first spec in `urls`.
##### Plugin system
@@ -58,6 +58,7 @@ Parameter Name | Description
`maxDisplayedTags` | `Number`. If set, limits the number of tagged operations displayed to at most this many. The default is to show all operations.
`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.
`showCommonExtensions` | `Boolean=false`. Controls the display of extensions (`pattern`, `maxLength`, `minLength`, `maximum`, `minimum`) fields and values for Parameters.
`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.
+19 -10
View File
@@ -1,10 +1,11 @@
var path = require("path")
var webpack = require('webpack')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
var deepExtend = require('deep-extend')
const {gitDescribeSync} = require('git-describe')
var webpack = require("webpack")
var ExtractTextPlugin = require("extract-text-webpack-plugin")
const CopyWebpackPlugin = require("copy-webpack-plugin")
const UglifyJsPlugin = require("uglifyjs-webpack-plugin")
var deepExtend = require("deep-extend")
const {gitDescribeSync} = require("git-describe")
const os = require("os")
var pkg = require("./package.json")
@@ -28,7 +29,10 @@ var commonRules = [
retainLines: true
}
}],
include: [ path.join(__dirname, "src") ]
include: [
path.join(__dirname, "src"),
path.join(__dirname, "node_modules", "object-assign-deep"),
]
},
{ test: /\.(txt|yaml)(\?.*)?$/,
loader: "raw-loader" },
@@ -48,6 +52,7 @@ module.exports = function(rules, options) {
hot: false,
separateStylesheets: true,
minimize: false,
mangle: false,
longTermCaching: false,
sourcemaps: false,
}, options._special)
@@ -61,10 +66,14 @@ module.exports = function(rules, options) {
}))
}
if( specialOptions.minimize ) { // production mode
if( specialOptions.minimize ) { // production mode
plugins.push(
new webpack.optimize.UglifyJsPlugin({
new UglifyJsPlugin({
uglifyOptions: {
mangle: specialOptions.mangle,
beautify: !specialOptions.mangle,
},
sourceMap: true,
}),
new webpack.LoaderOptionsPlugin({
@@ -76,8 +85,8 @@ module.exports = function(rules, options) {
plugins.push( new webpack.NoEmitOnErrorsPlugin())
} else { // development mode
plugins.push(new CopyWebpackPlugin([ { from: 'test/e2e/specs', to: 'test-specs' } ]))
} else { // development mode
plugins.push(new CopyWebpackPlugin([ { from: "test/e2e/specs", to: "test-specs" } ]))
}
plugins.push(
+15 -8
View File
@@ -1,6 +1,6 @@
{
"name": "swagger-ui",
"version": "3.13.3",
"version": "3.17.0",
"main": "dist/swagger-ui.js",
"repository": "git@github.com:swagger-api/swagger-ui.git",
"contributors": [
@@ -28,16 +28,19 @@
"lint": "eslint --cache --ext '.js,.jsx' src test",
"lint-errors": "eslint --cache --quiet --ext '.js,.jsx' src test",
"lint-fix": "eslint --cache --ext '.js,.jsx' src test --fix",
"test": "npm run lint-errors && npm run just-test-in-node",
"test": "npm run just-test-in-node && npm run lint-errors",
"test-in-node": "npm run lint-errors && npm run just-test-in-node",
"just-test": "karma start --config karma.conf.js",
"just-test-in-node": "mocha --recursive --compilers js:babel-core/register test/core test/components test/bugs test/swagger-ui-dist-package test/xss",
"just-test-in-node": "mocha --require test/setup.js --recursive --compilers js:babel-core/register test/core test/components test/bugs test/swagger-ui-dist-package test/xss",
"just-check-coverage": "nyc npm run just-test-in-node",
"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",
"hot-e2e-server": "webpack-dev-server --port 3230 --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"
"e2e": "npm-run-all --parallel -r hot-e2e-server mock-api test-e2e",
"open-static": "node -e 'require(\"open\")(\"http://localhost:3002\")'",
"serve-static": "http-server dist/ -i -a 0.0.0.0 -p 3002",
"start": "npm-run-all --parallel serve-static open-static"
},
"dependencies": {
"@braintree/sanitize-url": "^2.0.2",
@@ -47,11 +50,13 @@
"commonmark": "^0.28.1",
"core-js": "^2.5.1",
"css.escape": "1.5.1",
"deep-extend": "0.4.1",
"deep-extend": "0.5.1",
"dompurify": "^1.0.4",
"expect": "1.20.2",
"getbase": "^2.8.2",
"ieee754": "^1.1.8",
"immutable": "^3.x.x",
"js-file-download": "^0.4.1",
"js-yaml": "^3.5.5",
"lodash": "4.17.5",
"matcher": "^0.1.2",
@@ -79,11 +84,10 @@
"redux-logger": "*",
"remarkable": "^1.7.1",
"reselect": "2.5.3",
"sanitize-html": "^1.14.1",
"scroll-to-element": "^2.0.0",
"serialize-error": "2.0.0",
"shallowequal": "0.2.2",
"swagger-client": "^3.6.0",
"swagger-client": "^3.8.5",
"url-parse": "^1.1.8",
"whatwg-fetch": "0.11.1",
"worker-loader": "^0.7.1",
@@ -98,6 +102,7 @@
"babel-eslint": "^7.1.1",
"babel-loader": "^7.1.0",
"babel-plugin-module-alias": "^1.6.0",
"babel-plugin-transform-es2015-constants": "^6.1.4",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-es2015": "^6.22.0",
"babel-preset-es2015-ie": "^6.6.2",
@@ -113,11 +118,12 @@
"eslint": "^4.1.1",
"eslint-plugin-import": "^2.6.0",
"eslint-plugin-mocha": "^4.11.0",
"eslint-plugin-react": "^7.1.0",
"eslint-plugin-react": "~7.7.0",
"extract-text-webpack-plugin": "^2.1.2",
"file-loader": "0.11.2",
"git-describe": "^4.0.1",
"imports-loader": "0.7.1",
"jsdom": "^11.10.0",
"json-loader": "0.5.4",
"json-server": "^0.11.0",
"karma": "^1.7.0",
@@ -145,6 +151,7 @@
"standard-loader": "^6.0.1",
"style-loader": "0.18.2",
"tachyons-sass": "^4.9.2",
"uglifyjs-webpack-plugin": "^1.2.5",
"url-loader": "0.5.9",
"webpack": "^2.6.1",
"webpack-bundle-size-analyzer": "^2.5.0"
+3 -2
View File
@@ -11,6 +11,7 @@ export default class ArrayModel extends Component {
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
displayName: PropTypes.string,
required: PropTypes.bool,
expandDepth: PropTypes.number,
specPath: ImPropTypes.list.isRequired,
@@ -18,10 +19,10 @@ export default class ArrayModel extends Component {
}
render(){
let { getComponent, getConfigs, schema, depth, expandDepth, name, specPath } = this.props
let { getComponent, getConfigs, schema, depth, expandDepth, name, displayName, specPath } = this.props
let description = schema.get("description")
let items = schema.get("items")
let title = schema.get("title") || name
let title = schema.get("title") || displayName || name
let properties = schema.filter( ( v, key) => ["type", "items", "description", "$$ref"].indexOf(key) === -1 )
const Markdown = getComponent("Markdown")
+43 -3
View File
@@ -1,11 +1,14 @@
import React, { Component } from "react"
import PropTypes from "prop-types"
import { highlight } from "core/utils"
import saveAs from "js-file-download"
export default class HighlightCode extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
className: PropTypes.string
className: PropTypes.string,
downloadable: PropTypes.bool,
fileName: PropTypes.string
}
componentDidMount() {
@@ -20,10 +23,47 @@ export default class HighlightCode extends Component {
this.el = c
}
downloadText = () => {
saveAs(this.props.value, this.props.fileName || "response.txt")
}
preventYScrollingBeyondElement = (e) => {
const target = e.target
var deltaY = e.nativeEvent.deltaY
var contentHeight = target.scrollHeight
var visibleHeight = target.offsetHeight
var scrollTop = target.scrollTop
const scrollOffset = visibleHeight + scrollTop
const isElementScrollable = contentHeight > visibleHeight
const isScrollingPastTop = scrollTop === 0 && deltaY < 0
const isScrollingPastBottom = scrollOffset >= contentHeight && deltaY > 0
if (isElementScrollable && (isScrollingPastTop || isScrollingPastBottom)) {
e.preventDefault()
}
}
render () {
let { value, className } = this.props
let { value, className, downloadable } = this.props
className = className || ""
return <pre ref={this.initializeComponent} className={className + " microlight"}>{ value }</pre>
return (
<div className="highlight-code">
{ !downloadable ? null :
<div className="download-contents" onClick={this.downloadText}>
Download
</div>
}
<pre
ref={this.initializeComponent}
onWheel={this.preventYScrollingBeyondElement}
className={className + " microlight"}>
{value}
</pre>
</div>
)
}
}
+8 -2
View File
@@ -39,6 +39,8 @@ export default class BaseLayout extends React.Component {
let schemes = specSelectors.schemes()
let servers = specSelectors.servers()
let SvgAssets = getComponent("SvgAssets")
let VersionPragmaFilter = getComponent("VersionPragmaFilter")
let Info = getComponent("info")
let Operations = getComponent("operations", true)
let Models = getComponent("Models", true)
@@ -48,6 +50,9 @@ export default class BaseLayout extends React.Component {
let Servers = getComponent("Servers")
let Errors = getComponent("errors", true)
let isSwagger2 = specSelectors.isSwagger2()
let isOAS3 = specSelectors.isOAS3()
let isLoading = specSelectors.loadingStatus() === "loading"
let isFailed = specSelectors.loadingStatus() === "failed"
let filter = layoutSelectors.currentFilter()
@@ -78,7 +83,8 @@ export default class BaseLayout extends React.Component {
return (
<div className='swagger-ui'>
<div>
<SvgAssets />
<VersionPragmaFilter isSwagger2={isSwagger2} isOAS3={isOAS3} alsoShow={<Errors/>}>
<Errors/>
<Row className="information-container">
<Col mobile={12}>
@@ -140,7 +146,7 @@ export default class BaseLayout extends React.Component {
<Models/>
</Col>
</Row>
</div>
</VersionPragmaFilter>
</div>
)
}
+1
View File
@@ -9,6 +9,7 @@ export default class ModelWrapper extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
name: PropTypes.string,
displayName: PropTypes.string,
getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
+3 -2
View File
@@ -10,6 +10,7 @@ export default class Model extends ImmutablePureComponent {
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
displayName: PropTypes.string,
isRef: PropTypes.bool,
required: PropTypes.bool,
expandDepth: PropTypes.number,
@@ -33,7 +34,7 @@ export default class Model extends ImmutablePureComponent {
}
render () {
let { getComponent, getConfigs, specSelectors, schema, required, name, isRef, specPath } = this.props
let { getComponent, getConfigs, specSelectors, schema, required, name, isRef, specPath, displayName } = this.props
const ObjectModel = getComponent("ObjectModel")
const ArrayModel = getComponent("ArrayModel")
const PrimitiveModel = getComponent("PrimitiveModel")
@@ -51,7 +52,7 @@ export default class Model extends ImmutablePureComponent {
if(!schema) {
return <span className="model model-title">
<span className="model-title__text">{ name }</span>
<span className="model-title__text">{ displayName || name }</span>
<img src={require("core/../img/rolling-load.svg")} height={"20px"} width={"20px"} style={{
marginLeft: "1em",
position: "relative",
+11 -4
View File
@@ -51,9 +51,12 @@ export default class Models extends Component {
</h4>
<Collapse isOpened={showModels}>
{
definitions.entrySeq().map( ( [ name ])=>{
definitions.entrySeq().map(([name])=>{
const schema = specSelectors.specResolvedSubtree([...specPathBase, name])
const fullPath = [...specPathBase, name]
const schema = specSelectors.specResolvedSubtree(fullPath)|| Im.Map()
const rawSchema = specSelectors.specJson().getIn(fullPath, Im.Map())
const displayName = schema.get("title") || rawSchema.get("title") || name
if(layoutSelectors.isShown(["models", name], false) && schema === undefined) {
// Firing an action in a container render is not great,
@@ -63,7 +66,8 @@ export default class Models extends Component {
const content = <ModelWrapper name={ name }
expandDepth={ defaultModelsExpandDepth }
schema={ schema || Im.Map() }
schema={ schema || Im.Map() }
displayName={displayName}
specPath={Im.List([...specPathBase, name])}
getComponent={ getComponent }
specSelectors={ specSelectors }
@@ -72,7 +76,9 @@ export default class Models extends Component {
layoutActions = {layoutActions}/>
const title = <span className="model-box">
<span className="model model-title">{name}</span>
<span className="model model-title">
{displayName}
</span>
</span>
return <div id={ `model-${name}` } className="model-container" key={ `models-section-${name}` }>
@@ -81,6 +87,7 @@ export default class Models extends Component {
collapsedContent={this.getCollapsedContent(name)}
onToggle={this.handleToggle}
title={title}
displayName={displayName}
modelName={name}
hideSelfOnExpand={true}
expanded={defaultModelsExpandDepth > 1}
+4 -3
View File
@@ -15,6 +15,7 @@ export default class ObjectModel extends Component {
onToggle: PropTypes.func,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
displayName: PropTypes.string,
isRef: PropTypes.bool,
expandDepth: PropTypes.number,
depth: PropTypes.number,
@@ -22,7 +23,7 @@ export default class ObjectModel extends Component {
}
render(){
let { schema, name, isRef, getComponent, getConfigs, depth, onToggle, expanded, specPath, ...otherProps } = this.props
let { schema, name, displayName, isRef, getComponent, getConfigs, depth, onToggle, expanded, specPath, ...otherProps } = this.props
let { specSelectors,expandDepth } = otherProps
const { isOAS3 } = specSelectors
@@ -35,7 +36,7 @@ export default class ObjectModel extends Component {
let description = schema.get("description")
let properties = schema.get("properties")
let additionalProperties = schema.get("additionalProperties")
let title = schema.get("title") || name
let title = schema.get("title") || displayName || name
let requiredProperties = schema.get("required")
const JumpToPath = getComponent("JumpToPath", true)
@@ -78,7 +79,7 @@ export default class ObjectModel extends Component {
{
<table className="model"><tbody>
{
!description ? null : <tr style={{ color: "#999", fontStyle: "italic" }}>
!description ? null : <tr style={{ color: "#666", fontStyle: "italic" }}>
<td>description:</td>
<td>
<Markdown source={ description } />
+15 -4
View File
@@ -1,6 +1,9 @@
import React from "react"
import URL from "url-parse"
import PropTypes from "prop-types"
import { sanitizeUrl } from "core/utils"
import win from "core/window"
export default class OnlineValidatorBadge extends React.Component {
static propTypes = {
@@ -11,20 +14,28 @@ export default class OnlineValidatorBadge extends React.Component {
constructor(props, context) {
super(props, context)
let { specSelectors, getConfigs } = props
let { getConfigs } = props
let { validatorUrl } = getConfigs()
this.state = {
url: specSelectors.url(),
url: this.getDefinitionUrl(),
validatorUrl: validatorUrl === undefined ? "https://online.swagger.io/validator" : validatorUrl
}
}
getDefinitionUrl = () => {
// TODO: test this behavior by stubbing `window.location` in an Enzyme/JSDom env
let { specSelectors } = this.props
const urlObject = new URL(specSelectors.url(), win.location)
return urlObject.toString()
}
componentWillReceiveProps(nextProps) {
let { specSelectors, getConfigs } = nextProps
let { getConfigs } = nextProps
let { validatorUrl } = getConfigs()
this.setState({
url: specSelectors.url(),
url: this.getDefinitionUrl(),
validatorUrl: validatorUrl === undefined ? "https://online.swagger.io/validator" : validatorUrl
})
}
+108
View File
@@ -0,0 +1,108 @@
import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import Im from "immutable"
import { createDeepLinkPath, sanitizeUrl } from "core/utils"
export default class OperationTag extends React.Component {
static defaultProps = {
tagObj: Im.fromJS({}),
tag: "",
}
static propTypes = {
tagObj: ImPropTypes.map.isRequired,
tag: PropTypes.string.isRequired,
layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired,
getConfigs: PropTypes.func.isRequired,
getComponent: PropTypes.func.isRequired,
children: PropTypes.element,
}
render() {
const {
tagObj,
tag,
children,
layoutSelectors,
layoutActions,
getConfigs,
getComponent,
} = this.props
let {
docExpansion,
deepLinking,
} = getConfigs()
const isDeepLinkingEnabled = deepLinking && deepLinking !== "false"
const Collapse = getComponent("Collapse")
const Markdown = getComponent("Markdown")
const DeepLink = getComponent("DeepLink")
let tagDescription = tagObj.getIn(["tagDetails", "description"], null)
let tagExternalDocsDescription = tagObj.getIn(["tagDetails", "externalDocs", "description"])
let tagExternalDocsUrl = tagObj.getIn(["tagDetails", "externalDocs", "url"])
let isShownKey = ["operations-tag", createDeepLinkPath(tag)]
let showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list")
return (
<div className={showTag ? "opblock-tag-section is-open" : "opblock-tag-section"} >
<h4
onClick={() => layoutActions.show(isShownKey, !showTag)}
className={!tagDescription ? "opblock-tag no-desc" : "opblock-tag" }
id={isShownKey.join("-")}>
<DeepLink
enabled={isDeepLinkingEnabled}
isShown={showTag}
path={tag}
text={tag} />
{ !tagDescription ? <small></small> :
<small>
<Markdown source={tagDescription} />
</small>
}
<div>
{ !tagExternalDocsDescription ? null :
<small>
{ tagExternalDocsDescription }
{ tagExternalDocsUrl ? ": " : null }
{ tagExternalDocsUrl ?
<a
href={sanitizeUrl(tagExternalDocsUrl)}
onClick={(e) => e.stopPropagation()}
target={"_blank"}
>{tagExternalDocsUrl}</a> : null
}
</small>
}
</div>
<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>
</button>
</h4>
<Collapse isOpened={showTag}>
{children}
</Collapse>
</div>
)
}
}
+39 -85
View File
@@ -1,7 +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 = [
"get", "put", "post", "delete", "options", "head", "patch"
@@ -38,18 +37,12 @@ export default class Operations extends React.Component {
let taggedOps = specSelectors.taggedOperations()
const OperationContainer = getComponent("OperationContainer", true)
const Collapse = getComponent("Collapse")
const Markdown = getComponent("Markdown")
const DeepLink = getComponent("DeepLink")
const OperationTag = getComponent("OperationTag")
let {
docExpansion,
maxDisplayedTags,
deepLinking
} = getConfigs()
const isDeepLinkingEnabled = deepLinking && deepLinking !== "false"
let filter = layoutSelectors.currentFilter()
if (filter) {
@@ -66,88 +59,49 @@ export default class Operations extends React.Component {
<div>
{
taggedOps.map( (tagObj, tag) => {
let operations = tagObj.get("operations")
let tagDescription = tagObj.getIn(["tagDetails", "description"], null)
let tagExternalDocsDescription = tagObj.getIn(["tagDetails", "externalDocs", "description"])
let tagExternalDocsUrl = tagObj.getIn(["tagDetails", "externalDocs", "url"])
let isShownKey = ["operations-tag", createDeepLinkPath(tag)]
let showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list")
const operations = tagObj.get("operations")
return (
<div className={showTag ? "opblock-tag-section is-open" : "opblock-tag-section"} key={"operation-" + tag}>
<h4
onClick={() => layoutActions.show(isShownKey, !showTag)}
className={!tagDescription ? "opblock-tag no-desc" : "opblock-tag" }
id={isShownKey.join("-")}>
<DeepLink
enabled={isDeepLinkingEnabled}
isShown={showTag}
path={tag}
text={tag} />
{ !tagDescription ? <small></small> :
<small>
<Markdown source={tagDescription} />
</small>
}
<div>
{ !tagExternalDocsDescription ? null :
<small>
{ tagExternalDocsDescription }
{ tagExternalDocsUrl ? ": " : null }
{ tagExternalDocsUrl ?
<a
href={sanitizeUrl(tagExternalDocsUrl)}
onClick={(e) => e.stopPropagation()}
target={"_blank"}
>{tagExternalDocsUrl}</a> : null
}
</small>
}
</div>
<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>
</button>
</h4>
<Collapse isOpened={showTag}>
{
operations.map( op => {
const path = op.get("path")
const method = op.get("method")
const specPath = Im.List(["paths", path, method])
<OperationTag
key={"operation-" + tag}
tagObj={tagObj}
tag={tag}
layoutSelectors={layoutSelectors}
layoutActions={layoutActions}
getConfigs={getConfigs}
getComponent={getComponent}>
{
operations.map( op => {
const path = op.get("path")
const method = op.get("method")
const specPath = Im.List(["paths", path, method])
// FIXME: (someday) this logic should probably be in a selector,
// but doing so would require further opening up
// selectors to the plugin system, to allow for dynamic
// overriding of low-level selectors that other selectors
// rely on. --KS, 12/17
const validMethods = specSelectors.isOAS3() ?
OAS3_OPERATION_METHODS : SWAGGER2_OPERATION_METHODS
// FIXME: (someday) this logic should probably be in a selector,
// but doing so would require further opening up
// selectors to the plugin system, to allow for dynamic
// overriding of low-level selectors that other selectors
// rely on. --KS, 12/17
const validMethods = specSelectors.isOAS3() ?
OAS3_OPERATION_METHODS : SWAGGER2_OPERATION_METHODS
if(validMethods.indexOf(method) === -1) {
return null
}
if(validMethods.indexOf(method) === -1) {
return null
}
return <OperationContainer
key={`${path}-${method}`}
specPath={specPath}
op={op}
path={path}
method={method}
tag={tag}
/>
}).toArray()
}
</Collapse>
</div>
)
return <OperationContainer
key={`${path}-${method}`}
specPath={specPath}
op={op}
path={path}
method={method}
tag={tag}
/>
}).toArray()
}
</OperationTag>
)
}).toArray()
}
+1 -1
View File
@@ -47,7 +47,7 @@ export default class ParamBody extends PureComponent {
updateValues = (props) => {
let { specSelectors, pathMethod, param, isExecute, consumesValue="" } = props
let parameter = specSelectors ? specSelectors.parameterWithMeta(pathMethod, param.get("name"), param.get("in")) : fromJS({})
let parameter = (specSelectors ? specSelectors.parameterWithMeta(pathMethod, param.get("name"), param.get("in")) : fromJS({})) || param
let isXml = /xml/i.test(consumesValue)
let isJson = /json/i.test(consumesValue)
let paramValue = isXml ? parameter.get("value_xml") : parameter.get("value")
+39 -25
View File
@@ -3,7 +3,7 @@ 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"
import { getExtensions, getCommonExtensions } from "core/utils"
export default class ParameterRow extends Component {
static propTypes = {
@@ -22,20 +22,7 @@ export default class ParameterRow extends Component {
constructor(props, context) {
super(props, context)
let { specSelectors, pathMethod, param } = props
let defaultValue = param.get("default")
let xExampleValue = param.get("x-example")
let parameter = specSelectors.parameterWithMeta(pathMethod, param.get("name"), param.get("in"))
let value = parameter ? parameter.get("value") : ""
if( param.get("in") !== "body" ) {
if ( xExampleValue !== undefined && value === undefined && specSelectors.isSwagger2() ) {
this.onChangeWrapper(xExampleValue)
} else if ( defaultValue !== undefined && value === undefined ) {
this.onChangeWrapper(defaultValue)
}
}
this.setDefaultValue()
}
componentWillReceiveProps(props) {
@@ -43,8 +30,7 @@ export default class ParameterRow extends Component {
let { isOAS3 } = specSelectors
let example = param.get("example")
let defaultValue = param.get("default")
let parameter = specSelectors.parameterWithMeta(pathMethod, param.get("name"), param.get("in"))
let parameter = specSelectors.parameterWithMeta(pathMethod, param.get("name"), param.get("in")) || param
let enumValue
if(isOAS3()) {
@@ -61,8 +47,6 @@ export default class ParameterRow extends Component {
value = paramValue
} else if ( example !== undefined ) {
value = example
} else if ( defaultValue !== undefined) {
value = defaultValue
} else if ( param.get("required") && enumValue && enumValue.size ) {
value = enumValue.first()
}
@@ -77,12 +61,35 @@ export default class ParameterRow extends Component {
return onChange(param, value)
}
setDefaultValue = () => {
let { specSelectors, pathMethod, param } = this.props
if (param.get("value") !== undefined) {
return
}
let schema = specSelectors.isOAS3() ? param.get("schema", Map({})) : param
let defaultValue = schema.get("default")
let xExampleValue = param.get("x-example") // Swagger 2 only
let parameter = specSelectors.parameterWithMeta(pathMethod, param.get("name"), param.get("in"))
let value = parameter ? parameter.get("value") : ""
if( param.get("in") !== "body" ) {
if ( xExampleValue !== undefined && value === undefined && specSelectors.isSwagger2() ) {
this.onChangeWrapper(xExampleValue)
} else if ( defaultValue !== undefined && value === undefined ) {
this.onChangeWrapper(defaultValue)
}
}
}
render() {
let {param, onChange, getComponent, getConfigs, isExecute, fn, onChangeConsumes, specSelectors, pathMethod, specPath} = this.props
let { isOAS3 } = specSelectors
const { showExtensions } = getConfigs()
const { showExtensions, showCommonExtensions } = getConfigs()
// const onChangeWrapper = (value) => onChange(param, value)
const JsonSchemaForm = getComponent("JsonSchemaForm")
@@ -106,15 +113,17 @@ export default class ParameterRow extends Component {
const ParameterExt = getComponent("ParameterExt")
let paramWithMeta = specSelectors.parameterWithMeta(pathMethod, param.get("name"), param.get("in"))
let format = param.get("format")
let schema = isOAS3 && isOAS3() ? param.get("schema") : param
let type = schema.get("type")
let isFormData = inType === "formData"
let isFormDataSupported = "FormData" in win
let required = param.get("required")
let itemType = schema.getIn(["items", "type"])
let value = paramWithMeta ? paramWithMeta.get("value") : ""
let extensions = getExtensions(param)
let commonExt = showCommonExtensions ? getCommonExtensions(param) : null
let extensions = showExtensions ? getExtensions(param) : null
let paramItems // undefined
let paramEnum // undefined
@@ -147,17 +156,22 @@ export default class ParameterRow extends Component {
}
return (
<tr>
<tr className="parameters">
<td className="col parameters-col_name">
<div className={required ? "parameter__name required" : "parameter__name"}>
{ param.get("name") }
{ !required ? null : <span style={{color: "red"}}>&nbsp;*</span> }
</div>
<div className="parameter__type">{ type } { itemType && `[${itemType}]` }</div>
<div className="parameter__type">
{ type }
{ itemType && `[${itemType}]` }
{ format && <span className="prop-format">(${format})</span>}
</div>
<div className="parameter__deprecated">
{ isOAS3 && isOAS3() && param.get("deprecated") ? "deprecated": null }
</div>
<div className="parameter__in">({ param.get("in") })</div>
{ !showCommonExtensions || !commonExt.size ? null : commonExt.map((v, key) => <ParameterExt key={`${key}-${v}`} xKey={key} xVal={v} /> )}
{ !showExtensions || !extensions.size ? null : extensions.map((v, key) => <ParameterExt key={`${key}-${v}`} xKey={key} xVal={v} /> )}
</td>
@@ -197,7 +211,7 @@ export default class ParameterRow extends Component {
getConfigs={ getConfigs }
isExecute={ isExecute }
specSelectors={ specSelectors }
schema={ schema }
schema={ param.get("schema") }
example={ bodyParam }/>
: null
}
+3 -2
View File
@@ -10,11 +10,12 @@ export default class Primitive extends Component {
getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired,
name: PropTypes.string,
displayName: PropTypes.string,
depth: PropTypes.number
}
render(){
let { schema, getComponent, getConfigs, name, depth } = this.props
let { schema, getComponent, getConfigs, name, displayName, depth } = this.props
const { showExtensions } = getConfigs()
@@ -27,7 +28,7 @@ export default class Primitive extends Component {
let format = schema.get("format")
let xml = schema.get("xml")
let enumArray = schema.get("enum")
let title = schema.get("title") || name
let title = schema.get("title") || displayName || name
let description = schema.get("description")
let extensions = getExtensions(schema)
let properties = schema
+5 -17
View File
@@ -1,7 +1,7 @@
import React from "react"
import PropTypes from "prop-types"
import Remarkable from "remarkable"
import sanitize from "sanitize-html"
import DomPurify from "dompurify"
import cx from "classnames"
// eslint-disable-next-line no-useless-escape
@@ -35,25 +35,13 @@ function Markdown({ source, className = "" }) {
Markdown.propTypes = {
source: PropTypes.string.isRequired,
className: PropTypes.string.isRequired
className: PropTypes.string
}
export default Markdown
const sanitizeOptions = {
allowedTags: sanitize.defaults.allowedTags.concat([ "h1", "h2", "img", "span" ]),
allowedAttributes: {
...sanitize.defaults.allowedAttributes,
"img": sanitize.defaults.allowedAttributes.img.concat(["title"]),
"td": [ "colspan" ],
"*": [ "class" ]
},
allowedSchemesByTag: { img: [ "http", "https", "data" ] },
textFilter: function(text) {
return text.replace(/&quot;/g, "\"")
}
}
export function sanitizer(str) {
return sanitize(str, sanitizeOptions)
return DomPurify.sanitize(str, {
ADD_ATTR: ["target"]
})
}
+57 -9
View File
@@ -5,7 +5,10 @@ import lowerCase from "lodash/lowerCase"
import { extractFileNameFromContentDispositionHeader } from "core/utils"
import win from "core/window"
export default class ResponseBody extends React.Component {
export default class ResponseBody extends React.PureComponent {
state = {
parsedContent: null
}
static propTypes = {
content: PropTypes.any.isRequired,
@@ -15,9 +18,41 @@ export default class ResponseBody extends React.Component {
url: PropTypes.string
}
updateParsedContent = (prevContent) => {
const { content } = this.props
if(prevContent === content) {
return
}
if(content && content instanceof Blob) {
var reader = new FileReader()
reader.onload = () => {
this.setState({
parsedContent: reader.result
})
}
reader.readAsText(content)
} else {
this.setState({
parsedContent: content.toString()
})
}
}
componentDidMount() {
this.updateParsedContent(null)
}
componentDidUpdate(prevProps) {
this.updateParsedContent(prevProps.content)
}
render() {
let { content, contentType, url, headers={}, getComponent } = this.props
const { parsedContent } = this.state
const HighlightCode = getComponent("highlightCode")
const downloadName = "response_" + new Date().getTime()
let body, bodyEl
url = url || ""
@@ -29,9 +64,7 @@ export default class ResponseBody extends React.Component {
(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) {
if ("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)
@@ -66,7 +99,7 @@ export default class ResponseBody extends React.Component {
body = "can't parse JSON. Raw result:\n\n" + content
}
bodyEl = <HighlightCode value={ body } />
bodyEl = <HighlightCode downloadable fileName={`${downloadName}.json`} value={ body } />
// XML
} else if (/xml/i.test(contentType)) {
@@ -74,11 +107,11 @@ export default class ResponseBody extends React.Component {
textNodesOnSameLine: true,
indentor: " "
})
bodyEl = <HighlightCode value={ body } />
bodyEl = <HighlightCode downloadable fileName={`${downloadName}.xml`} value={ body } />
// HTML or Plain Text
} else if (lowerCase(contentType) === "text/html" || /text\/plain/.test(contentType)) {
bodyEl = <HighlightCode value={ content } />
bodyEl = <HighlightCode downloadable fileName={`${downloadName}.html`} value={ content } />
// Image
} else if (/^image\//i.test(contentType)) {
@@ -92,10 +125,25 @@ export default class ResponseBody extends React.Component {
} else if (/^audio\//i.test(contentType)) {
bodyEl = <pre><audio controls><source src={ url } type={ contentType } /></audio></pre>
} else if (typeof content === "string") {
bodyEl = <HighlightCode value={ content } />
bodyEl = <HighlightCode downloadable fileName={`${downloadName}.txt`} value={ content } />
} else if ( content.size > 0 ) {
// We don't know the contentType, but there was some content returned
bodyEl = <div>Unknown response type</div>
if(parsedContent) {
// We were able to squeeze something out of content
// in `updateParsedContent`, so let's display it
bodyEl = <div>
<p className="i">
Unrecognized response type; displaying content as text.
</p>
<HighlightCode downloadable fileName={`${downloadName}.txt`} value={ parsedContent } />
</div>
} else {
// Give up
bodyEl = <p className="i">
Unrecognized response type; unable to display.
</p>
}
} else {
// We don't know the contentType and there was no content returned
bodyEl = null
+2 -2
View File
@@ -37,12 +37,12 @@ export default class Schemes extends React.Component {
}
render() {
let { schemes } = this.props
let { schemes, currentScheme } = this.props
return (
<label htmlFor="schemes">
<span className="schemes-title">Schemes</span>
<select onChange={ this.onChange }>
<select onChange={ this.onChange } value={currentScheme}>
{ schemes.valueSeq().map(
( scheme ) => <option value={ scheme } key={ scheme }>{ scheme }</option>
).toArray()}
+43
View File
@@ -0,0 +1,43 @@
import React from "react"
const SvgAssets = () =>
<div>
<svg xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" style={{
position: "absolute",
width: 0,
height: 0
}}>
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
</symbol>
</defs>
</svg>
</div>
export default SvgAssets
@@ -0,0 +1,54 @@
import React from "react"
import PropTypes from "prop-types"
export default class VersionPragmaFilter extends React.PureComponent {
static propTypes = {
isSwagger2: PropTypes.bool.isRequired,
isOAS3: PropTypes.bool.isRequired,
bypass: PropTypes.bool,
alsoShow: PropTypes.element,
children: PropTypes.any,
}
static defaultProps = {
alsoShow: null,
children: null,
bypass: false,
}
render() {
const { bypass, isSwagger2, isOAS3, alsoShow } = this.props
if(bypass) {
return <div>{ this.props.children }</div>
}
if(isSwagger2 && isOAS3) {
return <div className="version-pragma">
{alsoShow}
<div className="version-pragma__message version-pragma__message--ambiguous">
<div>
<h3>Unable to render this definition</h3>
<p><code>swagger</code> and <code>openapi</code> fields cannot be present in the same Swagger or OpenAPI definition. Please remove one of the fields.</p>
<p>Supported version fields are <code>swagger: {"\"2.0\""}</code> and those that match <code>openapi: 3.0.n</code> (for example, <code>openapi: 3.0.0</code>).</p>
</div>
</div>
</div>
}
if(!isSwagger2 && !isOAS3) {
return <div className="version-pragma">
{alsoShow}
<div className="version-pragma__message version-pragma__message--missing">
<div>
<h3>Unable to render this definition</h3>
<p>The provided definition does not specify a valid version field.</p>
<p>Please indicate a valid Swagger or OpenAPI version field. Supported version fields are <code>swagger: {"\"2.0\""}</code> and those that match <code>openapi: 3.0.n</code> (for example, <code>openapi: 3.0.0</code>).</p>
</div>
</div>
</div>
}
return <div>{ this.props.children }</div>
}
}
+18 -7
View File
@@ -27,7 +27,7 @@ module.exports = function SwaggerUI(opts) {
const defaults = {
// Some general settings, that we floated to the top
dom_id: null,
dom_id: null, // eslint-disable-line camelcase
domNode: null,
spec: {},
url: "",
@@ -49,6 +49,7 @@ module.exports = function SwaggerUI(opts) {
defaultModelExpandDepth: 1,
defaultModelsExpandDepth: 1,
showExtensions: false,
showCommonExtensions: false,
supportedSubmitMethods: [
"get",
"put",
@@ -130,10 +131,9 @@ module.exports = function SwaggerUI(opts) {
var system = store.getSystem()
const downloadSpec = (fetchedConfig) => {
if(typeof constructorConfig !== "object") {
return system
}
if(typeof constructorConfig !== "object") {
return system
}
let localConfig = system.specSelectors.getLocalConfig ? system.specSelectors.getLocalConfig() : {}
let mergedConfig = deepExtend({}, localConfig, constructorConfig, fetchedConfig || {}, queryConfig)
@@ -144,6 +144,10 @@ module.exports = function SwaggerUI(opts) {
store.setConfigs(mergedConfig)
if(system.configsActions) {
system.configsActions.loaded()
}
if (fetchedConfig !== null) {
if (!queryConfig.url && typeof mergedConfig.spec === "object" && Object.keys(mergedConfig.spec).length) {
system.specActions.updateUrl("")
@@ -170,10 +174,17 @@ module.exports = function SwaggerUI(opts) {
return system
}
let configUrl = queryConfig.config || constructorConfig.configUrl
const configUrl = queryConfig.config || constructorConfig.configUrl
if (!configUrl || !system.specActions.getConfigByUrl || system.specActions.getConfigByUrl && !system.specActions.getConfigByUrl(configUrl, downloadSpec)) {
if (!configUrl || !system.specActions || !system.specActions.getConfigByUrl || system.specActions.getConfigByUrl && !system.specActions.getConfigByUrl({
url: configUrl,
loadRemoteConfig: true,
requestInterceptor: constructorConfig.requestInterceptor,
responseInterceptor: constructorConfig.responseInterceptor,
}, downloadSpec)) {
return downloadSpec()
} else {
system.specActions.getConfigByUrl(configUrl, downloadSpec)
}
return system
+62 -2
View File
@@ -1,8 +1,10 @@
import React, { PureComponent, Component } from "react"
import PropTypes from "prop-types"
import { List, fromJS } from "immutable"
import cx from "classnames"
import ImPropTypes from "react-immutable-proptypes"
import DebounceInput from "react-debounce-input"
import { getSampleSchema } from "core/utils"
//import "less/json-schema-form"
const noop = ()=> {}
@@ -15,6 +17,7 @@ const JsonSchemaPropShape = {
schema: PropTypes.object,
errors: ImPropTypes.list,
required: PropTypes.bool,
dispatchInitialValue: PropTypes.bool,
description: PropTypes.any
}
@@ -32,6 +35,13 @@ export class JsonSchemaForm extends Component {
static propTypes = JsonSchemaPropShape
static defaultProps = JsonSchemaDefaultProps
componentDidMount() {
const { dispatchInitialValue, value, onChange } = this.props
if(dispatchInitialValue) {
onChange(value)
}
}
render() {
let { schema, errors, value, onChange, getComponent, fn } = this.props
@@ -191,7 +201,7 @@ export class JsonSchema_boolean extends Component {
onEnumChange = (val) => this.props.onChange(val)
render() {
let { getComponent, value, errors, schema } = this.props
let { getComponent, value, errors, schema, required } = this.props
errors = errors.toJS ? errors.toJS() : []
const Select = getComponent("Select")
@@ -200,7 +210,57 @@ export class JsonSchema_boolean extends Component {
title={ errors.length ? errors : ""}
value={ String(value) }
allowedValues={ fromJS(schema.enum || ["true", "false"]) }
allowEmptyValue={ !this.props.required }
allowEmptyValue={ !schema.enum || !required }
onChange={ this.onEnumChange }/>)
}
}
export class JsonSchema_object extends PureComponent {
constructor() {
super()
}
static propTypes = JsonSchemaPropShape
static defaultProps = JsonSchemaDefaultProps
componentDidMount() {
if(!this.props.value && this.props.schema) {
this.resetValueToSample()
}
}
resetValueToSample = () => {
this.onChange(getSampleSchema(this.props.schema) )
}
onChange = (value) => {
this.props.onChange(value)
}
handleOnChange = e => {
const inputValue = e.target.value
this.onChange(inputValue)
}
render() {
let {
getComponent,
value,
errors
} = this.props
const TextArea = getComponent("TextArea")
return (
<div>
<TextArea
className={cx({ invalid: errors.size })}
title={ errors.size ? errors.join(", ") : ""}
value={value}
onChange={ this.handleOnChange }/>
</div>
)
}
}
+2 -2
View File
@@ -81,8 +81,8 @@ export function getLineNumberForPath(yaml, path) {
* YAML or JSON string
* @param {array} path
* an array of stings that constructs a
* JSON Path similiar to JSON Pointers(RFC 6901). The difference is, each
* component of path is an item of the array intead of beinf seperated with
* JSON Path similar to JSON Pointers(RFC 6901). The difference is, each
* component of path is an item of the array instead of being separated with
* slash(/) in a string
*/
export function positionRangeForPath(yaml, path) {
+1 -1
View File
@@ -1,6 +1,6 @@
import React from "react"
// Nothing by default- component can be overriden by another plugin.
// Nothing by default- component can be overridden by another plugin.
export default class JumpToPath extends React.Component {
render() {
+4
View File
@@ -18,3 +18,7 @@ export function toggle(configName) {
payload: configName,
}
}
// Hook
export const loaded = () => () => {}
+12
View File
@@ -0,0 +1,12 @@
import YAML from "js-yaml"
export const parseYamlConfig = (yaml, system) => {
try {
return YAML.safeLoad(yaml)
} catch(e) {
if (system) {
system.errActions.newThrownErr( new Error(e) )
}
return {}
}
}
+2 -38
View File
@@ -1,46 +1,10 @@
import YAML from "js-yaml"
import yamlConfig from "root/swagger-config.yaml"
import { parseYamlConfig } from "./helpers"
import * as actions from "./actions"
import * as specActions from "./spec-actions"
import * as selectors from "./selectors"
import reducers from "./reducers"
const parseYamlConfig = (yaml, system) => {
try {
return YAML.safeLoad(yaml)
} catch(e) {
if (system) {
system.errActions.newThrownErr( new Error(e) )
}
return {}
}
}
const specActions = {
downloadConfig: (url) => ({fn}) => {
let {fetch} = fn
return fetch(url)
},
getConfigByUrl: (configUrl, cb)=> ({ specActions }) => {
if (configUrl) {
return specActions.downloadConfig(configUrl).then(next, next)
}
function next(res) {
if (res instanceof Error || res.status >= 400) {
specActions.updateLoadingStatus("failedConfig")
specActions.updateLoadingStatus("failedConfig")
specActions.updateUrl("")
console.error(res.statusText + " " + configUrl)
cb(null)
} else {
cb(parseYamlConfig(res.text))
}
}
}
}
const specSelectors = {
getLocalConfig: () => {
return parseYamlConfig(yamlConfig)
+25
View File
@@ -0,0 +1,25 @@
import { parseYamlConfig } from "./helpers"
export const downloadConfig = (req) => (system) => {
const {fn: { fetch }} = system
return fetch(req)
}
export const getConfigByUrl = (req, cb)=> ({ specActions }) => {
if (req) {
return specActions.downloadConfig(req).then(next, next)
}
function next(res) {
if (res instanceof Error || res.status >= 400) {
specActions.updateLoadingStatus("failedConfig")
specActions.updateLoadingStatus("failedConfig")
specActions.updateUrl("")
console.error(res.statusText + " " + req.url)
cb(null)
} else {
cb(parseYamlConfig(res.text))
}
}
}
+18 -13
View File
@@ -1,18 +1,23 @@
// import reducers from "./reducers"
// import * as actions from "./actions"
// import * as selectors from "./selectors"
import * as specWrapActions from "./spec-wrap-actions"
import * as layoutWrapActions from "./layout-wrap-actions"
import layout from "./layout"
import OperationWrapper from "./operation-wrapper"
import OperationTagWrapper from "./operation-tag-wrapper"
export default function() {
return {
return [layout, {
statePlugins: {
spec: {
wrapActions: specWrapActions
},
layout: {
wrapActions: layoutWrapActions
configs: {
wrapActions: {
loaded: (ori, system) => (...args) => {
ori(...args)
const hash = window.location.hash
system.layoutActions.parseDeepLinkHash(hash)
}
}
}
}
}
},
wrapComponents: {
operation: OperationWrapper,
OperationTag: OperationTagWrapper,
},
}]
}
@@ -1,37 +0,0 @@
import { setHash } from "./helpers"
import { createDeepLinkPath } from "core/utils"
export const show = (ori, { getConfigs }) => (...args) => {
ori(...args)
const isDeepLinkingEnabled = getConfigs().deepLinking
if(!isDeepLinkingEnabled || isDeepLinkingEnabled === "false") {
return
}
try {
let [thing, shown] = args
let [type] = thing
if(type === "operations-tag" || type === "operations") {
if(!shown) {
return setHash("/")
}
if(type === "operations") {
let [, tag, operationId] = thing
setHash(`/${createDeepLinkPath(tag)}/${createDeepLinkPath(operationId)}`)
}
if(type === "operations-tag") {
let [, tag] = thing
setHash(`/${createDeepLinkPath(tag)}`)
}
}
} catch(e) {
// This functionality is not mission critical, so if something goes wrong
// we'll just move on
console.error(e)
}
}
+181
View File
@@ -0,0 +1,181 @@
import { setHash } from "./helpers"
import zenscroll from "zenscroll"
import Im, { fromJS } from "immutable"
const SCROLL_TO = "layout_scroll_to"
const CLEAR_SCROLL_TO = "layout_clear_scroll"
export const show = (ori, { getConfigs, layoutSelectors }) => (...args) => {
ori(...args)
if(!getConfigs().deepLinking) {
return
}
try {
let [tokenArray, shown] = args
//Coerce in to array
tokenArray = Array.isArray(tokenArray) ? tokenArray : [tokenArray]
// Convert into something we can put in the URL hash
// Or return empty, if we cannot
const urlHashArray = layoutSelectors.urlHashArrayFromIsShownKey(tokenArray) // Will convert
// No hash friendly list?
if(!urlHashArray.length)
return
const [type, assetName] = urlHashArray
if (!shown) {
return setHash("/")
}
if (urlHashArray.length === 2) {
setHash(`/${type}/${assetName}`)
} else if (urlHashArray.length === 1) {
setHash(`/${type}`)
}
} catch (e) {
// This functionality is not mission critical, so if something goes wrong
// we'll just move on
console.error(e) // eslint-disable-line no-console
}
}
export const scrollTo = (path) => {
return {
type: SCROLL_TO,
payload: Array.isArray(path) ? path : [path]
}
}
export const parseDeepLinkHash = (rawHash) => ({ layoutActions, layoutSelectors, getConfigs }) => {
if(!getConfigs().deepLinking) {
return
}
if(rawHash) {
let hash = rawHash.slice(1) // # is first character
if(hash[0] === "!") {
// Parse UI 2.x shebangs
hash = hash.slice(1)
}
if(hash[0] === "/") {
// "/pet/addPet" => "pet/addPet"
// makes the split result cleaner
// also handles forgotten leading slash
hash = hash.slice(1)
}
const isShownKey = layoutSelectors.isShownKeyFromUrlHashArray(hash.split("/"))
layoutActions.show(isShownKey, true) // TODO: 'show' operation tag
layoutActions.scrollTo(isShownKey)
}
}
export const readyToScroll = (isShownKey, ref) => (system) => {
const scrollToKey = system.layoutSelectors.getScrollToKey()
if(Im.is(scrollToKey, fromJS(isShownKey))) {
system.layoutActions.scrollToElement(ref)
system.layoutActions.clearScrollTo()
}
}
// Scroll to "ref" (dom node) with the scrollbar on "container" or the nearest parent
export const scrollToElement = (ref, container) => (system) => {
try {
container = container || system.fn.getScrollParent(ref)
let myScroller = zenscroll.createScroller(container)
myScroller.to(ref)
} catch(e) {
console.error(e) // eslint-disable-line no-console
}
}
export const clearScrollTo = () => {
return {
type: CLEAR_SCROLL_TO,
}
}
// From: https://stackoverflow.com/a/42543908/3933724
// Modified to return html instead of body element as last resort
function getScrollParent(element, includeHidden) {
const LAST_RESORT = document.documentElement
let style = getComputedStyle(element)
const excludeStaticParent = style.position === "absolute"
const overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/
if (style.position === "fixed")
return LAST_RESORT
for (let parent = element; (parent = parent.parentElement);) {
style = getComputedStyle(parent)
if (excludeStaticParent && style.position === "static") {
continue
}
if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX))
return parent
}
return LAST_RESORT
}
export default {
fn: {
getScrollParent,
},
statePlugins: {
layout: {
actions: {
scrollToElement,
scrollTo,
clearScrollTo,
readyToScroll,
parseDeepLinkHash
},
selectors: {
getScrollToKey(state) {
return state.get("scrollToKey")
},
isShownKeyFromUrlHashArray(state, urlHashArray) {
const [tag, operationId] = urlHashArray
// We only put operations in the URL
if(operationId) {
return ["operations", tag, operationId]
} else if (tag) {
return ["operations-tag", tag]
}
return []
},
urlHashArrayFromIsShownKey(state, isShownKey) {
let [type, tag, operationId] = isShownKey
// We only put operations in the URL
if(type == "operations") {
return [tag, operationId]
} else if (type == "operations-tag") {
return [tag]
}
return []
},
},
reducers: {
[SCROLL_TO](state, action) {
return state.set("scrollToKey", Im.fromJS(action.payload))
},
[CLEAR_SCROLL_TO](state) {
return state.delete("scrollToKey")
}
},
wrapActions: {
show
}
}
}
}
@@ -0,0 +1,25 @@
import React from "react"
import { PropTypes } from "prop-types"
const Wrapper = (Ori, system) => class OperationTagWrapper extends React.Component {
static propTypes = {
tag: PropTypes.object.isRequired,
}
onLoad = (ref) => {
const { tag } = this.props
const isShownKey = ["operations-tag", tag]
system.layoutActions.readyToScroll(isShownKey, ref)
}
render() {
return (
<span ref={this.onLoad}>
<Ori {...this.props} />
</span>
)
}
}
export default Wrapper
@@ -0,0 +1,26 @@
import React from "react"
import ImPropTypes from "react-immutable-proptypes"
const Wrapper = (Ori, system) => class OperationWrapper extends React.Component {
static propTypes = {
operation: ImPropTypes.map.isRequired,
}
onLoad = (ref) => {
const { operation } = this.props
const { tag, operationId } = operation.toObject()
const isShownKey = ["operations", tag, operationId]
system.layoutActions.readyToScroll(isShownKey, ref)
}
render() {
return (
<span ref={this.onLoad}>
<Ori {...this.props} />
</span>
)
}
}
export default Wrapper
@@ -1,64 +0,0 @@
import zenscroll from "zenscroll"
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 updateJsonSpec = (ori, { layoutActions, getConfigs }) => (...args) => {
ori(...args)
const isDeepLinkingEnabled = getConfigs().deepLinking
if(!isDeepLinkingEnabled || isDeepLinkingEnabled === "false") {
return
}
if(window.location.hash && !hasHashBeenParsed ) {
let hash = window.location.hash.slice(1) // # is first character
if(hash[0] === "!") {
// Parse UI 2.x shebangs
hash = hash.slice(1)
}
if(hash[0] === "/") {
// "/pet/addPet" => "pet/addPet"
// makes the split result cleaner
// also handles forgotten leading slash
hash = hash.slice(1)
}
let [tag, operationId] = hash.split("/")
let swaggerUI = document.querySelector(".swagger-ui")
let myScroller = zenscroll.createScroller(swaggerUI)
let target
if(tag && operationId) {
// Pre-expand and scroll to the operation
layoutActions.show(["operations-tag", tag], true)
layoutActions.show(["operations", tag, operationId], true)
target = document
.getElementById(`operations-${escapeDeepLinkPath(tag)}-${escapeDeepLinkPath(operationId)}`)
} else if(tag) {
// Pre-expand and scroll to the tag
layoutActions.show(["operations-tag", tag], true)
target = document.getElementById(`operations-tag-${escapeDeepLinkPath(tag)}`)
}
if(target) {
myScroller.to(target)
setTimeout(() => {
// Backup functionality: if we're still at the top of the document,
// scroll on the entire page (not within the Swagger-UI container)
if(zenscroll.getY() === 0) {
zenscroll.to(target)
}
}, 50)
}
}
hasHashBeenParsed = true
}
@@ -16,8 +16,14 @@ const Callbacks = (props) => {
return <div key={callbackName}>
<h2>{callbackName}</h2>
{ callback.map((pathItem, pathItemName) => {
if(pathItemName === "$$ref") {
return null
}
return <div key={pathItemName}>
{ pathItem.map((operation, method) => {
if(method === "$$ref") {
return null
}
let op = fromJS({
operation
})
@@ -33,7 +33,8 @@ export default class HttpAuth extends React.Component {
let { onChange } = this.props
let { value, name } = e.target
let newValue = this.state.value || {}
let newValue = Object.assign({}, this.state.value)
if(name) {
newValue[name] = value
} else {
@@ -53,7 +54,7 @@ export default class HttpAuth extends React.Component {
const Markdown = getComponent( "Markdown" )
const JumpToPath = getComponent("JumpToPath", true)
const scheme = schema.get("scheme")
const scheme = (schema.get("scheme") || "").toLowerCase()
let value = this.getValue()
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
@@ -125,7 +126,7 @@ export default class HttpAuth extends React.Component {
)
}
return <div>
<em><b>{name}</b> HTTP authentication: unsupported or missing scheme</em>
<em><b>{name}</b> HTTP authentication: unsupported scheme {`'${scheme}'`}</em>
</div>
}
}
@@ -1,10 +1,12 @@
import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { OrderedMap } from "immutable"
import { getSampleSchema } from "core/utils"
import { Map, OrderedMap } from "immutable"
const RequestBody = ({
requestBody,
requestBodyValue,
getComponent,
getConfigs,
specSelectors,
@@ -13,6 +15,10 @@ const RequestBody = ({
specPath,
onChange
}) => {
const handleFile = (e) => {
onChange(e.target.files[0])
}
const Markdown = getComponent("Markdown")
const ModelExample = getComponent("modelExample")
const RequestBodyEditor = getComponent("RequestBodyEditor")
@@ -23,10 +29,80 @@ const RequestBody = ({
const mediaTypeValue = requestBodyContent.get(contentType)
const isObjectContent = mediaTypeValue.getIn(["schema", "type"]) === "object"
if(!mediaTypeValue) {
return null
}
if(contentType === "application/octet-stream") {
const Input = getComponent("Input")
if(!isExecute) {
return <i>
Example values are not available for <code>application/octet-stream</code> media types.
</i>
}
return <Input type={"file"} onChange={handleFile} />
}
if(
isObjectContent &&
(contentType === "application/x-www-form-urlencoded"
|| contentType.indexOf("multipart/") === 0))
{
const JsonSchemaForm = getComponent("JsonSchemaForm")
const HighlightCode = getComponent("highlightCode")
const bodyProperties = requestBody.getIn(["content", contentType, "schema", "properties"], OrderedMap())
requestBodyValue = Map.isMap(requestBodyValue) ? requestBodyValue : OrderedMap()
return <div className="table-container">
<table>
<tbody>
{
bodyProperties.map((prop, key) => {
const required = prop.get("required")
const type = prop.get("type")
const format = prop.get("format")
const isFile = type === "string" && (format === "binary" || format === "base64")
return <tr key={key} className="parameters">
<td className="col parameters-col_name">
<div className={required ? "parameter__name required" : "parameter__name"}>
{ key }
{ !required ? null : <span style={{color: "red"}}>&nbsp;*</span> }
</div>
<div className="parameter__type">
{ type }
{ format && <span className="prop-format">(${format})</span>}
</div>
<div className="parameter__deprecated">
{ prop.get("deprecated") ? "deprecated": null }
</div>
</td>
<td className="col parameters-col_description">
{isExecute ?
<JsonSchemaForm
dispatchInitialValue={!isFile}
schema={prop}
getComponent={getComponent}
value={requestBodyValue.get(key) || getSampleSchema(prop)}
onChange={(value) => {
onChange(value, [key])
}}
/>
: <HighlightCode className="example" value={ getSampleSchema(prop) } />}
</td>
</tr>
})
}
</tbody>
</table>
</div>
}
return <div>
{ requestBodyDescription &&
<Markdown source={requestBodyDescription} />
@@ -53,6 +129,7 @@ const RequestBody = ({
RequestBody.propTypes = {
requestBody: ImPropTypes.orderedMap.isRequired,
requestBodyValue: ImPropTypes.orderedMap.isRequired,
getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
+2 -2
View File
@@ -6,7 +6,7 @@ export function isOAS3(jsSpec) {
return false
}
return oasVersion.startsWith("3")
return oasVersion.startsWith("3.0.")
}
export function isSwagger2(jsSpec) {
@@ -15,7 +15,7 @@ export function isSwagger2(jsSpec) {
return false
}
return swaggerVersion.startsWith("2")
return swaggerVersion.startsWith("2.0")
}
export function OAS3ComponentWrapFactory(Component) {
@@ -4,11 +4,13 @@ import parameters from "./parameters"
import VersionStamp from "./version-stamp"
import OnlineValidatorBadge from "./online-validator-badge"
import Model from "./model"
import JsonSchema_string from "./json-schema-string"
export default {
Markdown,
AuthItem,
parameters,
JsonSchema_string,
VersionStamp,
model: Model,
onlineValidatorBadge: OnlineValidatorBadge,
@@ -0,0 +1,26 @@
import React from "react"
import { OAS3ComponentWrapFactory } from "../helpers"
export default OAS3ComponentWrapFactory(({ Ori, ...props }) => {
const {
schema,
getComponent,
errors,
onChange
} = props
const { type, format } = schema
const Input = getComponent("Input")
if(type === "string" && (format === "binary" || format === "base64")) {
return <Input type="file"
className={ errors.length ? "invalid" : ""}
title={ errors.length ? errors : ""}
onChange={(e) => {
onChange(e.target.files[0])
}}
disabled={Ori.isDisabled}/>
} else {
return <Ori {...props} />
}
})
@@ -185,8 +185,17 @@ class Parameters extends Component {
<RequestBody
specPath={requestBodySpecPath}
requestBody={requestBody}
requestBodyValue={oas3Selectors.requestBodyValue(...pathMethod) || Map()}
isExecute={isExecute}
onChange={(value) => {
onChange={(value, path) => {
if(path) {
const lastValue = oas3Selectors.requestBodyValue(...pathMethod)
const usableValue = Map.isMap(lastValue) ? lastValue : Map()
return oas3Actions.setRequestBodyValue({
pathMethod,
value: usableValue.setIn(path, value)
})
}
oas3Actions.setRequestBodyValue({ value, pathMethod })
}}
contentType={oas3Selectors.requestContentType(...pathMethod)}/>
+3 -1
View File
@@ -348,7 +348,9 @@ export const executeRequest = (req) =>
if(isJSONObject(requestBody)) {
req.requestBody = JSON.parse(requestBody)
} else {
} else if(requestBody && requestBody.toJS) {
req.requestBody = requestBody.toJS()
} else{
req.requestBody = requestBody
}
}
+5 -1
View File
@@ -118,7 +118,11 @@ export default {
let operationPath = ["paths", ...path]
let metaPath = ["meta", "paths", ...path]
if(!state.getIn(["json", ...operationPath]) && !state.getIn(["resolved", ...operationPath])) {
if(
!state.getIn(["json", ...operationPath])
&& !state.getIn(["resolved", ...operationPath])
&& !state.getIn(["resolvedSubtrees", ...operationPath])
) {
// do nothing if the operation does not exist
return state
}
+1 -1
View File
@@ -12,7 +12,7 @@ export const updateJsonSpec = (ori, {specActions}) => (...args) => {
// Trigger resolution of any path-level $refs.
const [json] = args
const pathItems = get(json, ["paths"])
const pathItems = get(json, ["paths"]) || {}
const pathItemKeys = Object.keys(pathItems)
pathItemKeys.forEach(k => {
+7 -1
View File
@@ -31,6 +31,7 @@ import Clear from "core/components/clear"
import LiveResponse from "core/components/live-response"
import OnlineValidatorBadge from "core/components/online-validator-badge"
import Operations from "core/components/operations"
import OperationTag from "core/components/operation-tag"
import Operation from "core/components/operation"
import OperationExt from "core/components/operation-extensions"
import OperationExtRow from "core/components/operation-extension-row"
@@ -65,8 +66,10 @@ import ArrayModel from "core/components/array-model"
import PrimitiveModel from "core/components/primitive-model"
import Property from "core/components/property"
import TryItOutButton from "core/components/try-it-out-button"
import VersionPragmaFilter from "core/components/version-pragma-filter"
import VersionStamp from "core/components/version-stamp"
import DeepLink from "core/components/deep-link"
import SvgAssets from "core/components/svg-assets"
import Markdown from "core/components/providers/markdown"
@@ -123,14 +126,17 @@ export default function() {
TryItOutButton,
Markdown,
BaseLayout,
VersionPragmaFilter,
VersionStamp,
OperationExt,
OperationExtRow,
ParameterExt,
OperationTag,
OperationContainer,
DeepLink,
InfoUrl,
InfoBasePath
InfoBasePath,
SvgAssets
}
}
+28 -4
View File
@@ -10,7 +10,7 @@ import { memoizedSampleFromSchema, memoizedCreateXMLExample } from "core/plugins
import win from "./window"
import cssEscape from "css.escape"
const DEFAULT_REPONSE_KEY = "default"
const DEFAULT_RESPONSE_KEY = "default"
export const isImmutable = (maybe) => Im.Iterable.isIterable(maybe)
@@ -128,7 +128,7 @@ export function systemThunkMiddleware(getSystem) {
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()
return codes.contains(DEFAULT_RESPONSE_KEY) ? DEFAULT_RESPONSE_KEY : codes.filter( key => (key+"")[0] === "2").sort().first()
}
@@ -193,7 +193,7 @@ export function highlight (el) {
// running through characters and highlighting
while (prev2 = prev1,
// escaping if needed (with except for comments)
// pervious character will not be therefore
// previous character will not be therefore
// recognized as a token finalize condition
prev1 = tokenType < 7 && prev1 == "\\" ? 1 : chr
) {
@@ -503,7 +503,30 @@ export const validateParam = (param, isXml, isOAS3 = false) => {
let numberCheck = type === "number" && (value || value === 0)
let integerCheck = type === "integer" && (value || value === 0)
if ( required && !(stringCheck || arrayCheck || listCheck || fileCheck || booleanCheck || numberCheck || integerCheck) ) {
let oas3ObjectCheck = false
if(false || isOAS3 && type === "object") {
if(typeof value === "object") {
oas3ObjectCheck = true
} else if(typeof value === "string") {
try {
JSON.parse(value)
oas3ObjectCheck = true
} catch(e) {
errors.push("Parameter string value must be valid JSON")
return errors
}
}
}
const allChecks = [
stringCheck, arrayCheck, listCheck, fileCheck, booleanCheck,
numberCheck, integerCheck, oas3ObjectCheck
]
const passedAnyCheck = allChecks.some(v => !!v)
if ( required && !passedAnyCheck ) {
errors.push("Required field is not provided")
return errors
}
@@ -712,6 +735,7 @@ export const createDeepLinkPath = (str) => typeof str == "string" || str instanc
export const escapeDeepLinkPath = (str) => cssEscape( createDeepLinkPath(str) )
export const getExtensions = (defObj) => defObj.filter((v, k) => /^x-/.test(k))
export const getCommonExtensions = (defObj) => defObj.filter((v, k) => /^pattern|maxLength|minLength|maximum|minimum/.test(k))
// Deeply strips a specific key from an object.
//
+1 -1
View File
@@ -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 recommended
- `actions` - fire and forget, that will eventually be handled by a reducer. You *can* rely on the result of async actions. But in general it's not recommended
- `wrapActions` - replace an action with a wrapped action (useful for hooking into existing `actions`)
- `components` - React components
- `fn` - commons functions
+1 -1
View File
@@ -140,7 +140,7 @@ export default class Topbar extends React.Component {
<div className="topbar">
<div className="wrapper">
<div className="topbar-wrapper">
<Link href="#">
<Link>
<img height="30" width="30" src={ Logo } alt="Swagger UI"/>
<span>swagger</span>
</Link>
+2 -1
View File
@@ -51,7 +51,8 @@ input[type=text],
input[type=password],
input[type=search],
input[type=email],
input[type=file]
input[type=file],
textarea
{
min-width: 100px;
margin: 5px 0;
+54
View File
@@ -626,6 +626,33 @@
}
}
.highlight-code {
position: relative;
> .microlight {
overflow-y: auto;
max-height: 400px;
min-height: 6em;
}
}
.download-contents {
position: absolute;
bottom: 10px;
right: 10px;
cursor: pointer;
background: #7d8293;
text-align: center;
padding: 5px;
border-radius: 4px;
font-family: 'Titillium Web', sans-serif;
font-weight: 600;
color: white;
font-size: 14px;
height: 30px;
width: 75px;
}
.scheme-container
{
margin: 0 0 20px 0;
@@ -769,3 +796,30 @@ a.nostyle {
cursor: pointer;
}
}
.version-pragma {
height: 100%;
padding: 5em 0px;
&__message {
display: flex;
justify-content: center;
height: 100%;
font-size: 1.2em;
text-align: center;
line-height: 1.5em;
padding: 0px .6em;
> div {
max-width: 55ch;
flex: 1;
}
code {
background-color: #dedede;
padding: 4px 4px 2px;
white-space: pre;
}
}
}
@@ -0,0 +1,67 @@
/* eslint-env mocha */
import React from "react"
import { List, fromJS } from "immutable"
import expect, { createSpy } from "expect"
import { render } from "enzyme"
import ParameterRow from "components/parameter-row"
describe("bug #4557: default parameter values", function(){
it("should apply a Swagger 2.0 default value", function(){
const paramValue = fromJS({
description: "a pet",
type: "string",
default: "MyDefaultValue"
})
let props = {
getComponent: ()=> "div",
specSelectors: {
security(){},
parameterWithMeta(){ return paramValue },
isOAS3(){ return false }
},
operation: {get: ()=>{}},
onChange: createSpy(),
param: paramValue,
onChangeConsumes: () => {},
pathMethod: [],
getConfigs: () => { return {} },
specPath: List([])
}
render(<ParameterRow {...props}/>)
expect(props.onChange).toHaveBeenCalledWith(paramValue, "MyDefaultValue")
})
it("should apply an OpenAPI 3.0 default value", function(){
const paramValue = fromJS({
description: "a pet",
schema: {
type: "string",
default: "MyDefaultValue"
}
})
let props = {
getComponent: ()=> "div",
specSelectors: {
security(){},
parameterWithMeta(){ return paramValue },
isOAS3(){ return true }
},
operation: {get: ()=>{}},
onChange: createSpy(),
param: paramValue,
onChangeConsumes: () => {},
pathMethod: [],
getConfigs: () => { return {} },
specPath: List([])
}
render(<ParameterRow {...props}/>)
expect(props.onChange).toHaveBeenCalledWith(paramValue, "MyDefaultValue")
})
})
+86 -4
View File
@@ -1,12 +1,13 @@
/* eslint-env mocha */
import React from "react"
import { List } from "immutable"
import expect, { createSpy } from "expect"
import { Select, Input } from "components/layout-utils"
import { render } from "enzyme"
import { Select, Input, TextArea } from "components/layout-utils"
import { mount, render } from "enzyme"
import * as JsonSchemaComponents from "core/json-schema-components"
import { JsonSchemaForm } from "core/json-schema-components"
const components = {...JsonSchemaComponents, Select, Input}
const components = {...JsonSchemaComponents, Select, Input, TextArea}
const getComponentStub = (name) => {
if(components[name]) return components[name]
@@ -85,6 +86,54 @@ describe("<JsonSchemaForm/>", function(){
expect(wrapper.find("select option").eq(2).text()).toEqual("false")
})
it("should render the correct options for an enum boolean parameter", function(){
let props = {
getComponent: getComponentStub,
value: "",
onChange: () => {},
keyName: "",
fn: {},
schema: {
type: "boolean",
enum: ["true"]
}
}
let wrapper = render(<JsonSchemaForm {...props}/>)
expect(wrapper.find("select").length).toEqual(1)
expect(wrapper.find("select option").length).toEqual(2)
expect(wrapper.find("select option").eq(0).text()).toEqual("--")
expect(wrapper.find("select option").eq(1).text()).toEqual("true")
expect(wrapper.find("select option:checked").first().text()).toEqual("--")
})
it("should render the correct options for a required boolean parameter", function(){
let props = {
getComponent: getComponentStub,
value: "",
onChange: () => {},
keyName: "",
fn: {},
schema: {
type: "boolean",
required: true
}
}
let wrapper = render(<JsonSchemaForm {...props}/>)
expect(wrapper.find("select").length).toEqual(1)
expect(wrapper.find("select option").length).toEqual(3)
expect(wrapper.find("select option").eq(0).text()).toEqual("--")
expect(wrapper.find("select option").eq(1).text()).toEqual("true")
expect(wrapper.find("select option").eq(2).text()).toEqual("false")
expect(wrapper.find("select option:checked").first().text()).toEqual("--")
})
it("should render the correct options for a required enum boolean parameter", function(){
let props = {
@@ -104,7 +153,40 @@ describe("<JsonSchemaForm/>", function(){
expect(wrapper.find("select").length).toEqual(1)
expect(wrapper.find("select option").length).toEqual(1)
expect(wrapper.find("select option").first().text()).toEqual("true")
expect(wrapper.find("select option").eq(0).text()).toEqual("true")
expect(wrapper.find("select option:checked").first().text()).toEqual("true")
})
})
describe("objects", function() {
it("should render the correct editor for an OAS3 object parameter", function(){
let updateQueue = []
let props = {
getComponent: getComponentStub,
value: "",
onChange: (value) => {
updateQueue.push({ value })
},
keyName: "",
fn: {},
errors: List(),
schema: {
type: "object",
properties: {
id: {
type: "string",
example: "abc123"
}
}
}
}
let wrapper = mount(<JsonSchemaForm {...props}/>)
updateQueue.forEach(newProps => wrapper.setProps(newProps))
expect(wrapper.find("textarea").length).toEqual(1)
expect(wrapper.find("textarea").text()).toEqual(`{\n "id": "abc123"\n}`)
})
})
describe("unknown types", function() {
+7 -7
View File
@@ -16,19 +16,19 @@ describe("Markdown component", function() {
it("allows td elements with colspan attrib", function() {
const str = `<table><tr><td>ABC</td></tr></table>`
const el = render(<Markdown source={str} />)
expect(el.html()).toEqual(`<div class="markdown"><table><tr><td>ABC</td></tr></table></div>`)
expect(el.html()).toEqual(`<div class="markdown"><table><tbody><tr><td>ABC</td></tr></tbody></table></div>`)
})
it("allows image elements", function() {
const str = `![Image alt text](http://image.source "Image title")`
const el = render(<Markdown source={str} />)
expect(el.html()).toEqual(`<div class="markdown"><p><img src="http://image.source" title="Image title"></p>\n</div>`)
expect(el.html()).toEqual(`<div class="markdown"><p><img title="Image title" alt="Image alt text" src="http://image.source"></p>\n</div>`)
})
it("allows image elements with https scheme", function() {
const str = `![Image alt text](https://image.source "Image title")`
const el = render(<Markdown source={str} />)
expect(el.html()).toEqual(`<div class="markdown"><p><img src="https://image.source" title="Image title"></p>\n</div>`)
expect(el.html()).toEqual(`<div class="markdown"><p><img title="Image title" alt="Image alt text" src="https://image.source"></p>\n</div>`)
})
it("allows image elements with data scheme", function() {
@@ -52,7 +52,7 @@ describe("Markdown component", function() {
it("allows links", function() {
const str = `[Link](https://example.com/)`
const el = render(<Markdown source={str} />)
expect(el.html()).toEqual(`<div class="markdown"><p><a href="https://example.com/" target="_blank">Link</a></p>\n</div>`)
expect(el.html()).toEqual(`<div class="markdown"><p><a target="_blank" href="https://example.com/">Link</a></p>\n</div>`)
})
})
@@ -60,13 +60,13 @@ describe("Markdown component", function() {
it("allows image elements", function() {
const str = `![Image alt text](http://image.source "Image title")`
const el = render(<OAS3Markdown source={str} />)
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><p><img src="http://image.source" title="Image title"></p></div></div>`)
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><p><img title="Image title" alt="Image alt text" src="http://image.source"></p></div></div>`)
})
it("allows image elements with https scheme", function() {
const str = `![Image alt text](https://image.source "Image title")`
const el = render(<OAS3Markdown source={str} />)
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><p><img src="https://image.source" title="Image title"></p></div></div>`)
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><p><img title="Image title" alt="Image alt text" src="https://image.source"></p></div></div>`)
})
it("allows image elements with data scheme", function() {
+2 -1
View File
@@ -2,7 +2,7 @@
import React from "react"
import expect, { createSpy } from "expect"
import { shallow } from "enzyme"
import { fromJS } from "immutable"
import { fromJS, Map } from "immutable"
import Models from "components/models"
import ModelCollpase from "components/model-collapse"
import ModelComponent from "components/model-wrapper"
@@ -19,6 +19,7 @@ describe("<Models/>", function(){
},
specSelectors: {
isOAS3: () => false,
specJson: () => Map(),
definitions: function() {
return fromJS({
def1: {},
+3 -2
View File
@@ -1,6 +1,6 @@
/* eslint-env mocha */
import React from "react"
import expect, { createSpy } from "expect"
import expect from "expect"
import { render } from "enzyme"
import { fromJS } from "immutable"
import DeepLink from "components/deep-link"
@@ -10,7 +10,8 @@ import {Collapse} from "components/layout-utils"
const components = {
Collapse,
DeepLink,
OperationContainer: ({ path, method }) => <span className="mocked-op" id={`${path}-${method}`} />
OperationContainer: ({ path, method }) => <span className="mocked-op" id={`${path}-${method}`} />,
OperationTag: "div",
}
describe("<Operations/>", function(){
+70
View File
@@ -0,0 +1,70 @@
/* eslint-env mocha */
import React from "react"
import expect, { createSpy } from "expect"
import { shallow } from "enzyme"
import { fromJS, Map } from "immutable"
import VersionPragmaFilter from "components/version-pragma-filter"
describe("<VersionPragmaFilter/>", function(){
it("renders children for a Swagger 2 definition", function(){
// When
let wrapper = shallow(
<VersionPragmaFilter isSwagger2={true} isOAS3={false}>
hello!
</VersionPragmaFilter>
)
// Then
expect(wrapper.find("div").length).toEqual(1)
expect(wrapper.find("div").text()).toEqual("hello!")
})
it("renders children for an OpenAPI 3 definition", function(){
// When
let wrapper = shallow(
<VersionPragmaFilter isSwagger2={false} isOAS3={true}>
hello!
</VersionPragmaFilter>
)
// Then
expect(wrapper.find("div").length).toEqual(1)
expect(wrapper.find("div").text()).toEqual("hello!")
})
it("renders children when a bypass prop is set", function(){
// When
let wrapper = shallow(
<VersionPragmaFilter bypass>
hello!
</VersionPragmaFilter>
)
// Then
expect(wrapper.find("div").length).toEqual(1)
expect(wrapper.find("div").text()).toEqual("hello!")
})
it("renders the correct message for an ambiguous-version definition", function(){
// When
let wrapper = shallow(
<VersionPragmaFilter isSwagger2={true} isOAS3={true}>
hello!
</VersionPragmaFilter>
)
// Then
expect(wrapper.find("div.version-pragma__message--ambiguous").length).toEqual(1)
expect(wrapper.find("div.version-pragma__message--missing").length).toEqual(0)
})
it("renders the correct message for a missing-version definition", function(){
// When
let wrapper = shallow(
<VersionPragmaFilter isSwagger2={false} isOAS3={false}>
hello!
</VersionPragmaFilter>
)
// Then
expect(wrapper.find("div.version-pragma__message--missing").length).toEqual(1)
expect(wrapper.find("div.version-pragma__message--ambiguous").length).toEqual(0)
})
})
+1 -1
View File
@@ -28,7 +28,7 @@ describe("oauth2", function () {
expect(win.open.calls[0].arguments[0]).toMatch("https://testAuthorizationUrl?response_type=code&redirect_uri=&state=")
})
it("should append query paramters to authorizeUrl with query parameters", function() {
it("should append query parameters to authorizeUrl with query parameters", function() {
win.open = createSpy()
mockSchema.authorizationUrl = "https://testAuthorizationUrl?param=1"
oauth2Authorize(authConfig)
+27
View File
@@ -0,0 +1,27 @@
/* eslint-env mocha */
import expect, { createSpy } from "expect"
import { downloadConfig } from "corePlugins/configs/spec-actions"
describe("configs plugin - actions", () => {
describe("downloadConfig", () => {
it("should call the system fetch helper with a provided request", () => {
const fetchSpy = createSpy(async () => {}).andCallThrough()
const system = {
fn: {
fetch: fetchSpy
}
}
const req = {
url: "http://swagger.io/one",
requestInterceptor: a => a,
responseInterceptor: a => a,
}
downloadConfig(req)(system)
expect(fetchSpy).toHaveBeenCalledWith(req)
})
})
})
+114 -3
View File
@@ -1,6 +1,6 @@
/* eslint-env mocha */
import expect from "expect"
import { fromJS, OrderedMap } from "immutable"
import { Map, fromJS, OrderedMap } from "immutable"
import {
mapToList,
parseSearch,
@@ -20,6 +20,8 @@ import {
getAcceptControllingResponse,
createDeepLinkPath,
escapeDeepLinkPath,
getExtensions,
getCommonExtensions,
sanitizeUrl,
extractFileNameFromContentDispositionHeader,
deeplyStripKey
@@ -348,6 +350,12 @@ describe("utils", function() {
expect( result ).toEqual( expectedError )
}
const assertValidateOas3Param = (param, expectedError) => {
// for cases where you _only_ want to try OAS3
result = validateParam( fromJS(param), false, true )
expect( result ).toEqual( expectedError )
}
it("should check the isOAS3 flag when validating parameters", function() {
// This should "skip" validation because there is no `schema` property
// and we are telling `validateParam` this is an OAS3 spec
@@ -359,6 +367,92 @@ describe("utils", function() {
expect( result ).toEqual( [] )
})
it("validates required OAS3 objects", function() {
// valid object
param = {
required: true,
schema: {
type: "object"
},
value: {
abc: 123
}
}
assertValidateOas3Param(param, [])
// valid object-as-string
param = {
required: true,
schema: {
type: "object"
},
value: JSON.stringify({
abc: 123
})
}
assertValidateOas3Param(param, [])
// invalid object-as-string
param = {
required: true,
schema: {
type: "object"
},
value: "{{}"
}
assertValidateOas3Param(param, ["Parameter string value must be valid JSON"])
// missing when required
param = {
required: true,
schema: {
type: "object"
},
}
assertValidateOas3Param(param, ["Required field is not provided"])
})
it("validates optional OAS3 objects", function() {
// valid object
param = {
schema: {
type: "object"
},
value: {
abc: 123
}
}
assertValidateOas3Param(param, [])
// valid object-as-string
param = {
schema: {
type: "object"
},
value: JSON.stringify({
abc: 123
})
}
assertValidateOas3Param(param, [])
// invalid object-as-string
param = {
schema: {
type: "object"
},
value: "{{}"
}
assertValidateOas3Param(param, ["Parameter string value must be valid JSON"])
// missing when not required
param = {
schema: {
type: "object"
},
}
assertValidateOas3Param(param, [])
})
it("validates required strings", function() {
// invalid string
param = {
@@ -943,6 +1037,24 @@ describe("utils", function() {
})
})
describe("getExtensions", function() {
const objTest = Map([[ "x-test", "a"], ["minimum", "b"]])
it("does not error on empty array", function() {
const result1 = getExtensions([])
expect(result1).toEqual([])
const result2 = getCommonExtensions([])
expect(result2).toEqual([])
})
it("gets only the x- keys", function() {
const result = getExtensions(objTest)
expect(result).toEqual(Map([[ "x-test", "a"]]))
})
it("gets the common keys", function() {
const result = getCommonExtensions(objTest, true)
expect(result).toEqual(Map([[ "minimum", "b"]]))
})
})
describe("deeplyStripKey", function() {
it("should filter out a specified key", function() {
const input = {
@@ -1045,8 +1157,7 @@ describe("utils", function() {
})
it("should sanitize a `data:` url", function() {
const res = sanitizeUrl(`data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGV
sbG8iKTs8L3NjcmlwdD4=`)
const res = sanitizeUrl(`data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=`)
expect(res).toEqual("about:blank")
})
+44
View File
@@ -0,0 +1,44 @@
describe("bug #4196: HTTP basic auth credential retention", function () {
let mainPage
beforeEach(function (client, done) {
mainPage = client
.url("localhost:3230")
.page.main()
client.waitForElementVisible(".download-url-input", 5000)
.pause(80)
.clearValue(".download-url-input")
.setValue(".download-url-input", "http://localhost:3230/test-specs/bugs/4196.yaml")
.click("button.download-url-button")
.pause(1000)
done()
})
afterEach(function (client, done) {
done()
})
it("should display the most recent auth data across modal close/opens", function (client) {
client.waitForElementVisible(".opblock-tag-section", 10000)
.click("button.btn.authorize") // Open modal
.waitForElementVisible("section>input", 5000)
.setValue("section>input", "aaa") // Set user
.waitForElementVisible(`section>input[type="password"]`, 5000)
.setValue(`section>input[type="password"]`, "aaa") // Set password
.click(".auth-btn-wrapper button:nth-child(1)") // Click Authorize
.assert.containsText("div.wrapper:nth-child(4)>code", "aaa")
.click(".auth-btn-wrapper button:nth-child(2)") // Close modal
.pause(50)
.click("button.btn.authorize") // Open modal
.pause(50)
.click(".auth-btn-wrapper button:nth-child(1)") // Logout
.waitForElementVisible("section>input", 5000)
.setValue("section>input", "bbb") // Set user
.waitForElementVisible(`section>input[type="password"]`, 5000)
.setValue(`section>input[type="password"]`, "bbb") // Set password
.click(".auth-btn-wrapper button:nth-child(1)") // Click Authorize
.pause(5000)
.assert.containsText("div.wrapper:nth-child(4)>code", "bbb")
client.end()
})
})
+32
View File
@@ -0,0 +1,32 @@
describe("bug #4445: callback-via-$ref rendering", function () {
let mainPage
beforeEach(function (client, done) {
mainPage = client
.url("localhost:3230")
.page.main()
client.waitForElementVisible(".download-url-input", 5000)
.pause(5000)
.clearValue(".download-url-input")
.setValue(".download-url-input", "http://localhost:3230/test-specs/bugs/4445.yaml")
.click("button.download-url-button")
.pause(1000)
done()
})
afterEach(function (client, done) {
done()
})
it("expands an operation that has a visible callback", function (client) {
client.waitForElementVisible(".opblock-tag-section", 10000)
.click(".opblock")
.waitForElementVisible(".opblock-body", 5000)
.click(".opblock-section-header > .tab-header > div.tab-item:nth-of-type(2)")
.waitForElementVisible(".callbacks-container .opblock", 5000)
.click(".callbacks-container .opblock")
.waitForElementVisible(".callbacks-container .opblock-body", 5000)
.assert.containsText(".callbacks-container .opblock-description-wrapper:nth-of-type(2) p", "subscription payload")
client.end()
})
})
+31
View File
@@ -0,0 +1,31 @@
describe("bug #4485: operation metadata storage when referenced via path item $ref", function () {
let mainPage
beforeEach(function (client, done) {
mainPage = client
.url("localhost:3230")
.page.main()
client.waitForElementVisible(".download-url-input", 5000)
.pause(2000)
.clearValue(".download-url-input")
.setValue(".download-url-input", "http://localhost:3230/test-specs/bugs/4485/main.yaml")
.click("button.download-url-button")
.pause(1000)
done()
})
afterEach(function (client, done) {
done()
})
it("sets a consumes value for a body parameter correctly", function (client) {
client.waitForElementVisible(".opblock-tag-section", 10000)
.click(".opblock")
.waitForElementVisible(".opblock-body", 5000)
.click("button.btn.try-out__btn")
.click("select.content-type [value=\"application/xml\"]")
.pause(500)
.assert.value("select.content-type", "application/xml")
client.end()
})
})
+29
View File
@@ -0,0 +1,29 @@
describe("bug #4536: model name consistency", function () {
let mainPage
beforeEach(function (client, done) {
mainPage = client
.url("localhost:3230")
.page.main()
client.waitForElementVisible(".download-url-input", 5000)
.pause(2000)
.clearValue(".download-url-input")
.setValue(".download-url-input", "http://localhost:3230/test-specs/bugs/4536.yaml")
.click("button.download-url-button")
.pause(1000)
done()
})
afterEach(function (client, done) {
done()
})
it("consistently displays a model's name regardless of expansion state", function (client) {
client.waitForElementVisible("span.model.model-title", 10000)
.assert.containsText("span.model.model-title", "TitleName")
.click("span.model.model-title")
.pause(500)
.assert.containsText("span.model-title__text", "TitleName")
client.end()
})
})
+19 -9
View File
@@ -71,6 +71,16 @@ describe("render pet api container", function () {
client.end()
})
it("Testing post /pet api model values", function (client) {
apiWrapper.waitForElementVisible("@petOperationPostContainer", 5000)
.click("@petOperationPostCollpase")
.waitForElementVisible("@petOperationPostCollapseContainer", 5000)
.click("a.tablinks[data-name=model]")
.assert.containsText("span.model-title > .model-title__text", `Pet`)
client.end()
})
it("render put /pet api container", function (client) {
apiWrapper.waitForElementVisible("@petOperationPutContainer", 5000)
.assert.containsText("@petOperationPutTitle", "/pet")
@@ -81,10 +91,10 @@ describe("render pet api container", function () {
.waitForElementVisible("@petOperationPutExecuteBtn", 1000)
.click("@petOperationPutTryBtn")
.assert.cssClassNotPresent("@petOperationPutTryBtn", "cancel")
client.end()
})
it("Testing put /pet api Mock data", function (client) {
apiWrapper.waitForElementVisible("@petOperationPutContainer", 5000)
.click("@petOperationPutCollpase")
@@ -115,7 +125,7 @@ describe("render pet api container", function () {
.waitForElementVisible("@petOperationGetByTagExecuteBtn", 1000)
.click("@petOperationGetByTagTryBtn")
.assert.cssClassNotPresent("@petOperationGetByTagTryBtn", "cancel")
client.end()
})
@@ -138,7 +148,7 @@ describe("render pet api container", function () {
client.end()
})
it("render get by ID /pet/{petId} api container", function (client) {
apiWrapper.waitForElementVisible("@petOperationGetByIdContainer", 5000)
.assert.containsText("@petOperationGetByIdTitle", "/pet/{petId}")
@@ -148,7 +158,7 @@ describe("render pet api container", function () {
.waitForElementVisible("@petOperationGetByTagExecuteBtn", 1000)
.click("@petOperationGetByTagTryBtn")
.assert.cssClassNotPresent("@petOperationGetByTagTryBtn", "cancel")
client.end()
})
@@ -167,7 +177,7 @@ describe("render pet api container", function () {
.assert.attributeEquals("@petOperationGetByIdParameter", "title", "Value must be an integer")
.click("@petOperationGetByTagTryBtn")
.assert.cssClassNotPresent("@petOperationGetByTagTryBtn", "cancel")
client.end()
})
@@ -180,10 +190,10 @@ describe("render pet api container", function () {
.waitForElementVisible("@petOperationDeleteExecuteBtn", 1000)
.click("@petOperationDeleteTryBtn")
.assert.cssClassNotPresent("@petOperationDeleteTryBtn", "cancel")
client.end()
})
it("Testing delete /pet api Mock data", function (client) {
apiWrapper.waitForElementVisible("@petOperationDeleteContainer", 3000)
.click("@petOperationDeleteCollpase")
@@ -203,4 +213,4 @@ describe("render pet api container", function () {
client.end()
})
})
})
+1 -1
View File
@@ -19,7 +19,7 @@ describe("Remote $ref rendering", function () {
})
it("renders a remote $ref correctly", function (client) {
mainPage.expect.element("#model-TestResponse > span > div > span > span > span.inner-object > table > tbody > tr:nth-child(2) > td:nth-child(2) > span > span > div > div > p").text.to.equal("this is a api2prop")
mainPage.expect.element("#model-TestResponse > span > div > span > span > span.inner-object > table > tbody > tr:nth-child(2) > td:nth-child(2) > span > span > div > div > p").text.to.equal("this is an api2prop")
client.end()
})
+85
View File
@@ -0,0 +1,85 @@
openapi: 3.0.0
info:
title: Demo API
description: First test
termsOfService: 'http://demo.io/terms-of-service/'
contact:
name: Demo Support
email: support@demo.io
version: 1.0.0
servers:
- url: '{server}/v1'
variables:
server:
default: https://api.demo.io
description: the API endpoint
paths:
/session:
put:
summary: Returns a new authentication token
tags:
- session
security:
- basicAuth: []
responses:
'201':
description: A session object
content:
application/json:
schema:
allOf:
- type: object
properties:
user_id:
type: string
format: uuid
readOnly: true
example: 110e8400-e29b-11d4-a716-446655440000
- $ref: '#/components/schemas/Session'
'401':
$ref: '#/components/responses/Unauthorized'
components:
securitySchemes:
basicAuth:
type: http
scheme: basic
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
Session:
required:
- token
properties:
token:
type: string
readOnly: true
example: >-
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.EkN-DOsnsuRjRO6BxXemmJDm3HbxrbRzXglbN2S4sOkopdU4IsDxTI8jO19W_A4K8ZPJijNLis4EZsHeY559a4DFOd50_OqgHGuERTqYZyuhtF39yxJPAjUESwxk2J5k_4zM3O-vtd1Ghyo4IbqKKSy6J9mTniYJPenn5-HIirE
Error:
required:
- message
properties:
message:
description: a human readable message explaining the error
type: string
reason:
description: a functionnal key about the error
type: string
responses:
Unauthorized:
description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Default:
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
+64
View File
@@ -0,0 +1,64 @@
openapi: 3.0.0
info:
title: Callback Example
version: 1.0.0
paths:
/streams:
post:
description: subscribes a client to receive out-of-band data
parameters:
- name: callbackUrl
in: query
required: true
description: |
the location where data will be sent. Must be network accessible
by the source server
schema:
type: string
format: uri
example: https://tonys-server.com
responses:
'201':
description: subscription successfully created
content:
application/json:
schema:
description: subscription information
required:
- subscriptionId
properties:
subscriptionId:
description: this unique identifier allows management of the subscription
type: string
example: 2531329f-fb09-4ef7-887e-84e648214436
callbacks:
# the name `onData` is a convenience locator
onData:
$ref: '#/components/callbacks/onData'
components:
callbacks:
onData:
# when data is sent, it will be sent to the `callbackUrl` provided
# when making the subscription PLUS the suffix `/data`
'{$request.query.callbackUrl}/data':
post:
requestBody:
description: subscription payload
content:
application/json:
schema:
properties:
timestamp:
type: string
format: date-time
userData:
type: string
responses:
'202':
description: |
Your server implementation should return this HTTP status code
if the data was received successfully
'204':
description: |
Your server should return this HTTP status code if no longer interested
in further updates
+15
View File
@@ -0,0 +1,15 @@
---
post:
description: Book
operationId: buy
summary: Buy a book
tags:
- Book
consumes:
- application/json
- application/xml
parameters:
- name: requestBody
in: body
description: Buy a Book
required: true
+5
View File
@@ -0,0 +1,5 @@
---
swagger: '2.0'
paths:
"/v1/book":
"$ref": "./book.yaml"
+9
View File
@@ -0,0 +1,9 @@
swagger: '2.0'
info:
version: 0.0.0
title: test
paths: {}
definitions:
ModelName:
title: TitleName
+1 -1
View File
@@ -21,7 +21,7 @@ components:
schemas:
Api2Prop:
type: string
description: this is a api2prop
description: this is an api2prop
example: 'api1prop-value'
TestResponse2:
+23
View File
@@ -0,0 +1,23 @@
const { JSDOM } = require("jsdom")
const win = require("core/window")
const jsdom = new JSDOM("<!doctype html><html><body></body></html>")
const { window } = jsdom
function copyProps(src, target) {
const props = Object.getOwnPropertyNames(src)
.filter(prop => typeof target[prop] === "undefined")
.reduce((result, prop) => ({
...result,
[prop]: Object.getOwnPropertyDescriptor(src, prop),
}), {})
Object.defineProperties(target, props)
}
global.window = window
global.document = window.document
global.navigator = {
userAgent: "node.js",
}
copyProps(win, window) // use UI's built-in window wrapper
copyProps(window, global)
+1
View File
@@ -20,6 +20,7 @@ module.exports = require("./make-webpack-config.js")(rules, {
_special: {
separateStylesheets: true,
minimize: true,
mangle: true,
sourcemaps: true,
},
+1
View File
@@ -20,6 +20,7 @@ module.exports = require("./make-webpack-config.js")(rules, {
_special: {
separateStylesheets: true,
minimize: true,
mangle: true,
sourcemaps: true,
},