Merge tag 'v2.0.0-rc.71' into sections-at-the-end

This commit is contained in:
Roberto Fernández 2022-06-02 13:53:04 +02:00
commit e76c0b12a1
71 changed files with 2412 additions and 552 deletions

View File

@ -6,7 +6,7 @@ jobs:
build-and-e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm ci
- run: npm run bundle
- run: npm run e2e
- uses: actions/checkout@v3
- run: npm ci
- run: npm run bundle
- run: npm run e2e

View File

@ -11,7 +11,7 @@ jobs:
contents: read
steps:
- name: Check out the repo
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
@ -48,7 +48,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Docker meta
id: docker_meta

View File

@ -11,10 +11,10 @@ jobs:
if: needs.check-version-cli.outputs.changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: npm-${{ hashFiles('package-lock.json') }}
@ -24,7 +24,7 @@ jobs:
- run: npm ci
- run: npm run bundle
- name: Store bundle artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: bundles-cli
path: bundles
@ -34,17 +34,17 @@ jobs:
if: needs.check-version-cli.outputs.changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v3
- run: npm ci && npm ci --prefix cli
- run: npm test
e2e-tests:
needs: [bundle]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v3
- run: npm ci
- name: Download bundled artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v3
with:
name: bundles-cli
path: bundles
@ -54,10 +54,10 @@ jobs:
if: needs.check-version-cli.outputs.changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
@ -69,7 +69,7 @@ jobs:
- name: Bundle
run: npm run compile:cli
- name: Store bundle artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: cli
path: cli
@ -81,9 +81,9 @@ jobs:
changed: ${{ steps.check.outputs.changed }}
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v3
- name: Check if version has been updated
id: check
uses: EndBug/version-check@v2.0.1
@ -96,18 +96,18 @@ jobs:
if: needs.check-version-cli.outputs.changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v1
- uses: actions/setup-node@v3
with:
node-version: '14.x'
registry-url: 'https://registry.npmjs.org'
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Download cli bundled artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v3
with:
name: cli
path: cli
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: npm-${{ hashFiles('package-lock.json') }}

View File

@ -9,10 +9,10 @@ jobs:
bundle:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: npm-${{ hashFiles('package-lock.json') }}
@ -22,7 +22,7 @@ jobs:
- run: npm ci
- run: npm run bundle
- name: Store bundle artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: bundles
path: bundles
@ -30,17 +30,17 @@ jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v3
- run: npm ci && npm ci --prefix cli
- run: npm test
e2e-tests:
needs: [bundle]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v3
- run: npm ci
- name: Download bundled artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v3
with:
name: bundles
path: bundles
@ -50,7 +50,7 @@ jobs:
# needs: [bundle, unit-tests, e2e-tests]
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v1
# - uses: actions/checkout@v3
# - name: Configure AWS Credentials
# uses: aws-actions/configure-aws-credentials@v1
# with:
@ -60,7 +60,7 @@ jobs:
# - name: Install dependencies
# run: npm ci
# - name: Download bundled artifacts
# uses: actions/download-artifact@v2
# uses: actions/download-artifact@v3
# with:
# name: bundles
# path: bundles
@ -71,21 +71,22 @@ jobs:
# - name: Invalidate
# run: aws cloudfront create-invalidation --distribution-id ${{ secrets.CF_DEMO_DISTRIBUTION_ID }} --paths "/*"
publish:
name: Publish to NPM
needs: [bundle, unit-tests, e2e-tests]
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v1
- uses: actions/setup-node@v3
with:
node-version: '14.x'
registry-url: 'https://registry.npmjs.org'
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Download bundled artifacts
uses: actions/download-artifact@v2
uses: actions/download-artifact@v3
with:
name: bundles
path: bundles
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: npm-${{ hashFiles('package-lock.json') }}
@ -98,3 +99,21 @@ jobs:
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
publish-cdn:
name: Publish to CDN
needs: [bundle, unit-tests, e2e-tests]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Configure AWS
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Download all artifact
uses: actions/download-artifact@v3
- name: Publish to S3
run: npm run publish-cdn

View File

@ -9,10 +9,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@master
uses: actions/checkout@v3
- name: Run GitHub File Sync
uses: Redocly/repo-file-sync-action@master
with:
GH_PAT: ${{ secrets.GH_PAT }}
COMMIT_PREFIX: "sync:"
COMMIT_PREFIX: 'sync:'
SKIP_PR: true

View File

@ -6,7 +6,7 @@ jobs:
build-and-unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v3
- run: npm ci && npm ci --prefix cli
- run: npm run bundle
- run: npm test

View File

@ -1,3 +1,33 @@
# [2.0.0-rc.71](https://github.com/Redocly/redoc/compare/v2.0.0-rc.70...v2.0.0-rc.71) (2022-05-31)
### Bug Fixes
* constraints label details ([eb0917d](https://github.com/Redocly/redoc/commit/eb0917d002e57353027fee9c8f07605de8f1ff6f))
* merge allOf in correct order ([#2020](https://github.com/Redocly/redoc/issues/2020)) ([1e4ea03](https://github.com/Redocly/redoc/commit/1e4ea03d4a9b7eddf3e4cc7cbdbd4d913583e837))
### Features
* add hideSecuritySection option allowing to disable the Security panel ([#2027](https://github.com/Redocly/redoc/issues/2027)) ([49cc11d](https://github.com/Redocly/redoc/commit/49cc11d91795653ca870e9276a1e0cd617964e25))
* add Redoc to Redocly CDN ([#2026](https://github.com/Redocly/redoc/issues/2026)) ([77104d6](https://github.com/Redocly/redoc/commit/77104d6c0d6f457aa08a158e93b52a45877be84e))
* add support prefix items ([27a9dba](https://github.com/Redocly/redoc/commit/27a9dbaf46aded01a6512645dab27870a85cc73b))
* remove auth section ([#2022](https://github.com/Redocly/redoc/issues/2022)) ([a863302](https://github.com/Redocly/redoc/commit/a863302cc803bdf27187c613157ba90af1040fc4))
* show minProperties maxProperties ([#2015](https://github.com/Redocly/redoc/issues/2015)) ([82712c5](https://github.com/Redocly/redoc/commit/82712c5b408dc6bc142307d45fb962de2a43ffba))
# [2.0.0-rc.70](https://github.com/Redocly/redoc/compare/2.0.0-rc.69...2.0.0-rc.70) (2022-05-17)
### Features
* display patternProperties ([#2008](https://github.com/Redocly/redoc/issues/2008)) ([660cc85](https://github.com/Redocly/redoc/commit/660cc857bc86787e16237b407fe5f5d7a493bb48))
* support conditional operators ([#1939](https://github.com/Redocly/redoc/issues/1939)) ([291b62a](https://github.com/Redocly/redoc/commit/291b62a206b68f8b4d98e4b74b71c0cad20a8b9b))
* theme add links textDecoration options ([#1599](https://github.com/Redocly/redoc/issues/1599)) ([ba06485](https://github.com/Redocly/redoc/commit/ba06485ece27acbb6b846500817f4bff3e4997ba))
# [2.0.0-rc.69](https://github.com/Redocly/redoc/compare/v2.0.0-rc.68.1...v2.0.0-rc.69) (2022-05-12)

View File

@ -5,7 +5,7 @@
[![Build Status](https://travis-ci.com/Redocly/redoc.svg?branch=master)](https://travis-ci.com/Redocly/redoc) [![Coverage Status](https://coveralls.io/repos/Redocly/redoc/badge.svg?branch=master&service=github)](https://coveralls.io/github/Redocly/redoc?branch=master) [![npm](http://img.shields.io/npm/v/redoc.svg)](https://www.npmjs.com/package/redoc) [![License](https://img.shields.io/npm/l/redoc.svg)](https://github.com/Redocly/redoc/blob/master/LICENSE)
[![bundle size](http://img.badgesize.io/https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js?compression=gzip&max=300000)](https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js) [![npm](https://img.shields.io/npm/dm/redoc.svg)](https://www.npmjs.com/package/redoc) [![](https://data.jsdelivr.com/v1/package/npm/redoc/badge)](https://www.jsdelivr.com/package/npm/redoc) [![Docker Build Status](https://img.shields.io/docker/build/redocly/redoc.svg)](https://hub.docker.com/r/redocly/redoc/)
[![bundle size](http://img.badgesize.io/https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js?compression=gzip&max=300000)](https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js) [![npm](https://img.shields.io/npm/dm/redoc.svg)](https://www.npmjs.com/package/redoc) [![](https://data.jsdelivr.com/v1/package/npm/redoc/badge)](https://www.jsdelivr.com/package/npm/redoc) [![Docker Build Status](https://img.shields.io/docker/build/redocly/redoc.svg)](https://hub.docker.com/r/redocly/redoc/)
</div>
**This is the README for the `2.x` version of Redoc (React-based).**
@ -102,9 +102,9 @@ Refer to the Redocly's documentation for more information on these products:
![](docs/images/code-samples-demo.gif)
## Releases
**Important:** all the 2.x releases are deployed to npm and can be used with jsdeliver:
- particular release, for example, `v2.0.0-alpha.15`: https://cdn.jsdelivr.net/npm/redoc@2.0.0-alpha.17/bundles/redoc.standalone.js
- `next` release: https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js
**Important:** all the 2.x releases are deployed to npm and can be used with Redocly-cdn:
- particular release, for example, `v2.0.0-rc.70`: https://cdn.redoc.ly/redoc/v2.0.0-rc.70/bundles/redoc.standalone.js
- `latest` release: https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js
Additionally, all the 1.x releases are hosted on our GitHub Pages-based CDN **(deprecated)**:
- particular release, for example `v1.2.0`: https://rebilly.github.io/ReDoc/releases/v1.2.0/redoc.min.js
@ -166,7 +166,7 @@ replace the `spec-url` attribute with the url or local file address to your defi
</head>
<body>
<redoc spec-url='http://petstore.swagger.io/v2/swagger.json'></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"> </script>
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"> </script>
</body>
</html>
@ -232,7 +232,6 @@ You can use all of the following options with the standalone version of the <red
* `lazyRendering` - _Not implemented yet_ ~~if set, enables lazy rendering mode in ReDoc. This mode is useful for APIs with big number of operations (e.g. > 50). In this mode ReDoc shows initial screen ASAP and then renders the rest operations asynchronously while showing progress bar on the top. Check out the [demo](\\redocly.github.io/redoc) for the example.~~
* `menuToggle` - if true clicking second time on expanded menu item will collapse it, default `true`.
* `nativeScrollbars` - use native scrollbar for sidemenu instead of perfect-scroll (scrolling performance optimization for big specs).
* `noAutoAuth` - do not inject Authentication section automatically.
* `onlyRequiredInSamples` - shows only required fields in request samples.
* `pathInMiddlePanel` - show path link and HTTP verb in the middle panel instead of the right one.
* `requiredPropsFirst` - show required properties first ordered in the same order as in `required` array.
@ -289,6 +288,8 @@ You can use all of the following options with the standalone version of the <red
* `color`: # COMPUTED: colors.primary.main
* `visited`: # COMPUTED: typography.links.color
* `hover`: # COMPUTED: lighten(0.2 typography.links.color)
* `textDecoration`: 'auto'
* `hoverTextDecoration`: 'auto'
* `sidebar`
* `width`: '260px'
* `backgroundColor`: '#fafafa'

View File

@ -1,8 +1,7 @@
import { spawnSync } from 'child_process';
describe('build with url', () => {
// FIXME: remove skip after release
it.skip('should not fail on resolving url', () => {
it('should not fail on resolving url', () => {
const r = spawnSync(
'ts-node',
[

124
cli/npm-shrinkwrap.json generated
View File

@ -1,12 +1,12 @@
{
"name": "redoc-cli",
"version": "0.13.12",
"version": "0.13.14",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "redoc-cli",
"version": "0.13.12",
"version": "0.13.14",
"license": "MIT",
"dependencies": {
"chokidar": "^3.5.1",
@ -17,7 +17,7 @@
"node-libs-browser": "^2.2.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"redoc": "2.0.0-rc.68",
"redoc": "2.0.0-rc.70",
"styled-components": "^5.3.0",
"yargs": "^17.3.1"
},
@ -216,9 +216,9 @@
}
},
"node_modules/@redocly/openapi-core": {
"version": "1.0.0-beta.96",
"resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.0.0-beta.96.tgz",
"integrity": "sha512-tcy0q+9PRWV4rcnVx5uHII/9Cq9qpUzWNppupAaVgutxjQRPWH45e24NLinn6lA8Q4por6HuMYkk/0QAJE8d3A==",
"version": "1.0.0-beta.97",
"resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.0.0-beta.97.tgz",
"integrity": "sha512-3WW9/6flosJuRtU3GI0Vw39OYFZqqXMDCp5TLa3EjXOb7Nm6AZTWRb3Y+I/+UdNJ/NTszVJkQczoa1t476ekiQ==",
"dependencies": {
"@redocly/ajv": "^8.6.4",
"@types/node": "^14.11.8",
@ -226,7 +226,7 @@
"js-levenshtein": "^1.1.6",
"js-yaml": "^4.1.0",
"lodash.isequal": "^4.5.0",
"minimatch": "^3.0.4",
"minimatch": "^5.0.1",
"node-fetch": "^2.6.1",
"pluralize": "^8.0.0",
"yaml-ast-parser": "0.0.43"
@ -236,9 +236,9 @@
}
},
"node_modules/@redocly/openapi-core/node_modules/@types/node": {
"version": "14.18.16",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.16.tgz",
"integrity": "sha512-X3bUMdK/VmvrWdoTkz+VCn6nwKwrKCFTHtqwBIaQJNx4RUIBBUFXM00bqPz/DsDd+Icjmzm6/tyYZzeGVqb6/Q=="
"version": "14.18.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.17.tgz",
"integrity": "sha512-oajWz4kOajqpKJMPgnCvBajPq8QAvl2xIWoFjlAJPKGu6n7pjov5SxGE45a+0RxHDoo4ycOMoZw1SCOWtDERbw=="
},
"node_modules/@types/chokidar": {
"version": "2.1.3",
@ -646,12 +646,11 @@
"integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw=="
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
"balanced-match": "^1.0.0"
}
},
"node_modules/braces": {
@ -922,11 +921,6 @@
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"peer": true
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"node_modules/console-browserify": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
@ -1253,9 +1247,9 @@
}
},
"node_modules/foreach": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
"integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k="
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz",
"integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg=="
},
"node_modules/fsevents": {
"version": "2.3.2",
@ -1686,14 +1680,14 @@
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
"integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
"dependencies": {
"brace-expansion": "^1.1.7"
"brace-expansion": "^2.0.1"
},
"engines": {
"node": "*"
"node": ">=10"
}
},
"node_modules/minimist": {
@ -1932,9 +1926,9 @@
}
},
"node_modules/openapi-sampler": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.2.1.tgz",
"integrity": "sha512-mHrYmyvcLD0qrfqPkPRBAL2z16hGT2rW0d0B7nklfoTcc3pmkJLkSZlKSeFgerUM41E5c7jlxf0Y19xrM7mWQQ==",
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.2.3.tgz",
"integrity": "sha512-dH2QYXqakorV5dxkP/f1BV3Ku4yNn21YmBsqJunnyrHLw7mnCNZZldftgrEpv/66b1m5oaUAmiJoJN+FqBEkJg==",
"dependencies": {
"@types/json-schema": "^7.0.7",
"json-pointer": "0.6.2"
@ -2207,11 +2201,11 @@
}
},
"node_modules/redoc": {
"version": "2.0.0-rc.68",
"resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0-rc.68.tgz",
"integrity": "sha512-sCz52OEhLDu2cIBimy4f6CaVoDxUzD16x63oZx4kpDQOTXYtk0hEOlph1s5VrgNg9pg+rJ9LCCfnwCuzx3Be8w==",
"version": "2.0.0-rc.70",
"resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0-rc.70.tgz",
"integrity": "sha512-sdmZ8FX4JjF50hTSjHJ64Ccu9Ewa2O8+Fo8pCLg8GHFrbaFJ2E+KBDK9pGuAqNi61fm3Z5c91Ur7zqpITkUpNg==",
"dependencies": {
"@redocly/openapi-core": "^1.0.0-beta.95",
"@redocly/openapi-core": "^1.0.0-beta.97",
"classnames": "^2.3.1",
"decko": "^1.2.0",
"dompurify": "^2.2.8",
@ -2221,7 +2215,7 @@
"mark.js": "^8.11.1",
"marked": "^4.0.15",
"mobx-react": "^7.2.0",
"openapi-sampler": "^1.2.1",
"openapi-sampler": "^1.2.3",
"path-browserify": "^1.0.1",
"perfect-scrollbar": "^1.5.1",
"polished": "^4.1.3",
@ -3135,9 +3129,9 @@
}
},
"@redocly/openapi-core": {
"version": "1.0.0-beta.96",
"resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.0.0-beta.96.tgz",
"integrity": "sha512-tcy0q+9PRWV4rcnVx5uHII/9Cq9qpUzWNppupAaVgutxjQRPWH45e24NLinn6lA8Q4por6HuMYkk/0QAJE8d3A==",
"version": "1.0.0-beta.97",
"resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.0.0-beta.97.tgz",
"integrity": "sha512-3WW9/6flosJuRtU3GI0Vw39OYFZqqXMDCp5TLa3EjXOb7Nm6AZTWRb3Y+I/+UdNJ/NTszVJkQczoa1t476ekiQ==",
"requires": {
"@redocly/ajv": "^8.6.4",
"@types/node": "^14.11.8",
@ -3145,16 +3139,16 @@
"js-levenshtein": "^1.1.6",
"js-yaml": "^4.1.0",
"lodash.isequal": "^4.5.0",
"minimatch": "^3.0.4",
"minimatch": "^5.0.1",
"node-fetch": "^2.6.1",
"pluralize": "^8.0.0",
"yaml-ast-parser": "0.0.43"
},
"dependencies": {
"@types/node": {
"version": "14.18.16",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.16.tgz",
"integrity": "sha512-X3bUMdK/VmvrWdoTkz+VCn6nwKwrKCFTHtqwBIaQJNx4RUIBBUFXM00bqPz/DsDd+Icjmzm6/tyYZzeGVqb6/Q=="
"version": "14.18.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.17.tgz",
"integrity": "sha512-oajWz4kOajqpKJMPgnCvBajPq8QAvl2xIWoFjlAJPKGu6n7pjov5SxGE45a+0RxHDoo4ycOMoZw1SCOWtDERbw=="
}
}
},
@ -3525,12 +3519,11 @@
"integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw=="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
"balanced-match": "^1.0.0"
}
},
"braces": {
@ -3771,11 +3764,6 @@
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"peer": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"console-browserify": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
@ -4060,9 +4048,9 @@
}
},
"foreach": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
"integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k="
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz",
"integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg=="
},
"fsevents": {
"version": "2.3.2",
@ -4396,11 +4384,11 @@
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
},
"minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
"integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
"requires": {
"brace-expansion": "^1.1.7"
"brace-expansion": "^2.0.1"
}
},
"minimist": {
@ -4563,9 +4551,9 @@
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"openapi-sampler": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.2.1.tgz",
"integrity": "sha512-mHrYmyvcLD0qrfqPkPRBAL2z16hGT2rW0d0B7nklfoTcc3pmkJLkSZlKSeFgerUM41E5c7jlxf0Y19xrM7mWQQ==",
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.2.3.tgz",
"integrity": "sha512-dH2QYXqakorV5dxkP/f1BV3Ku4yNn21YmBsqJunnyrHLw7mnCNZZldftgrEpv/66b1m5oaUAmiJoJN+FqBEkJg==",
"requires": {
"@types/json-schema": "^7.0.7",
"json-pointer": "0.6.2"
@ -4805,11 +4793,11 @@
}
},
"redoc": {
"version": "2.0.0-rc.68",
"resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0-rc.68.tgz",
"integrity": "sha512-sCz52OEhLDu2cIBimy4f6CaVoDxUzD16x63oZx4kpDQOTXYtk0hEOlph1s5VrgNg9pg+rJ9LCCfnwCuzx3Be8w==",
"version": "2.0.0-rc.70",
"resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0-rc.70.tgz",
"integrity": "sha512-sdmZ8FX4JjF50hTSjHJ64Ccu9Ewa2O8+Fo8pCLg8GHFrbaFJ2E+KBDK9pGuAqNi61fm3Z5c91Ur7zqpITkUpNg==",
"requires": {
"@redocly/openapi-core": "^1.0.0-beta.95",
"@redocly/openapi-core": "^1.0.0-beta.97",
"classnames": "^2.3.1",
"decko": "^1.2.0",
"dompurify": "^2.2.8",
@ -4819,7 +4807,7 @@
"mark.js": "^8.11.1",
"marked": "^4.0.15",
"mobx-react": "^7.2.0",
"openapi-sampler": "^1.2.1",
"openapi-sampler": "^1.2.3",
"path-browserify": "^1.0.1",
"perfect-scrollbar": "^1.5.1",
"polished": "^4.1.3",

View File

@ -1,6 +1,6 @@
{
"name": "redoc-cli",
"version": "0.13.12",
"version": "0.13.14",
"description": "ReDoc's Command Line Interface",
"main": "index.js",
"bin": "index.js",
@ -19,7 +19,7 @@
"node-libs-browser": "^2.2.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"redoc": "2.0.0-rc.68",
"redoc": "2.0.0-rc.70",
"styled-components": "^5.3.0",
"yargs": "^17.3.1"
},

View File

@ -5,14 +5,14 @@
# npm i -g http-server
# http-server -p 8000 --cors
FROM node:alpine
FROM node:12-alpine
RUN apk update && apk add --no-cache git
# Install dependencies
WORKDIR /build
COPY package.json package-lock.json /build/
RUN npm ci --no-optional --ignore-scripts --force
RUN npm ci --no-optional --ignore-scripts
# copy only required for the build files
COPY src /build/src

View File

@ -960,6 +960,33 @@ components:
schemas:
ApiResponse:
type: object
patternProperties:
^S_\\w+\\.[1-9]{2,4}$:
description: The measured skill for hunting
if:
x-displayName: fieldName === 'status'
else:
minLength: 1
maxLength: 10
then:
format: url
type: string
enum:
- success
- failed
^O_\\w+\\.[1-9]{2,4}$:
type: object
properties:
nestedProperty:
type: [string, boolean]
description: The measured skill for hunting
default: lazy
example: adventurous
enum:
- clueless
- lazy
- adventurous
- aggressive
properties:
code:
type: integer
@ -975,7 +1002,7 @@ components:
- type: object
properties:
huntingSkill:
type: string
type: [string, boolean]
description: The measured skill for hunting
default: lazy
example: adventurous
@ -1099,15 +1126,26 @@ components:
example: Guru
photoUrls:
description: The list of URL to a cute photos featuring pet
type: [string, integer, 'null', array]
type: [string, integer, 'null']
minItems: 1
maxItems: 20
maxItems: 10
xml:
name: photoUrl
wrapped: true
items:
type: string
format: url
if:
x-displayName: isString
type: string
then:
minItems: 1
maxItems: 15
else:
x-displayName: notString
type: [integer, 'null']
minItems: 1
maxItems: 20
friend:
$ref: '#/components/schemas/Pet'
tags:
@ -1131,6 +1169,12 @@ components:
petType:
description: Type of a pet
type: string
huntingSkill:
type: [integer]
enum:
- 0
- 1
- 2
xml:
name: Pet
Tag:
@ -1198,6 +1242,35 @@ components:
type: string
contentEncoding: base64
contentMediaType: image/png
addresses:
type: array
minItems: 0
maxLength: 10
prefixItems:
- type: object
properties:
city:
type: string
minLength: 0
country:
type: string
minLength: 0
street:
description: includes build/apartment number
type: string
minLength: 0
- type: number
items:
type: string
if:
title: userStatus === 10
properties:
userStatus:
enum: [10]
then:
required: ['phone']
else:
required: []
xml:
name: User
requestBodies:

View File

@ -88,7 +88,7 @@ paths:
parameters:
- name: Accept-Language
in: header
description: "The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US"
description: 'The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US'
example: en-US
required: false
schema:
@ -254,7 +254,7 @@ paths:
required: false
schema:
type: string
example: "Bearer <TOKEN>"
example: 'Bearer <TOKEN>'
- name: petId
in: path
description: Pet id to delete
@ -401,6 +401,7 @@ paths:
application/json:
schema:
type: object
minProperties: 2
additionalProperties:
type: integer
format: int32
@ -429,7 +430,7 @@ paths:
application/json:
example:
status: 400
message: "Invalid Order"
message: 'Invalid Order'
requestBody:
content:
application/json:
@ -877,11 +878,11 @@ paths:
type: string
examples:
response:
value: <Message> OK </Message>
value: <Message> OK </Message>
text/plain:
examples:
response:
value: OK
value: OK
'400':
description: Invalid username/password supplied
/user/logout:
@ -1027,8 +1028,8 @@ components:
properties:
id:
externalDocs:
description: "Find more info here"
url: "https://example.com"
description: 'Find more info here'
url: 'https://example.com'
description: Pet ID
allOf:
- $ref: '#/components/schemas/Id'
@ -1134,6 +1135,26 @@ components:
description: User status
type: integer
format: int32
addresses:
type: array
minItems: 0
maxLength: 10
items:
- type: object
properties:
city:
type: string
minLength: 0
country:
type: string
minLength: 0
street:
description: includes build/apartment number
type: string
minLength: 0
- type: number
additionalItems:
type: string
xml:
name: User
requestBodies:
@ -1201,7 +1222,7 @@ x-webhooks:
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
$ref: '#/components/schemas/Pet'
responses:
"200":
'200':
description: Return a 200 status to indicate that the data was received successfully

View File

@ -51,7 +51,7 @@ or the files located in your `node modules` folder.
To reference the Redoc script with a CDN link:
```html
<script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"> </script>
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"> </script>
```
### Node modules link
@ -97,7 +97,7 @@ Redoc.init(specOrSpecUrl, options, element, callback)
```
- `specOrSpecUrl`: Either a JSON object with the OpenAPI definition or a URL to the
definition in JSON or YAML format.
- `options`: See [options object](https://redocly.com/docs/api-reference-docs/configuration/) reference.
- `options`: See [features.openapi object](/docs/api-reference-docs/configuration/functionality.mdx) reference.
- `element`: DOM element Redoc will be inserted into.
- `callback`(optional): Callback to be called after Redoc has been fully rendered.
It is also called on errors with `error` as the first argument.

View File

@ -38,7 +38,7 @@ replace the `spec-url` attribute with the URL or local file address to your defi
<!--
Link to Redoc JavaScript on CDN for rendering standalone element
-->
<script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"></script>
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
</body>
</html>
```

18
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "redoc",
"version": "2.0.0-rc.69",
"version": "2.0.0-rc.71",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "redoc",
"version": "2.0.0-rc.69",
"version": "2.0.0-rc.71",
"license": "MIT",
"dependencies": {
"@redocly/openapi-core": "^1.0.0-beta.97",
@ -19,7 +19,7 @@
"mark.js": "^8.11.1",
"marked": "^4.0.15",
"mobx-react": "^7.2.0",
"openapi-sampler": "^1.2.3",
"openapi-sampler": "^1.3.0",
"path-browserify": "^1.0.1",
"perfect-scrollbar": "^1.5.1",
"polished": "^4.1.3",
@ -14217,9 +14217,9 @@
}
},
"node_modules/openapi-sampler": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.2.3.tgz",
"integrity": "sha512-dH2QYXqakorV5dxkP/f1BV3Ku4yNn21YmBsqJunnyrHLw7mnCNZZldftgrEpv/66b1m5oaUAmiJoJN+FqBEkJg==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.3.0.tgz",
"integrity": "sha512-2QfjK1oM9Sv0q82Ae1RrUe3yfFmAyjF548+6eAeb+h/cL1Uj51TW4UezraBEvwEdzoBgfo4AaTLVFGTKj+yYDw==",
"dependencies": {
"@types/json-schema": "^7.0.7",
"json-pointer": "0.6.2"
@ -29872,9 +29872,9 @@
}
},
"openapi-sampler": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.2.3.tgz",
"integrity": "sha512-dH2QYXqakorV5dxkP/f1BV3Ku4yNn21YmBsqJunnyrHLw7mnCNZZldftgrEpv/66b1m5oaUAmiJoJN+FqBEkJg==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.3.0.tgz",
"integrity": "sha512-2QfjK1oM9Sv0q82Ae1RrUe3yfFmAyjF548+6eAeb+h/cL1Uj51TW4UezraBEvwEdzoBgfo4AaTLVFGTKj+yYDw==",
"requires": {
"@types/json-schema": "^7.0.7",
"json-pointer": "0.6.2"

View File

@ -1,6 +1,6 @@
{
"name": "redoc",
"version": "2.0.0-rc.69",
"version": "2.0.0-rc.71",
"description": "ReDoc",
"repository": {
"type": "git",
@ -34,6 +34,7 @@
"start:benchmark": "webpack serve --mode=production --env.bench --config demo/webpack.config.ts",
"test": "npm run unit && npm run license-check",
"unit": "jest --coverage",
"test:update-snapshot": "jest --updateSnapshot",
"e2e": "cypress run",
"e2e-ci": "cypress run --record",
"bundlesize": "size-limit",
@ -53,6 +54,7 @@
"start:demo": "webpack serve --hot --config demo/webpack.config.ts --mode=development",
"compile:cli": "tsc custom.d.ts cli/index.ts --target es6 --module commonjs --types yargs",
"build:demo": "webpack --mode=production --config demo/webpack.config.ts",
"publish-cdn": "scripts/publish-cdn.sh",
"deploy:demo": "aws s3 sync demo/dist s3://production-redoc-demo --acl=public-read",
"license-check": "license-checker --production --onlyAllow 'MIT;ISC;Apache-2.0;BSD;BSD-2-Clause;BSD-3-Clause;CC-BY-4.0;Python-2.0' --summary",
"docker:build": "docker build -f config/docker/Dockerfile -t redoc .",
@ -148,7 +150,7 @@
"mark.js": "^8.11.1",
"marked": "^4.0.15",
"mobx-react": "^7.2.0",
"openapi-sampler": "^1.2.3",
"openapi-sampler": "^1.3.0",
"path-browserify": "^1.0.1",
"perfect-scrollbar": "^1.5.1",
"polished": "^4.1.3",

38
scripts/publish-cdn.sh Executable file
View File

@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -e # exit on error
# TODO: Update script!
VERSION=$(node scripts/version.js)
VERSION_TAG=v${VERSION:0:1}.x
copy_to_s3 () {
aws s3 cp bundles "s3://redocly-cdn/redoc/$1/bundles" --recursive "$2"
aws s3 cp CHANGELOG.md "s3://redocly-cdn/redoc/$1/CHANGELOG.md" "$2"
aws s3 cp LICENSE "s3://redocly-cdn/redoc/$1/LICENSE" "$2"
aws s3 cp package.json "s3://redocly-cdn/redoc/$1/package.json" "$2"
aws s3 cp README.md "s3://redocly-cdn/redoc/$1/README.md" "$2"
}
if aws s3 ls "redocly-cdn/redoc/v$VERSION/" "$@"; then
echo "Version $VERSION already exists"
exit 1
else
echo Releasing $VERSION
echo Uploading to S3 $VERSION
copy_to_s3 "v$VERSION" $@
echo Uploading to S3 $VERSION_TAG
copy_to_s3 "$VERSION_TAG" $@
if [[ "$VERSION_TAG" == "v2.x" ]]; then
echo Uploading to S3 latest
copy_to_s3 latest $@
fi
echo
echo Deployed successfully
exit 0
fi

1
scripts/version.js Normal file
View File

@ -0,0 +1 @@
console.log(require('../package.json').version);

View File

@ -1,4 +1,4 @@
import styled, { extensionsHook, media } from '../styled-components';
import styled, { extensionsHook, media, css } from '../styled-components';
import { deprecatedCss } from './mixins';
export const PropertiesTableCaption = styled.caption`
@ -72,7 +72,26 @@ export const PropertyNameCell = styled(PropertyCell)`
${deprecatedCss};
}
${({ kind }) => (kind !== 'field' ? 'font-style: italic' : '')};
${({ kind }) =>
kind === 'patternProperties' &&
css`
> span.property-name {
display: inline-table;
white-space: break-spaces;
margin-right: 20px;
::before,
::after {
content: '/';
filter: opacity(0.2);
}
}
`}
${({ kind = '' }) =>
['field', 'additionalProperties', 'patternProperties'].includes(kind)
? ''
: 'font-style: italic'};
${extensionsHook('PropertyNameCell')};
`;

View File

@ -1,6 +1,6 @@
import { transparentize } from 'polished';
import styled, { extensionsHook } from '../styled-components';
import styled, { extensionsHook, css } from '../styled-components';
import { PropertyNameCell } from './fields-layout';
import { ShelfIcon } from './shelfs';
@ -17,6 +17,27 @@ export const ClickablePropertyNameCell = styled(PropertyNameCell)`
&:focus {
font-weight: ${({ theme }) => theme.typography.fontWeightBold};
}
${({ kind }) =>
kind === 'patternProperties' &&
css`
display: inline-flex;
margin-right: 20px;
> span.property-name {
white-space: break-spaces;
text-align: left;
::before,
::after {
content: '/';
filter: opacity(0.2);
}
}
> svg {
align-self: center;
}
`}
}
${ShelfIcon} {
height: ${({ theme }) => theme.schema.arrow.size};
@ -56,6 +77,10 @@ export const RequiredLabel = styled(FieldLabel.withComponent('div'))`
line-height: 1;
`;
export const PropertyLabel = styled(RequiredLabel)`
color: ${props => props.theme.colors.primary.light};
`;
export const RecursiveLabel = styled(FieldLabel)`
color: ${({ theme }) => theme.colors.warning.main};
font-size: 13px;

View File

@ -37,6 +37,7 @@ class IntShelfIcon extends React.PureComponent<{
export const ShelfIcon = styled(IntShelfIcon)`
height: ${props => props.size || '18px'};
width: ${props => props.size || '18px'};
min-width: ${props => props.size || '18px'};
vertical-align: middle;
float: ${props => props.float || ''};
transition: transform 0.2s ease-out;

View File

@ -4,9 +4,20 @@ import { ConstraintsView } from './FieldContstraints';
import { Pattern } from './Pattern';
import { SchemaModel } from '../../services';
import styled from '../../styled-components';
import { OptionsContext } from '../OptionsProvider';
export function ArrayItemDetails({ schema }: { schema: SchemaModel }) {
if (!schema || (schema.type === 'string' && !schema.constraints.length)) return null;
const { hideSchemaPattern } = React.useContext(OptionsContext);
if (
!schema ||
(schema.type === 'string' && !schema.constraints.length) ||
((!schema?.pattern || hideSchemaPattern) &&
!schema.items &&
!schema.displayFormat &&
!schema.constraints.length) // return null for cases where all constraints are empty
) {
return null;
}
return (
<Wrapper>

View File

@ -1,9 +1,12 @@
import { observer } from 'mobx-react';
import * as React from 'react';
import { ClickablePropertyNameCell, RequiredLabel } from '../../common-elements/fields';
import {
ClickablePropertyNameCell,
PropertyLabel,
RequiredLabel,
} from '../../common-elements/fields';
import { FieldDetails } from './FieldDetails';
import {
InnerPropertiesWrap,
PropertyBullet,
@ -11,11 +14,11 @@ import {
PropertyDetailsCell,
PropertyNameCell,
} from '../../common-elements/fields-layout';
import { ShelfIcon } from '../../common-elements/';
import { Schema } from '../Schema/Schema';
import { FieldModel } from '../../services/models';
import { Schema, SchemaOptions } from '../Schema/Schema';
import type { SchemaOptions } from '../Schema/Schema';
import type { FieldModel } from '../../services/models';
export interface FieldProps extends SchemaOptions {
className?: string;
@ -46,12 +49,20 @@ export class Field extends React.Component<FieldProps> {
};
render() {
const { className, field, isLast, expandByDefault } = this.props;
const { className = '', field, isLast, expandByDefault } = this.props;
const { name, deprecated, required, kind } = field;
const withSubSchema = !field.schema.isPrimitive && !field.schema.isCircular;
const expanded = field.expanded === undefined ? expandByDefault : field.expanded;
const labels = (
<>
{kind === 'additionalProperties' && <PropertyLabel>additional property</PropertyLabel>}
{kind === 'patternProperties' && <PropertyLabel>pattern property</PropertyLabel>}
{required && <RequiredLabel>required</RequiredLabel>}
</>
);
const paramName = withSubSchema ? (
<ClickablePropertyNameCell
className={deprecated ? 'deprecated' : ''}
@ -64,16 +75,16 @@ export class Field extends React.Component<FieldProps> {
onKeyPress={this.handleKeyPress}
aria-label="expand properties"
>
<span>{name}</span>
<span className="property-name">{name}</span>
<ShelfIcon direction={expanded ? 'down' : 'right'} />
</button>
{required && <RequiredLabel> required </RequiredLabel>}
{labels}
</ClickablePropertyNameCell>
) : (
<PropertyNameCell className={deprecated ? 'deprecated' : undefined} kind={kind} title={name}>
<PropertyBullet />
<span>{name}</span>
{required && <RequiredLabel> required </RequiredLabel>}
<span className="property-name">{name}</span>
{labels}
</PropertyNameCell>
);

View File

@ -1,4 +1,5 @@
import * as React from 'react';
import { observer } from 'mobx-react';
import {
RecursiveLabel,
@ -24,7 +25,7 @@ import { OptionsContext } from '../OptionsProvider';
import { Pattern } from './Pattern';
import { ArrayItemDetails } from './ArrayItemDetails';
function FieldDetailsComponent(props: FieldProps) {
export const FieldDetailsComponent = observer((props: FieldProps) => {
const { enumSkipQuotes, hideSchemaTitles } = React.useContext(OptionsContext);
const { showExamples, field, renderDiscriminatorSwitch } = props;
@ -107,6 +108,6 @@ function FieldDetailsComponent(props: FieldProps) {
{(_const && <FieldDetail label={l('const') + ':'} value={_const} />) || null}
</div>
);
}
});
export const FieldDetails = React.memo<FieldProps>(FieldDetailsComponent);

View File

@ -27,20 +27,20 @@ class Json extends React.PureComponent<JsonProps> {
}
renderInner = ({ renderCopyButton }) => {
const showFoldingButtons = this.props.data && Object.values(this.props.data).some(
(value) => typeof value === 'object' && value !== null,
);
const showFoldingButtons =
this.props.data &&
Object.values(this.props.data).some(value => typeof value === 'object' && value !== null);
return (
<JsonViewerWrap>
<SampleControls>
{renderCopyButton()}
{showFoldingButtons &&
{showFoldingButtons && (
<>
<button onClick={this.expandAll}> Expand all </button>
<button onClick={this.collapseAll}> Collapse all </button>
</>
}
)}
</SampleControls>
<OptionsContext.Consumer>
{options => (

View File

@ -6,7 +6,7 @@ import { StyledComponent } from 'styled-components';
export const linksCss = css`
a {
text-decoration: none;
text-decoration: ${props => props.theme.typography.links.textDecoration};
color: ${props => props.theme.typography.links.color};
&:visited {
@ -15,6 +15,7 @@ export const linksCss = css`
&:hover {
color: ${props => props.theme.typography.links.hover};
text-decoration: ${props => props.theme.typography.links.hoverTextDecoration};
}
}
`;

View File

@ -10,6 +10,7 @@ import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
import { Schema } from '../Schema';
import { Markdown } from '../Markdown/Markdown';
import { ConstraintsView } from '../Fields/FieldContstraints';
function safePush(obj, prop, item) {
if (!obj[prop]) {
@ -79,6 +80,9 @@ export function BodyContent(props: {
return (
<>
{description !== undefined && <Markdown source={description} />}
{schema?.type === 'object' && (
<ConstraintsView constraints={schema?.constraints || []} />
)}
<Schema
skipReadOnly={isRequestType}
skipWriteOnly={!isRequestType}

View File

@ -10,6 +10,7 @@ import { Schema } from '../Schema';
import { Extensions } from '../Fields/Extensions';
import { Markdown } from '../Markdown/Markdown';
import { ResponseHeaders } from './ResponseHeaders';
import { ConstraintsView } from '../Fields/FieldContstraints';
export class ResponseDetails extends React.PureComponent<{ response: ResponseModel }> {
render() {
@ -21,7 +22,14 @@ export class ResponseDetails extends React.PureComponent<{ response: ResponseMod
<ResponseHeaders headers={headers} />
<MediaTypesSwitch content={content} renderDropdown={this.renderDropdown}>
{({ schema }) => {
return <Schema skipWriteOnly={true} key="schema" schema={schema} />;
return (
<>
{schema?.type === 'object' && (
<ConstraintsView constraints={schema?.constraints || []} />
)}
<Schema skipWriteOnly={true} key="schema" schema={schema} />
</>
);
}}
</MediaTypesSwitch>
</>

View File

@ -6,6 +6,7 @@ import { ArrayClosingLabel, ArrayOpenningLabel } from '../../common-elements';
import styled from '../../styled-components';
import { humanizeConstraints } from '../../utils';
import { TypeName } from '../../common-elements/fields';
import { ObjectSchema } from './ObjectSchema';
const PaddedSchema = styled.div`
padding-left: ${({ theme }) => theme.spacing.unit * 2}px;
@ -21,6 +22,9 @@ export class ArraySchema extends React.PureComponent<SchemaProps> {
? ''
: `(${humanizeConstraints(schema)})`;
if (schema.fields) {
return <ObjectSchema {...(this.props as any)} level={this.props.level} />;
}
if (schema.displayType && !itemsSchema && !minMaxItems.length) {
return (
<div>

View File

@ -8,6 +8,7 @@ import {
} from '../../common-elements/schema';
import { Badge } from '../../common-elements/shelfs';
import { SchemaModel } from '../../services/models';
import { ConstraintsView } from '../Fields/FieldContstraints';
import { Schema, SchemaProps } from './Schema';
export interface OneOfButtonProps {
@ -47,6 +48,8 @@ export class OneOfSchema extends React.Component<SchemaProps> {
if (oneOf === undefined) {
return null;
}
const activeSchema = oneOf[schema.activeOneOf];
return (
<div>
<OneOfLabel> {schema.oneOfType} </OneOfLabel>
@ -58,7 +61,8 @@ export class OneOfSchema extends React.Component<SchemaProps> {
<div>
{oneOf[schema.activeOneOf].deprecated && <Badge type="warning">Deprecated</Badge>}
</div>
<Schema {...this.props} schema={oneOf[schema.activeOneOf]} />
<ConstraintsView constraints={activeSchema.constraints} />
<Schema {...this.props} schema={activeSchema} />
</div>
);
}

View File

@ -0,0 +1,71 @@
import * as React from 'react';
import { OpenAPISecurityScheme } from '../../types';
import { SecurityRow } from './styled.elements';
import { SeeMore } from '../SeeMore/SeeMore';
import { Markdown } from '../Markdown/Markdown';
export interface OAuthFlowProps {
type: string;
flow: OpenAPISecurityScheme['flows'][keyof OpenAPISecurityScheme['flows']];
RequiredScopes?: JSX.Element;
}
export function OAuthFlowComponent(props: OAuthFlowProps) {
const { type, flow, RequiredScopes } = props;
const scopesNames = Object.keys(flow?.scopes || {});
console.log('rended');
return (
<>
<SecurityRow>
<b>Flow type: </b>
<code>{type} </code>
</SecurityRow>
{(type === 'implicit' || type === 'authorizationCode') && (
<SecurityRow>
<strong> Authorization URL: </strong>
<code>
<a target="_blank" rel="noopener noreferrer" href={(flow as any).authorizationUrl}>
{(flow as any).authorizationUrl}
</a>
</code>
</SecurityRow>
)}
{(type === 'password' || type === 'clientCredentials' || type === 'authorizationCode') && (
<SecurityRow>
<b> Token URL: </b>
<code>{(flow as any).tokenUrl}</code>
</SecurityRow>
)}
{flow!.refreshUrl && (
<SecurityRow>
<strong> Refresh URL: </strong>
{flow!.refreshUrl}
</SecurityRow>
)}
{!!scopesNames.length && (
<>
{RequiredScopes || null}
<SecurityRow>
<b> Scopes: </b>
</SecurityRow>
<SeeMore height="4em">
<ul>
{scopesNames.map(scope => (
<li key={scope}>
<code>{scope}</code> -{' '}
<Markdown
className={'redoc-markdown'}
inline={true}
source={flow!.scopes[scope] || ''}
/>
</li>
))}
</ul>
</SeeMore>
</>
)}
</>
);
}
export const OAuthFlow = React.memo<OAuthFlowProps>(OAuthFlowComponent);

View File

@ -0,0 +1,18 @@
import * as React from 'react';
export const RequiredScopesRow = ({ scopes }: { scopes: string[] }): JSX.Element | null => {
if (!scopes.length) return null;
return (
<div>
<b>Required scopes: </b>
{scopes.map((scope, idx) => {
return (
<React.Fragment key={idx}>
<code>{scope}</code>{' '}
</React.Fragment>
);
})}
</div>
);
};

View File

@ -0,0 +1,65 @@
import * as React from 'react';
import { SecuritySchemeModel } from '../../services';
import { titleize } from '../../utils';
import { StyledMarkdownBlock } from '../Markdown/styled.elements';
import { SecurityRow } from './styled.elements';
import { OAuthFlow } from './OAuthFlow';
interface SecuritySchemaProps {
RequiredScopes?: JSX.Element;
scheme: SecuritySchemeModel;
}
export function SecurityDetails(props: SecuritySchemaProps) {
const { RequiredScopes, scheme } = props;
return (
<StyledMarkdownBlock>
{scheme.apiKey ? (
<>
<SecurityRow>
<b>{titleize(scheme.apiKey.in || '')} parameter name: </b>
<code>{scheme.apiKey.name}</code>
</SecurityRow>
{RequiredScopes}
</>
) : scheme.http ? (
<>
<SecurityRow>
<b>HTTP Authorization Scheme: </b>
<code>{scheme.http.scheme}</code>
</SecurityRow>
<SecurityRow>
{scheme.http.scheme === 'bearer' && scheme.http.bearerFormat && (
<>
<b>Bearer format: </b>
<code>{scheme.http.bearerFormat}</code>
</>
)}
</SecurityRow>
{RequiredScopes}
</>
) : scheme.openId ? (
<>
<SecurityRow>
<b>Connect URL: </b>
<code>
<a target="_blank" rel="noopener noreferrer" href={scheme.openId.connectUrl}>
{scheme.openId.connectUrl}
</a>
</code>
</SecurityRow>
{RequiredScopes}
</>
) : scheme.flows ? (
Object.keys(scheme.flows).map(type => (
<OAuthFlow
key={type}
type={type}
RequiredScopes={RequiredScopes}
flow={scheme.flows[type]}
/>
))
) : null}
</StyledMarkdownBlock>
);
}

View File

@ -0,0 +1,43 @@
import { SecurityRequirementModel } from '../../services/models/SecurityRequirement';
import {
ScopeName,
SecurityRequirementAndWrap,
SecurityRequirementOrWrap,
} from './styled.elements';
import * as React from 'react';
import { AUTH_TYPES } from '../SecuritySchemes/SecuritySchemes';
export interface SecurityRequirementProps {
security: SecurityRequirementModel;
showSecuritySchemeType?: boolean;
expanded: boolean;
}
export function SecurityHeader(props: SecurityRequirementProps) {
const { security, showSecuritySchemeType, expanded } = props;
const grouping = security.schemes.length > 1;
return (
<SecurityRequirementOrWrap expanded={expanded}>
{grouping && '('}
{security.schemes.map(scheme => {
return (
<SecurityRequirementAndWrap key={scheme.id}>
{showSecuritySchemeType && `${AUTH_TYPES[scheme.type] || scheme.type}: `}
<i>{scheme.displayName}</i>
{expanded && scheme.scopes.length
? [
' (',
scheme.scopes.map<React.ReactNode>(scope => (
<ScopeName key={scope}>{scope}</ScopeName>
)),
') ',
]
: null}
</SecurityRequirementAndWrap>
);
})}
{grouping && ') '}
</SecurityRequirementOrWrap>
);
}

View File

@ -1,153 +1,102 @@
import * as React from 'react';
import styled, { media } from '../../styled-components';
import { Link, UnderlinedHeader } from '../../common-elements/';
import { useState } from 'react';
import { SecurityRequirementModel } from '../../services/models/SecurityRequirement';
import { linksCss } from '../Markdown/styled.elements';
const ScopeNameList = styled.ul`
display: inline;
list-style: none;
padding: 0;
li {
display: inherit;
&:after {
content: ',';
}
&:last-child:after {
content: none;
}
}
`;
const ScopeName = styled.code`
font-size: ${props => props.theme.typography.code.fontSize};
font-family: ${props => props.theme.typography.code.fontFamily};
border: 1px solid ${({ theme }) => theme.colors.border.dark};
margin: 0 3px;
padding: 0.2em;
display: inline-block;
line-height: 1;
`;
const SecurityRequirementAndWrap = styled.span`
&:after {
content: ' AND ';
font-weight: bold;
}
&:last-child:after {
content: none;
}
${linksCss};
`;
const SecurityRequirementOrWrap = styled.span`
&:before {
content: '( ';
font-weight: bold;
}
&:after {
content: ' ) OR ';
font-weight: bold;
}
&:last-child:after {
content: ' )';
}
&:only-child:before,
&:only-child:after {
content: none;
}
${linksCss};
`;
export interface SecurityRequirementProps {
security: SecurityRequirementModel;
}
export class SecurityRequirement extends React.PureComponent<SecurityRequirementProps> {
render() {
const security = this.props.security;
return (
<SecurityRequirementOrWrap>
{security.schemes.length ? (
security.schemes.map(scheme => {
return (
<SecurityRequirementAndWrap key={scheme.id}>
<Link to={scheme.sectionId}>{scheme.displayName}</Link>
{scheme.scopes.length > 0 && ' ('}
<ScopeNameList>
{scheme.scopes.map(scope => (
<li key={scope}>
<ScopeName>{scope}</ScopeName>
</li>
))}
</ScopeNameList>
{scheme.scopes.length > 0 && ') '}
</SecurityRequirementAndWrap>
);
})
) : (
<SecurityRequirementAndWrap>None</SecurityRequirementAndWrap>
)}
</SecurityRequirementOrWrap>
);
}
}
const AuthHeaderColumn = styled.div`
flex: 1 1 auto;
`;
const SecuritiesColumn = styled.div`
width: ${props => props.theme.schema.defaultDetailsWidth};
${media.lessThan('small')`
margin-top: 10px;
`}
`;
const AuthHeader = styled(UnderlinedHeader)`
display: inline-block;
margin: 0;
`;
const Wrap = styled.div`
width: 100%;
display: flex;
margin: 1em 0;
${media.lessThan('small')`
flex-direction: column;
`}
`;
import {
AuthHeader,
AuthHeaderColumn,
SecuritiesColumn,
SecurityDetailsStyle,
Wrap,
} from './styled.elements';
import { useStore } from '../StoreBuilder';
import { SecurityHeader } from './SecurityHeader';
import { RequiredScopesRow } from './RequiredScopesRow';
import { AUTH_TYPES } from '../SecuritySchemes/SecuritySchemes';
import { Markdown } from '../Markdown/Markdown';
import { SecurityDetails } from './SecurityDetails';
import { ShelfIcon } from '../../common-elements';
export interface SecurityRequirementsProps {
securities: SecurityRequirementModel[];
}
export class SecurityRequirements extends React.PureComponent<SecurityRequirementsProps> {
render() {
const securities = this.props.securities;
if (!securities.length) {
return null;
}
return (
<Wrap>
<AuthHeaderColumn>
<AuthHeader>Authorizations: </AuthHeader>
export function SecurityRequirements(props: SecurityRequirementsProps) {
const store = useStore();
const showSecuritySchemeType = store?.options.showSecuritySchemeType;
const [expanded, setExpanded] = useState(false);
const { securities } = props;
if (!securities?.length || store?.options.hideSecuritySection) {
return null;
}
const operationSecuritySchemes = store?.spec.securitySchemes.schemes.filter(({ id }) => {
return securities.find(security => security.schemes.find(scheme => scheme.id === id));
});
return (
<>
<Wrap expanded={expanded}>
<AuthHeaderColumn onClick={() => setExpanded(!expanded)}>
<AuthHeader>Authorizations:</AuthHeader>
<ShelfIcon size={'1.3em'} direction={expanded ? 'down' : 'right'} />
</AuthHeaderColumn>
<SecuritiesColumn>
<SecuritiesColumn expanded={expanded}>
{securities.map((security, idx) => (
<SecurityRequirement key={idx} security={security} />
<SecurityHeader
key={idx}
expanded={expanded}
showSecuritySchemeType={showSecuritySchemeType}
security={security}
/>
))}
</SecuritiesColumn>
</Wrap>
);
}
{expanded &&
operationSecuritySchemes?.length &&
operationSecuritySchemes.map((scheme, idx) => (
<SecurityDetailsStyle key={idx}>
<h5>
<LockIcon /> {AUTH_TYPES[scheme.type] || scheme.type}: {scheme.id}
</h5>
<Markdown source={scheme.description || ''} />
<SecurityDetails
key={scheme.id}
scheme={scheme}
RequiredScopes={
<RequiredScopesRow scopes={getRequiredScopes(scheme.id, securities)} />
}
/>
</SecurityDetailsStyle>
))}
</>
);
}
const LockIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="11" height="11">
<path
fill="currentColor"
d="M18 10V6A6 6 0 0 0 6 6v4H3v14h18V10h-3zM8 6c0-2.206 1.794-4 4-4s4 1.794 4 4v4H8V6zm11 16H5V12h14v10z"
/>
</svg>
);
function getRequiredScopes(id: string, securities: SecurityRequirementModel[]): string[] {
const allScopes: string[] = [];
let securitiesLength = securities.length;
while (securitiesLength--) {
const security = securities[securitiesLength];
let schemesLength = security.schemes.length;
while (schemesLength--) {
const scheme = security.schemes[schemesLength];
if (scheme.id === id) {
allScopes.push(...scheme.scopes);
}
}
}
return Array.from(new Set(allScopes));
}

View File

@ -0,0 +1,129 @@
import styled from 'styled-components';
import { linksCss } from '../Markdown/styled.elements';
import { media } from '../../styled-components';
import { UnderlinedHeader } from '../../common-elements';
export const Header = styled.div`
background-color: #e4e7eb;
`;
export const ScopeNameList = styled.ul`
display: inline;
list-style: none;
padding: 0;
li {
display: inherit;
&:after {
content: ',';
}
&:last-child:after {
content: none;
}
}
`;
export const ScopeName = styled.code`
font-size: ${props => props.theme.typography.code.fontSize};
font-family: ${props => props.theme.typography.code.fontFamily};
margin: 0 3px;
padding: 0.2em;
display: inline-block;
line-height: 1;
&:after {
content: ',';
font-weight: normal;
}
&:last-child:after {
content: none;
}
`;
export const SecurityRequirementAndWrap = styled.span`
&:after {
content: ' and ';
font-weight: normal;
}
&:last-child:after {
content: none;
}
${linksCss};
`;
export const SecurityRequirementOrWrap = styled.span<{ expanded?: boolean }>`
${p => !p.expanded && `white-space: nowrap;`}
&:after {
content: ' or ';
${p => p.expanded && `content: ' or \\a';`}
white-space: pre;
}
&:last-child:after,
&:only-child:after {
content: none;
}
${linksCss};
`;
export const AuthHeaderColumn = styled.div`
flex: 1 1 auto;
cursor: pointer;
`;
export const SecuritiesColumn = styled.div<{ expanded?: boolean }>`
width: ${props => props.theme.schema.defaultDetailsWidth};
text-overflow: ellipsis;
border-radius: 4px;
overflow: hidden;
${p =>
p.expanded &&
`background: ${p.theme.colors.gray['100']};
padding: 8px 9.6px;
margin: 20px 0;
width: 100%;
`};
${media.lessThan('small')`
margin-top: 10px;
`}
`;
export const AuthHeader = styled(UnderlinedHeader)`
display: inline-block;
margin: 0;
`;
export const Wrap = styled.div<{ expanded?: boolean }>`
width: 100%;
display: flex;
margin: 1em 0;
flex-direction: ${p => (p.expanded ? 'column' : 'row')};
${media.lessThan('small')`
flex-direction: column;
`}
`;
export const SecurityRow = styled.div`
margin: 0.5em 0;
`;
export const SecurityDetailsStyle = styled.div`
border-bottom: 1px solid ${({ theme }) => theme.colors.border.dark};
margin-bottom: 1.5em;
padding-bottom: 0.7em;
h5 {
line-height: 1em;
margin: 0 0 0.6em;
font-size: ${({ theme }) => theme.typography.fontSize};
}
.redoc-markdown p:first-child {
display: inline;
}
`;

View File

@ -1,72 +1,18 @@
import * as React from 'react';
import { SecuritySchemesModel } from '../../services/models';
import { H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
import { OpenAPISecurityScheme } from '../../types';
import { titleize } from '../../utils/helpers';
import { SecuritySchemesModel } from '../../services';
import { H2, Row, ShareLink, MiddlePanel, Section } from '../../common-elements';
import { Markdown } from '../Markdown/Markdown';
import { StyledMarkdownBlock } from '../Markdown/styled.elements';
import { SecurityDetails } from '../SecurityRequirement/SecurityDetails';
import { SecurityDetailsStyle, SecurityRow } from '../SecurityRequirement/styled.elements';
const AUTH_TYPES = {
export const AUTH_TYPES = {
oauth2: 'OAuth2',
apiKey: 'API Key',
http: 'HTTP',
openIdConnect: 'OpenID Connect',
};
export interface OAuthFlowProps {
type: string;
flow: OpenAPISecurityScheme['flows'][keyof OpenAPISecurityScheme['flows']];
}
export class OAuthFlow extends React.PureComponent<OAuthFlowProps> {
render() {
const { type, flow } = this.props;
const scopesNames = Object.keys(flow?.scopes || {});
return (
<tr>
<th> {type} OAuth Flow </th>
<td>
{type === 'implicit' || type === 'authorizationCode' ? (
<div>
<strong> Authorization URL: </strong>
{(flow as any).authorizationUrl}
</div>
) : null}
{type === 'password' || type === 'clientCredentials' || type === 'authorizationCode' ? (
<div>
<strong> Token URL: </strong>
{(flow as any).tokenUrl}
</div>
) : null}
{flow!.refreshUrl && (
<div>
<strong> Refresh URL: </strong>
{flow!.refreshUrl}
</div>
)}
{!!scopesNames.length && (
<>
<div>
<strong> Scopes: </strong>
</div>
<ul>
{scopesNames.map(scope => (
<li key={scope}>
<code>{scope}</code> -{' '}
<Markdown inline={true} source={flow!.scopes[scope] || ''} />
</li>
))}
</ul>
</>
)}
</td>
</tr>
);
}
}
export interface SecurityDefsProps {
securitySchemes: SecuritySchemesModel;
}
@ -82,52 +28,13 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
{scheme.displayName}
</H2>
<Markdown source={scheme.description || ''} />
<StyledMarkdownBlock>
<table className="security-details">
<tbody>
<tr>
<th> Security Scheme Type </th>
<td> {AUTH_TYPES[scheme.type] || scheme.type} </td>
</tr>
{scheme.apiKey ? (
<tr>
<th> {titleize(scheme.apiKey.in || '')} parameter name:</th>
<td> {scheme.apiKey.name} </td>
</tr>
) : scheme.http ? (
[
<tr key="scheme">
<th> HTTP Authorization Scheme </th>
<td> {scheme.http.scheme} </td>
</tr>,
scheme.http.scheme === 'bearer' && scheme.http.bearerFormat && (
<tr key="bearer">
<th> Bearer format </th>
<td> &quot;{scheme.http.bearerFormat}&quot; </td>
</tr>
),
]
) : scheme.openId ? (
<tr>
<th> Connect URL </th>
<td>
<a
target="_blank"
rel="noopener noreferrer"
href={scheme.openId.connectUrl}
>
{scheme.openId.connectUrl}
</a>
</td>
</tr>
) : scheme.flows ? (
Object.keys(scheme.flows).map(type => (
<OAuthFlow key={type} type={type} flow={scheme.flows[type]} />
))
) : null}
</tbody>
</table>
</StyledMarkdownBlock>
<SecurityDetailsStyle>
<SecurityRow>
<b>Security Scheme Type: </b>
<span>{AUTH_TYPES[scheme.type] || scheme.type}</span>
</SecurityRow>
<SecurityDetails scheme={scheme} />
</SecurityDetailsStyle>
</MiddlePanel>
</Row>
</Section>

View File

@ -0,0 +1,65 @@
import * as React from 'react';
import styled from 'styled-components';
const TOLERANCE_PX = 20;
interface SeeMoreProps {
children?: React.ReactNode;
height: string;
}
export function SeeMore({ children, height }: SeeMoreProps): JSX.Element {
const ref = React.createRef() as React.RefObject<HTMLDivElement>;
const [showMore, setShowMore] = React.useState(false);
const [showLink, setShowLink] = React.useState(false);
React.useEffect(() => {
if (ref.current && ref.current.clientHeight + TOLERANCE_PX < ref.current.scrollHeight) {
setShowLink(true);
}
}, [ref]);
const onClickMore = () => {
setShowMore(!showMore);
};
return (
<>
<Container
ref={ref}
className={showMore ? '' : 'container'}
style={{ height: showMore ? 'auto' : height }}
>
{children}
</Container>
<ButtonContainer dimmed={!showMore}>
{showLink && (
<ButtonLinkStyled onClick={onClickMore}>
{showMore ? 'See less' : 'See more'}
</ButtonLinkStyled>
)}
</ButtonContainer>
</>
);
}
const Container = styled.div`
overflow-y: hidden;
`;
const ButtonContainer = styled.div<{ dimmed?: boolean }>`
text-align: center;
line-height: 1.5em;
${({ dimmed }) =>
dimmed &&
`background-image: linear-gradient(to bottom, transparent,rgb(255 255 255));
position: relative;
top: -0.5em;
padding-top: 0.5em;
background-position-y: -1em;
`}
`;
const ButtonLinkStyled = styled.a`
cursor: pointer;
`;

View File

@ -1,5 +1,5 @@
import * as React from 'react';
import { createContext } from 'react';
import { createContext, useContext } from 'react';
import { AppStore } from '../services/';
import { RedocRawOptions } from '../services/RedocNormalizedOptions';
@ -79,3 +79,7 @@ export function StoreBuilder(props: StoreBuilderProps) {
store,
});
}
export function useStore(): AppStore | undefined {
return useContext(StoreContext);
}

View File

@ -53,5 +53,30 @@ describe('Components', () => {
expect(component.render()).toMatchSnapshot();
});
});
describe('Show minProperties/maxProperties constraints oneOf', () => {
const schema = new SchemaModel(
parser,
{
oneOf: [
{
type: 'object',
description: 'Test description',
minProperties: 1,
maxProperties: 1,
additionalProperties: {
type: 'string',
description: 'The name and value o',
},
},
],
},
'',
options,
);
const component = shallow(withTheme(<Schema schema={schema} />));
expect(component.html().includes('= 1 properties')).toBe(true);
});
});
});

View File

@ -0,0 +1,67 @@
/* tslint:disable:no-implicit-dependencies */
import { shallow } from 'enzyme';
import * as React from 'react';
import { Schema } from '../';
import { OpenAPIParser, SchemaModel } from '../../services';
import { RedocNormalizedOptions } from '../../services/RedocNormalizedOptions';
import { withTheme } from '../testProviders';
const options = new RedocNormalizedOptions({});
describe('Components', () => {
describe('SchemaView', () => {
const parser = new OpenAPIParser(
{ openapi: '3.0', info: { title: 'test', version: '0' }, paths: {} },
undefined,
options,
);
describe('Show minProperties/maxProperties constraints', () => {
const schema = new SchemaModel(
parser,
{
properties: {
name: {
type: 'object',
minProperties: 1,
properties: {
address: {
type: 'string',
},
},
},
},
},
'',
options,
);
const component = shallow(withTheme(<Schema schema={schema} />));
expect(component.html().includes('non-empty')).toBe(true);
});
describe('Show range minProperties/maxProperties constraints', () => {
const schema = new SchemaModel(
parser,
{
properties: {
name: {
type: 'object',
minProperties: 2,
maxProperties: 10,
additionalProperties: {
type: 'string',
},
},
},
},
'',
options,
);
it('should includes [ 2 .. 10 ] properties', () => {
const component = shallow(withTheme(<Schema schema={schema} />));
expect(component.html().includes('[ 2 .. 10 ] properties')).toBe(true);
});
});
});
});

View File

@ -1,28 +1,72 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import { mount } from 'enzyme';
import { OpenAPIParser } from '../../services';
import { SecurityRequirementModel } from '../../services/models/SecurityRequirement';
import { SecurityRequirement } from '../SecurityRequirement/SecurityRequirement';
import { RedocNormalizedOptions } from '../../services/RedocNormalizedOptions';
import {
createStore,
OpenAPIParser,
OperationModel,
RedocNormalizedOptions,
SecuritySchemesModel,
} from '../../services';
import { StoreProvider } from '../StoreBuilder';
import { SecurityRequirements } from '../SecurityRequirement/SecurityRequirement';
import { withTheme } from '../testProviders';
import { SecurityDefs } from '../SecuritySchemes/SecuritySchemes';
import * as simpleSecurityFixture from './fixtures/simple-security-fixture.json';
const options = new RedocNormalizedOptions({});
describe('Components', () => {
describe('SecurityRequirement', () => {
describe('SecurityRequirement', () => {
it("should render 'None' when empty object in security open api", () => {
const parser = new OpenAPIParser(
{ openapi: '3.0', info: { title: 'test', version: '0' }, paths: {} },
undefined,
options,
describe('SecurityRequirement', () => {
it('should render authDefinition', async () => {
const store = await createStore(simpleSecurityFixture, undefined, {
showSecuritySchemeType: true,
});
store.spec.contentItems.forEach((item: OperationModel) => {
if (item.security) {
const component = mount(
withTheme(
<StoreProvider value={store}>
<SecurityRequirements securities={item.security} />,
</StoreProvider>,
),
);
const securityRequirement = new SecurityRequirementModel({}, parser);
const securityElement = shallow(
<SecurityRequirement key={1} security={securityRequirement} />,
).getElement();
expect(securityElement.props.children.type.target).toEqual('span');
expect(securityElement.props.children.props.children).toEqual('None');
});
expect(component.html()).toMatchSnapshot();
component.find('svg').simulate('click');
//Security expanded
expect(component.html()).toMatchSnapshot();
}
});
});
it('should render SecurityDefs', async () => {
const parser = new OpenAPIParser(
simpleSecurityFixture,
undefined,
new RedocNormalizedOptions({}),
);
const component = mount(
withTheme(<SecurityDefs securitySchemes={new SecuritySchemesModel(parser)} />),
);
expect(component.html()).toMatchSnapshot();
});
it('should hide authDefinition', async () => {
const store = await createStore(simpleSecurityFixture, undefined, {
hideSecuritySection: true,
});
store.spec.contentItems.forEach((item: OperationModel) => {
if (item.security) {
const component = mount(
withTheme(
<StoreProvider value={store}>
<SecurityRequirements securities={item.security} />,
</StoreProvider>,
),
);
expect(component.html().includes('Authorizations')).toBe(false);
expect(component.html().includes('svg')).toBe(false);
}
});
});
});

View File

@ -89,6 +89,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"hideHostname": false,
"hideSchemaPattern": false,
"hideSchemaTitles": false,
"hideSecuritySection": false,
"hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2,
@ -96,7 +97,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"menuToggle": true,
"minCharacterLengthToInitSearch": 3,
"nativeScrollbars": false,
"noAutoAuth": false,
"nonce": undefined,
"onlyRequiredInSamples": false,
"pathInMiddlePanel": false,
@ -107,6 +107,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sectionsAtTheEnd": Array [],
"showExtensions": false,
"showObjectSchemaExamples": false,
"showSecuritySchemeType": false,
"showWebhookVerb": false,
"sideNavStyle": "summary-only",
"simpleOneOfTypeLabel": false,
@ -273,6 +274,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"links": Object {
"color": "#32329f",
"hover": "#6868cf",
"hoverTextDecoration": "auto",
"textDecoration": "auto",
"visited": "#32329f",
},
"optimizeSpeed": true,
@ -345,6 +348,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"hideHostname": false,
"hideSchemaPattern": false,
"hideSchemaTitles": false,
"hideSecuritySection": false,
"hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2,
@ -352,7 +356,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"menuToggle": true,
"minCharacterLengthToInitSearch": 3,
"nativeScrollbars": false,
"noAutoAuth": false,
"nonce": undefined,
"onlyRequiredInSamples": false,
"pathInMiddlePanel": false,
@ -363,6 +366,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sectionsAtTheEnd": Array [],
"showExtensions": false,
"showObjectSchemaExamples": false,
"showSecuritySchemeType": false,
"showWebhookVerb": false,
"sideNavStyle": "summary-only",
"simpleOneOfTypeLabel": false,
@ -529,6 +533,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"links": Object {
"color": "#32329f",
"hover": "#6868cf",
"hoverTextDecoration": "auto",
"textDecoration": "auto",
"visited": "#32329f",
},
"optimizeSpeed": true,
@ -576,6 +582,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"hideHostname": false,
"hideSchemaPattern": false,
"hideSchemaTitles": false,
"hideSecuritySection": false,
"hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2,
@ -583,7 +590,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"menuToggle": true,
"minCharacterLengthToInitSearch": 3,
"nativeScrollbars": false,
"noAutoAuth": false,
"nonce": undefined,
"onlyRequiredInSamples": false,
"pathInMiddlePanel": false,
@ -594,6 +600,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sectionsAtTheEnd": Array [],
"showExtensions": false,
"showObjectSchemaExamples": false,
"showSecuritySchemeType": false,
"showWebhookVerb": false,
"sideNavStyle": "summary-only",
"simpleOneOfTypeLabel": false,
@ -760,6 +767,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"links": Object {
"color": "#32329f",
"hover": "#6868cf",
"hoverTextDecoration": "auto",
"textDecoration": "auto",
"visited": "#32329f",
},
"optimizeSpeed": true,
@ -874,6 +883,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"hideHostname": false,
"hideSchemaPattern": false,
"hideSchemaTitles": false,
"hideSecuritySection": false,
"hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2,
@ -881,7 +891,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"menuToggle": true,
"minCharacterLengthToInitSearch": 3,
"nativeScrollbars": false,
"noAutoAuth": false,
"nonce": undefined,
"onlyRequiredInSamples": false,
"pathInMiddlePanel": false,
@ -892,6 +901,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sectionsAtTheEnd": Array [],
"showExtensions": false,
"showObjectSchemaExamples": false,
"showSecuritySchemeType": false,
"showWebhookVerb": false,
"sideNavStyle": "summary-only",
"simpleOneOfTypeLabel": false,
@ -1058,6 +1068,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"links": Object {
"color": "#32329f",
"hover": "#6868cf",
"hoverTextDecoration": "auto",
"textDecoration": "auto",
"visited": "#32329f",
},
"optimizeSpeed": true,
@ -1130,6 +1142,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"hideHostname": false,
"hideSchemaPattern": false,
"hideSchemaTitles": false,
"hideSecuritySection": false,
"hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2,
@ -1137,7 +1150,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"menuToggle": true,
"minCharacterLengthToInitSearch": 3,
"nativeScrollbars": false,
"noAutoAuth": false,
"nonce": undefined,
"onlyRequiredInSamples": false,
"pathInMiddlePanel": false,
@ -1148,6 +1160,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sectionsAtTheEnd": Array [],
"showExtensions": false,
"showObjectSchemaExamples": false,
"showSecuritySchemeType": false,
"showWebhookVerb": false,
"sideNavStyle": "summary-only",
"simpleOneOfTypeLabel": false,
@ -1314,6 +1327,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"links": Object {
"color": "#32329f",
"hover": "#6868cf",
"hoverTextDecoration": "auto",
"textDecoration": "auto",
"visited": "#32329f",
},
"optimizeSpeed": true,
@ -1361,6 +1376,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"hideHostname": false,
"hideSchemaPattern": false,
"hideSchemaTitles": false,
"hideSecuritySection": false,
"hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2,
@ -1368,7 +1384,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"menuToggle": true,
"minCharacterLengthToInitSearch": 3,
"nativeScrollbars": false,
"noAutoAuth": false,
"nonce": undefined,
"onlyRequiredInSamples": false,
"pathInMiddlePanel": false,
@ -1379,6 +1394,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sectionsAtTheEnd": Array [],
"showExtensions": false,
"showObjectSchemaExamples": false,
"showSecuritySchemeType": false,
"showWebhookVerb": false,
"sideNavStyle": "summary-only",
"simpleOneOfTypeLabel": false,
@ -1545,6 +1561,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"links": Object {
"color": "#32329f",
"hover": "#6868cf",
"hoverTextDecoration": "auto",
"textDecoration": "auto",
"visited": "#32329f",
},
"optimizeSpeed": true,
@ -1615,6 +1633,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"hideHostname": false,
"hideSchemaPattern": false,
"hideSchemaTitles": false,
"hideSecuritySection": false,
"hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2,
@ -1622,7 +1641,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"menuToggle": true,
"minCharacterLengthToInitSearch": 3,
"nativeScrollbars": false,
"noAutoAuth": false,
"nonce": undefined,
"onlyRequiredInSamples": false,
"pathInMiddlePanel": false,
@ -1633,6 +1651,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sectionsAtTheEnd": Array [],
"showExtensions": false,
"showObjectSchemaExamples": false,
"showSecuritySchemeType": false,
"showWebhookVerb": false,
"sideNavStyle": "summary-only",
"simpleOneOfTypeLabel": false,
@ -1799,6 +1818,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"links": Object {
"color": "#32329f",
"hover": "#6868cf",
"hoverTextDecoration": "auto",
"textDecoration": "auto",
"visited": "#32329f",
},
"optimizeSpeed": true,
@ -1910,6 +1931,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"hideHostname": false,
"hideSchemaPattern": false,
"hideSchemaTitles": false,
"hideSecuritySection": false,
"hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2,
@ -1917,7 +1939,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"menuToggle": true,
"minCharacterLengthToInitSearch": 3,
"nativeScrollbars": false,
"noAutoAuth": false,
"nonce": undefined,
"onlyRequiredInSamples": false,
"pathInMiddlePanel": false,
@ -1928,6 +1949,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sectionsAtTheEnd": Array [],
"showExtensions": false,
"showObjectSchemaExamples": false,
"showSecuritySchemeType": false,
"showWebhookVerb": false,
"sideNavStyle": "summary-only",
"simpleOneOfTypeLabel": false,
@ -2094,6 +2116,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"links": Object {
"color": "#32329f",
"hover": "#6868cf",
"hoverTextDecoration": "auto",
"textDecoration": "auto",
"visited": "#32329f",
},
"optimizeSpeed": true,
@ -2166,6 +2190,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"hideHostname": false,
"hideSchemaPattern": false,
"hideSchemaTitles": false,
"hideSecuritySection": false,
"hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2,
@ -2173,7 +2198,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"menuToggle": true,
"minCharacterLengthToInitSearch": 3,
"nativeScrollbars": false,
"noAutoAuth": false,
"nonce": undefined,
"onlyRequiredInSamples": false,
"pathInMiddlePanel": false,
@ -2184,6 +2208,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sectionsAtTheEnd": Array [],
"showExtensions": false,
"showObjectSchemaExamples": false,
"showSecuritySchemeType": false,
"showWebhookVerb": false,
"sideNavStyle": "summary-only",
"simpleOneOfTypeLabel": false,
@ -2350,6 +2375,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"links": Object {
"color": "#32329f",
"hover": "#6868cf",
"hoverTextDecoration": "auto",
"textDecoration": "auto",
"visited": "#32329f",
},
"optimizeSpeed": true,
@ -2397,6 +2424,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"hideHostname": false,
"hideSchemaPattern": false,
"hideSchemaTitles": false,
"hideSecuritySection": false,
"hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2,
@ -2404,7 +2432,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"menuToggle": true,
"minCharacterLengthToInitSearch": 3,
"nativeScrollbars": false,
"noAutoAuth": false,
"nonce": undefined,
"onlyRequiredInSamples": false,
"pathInMiddlePanel": false,
@ -2415,6 +2442,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sectionsAtTheEnd": Array [],
"showExtensions": false,
"showObjectSchemaExamples": false,
"showSecuritySchemeType": false,
"showWebhookVerb": false,
"sideNavStyle": "summary-only",
"simpleOneOfTypeLabel": false,
@ -2581,6 +2609,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"links": Object {
"color": "#32329f",
"hover": "#6868cf",
"hoverTextDecoration": "auto",
"textDecoration": "auto",
"visited": "#32329f",
},
"optimizeSpeed": true,

View File

@ -44,7 +44,7 @@ exports[`Components SchemaView OneOf deprecated should match snapshot 1`] = `
<div>
<div
class="sc-iJCRrE sc-ciSkZP jCdxGr emlfPd"
class="sc-iJCRrE sc-ciSkZP jCdxGr lhENGb"
/>
</div>
</div>

View File

@ -0,0 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SecurityRequirement should render SecurityDefs 1`] = `
"<div id=\\"section/Authentication/petstore_auth\\" data-section-id=\\"section/Authentication/petstore_auth\\" class=\\"sc-eCApnc jlMQbh\\"><div class=\\"sc-iCoGMd gLxhOh\\"><div class=\\"sc-hKFxyN juinod\\"><h2 class=\\"sc-pNWdM eftmgB\\">petstore_auth</h2><div class=\\"sc-iJCRrE sc-ciSkZP jCdxGr QGruV\\"><p>Get access to data while protecting your account credentials.
OAuth2 is also a safer and more secure way to give you access.</p>
</div><div class=\\"sc-EZqKI aOkZE\\"><div class=\\"sc-fXgAZx gZCyoW\\"><b>Security Scheme Type: </b><span>OAuth2</span></div><div class=\\"sc-iJCRrE sc-ciSkZP jCdxGr QGruV\\"><div class=\\"sc-fXgAZx gZCyoW\\"><b>Flow type: </b><code>implicit </code></div><div class=\\"sc-fXgAZx gZCyoW\\"><strong> Authorization URL: </strong><code><a target=\\"_blank\\" rel=\\"noopener noreferrer\\" href=\\"http://petstore.swagger.io/api/oauth/dialog\\">http://petstore.swagger.io/api/oauth/dialog</a></code></div><div class=\\"sc-fXgAZx gZCyoW\\"><b> Scopes: </b></div><div class=\\"sc-jXcxbT blWOKY container\\" style=\\"height: 4em;\\"><ul><li><code>write:pets</code> - <span class=\\"sc-carFqZ bmTzxo redoc-markdown\\"><p>modify pets in your account</p>
</span></li><li><code>read:pets</code> - <span class=\\"sc-carFqZ bmTzxo redoc-markdown\\"><p>read your pets</p>
</span></li></ul></div><div class=\\"sc-eEVmNe gbLbHj\\"></div></div></div></div></div></div><div id=\\"section/Authentication/GitLab_PersonalAccessToken\\" data-section-id=\\"section/Authentication/GitLab_PersonalAccessToken\\" class=\\"sc-eCApnc jlMQbh\\"><div class=\\"sc-iCoGMd gLxhOh\\"><div class=\\"sc-hKFxyN juinod\\"><h2 class=\\"sc-pNWdM eftmgB\\">GitLab_PersonalAccessToken</h2><div class=\\"sc-iJCRrE sc-ciSkZP jCdxGr QGruV\\"><p>GitLab Personal Access Token description</p>
</div><div class=\\"sc-EZqKI aOkZE\\"><div class=\\"sc-fXgAZx gZCyoW\\"><b>Security Scheme Type: </b><span>API Key</span></div><div class=\\"sc-iJCRrE sc-ciSkZP jCdxGr QGruV\\"><div class=\\"sc-fXgAZx gZCyoW\\"><b>Header parameter name: </b><code>PRIVATE-TOKEN</code></div></div></div></div></div></div><div id=\\"section/Authentication/GitLab_OpenIdConnect\\" data-section-id=\\"section/Authentication/GitLab_OpenIdConnect\\" class=\\"sc-eCApnc jlMQbh\\"><div class=\\"sc-iCoGMd gLxhOh\\"><div class=\\"sc-hKFxyN juinod\\"><h2 class=\\"sc-pNWdM eftmgB\\">GitLab_OpenIdConnect</h2><div class=\\"sc-iJCRrE sc-ciSkZP jCdxGr QGruV\\"><p>GitLab OpenIdConnect description</p>
</div><div class=\\"sc-EZqKI aOkZE\\"><div class=\\"sc-fXgAZx gZCyoW\\"><b>Security Scheme Type: </b><span>OpenID Connect</span></div><div class=\\"sc-iJCRrE sc-ciSkZP jCdxGr QGruV\\"><div class=\\"sc-fXgAZx gZCyoW\\"><b>Connect URL: </b><code><a target=\\"_blank\\" rel=\\"noopener noreferrer\\" href=\\"https://gitlab.com/.well-known/openid-configuration\\">https://gitlab.com/.well-known/openid-configuration</a></code></div></div></div></div></div></div><div id=\\"section/Authentication/basicAuth\\" data-section-id=\\"section/Authentication/basicAuth\\" class=\\"sc-eCApnc jlMQbh\\"><div class=\\"sc-iCoGMd gLxhOh\\"><div class=\\"sc-hKFxyN juinod\\"><h2 class=\\"sc-pNWdM eftmgB\\">basicAuth</h2><div class=\\"sc-iJCRrE sc-ciSkZP jCdxGr QGruV\\"></div><div class=\\"sc-EZqKI aOkZE\\"><div class=\\"sc-fXgAZx gZCyoW\\"><b>Security Scheme Type: </b><span>HTTP</span></div><div class=\\"sc-iJCRrE sc-ciSkZP jCdxGr QGruV\\"><div class=\\"sc-fXgAZx gZCyoW\\"><b>HTTP Authorization Scheme: </b><code>basic</code></div><div class=\\"sc-fXgAZx gZCyoW\\"></div></div></div></div></div></div>"
`;
exports[`SecurityRequirement should render authDefinition 1`] = `"<div class=\\"sc-bQCEYZ eDdCgW\\"><div class=\\"sc-xGAEC femyTb\\"><h5 class=\\"sc-iqAclL sc-jHcXXw eONCmm keQLTh\\">Authorizations:</h5><svg class=\\"sc-dIsUp iPqByX\\" version=\\"1.1\\" viewBox=\\"0 0 24 24\\" x=\\"0\\" xmlns=\\"http://www.w3.org/2000/svg\\" y=\\"0\\" aria-hidden=\\"true\\"><polygon points=\\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \\"></polygon></svg></div><div class=\\"sc-dWBRfb kJFNCL\\"><span class=\\"sc-kYPZxB irJeRy\\">(<span class=\\"sc-hzUIXc gcouO\\">API Key: <i>GitLab_PersonalAccessToken</i></span><span class=\\"sc-hzUIXc gcouO\\">OpenID Connect: <i>GitLab_OpenIdConnect</i></span><span class=\\"sc-hzUIXc gcouO\\">HTTP: <i>basicAuth</i></span>) </span><span class=\\"sc-kYPZxB irJeRy\\"><span class=\\"sc-hzUIXc gcouO\\">OAuth2: <i>petstore_auth</i></span></span></div></div>,"`;
exports[`SecurityRequirement should render authDefinition 2`] = `
"<div class=\\"sc-bQCEYZ dSwEDq\\"><div class=\\"sc-xGAEC femyTb\\"><h5 class=\\"sc-iqAclL sc-jHcXXw eONCmm keQLTh\\">Authorizations:</h5><svg class=\\"sc-dIsUp fVWtGJ\\" version=\\"1.1\\" viewBox=\\"0 0 24 24\\" x=\\"0\\" xmlns=\\"http://www.w3.org/2000/svg\\" y=\\"0\\" aria-hidden=\\"true\\"><polygon points=\\"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 \\"></polygon></svg></div><div class=\\"sc-dWBRfb ekRdav\\"><span class=\\"sc-kYPZxB fhGdrc\\">(<span class=\\"sc-hzUIXc gcouO\\">API Key: <i>GitLab_PersonalAccessToken</i></span><span class=\\"sc-hzUIXc gcouO\\">OpenID Connect: <i>GitLab_OpenIdConnect</i></span><span class=\\"sc-hzUIXc gcouO\\">HTTP: <i>basicAuth</i></span>) </span><span class=\\"sc-kYPZxB fhGdrc\\"><span class=\\"sc-hzUIXc gcouO\\">OAuth2: <i>petstore_auth</i> (<code class=\\"sc-eHEENL fwFTyL\\">write:pets</code><code class=\\"sc-eHEENL fwFTyL\\">read:pets</code>) </span></span></div></div><div class=\\"sc-EZqKI aOkZE\\"><h5><svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 24 24\\" width=\\"11\\" height=\\"11\\"><path fill=\\"currentColor\\" d=\\"M18 10V6A6 6 0 0 0 6 6v4H3v14h18V10h-3zM8 6c0-2.206 1.794-4 4-4s4 1.794 4 4v4H8V6zm11 16H5V12h14v10z\\"></path></svg> OAuth2: petstore_auth</h5><div class=\\"sc-iJCRrE sc-ciSkZP jCdxGr QGruV\\"><p>Get access to data while protecting your account credentials.
OAuth2 is also a safer and more secure way to give you access.</p>
</div><div class=\\"sc-iJCRrE sc-ciSkZP jCdxGr QGruV\\"><div class=\\"sc-fXgAZx gZCyoW\\"><b>Flow type: </b><code>implicit </code></div><div class=\\"sc-fXgAZx gZCyoW\\"><strong> Authorization URL: </strong><code><a target=\\"_blank\\" rel=\\"noopener noreferrer\\" href=\\"http://petstore.swagger.io/api/oauth/dialog\\">http://petstore.swagger.io/api/oauth/dialog</a></code></div><div><b>Required scopes: </b><code>write:pets</code> <code>read:pets</code> </div><div class=\\"sc-fXgAZx gZCyoW\\"><b> Scopes: </b></div><div class=\\"sc-jXcxbT blWOKY container\\" style=\\"height: 4em;\\"><ul><li><code>write:pets</code> - <span class=\\"sc-carFqZ bmTzxo redoc-markdown\\"><p>modify pets in your account</p>
</span></li><li><code>read:pets</code> - <span class=\\"sc-carFqZ bmTzxo redoc-markdown\\"><p>read your pets</p>
</span></li></ul></div><div class=\\"sc-eEVmNe gbLbHj\\"></div></div></div><div class=\\"sc-EZqKI aOkZE\\"><h5><svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 24 24\\" width=\\"11\\" height=\\"11\\"><path fill=\\"currentColor\\" d=\\"M18 10V6A6 6 0 0 0 6 6v4H3v14h18V10h-3zM8 6c0-2.206 1.794-4 4-4s4 1.794 4 4v4H8V6zm11 16H5V12h14v10z\\"></path></svg> API Key: GitLab_PersonalAccessToken</h5><div class=\\"sc-iJCRrE sc-ciSkZP jCdxGr QGruV\\"><p>GitLab Personal Access Token description</p>
</div><div class=\\"sc-iJCRrE sc-ciSkZP jCdxGr QGruV\\"><div class=\\"sc-fXgAZx gZCyoW\\"><b>Header parameter name: </b><code>PRIVATE-TOKEN</code></div></div></div><div class=\\"sc-EZqKI aOkZE\\"><h5><svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 24 24\\" width=\\"11\\" height=\\"11\\"><path fill=\\"currentColor\\" d=\\"M18 10V6A6 6 0 0 0 6 6v4H3v14h18V10h-3zM8 6c0-2.206 1.794-4 4-4s4 1.794 4 4v4H8V6zm11 16H5V12h14v10z\\"></path></svg> OpenID Connect: GitLab_OpenIdConnect</h5><div class=\\"sc-iJCRrE sc-ciSkZP jCdxGr QGruV\\"><p>GitLab OpenIdConnect description</p>
</div><div class=\\"sc-iJCRrE sc-ciSkZP jCdxGr QGruV\\"><div class=\\"sc-fXgAZx gZCyoW\\"><b>Connect URL: </b><code><a target=\\"_blank\\" rel=\\"noopener noreferrer\\" href=\\"https://gitlab.com/.well-known/openid-configuration\\">https://gitlab.com/.well-known/openid-configuration</a></code></div></div></div><div class=\\"sc-EZqKI aOkZE\\"><h5><svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 24 24\\" width=\\"11\\" height=\\"11\\"><path fill=\\"currentColor\\" d=\\"M18 10V6A6 6 0 0 0 6 6v4H3v14h18V10h-3zM8 6c0-2.206 1.794-4 4-4s4 1.794 4 4v4H8V6zm11 16H5V12h14v10z\\"></path></svg> HTTP: basicAuth</h5><div class=\\"sc-iJCRrE sc-ciSkZP jCdxGr QGruV\\"></div><div class=\\"sc-iJCRrE sc-ciSkZP jCdxGr QGruV\\"><div class=\\"sc-fXgAZx gZCyoW\\"><b>HTTP Authorization Scheme: </b><code>basic</code></div><div class=\\"sc-fXgAZx gZCyoW\\"></div></div></div>,"
`;

View File

@ -0,0 +1,67 @@
{
"openapi": "3.0",
"info": {
"title": "test",
"version": "0"
},
"paths": {
"/pet": {
"put": {
"summary": "Add a new pet to the store",
"description": "Add new pet to the store inventory.",
"operationId": "updatePet",
"responses": {
"405": {
"description": "Invalid input"
}
},
"security": [
{
"GitLab_PersonalAccessToken": [],
"GitLab_OpenIdConnect": [],
"basicAuth": []
},
{
"petstore_auth": ["write:pets", "read:pets"]
}
]
}
}
},
"components": {
"securitySchemes": {
"petstore_auth": {
"description": "Get access to data while protecting your account credentials.\nOAuth2 is also a safer and more secure way to give you access.\n",
"type": "oauth2",
"bearerFormat": "",
"flows": {
"implicit": {
"authorizationUrl": "http://petstore.swagger.io/api/oauth/dialog",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets"
}
}
}
},
"GitLab_PersonalAccessToken": {
"description": "GitLab Personal Access Token description",
"type": "apiKey",
"name": "PRIVATE-TOKEN",
"in": "header",
"bearerFormat": "",
"flows": {}
},
"GitLab_OpenIdConnect": {
"description": "GitLab OpenIdConnect description",
"bearerFormat": "",
"type": "openIdConnect",
"openIdConnectUrl": "https://gitlab.com/.well-known/openid-configuration"
},
"basicAuth": {
"type": "http",
"scheme": "basic"
}
}
}
}

View File

@ -12,11 +12,7 @@ import { SearchStore } from './SearchStore';
import { SchemaDefinition } from '../components/SchemaDefinition/SchemaDefinition';
import { SecurityDefs } from '../components/SecuritySchemes/SecuritySchemes';
import {
SCHEMA_DEFINITION_JSX_NAME,
SECURITY_DEFINITIONS_COMPONENT_NAME,
SECURITY_DEFINITIONS_JSX_NAME,
} from '../utils/openapi';
import { SCHEMA_DEFINITION_JSX_NAME, SECURITY_DEFINITIONS_JSX_NAME } from '../utils/openapi';
import { IS_BROWSER } from '../utils';
@ -158,12 +154,6 @@ export class AppStore {
const DEFAULT_OPTIONS: RedocRawOptions = {
allowedMdComponents: {
[SECURITY_DEFINITIONS_COMPONENT_NAME]: {
component: SecurityDefs,
propsSelector: (store: AppStore) => ({
securitySchemes: store.spec.securitySchemes,
}),
},
[SECURITY_DEFINITIONS_JSX_NAME]: {
component: SecurityDefs,
propsSelector: (store: AppStore) => ({

View File

@ -7,13 +7,7 @@ import {
OpenAPIServer,
OpenAPIPaths,
} from '../types';
import {
isOperationName,
SECURITY_DEFINITIONS_COMPONENT_NAME,
setSecuritySchemePrefix,
JsonPointer,
alphabeticallyByProp,
} from '../utils';
import { isOperationName, JsonPointer, alphabeticallyByProp } from '../utils';
import { MarkdownRenderer } from './MarkdownRenderer';
import { GroupModel, OperationModel } from './models';
import { OpenAPIParser } from './OpenAPIParser';
@ -110,14 +104,7 @@ export class MenuBuilder {
if (heading.items) {
group.items = mapHeadingsDeep(group, heading.items, depth + 1);
}
if (
MarkdownRenderer.containsComponent(
group.description || '',
SECURITY_DEFINITIONS_COMPONENT_NAME,
)
) {
setSecuritySchemePrefix(group.id + '/');
}
return group;
});

View File

@ -147,6 +147,7 @@ export class MenuStore {
let item: IMenuItem | undefined;
item = this.flatItems.find(i => i.id === id);
if (item) {
this.activateAndScroll(item, false);
} else {

View File

@ -1,14 +1,8 @@
import { OpenAPIRef, OpenAPISchema, OpenAPISpec, Referenced } from '../types';
import { appendToMdHeading, isArray, IS_BROWSER } from '../utils/';
import { isArray, isBoolean, IS_BROWSER } from '../utils/';
import { JsonPointer } from '../utils/JsonPointer';
import {
getDefinitionName,
isNamedDefinition,
SECURITY_DEFINITIONS_COMPONENT_NAME,
SECURITY_DEFINITIONS_JSX_NAME,
} from '../utils/openapi';
import { buildComponentComment, MarkdownRenderer } from './MarkdownRenderer';
import { getDefinitionName, isNamedDefinition } from '../utils/openapi';
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
export type MergedOpenAPISchema = OpenAPISchema & { parentRefs?: string[] };
@ -53,7 +47,6 @@ export class OpenAPIParser {
private options: RedocNormalizedOptions = new RedocNormalizedOptions({}),
) {
this.validate(spec);
this.preprocess(spec);
this.spec = spec;
this.allowMergeRefs = spec.openapi.startsWith('3.1');
@ -70,25 +63,6 @@ export class OpenAPIParser {
}
}
preprocess(spec: OpenAPISpec) {
if (
!this.options.noAutoAuth &&
spec.info &&
spec.components &&
spec.components.securitySchemes
) {
// Automatically inject Authentication section with SecurityDefinitions component
const description = spec.info.description || '';
if (
!MarkdownRenderer.containsComponent(description, SECURITY_DEFINITIONS_COMPONENT_NAME) &&
!MarkdownRenderer.containsComponent(description, SECURITY_DEFINITIONS_JSX_NAME)
) {
const comment = buildComponentComment(SECURITY_DEFINITIONS_COMPONENT_NAME);
spec.info.description = appendToMdHeading(description, 'Authentication', comment);
}
}
}
/**
* get spec part by JsonPointer ($ref)
*/
@ -268,29 +242,47 @@ export class OpenAPIParser {
}>;
for (const { $ref: subSchemaRef, schema: subSchema } of allOfSchemas) {
if (
receiver.type !== subSchema.type &&
receiver.type !== undefined &&
subSchema.type !== undefined
) {
console.warn(
`Incompatible types in allOf at "${$ref}": "${receiver.type}" and "${subSchema.type}"`,
);
const {
type,
enum: enumProperty,
properties,
items,
required,
oneOf,
anyOf,
title,
...otherConstraints
} = subSchema;
if (receiver.type !== type && receiver.type !== undefined && type !== undefined) {
console.warn(`Incompatible types in allOf at "${$ref}": "${receiver.type}" and "${type}"`);
}
if (subSchema.type !== undefined) {
receiver.type = subSchema.type;
if (type !== undefined) {
if (Array.isArray(type) && Array.isArray(receiver.type)) {
receiver.type = [...type, ...receiver.type];
} else {
receiver.type = type;
}
}
if (subSchema.properties !== undefined) {
if (enumProperty !== undefined) {
if (Array.isArray(enumProperty) && Array.isArray(receiver.enum)) {
receiver.enum = [...enumProperty, ...receiver.enum];
} else {
receiver.enum = enumProperty;
}
}
if (properties !== undefined) {
receiver.properties = receiver.properties || {};
for (const prop in subSchema.properties) {
for (const prop in properties) {
if (!receiver.properties[prop]) {
receiver.properties[prop] = subSchema.properties[prop];
receiver.properties[prop] = properties[prop];
} else {
// merge inner properties
const mergedProp = this.mergeAllOf(
{ allOf: [receiver.properties[prop], subSchema.properties[prop]] },
{ allOf: [receiver.properties[prop], properties[prop]] },
$ref + '/properties/' + prop,
);
receiver.properties[prop] = mergedProp;
@ -299,22 +291,37 @@ export class OpenAPIParser {
}
}
if (subSchema.items !== undefined) {
receiver.items = receiver.items || {};
if (items !== undefined) {
const receiverItems = isBoolean(receiver.items)
? { items: receiver.items }
: receiver.items
? (Object.assign({}, receiver.items) as OpenAPISchema)
: {};
const subSchemaItems = isBoolean(items)
? { items }
: (Object.assign({}, items) as OpenAPISchema);
// merge inner properties
receiver.items = this.mergeAllOf(
{ allOf: [receiver.items, subSchema.items] },
{ allOf: [receiverItems, subSchemaItems] },
$ref + '/items',
);
}
if (subSchema.required !== undefined) {
receiver.required = (receiver.required || []).concat(subSchema.required);
if (required !== undefined) {
receiver.required = (receiver.required || []).concat(required);
}
if (oneOf !== undefined) {
receiver.oneOf = oneOf;
}
if (anyOf !== undefined) {
receiver.anyOf = anyOf;
}
// merge rest of constraints
// TODO: do more intelligent merge
receiver = { ...subSchema, ...receiver };
receiver = { ...receiver, title: receiver.title || title, ...otherConstraints };
if (subSchemaRef) {
receiver.parentRefs!.push(subSchemaRef);

View File

@ -21,7 +21,6 @@ export interface RedocRawOptions {
sortEnumValuesAlphabetically?: boolean | string;
sortOperationsAlphabetically?: boolean | string;
sortTagsAlphabetically?: boolean | string;
noAutoAuth?: boolean | string;
nativeScrollbars?: boolean | string;
pathInMiddlePanel?: boolean | string;
untrustedSpec?: boolean | string;
@ -42,6 +41,8 @@ export interface RedocRawOptions {
expandSingleSchemaField?: boolean | string;
schemaExpansionLevel?: number | string | 'all';
showObjectSchemaExamples?: boolean | string;
showSecuritySchemeType?: boolean;
hideSecuritySection?: boolean;
sectionsAtTheEnd?: string | string[];
unstable_ignoreMimeParameters?: boolean;
@ -233,7 +234,6 @@ export class RedocNormalizedOptions {
sortEnumValuesAlphabetically: boolean;
sortOperationsAlphabetically: boolean;
sortTagsAlphabetically: boolean;
noAutoAuth: boolean;
nativeScrollbars: boolean;
pathInMiddlePanel: boolean;
untrustedSpec: boolean;
@ -254,6 +254,8 @@ export class RedocNormalizedOptions {
expandSingleSchemaField: boolean;
schemaExpansionLevel: number;
showObjectSchemaExamples: boolean;
showSecuritySchemeType?: boolean;
hideSecuritySection?: boolean;
sectionsAtTheEnd: string[];
/* tslint:disable-next-line */
@ -304,7 +306,6 @@ export class RedocNormalizedOptions {
this.sortEnumValuesAlphabetically = argValueToBoolean(raw.sortEnumValuesAlphabetically);
this.sortOperationsAlphabetically = argValueToBoolean(raw.sortOperationsAlphabetically);
this.sortTagsAlphabetically = argValueToBoolean(raw.sortTagsAlphabetically);
this.noAutoAuth = argValueToBoolean(raw.noAutoAuth);
this.nativeScrollbars = argValueToBoolean(raw.nativeScrollbars);
this.pathInMiddlePanel = argValueToBoolean(raw.pathInMiddlePanel);
this.untrustedSpec = argValueToBoolean(raw.untrustedSpec);
@ -327,6 +328,8 @@ export class RedocNormalizedOptions {
this.expandSingleSchemaField = argValueToBoolean(raw.expandSingleSchemaField);
this.schemaExpansionLevel = argValueToExpandLevel(raw.schemaExpansionLevel);
this.showObjectSchemaExamples = argValueToBoolean(raw.showObjectSchemaExamples);
this.showSecuritySchemeType = argValueToBoolean(raw.showSecuritySchemeType);
this.hideSecuritySection = argValueToBoolean(raw.hideSecuritySection);
this.sectionsAtTheEnd = RedocNormalizedOptions.normalizeSectionsAtTheEnd(raw.sectionsAtTheEnd);
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);

View File

@ -9,14 +9,50 @@ describe('Models', () => {
let parser;
test('should hoist oneOfs when mergin allOf', () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const spec = require('./fixtures/oneOfHoist.json');
parser = new OpenAPIParser(spec, undefined, opts);
expect(parser.mergeAllOf(spec.components.schemas.test)).toMatchSnapshot();
});
test('should get schema name from named schema', () => {
const spec = require('./fixtures/mergeAllOf.json');
parser = new OpenAPIParser(spec, undefined, opts);
const schema = parser.mergeAllOf(spec.components.schemas.Case1, '#/components/schemas/Case1');
expect(schema.title).toEqual('Case1');
});
test('should get schema name from first allOf', () => {
const spec = require('./fixtures/mergeAllOf.json');
parser = new OpenAPIParser(spec, undefined, opts);
const schema = parser.mergeAllOf(
spec.components.schemas.Case2.properties.a,
'#components/schemas/Case2/properties/a',
);
expect(schema.title).toEqual('Bar');
});
test('should get schema name from named schema', () => {
const spec = require('./fixtures/mergeAllOf.json');
parser = new OpenAPIParser(spec, undefined, opts);
const schema = parser.mergeAllOf(
spec.components.schemas.Case3.schemas.Foo,
'#components/schemas/Case3/schemas/Foo',
);
expect(schema.title).toEqual('Foo');
});
test('should merge oneOff to inside allOff', () => {
// TODO: should hoist
const spec = require('./fixtures/mergeAllOf.json');
parser = new OpenAPIParser(spec, undefined, opts);
const schema = parser.mergeAllOf(spec.components.schemas.Case4);
expect(schema.title).toEqual('Foo');
expect(schema.parentRefs).toHaveLength(1);
expect(schema.parentRefs[0]).toEqual('#/components/schemas/Ref');
expect(schema.oneOf).toEqual([{ title: 'Bar' }, { title: 'Baz' }]);
});
test('should override description from $ref of the referenced component, when sibling description exists ', () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const spec = require('./fixtures/siblingRefDescription.json');
parser = new OpenAPIParser(spec, undefined, opts);
const schemaOrRef: Referenced<OpenAPIParameter> = {
@ -28,7 +64,6 @@ describe('Models', () => {
});
test('should correct resolve double $ref if no need sibling', () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const spec = require('./fixtures/3.1/schemaDefinition.json');
parser = new OpenAPIParser(spec, undefined, opts);
const schemaOrRef: Referenced<OpenAPIParameter> = {

View File

@ -0,0 +1,40 @@
{
"openapi": "3.1.0",
"info": {
"title": "Schema definition field with conditional operators",
"version": "1.0.0"
},
"components": {
"schemas": {
"Test": {
"type": "object",
"properties": {
"test": {
"type": ["string", "integer", "null"],
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"format": "url"
},
"if": {
"x-displayName": "isString",
"type": "string"
},
"then": {
"type": "string",
"minItems": 1,
"maxItems": 20
},
"else": {
"x-displayName": "notString",
"minItems": 1,
"maxItems": 10,
"pattern": "\\d+"
}
}
}
}
}
}
}

View File

@ -0,0 +1,40 @@
{
"openapi": "3.1.0",
"info": {
"title": "Schema definition with conditional operators",
"version": "1.0.0"
},
"components": {
"schemas": {
"Test": {
"type": "object",
"properties": {
"test": {
"description": "The list of URL to a cute photos featuring pet",
"type": ["string", "integer", "null"],
"minItems": 1,
"maxItems": 20,
"items": {
"type": "string",
"format": "url"
}
}
},
"if": {
"title": "=== 10",
"properties": {
"test": {
"enum": [10]
}
}
},
"then": {
"maxItems": 2
},
"else": {
"maxItems": 20
}
}
}
}
}

View File

@ -0,0 +1,32 @@
{
"openapi": "3.1.0",
"info": {
"title": "Schema definition with unevaluatedProperties",
"version": "1.0.0"
},
"servers": [
{
"url": "example.com"
}
],
"components": {
"schemas": {
"Patterns": {
"type": "object",
"patternProperties": {
"^S_\\w+\\.[1-9]{2,4}$": {
"type": "string"
},
"^O_\\w+\\.[1-9]{2,4}$": {
"type": "object",
"properties": {
"x-nestedProperty": {
"type": "string"
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,154 @@
{
"openapi": "3.1.0",
"info": {
"title": "Schema definition with prefixItems",
"version": "1.0.0"
},
"servers": [
{
"url": "example.com"
}
],
"components": {
"schemas": {
"Case1": {
"type": "array",
"minItems": 1,
"maxItems": 10,
"prefixItems": [
{
"type": "string",
"minLength": 0,
"maxLength": 10
},
{
"type": "number",
"minimum": 0,
"maximum": 10
},
{
"$ref": "#/components/schemas/Cat"
}
],
"items": false
},
"Case2": {
"type": "array",
"minItems": 1,
"maxItems": 10,
"prefixItems": [
{
"type": "string",
"minLength": 0,
"maxLength": 10
},
{
"type": "number",
"minimum": 0,
"maximum": 10
},
{
"$ref": "#/components/schemas/Cat"
}
],
"items": true
},
"Case3": {
"type": "array",
"minItems": 1,
"maxItems": 10,
"prefixItems": [
{
"type": "string",
"minLength": 0,
"maxLength": 10
},
{
"type": "number",
"minimum": 0,
"maximum": 10
},
{
"$ref": "#/components/schemas/Cat"
}
],
"items": {
"$ref": "#/components/schemas/Dog"
}
},
"Case4": {
"type": "array",
"minItems": 1,
"maxItems": 10,
"prefixItems": [
{
"type": "string",
"minLength": 0,
"maxLength": 10
},
{
"type": "number",
"minimum": 0,
"maximum": 10
},
{
"$ref": "#/components/schemas/Cat"
}
],
"items": {
"type": "object",
"properties": {
"firstItem": {
"type": "string"
}
}
}
},
"Case5": {
"type": "array",
"minItems": 1,
"maxItems": 10,
"prefixItems": [
{
"type": "string",
"minLength": 0,
"maxLength": 10
},
{
"type": "number",
"minimum": 0,
"maximum": 10
},
{
"$ref": "#/components/schemas/Cat"
}
],
"items": {
"type": "array",
"items": [
{
"type": "string",
"minLength": 0
}
]
}
},
"Cat": {
"type": "object",
"properties": {
"color": {
"type": "string"
}
}
},
"Dog": {
"type": "object",
"properties": {
"size": {
"type": "string"
}
}
}
}
}
}

View File

@ -0,0 +1,154 @@
{
"openapi": "3.0.0",
"info": {
"title": "Schema definition with array items",
"version": "1.0.0"
},
"servers": [
{
"url": "example.com"
}
],
"components": {
"schemas": {
"Case1": {
"type": "array",
"minItems": 1,
"maxItems": 10,
"items": [
{
"type": "string",
"minLength": 0,
"maxLength": 10
},
{
"type": "number",
"minimum": 0,
"maximum": 10
},
{
"$ref": "#/components/schemas/Cat"
}
],
"additionalItems": false
},
"Case2": {
"type": "array",
"minItems": 1,
"maxItems": 10,
"items": [
{
"type": "string",
"minLength": 0,
"maxLength": 10
},
{
"type": "number",
"minimum": 0,
"maximum": 10
},
{
"$ref": "#/components/schemas/Cat"
}
],
"additionalItems": true
},
"Case3": {
"type": "array",
"minItems": 1,
"maxItems": 10,
"items": [
{
"type": "string",
"minLength": 0,
"maxLength": 10
},
{
"type": "number",
"minimum": 0,
"maximum": 10
},
{
"$ref": "#/components/schemas/Cat"
}
],
"additionalItems": {
"$ref": "#/components/schemas/Dog"
}
},
"Case4": {
"type": "array",
"minItems": 1,
"maxItems": 10,
"items": [
{
"type": "string",
"minLength": 0,
"maxLength": 10
},
{
"type": "number",
"minimum": 0,
"maximum": 10
},
{
"$ref": "#/components/schemas/Cat"
}
],
"additionalItems": {
"type": "object",
"properties": {
"firstItem": {
"type": "string"
}
}
}
},
"Case5": {
"type": "array",
"minItems": 1,
"maxItems": 10,
"items": [
{
"type": "string",
"minLength": 0,
"maxLength": 10
},
{
"type": "number",
"minimum": 0,
"maximum": 10
},
{
"$ref": "#/components/schemas/Cat"
}
],
"additionalItems": {
"type": "array",
"items": [
{
"type": "string",
"minLength": 0
}
]
}
},
"Cat": {
"type": "object",
"properties": {
"color": {
"type": "string"
}
}
},
"Dog": {
"type": "object",
"properties": {
"size": {
"type": "string"
}
}
}
}
}
}

View File

@ -0,0 +1,70 @@
{
"openapi": "3.0.0",
"info": {
"version": "1.0",
"title": "Foo"
},
"components": {
"schemas": {
"Case1": {
"allOf": [
{
"title": "Bar"
},
{
"title": "Baz"
}
]
},
"Case2": {
"properties": {
"a": {
"allOf": [
{
"title": "Bar"
},
{
"title": "Baz"
}
]
}
}
},
"Case3": {
"schemas": {
"Foo": {
"title": "Foo",
"allOf": [
{
"title": "Bar"
},
{
"title": "Baz"
}
]
}
}
},
"Case4": {
"allOf": [
{
"title": "Foo"
},
{
"$ref": "#/components/schemas/Ref"
}
]
},
"Ref": {
"oneOf": [
{
"title": "Bar"
},
{
"title": "Baz"
}
]
}
}
}
}

View File

@ -49,6 +49,32 @@ describe('Models', () => {
expect(schema.pointer).toBe('#/components/schemas/Child');
});
test('schemaDefinition should resolve schema with conditional operators', () => {
const spec = require('../fixtures/3.1/conditionalSchema.json');
parser = new OpenAPIParser(spec, undefined, opts);
const schema = new SchemaModel(parser, spec.components.schemas.Test, '', opts);
expect(schema.oneOf).toHaveLength(2);
expect(schema.oneOf![0].schema.title).toBe('=== 10');
expect(schema.oneOf![1].schema.title).toBe('case 2');
expect(schema.oneOf![0].schema).toMatchSnapshot();
expect(schema.oneOf![1].schema).toMatchSnapshot();
});
test('schemaDefinition should resolve field with conditional operators', () => {
const spec = require('../fixtures/3.1/conditionalField.json');
parser = new OpenAPIParser(spec, undefined, opts);
const schema = new SchemaModel(parser, spec.components.schemas.Test, '', opts);
expect(schema.fields).toHaveLength(1);
expect(schema.fields && schema.fields[0].schema.oneOf).toHaveLength(2);
expect(schema.fields && schema.fields[0].schema.oneOf![0].schema.title).toBe('isString');
expect(schema.fields && schema.fields[0].schema.oneOf![1].schema.title).toBe('notString');
expect(schema.fields && schema.fields[0].schema.oneOf![0].schema).toMatchSnapshot();
expect(schema.fields && schema.fields[0].schema.oneOf![1].schema).toMatchSnapshot();
});
test('schemaDefinition should resolve unevaluatedProperties in properties', () => {
const spec = require('../fixtures/3.1/unevaluatedProperties.json');
parser = new OpenAPIParser(spec, undefined, opts);
@ -76,5 +102,116 @@ describe('Models', () => {
expect(schema.fields![1].kind).toEqual('additionalProperties');
expect(schema.fields![1].schema.type).toEqual('boolean');
});
test('schemaDefinition should resolve patternProperties', () => {
const spec = require('../fixtures/3.1/patternProperties.json');
parser = new OpenAPIParser(spec, undefined, opts);
const schema = new SchemaModel(parser, spec.components.schemas.Patterns, '', opts);
expect(schema.fields).toHaveLength(2);
expect(schema.fields![0].kind).toEqual('patternProperties');
expect(schema.fields![0].schema.type).toEqual('string');
expect(schema.fields![1].kind).toEqual('patternProperties');
expect(schema.fields![1].schema.type).toEqual('object');
});
describe('type array', () => {
function testImmutablePart(schema: SchemaModel) {
expect(schema.minItems).toEqual(1);
expect(schema.maxItems).toEqual(10);
expect(schema.fields![0].schema.type).toEqual('string');
expect(schema.fields![1].schema.type).toEqual('number');
}
const eachArray = ['../fixtures/3.1/prefixItems.json', '../fixtures/arrayItems.json'];
test.each(eachArray)(
'schemaDefinition should resolve prefixItems without additional items',
specFixture => {
const spec = require(specFixture);
const parser = new OpenAPIParser(spec, undefined, opts);
const schema = new SchemaModel(parser, spec.components.schemas.Case1, '', opts);
testImmutablePart(schema);
expect(schema.fields).toHaveLength(3);
expect(schema.fields![2].name).toEqual('[2]');
expect(schema.fields![2].schema.pointer).toEqual('#/components/schemas/Cat');
expect(schema.fields![2].schema.type).toEqual('object');
},
);
test.each(eachArray)(
'schemaDefinition should resolve prefixItems with additional items',
specFixture => {
const spec = require(specFixture);
const parser = new OpenAPIParser(spec, undefined, opts);
const schema = new SchemaModel(parser, spec.components.schemas.Case2, '', opts);
testImmutablePart(schema);
expect(schema.fields).toHaveLength(4);
expect(schema.fields![3].name).toEqual('[3...]');
expect(schema.fields![2].schema.type).toEqual('object');
expect(schema.fields![2].schema.pointer).toEqual('#/components/schemas/Cat');
expect(schema.fields![3].schema.type).toEqual('any');
},
);
test.each(eachArray)(
'schemaDefinition should resolve prefixItems with additional items with $ref',
specFixture => {
const spec = require(specFixture);
const parser = new OpenAPIParser(spec, undefined, opts);
const schema = new SchemaModel(parser, spec.components.schemas.Case3, '', opts);
testImmutablePart(schema);
expect(schema.fields).toHaveLength(4);
expect(schema.fields![3].name).toEqual('[3...]');
expect(schema.fields![2].schema.type).toEqual('object');
expect(schema.fields![2].schema.pointer).toEqual('#/components/schemas/Cat');
expect(schema.fields![3].schema.type).toEqual('object');
expect(schema.fields![3].schema.pointer).toEqual('#/components/schemas/Dog');
},
);
test.each(eachArray)(
'schemaDefinition should resolve prefixItems with additional schema items',
specFixture => {
const spec = require(specFixture);
const parser = new OpenAPIParser(spec, undefined, opts);
const schema = new SchemaModel(parser, spec.components.schemas.Case4, '', opts);
testImmutablePart(schema);
expect(schema.fields).toHaveLength(4);
expect(schema.fields![3].name).toEqual('[3...]');
expect(schema.fields![2].schema.type).toEqual('object');
expect(schema.fields![2].schema.pointer).toEqual('#/components/schemas/Cat');
expect(schema.fields![3].schema.type).toEqual('object');
},
);
test.each(eachArray)(
'schemaDefinition should resolve prefixItems with additional array items',
specFixture => {
const spec = require(specFixture);
const parser = new OpenAPIParser(spec, undefined, opts);
const schema = new SchemaModel(parser, spec.components.schemas.Case5, '', opts);
testImmutablePart(schema);
expect(schema.fields).toHaveLength(4);
expect(schema.fields![3].name).toEqual('[3...]');
expect(schema.fields![2].schema.type).toEqual('object');
expect(schema.fields![2].schema.pointer).toEqual('#/components/schemas/Cat');
expect(schema.fields![3].schema.type).toEqual('array');
expect(schema.fields![3].schema.fields).toHaveLength(1);
expect(schema.fields![3].schema.fields![0].schema.type).toEqual('string');
expect(schema.fields![3].schema.fields![0].schema.constraints).toEqual([
'>= 0 characters',
]);
},
);
});
});
});

View File

@ -0,0 +1,107 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Models Schema schemaDefinition should resolve field with conditional operators 1`] = `
Object {
"allOf": undefined,
"default": undefined,
"items": Object {
"allOf": undefined,
"format": "url",
"parentRefs": Array [],
"title": undefined,
"type": "string",
},
"maxItems": 20,
"minItems": 1,
"parentRefs": Array [],
"title": "isString",
"type": "string",
"x-displayName": "isString",
}
`;
exports[`Models Schema schemaDefinition should resolve field with conditional operators 2`] = `
Object {
"allOf": undefined,
"default": undefined,
"items": Object {
"allOf": undefined,
"format": "url",
"parentRefs": Array [],
"title": undefined,
"type": "string",
},
"maxItems": 10,
"minItems": 1,
"parentRefs": Array [],
"pattern": "\\\\d+",
"title": "notString",
"type": Array [
"string",
"integer",
"null",
],
"x-displayName": "notString",
}
`;
exports[`Models Schema schemaDefinition should resolve schema with conditional operators 1`] = `
Object {
"allOf": undefined,
"maxItems": 2,
"parentRefs": Array [],
"properties": Object {
"test": Object {
"allOf": undefined,
"description": "The list of URL to a cute photos featuring pet",
"enum": Array [
10,
],
"items": Object {
"allOf": undefined,
"format": "url",
"parentRefs": Array [],
"title": undefined,
"type": "string",
},
"maxItems": 20,
"minItems": 1,
"parentRefs": Array [],
"title": undefined,
"type": Array [
"string",
"integer",
"null",
],
},
},
"title": "=== 10",
"type": "object",
}
`;
exports[`Models Schema schemaDefinition should resolve schema with conditional operators 2`] = `
Object {
"allOf": undefined,
"maxItems": 20,
"parentRefs": Array [],
"properties": Object {
"test": Object {
"description": "The list of URL to a cute photos featuring pet",
"items": Object {
"format": "url",
"type": "string",
},
"maxItems": 20,
"minItems": 1,
"type": Array [
"string",
"integer",
"null",
],
},
},
"title": "case 2",
"type": "object",
}
`;

View File

@ -12,7 +12,9 @@ import {
extractExtensions,
humanizeConstraints,
isArray,
isBoolean,
isNamedDefinition,
isObject,
isPrimitiveType,
JsonPointer,
pluralizeType,
@ -152,6 +154,11 @@ export class SchemaModel {
return;
}
if ((schema.if && schema.then) || (schema.if && schema.else)) {
this.initConditionalOperators(schema, parser);
return;
}
if (!isChild && getDiscriminator(schema) !== undefined) {
this.initDiscriminator(schema, parser);
return;
@ -183,17 +190,31 @@ export class SchemaModel {
if (this.hasType('object')) {
this.fields = buildFields(parser, schema, this.pointer, this.options);
} else if (this.hasType('array') && schema.items) {
this.items = new SchemaModel(parser, schema.items, this.pointer + '/items', this.options);
this.displayType = pluralizeType(this.items.displayType);
this.displayFormat = this.items.format;
this.typePrefix = this.items.typePrefix + l('arrayOf');
this.title = this.title || this.items.title;
this.isPrimitive = this.items.isPrimitive;
if (this.example === undefined && this.items.example !== undefined) {
} else if (this.hasType('array')) {
if (isArray(schema.items) || isArray(schema.prefixItems)) {
this.fields = buildFields(parser, schema, this.pointer, this.options);
} else if (isObject(schema.items)) {
this.items = new SchemaModel(
parser,
schema.items as OpenAPISchema,
this.pointer + '/items',
this.options,
);
}
this.displayType =
schema.prefixItems || isArray(schema.items)
? 'items'
: pluralizeType(this.items?.displayType || this.displayType);
this.displayFormat = this.items?.format || '';
this.typePrefix = this.items?.typePrefix || '' + l('arrayOf');
this.title = this.title || this.items?.title || '';
this.isPrimitive = this.items?.isPrimitive || this.isPrimitive;
if (this.example === undefined && this.items?.example !== undefined) {
this.example = [this.items.example];
}
if (this.items.isPrimitive) {
if (this.items?.isPrimitive) {
this.enum = this.items.enum;
}
if (isArray(this.type)) {
@ -355,6 +376,38 @@ export class SchemaModel {
return innerSchema;
});
}
private initConditionalOperators(schema: OpenAPISchema, parser: OpenAPIParser) {
const {
if: ifOperator,
else: elseOperator = {},
then: thenOperator = {},
...restSchema
} = schema;
const groupedOperators = [
{
allOf: [restSchema, thenOperator, ifOperator],
title: (ifOperator && ifOperator['x-displayName']) || ifOperator?.title || 'case 1',
},
{
allOf: [restSchema, elseOperator],
title: (elseOperator && elseOperator['x-displayName']) || elseOperator?.title || 'case 2',
},
];
this.oneOf = groupedOperators.map(
(variant, idx) =>
new SchemaModel(
parser,
{
...variant,
} as OpenAPISchema,
this.pointer + '/oneOf/' + idx,
this.options,
),
);
this.oneOfType = 'One of';
}
}
function buildFields(
@ -363,8 +416,10 @@ function buildFields(
$ref: string,
options: RedocNormalizedOptions,
): FieldModel[] {
const props = schema.properties || {};
const props = schema.properties || schema.prefixItems || schema.items || {};
const patternProps = schema.patternProperties || {};
const additionalProps = schema.additionalProperties || schema.unevaluatedProperties;
const itemsProps = schema.prefixItems ? schema.items : schema.additionalItems;
const defaults = schema.default;
let fields = Object.keys(props || []).map(fieldName => {
let field = props[fieldName];
@ -382,7 +437,7 @@ function buildFields(
return new FieldModel(
parser,
{
name: fieldName,
name: schema.properties ? fieldName : `[${fieldName}]`,
required,
schema: {
...field,
@ -402,6 +457,31 @@ function buildFields(
fields = sortByRequired(fields, !options.sortPropsAlphabetically ? schema.required : undefined);
}
fields.push(
...Object.keys(patternProps).map(fieldName => {
let field = patternProps[fieldName];
if (!field) {
console.warn(
`Field "${fieldName}" is invalid, skipping.\n Field must be an object but got ${typeof field} at "${$ref}"`,
);
field = {};
}
return new FieldModel(
parser,
{
name: fieldName,
required: false,
schema: field,
kind: 'patternProperties',
},
`${$ref}/patternProperties/${fieldName}`,
options,
);
}),
);
if (typeof additionalProps === 'object' || additionalProps === true) {
fields.push(
new FieldModel(
@ -421,9 +501,82 @@ function buildFields(
);
}
fields.push(
...buildAdditionalItems({
parser,
schema: itemsProps,
fieldsCount: fields.length,
$ref,
options,
}),
);
return fields;
}
function buildAdditionalItems({
parser,
schema = false,
fieldsCount,
$ref,
options,
}: {
parser: OpenAPIParser;
schema?: OpenAPISchema | OpenAPISchema[] | boolean;
fieldsCount: number;
$ref: string;
options: RedocNormalizedOptions;
}) {
if (isBoolean(schema)) {
return schema
? [
new FieldModel(
parser,
{
name: `[${fieldsCount}...]`,
schema: {},
},
`${$ref}/additionalItems`,
options,
),
]
: [];
}
if (isArray(schema)) {
return [
...schema.map(
(field, idx) =>
new FieldModel(
parser,
{
name: `[${fieldsCount + idx}]`,
schema: field,
},
`${$ref}/additionalItems`,
options,
),
),
];
}
if (isObject(schema)) {
return [
new FieldModel(
parser,
{
name: `[${fieldsCount}...]`,
schema: schema,
},
`${$ref}/additionalItems`,
options,
),
];
}
return [];
}
function getDiscriminator(schema: OpenAPISchema): OpenAPISchema['discriminator'] {
return schema.discriminator || schema['x-discriminator'];
}

View File

@ -1,5 +1,4 @@
import { OpenAPISecurityRequirement, OpenAPISecurityScheme } from '../../types';
import { SECURITY_SCHEMES_SECTION_PREFIX } from '../../utils/openapi';
import { OpenAPIParser } from '../OpenAPIParser';
export interface SecurityScheme extends OpenAPISecurityScheme {
@ -29,7 +28,7 @@ export class SecurityRequirementModel {
return {
...scheme,
id,
sectionId: SECURITY_SCHEMES_SECTION_PREFIX + id,
sectionId: id,
displayName,
scopes,
};

View File

@ -1,5 +1,5 @@
import { OpenAPISecurityScheme, Referenced } from '../../types';
import { SECURITY_SCHEMES_SECTION_PREFIX } from '../../utils/openapi';
import { SECURITY_SCHEMES_SECTION_PREFIX } from '../../utils';
import { OpenAPIParser } from '../OpenAPIParser';
export class SecuritySchemeModel {

View File

@ -128,6 +128,8 @@ const defaultTheme: ThemeInterface = {
color: ({ colors }) => colors.primary.main,
visited: ({ typography }) => typography.links.color,
hover: ({ typography }) => lighten(0.2, typography.links.color),
textDecoration: 'auto',
hoverTextDecoration: 'auto',
},
},
sidebar: {
@ -315,6 +317,8 @@ export interface ResolvedThemeInterface {
color: string;
visited: string;
hover: string;
textDecoration: string;
hoverTextDecoration: string;
};
};
sidebar: {

View File

@ -113,11 +113,12 @@ export interface OpenAPISchema {
$ref?: string;
type?: string | string[];
properties?: { [name: string]: OpenAPISchema };
patternProperties?: { [name: string]: OpenAPISchema };
additionalProperties?: boolean | OpenAPISchema;
unevaluatedProperties?: boolean | OpenAPISchema;
description?: string;
default?: any;
items?: OpenAPISchema;
items?: OpenAPISchema | OpenAPISchema[] | boolean;
required?: string[];
readOnly?: boolean;
writeOnly?: boolean;
@ -147,10 +148,16 @@ export interface OpenAPISchema {
minProperties?: number;
enum?: any[];
example?: any;
if?: OpenAPISchema;
else?: OpenAPISchema;
then?: OpenAPISchema;
examples?: any[];
const?: string;
contentEncoding?: string;
contentMediaType?: string;
prefixItems?: OpenAPISchema[];
additionalItems?: OpenAPISchema | boolean;
}
export interface OpenAPIDiscriminator {

View File

@ -352,6 +352,37 @@ Object {
},
"User": Object {
"properties": Object {
"addresses": Object {
"additionalItems": Object {
"type": "string",
},
"items": Array [
Object {
"properties": Object {
"city": Object {
"minLength": 0,
"type": "string",
},
"country": Object {
"minLength": 0,
"type": "string",
},
"street": Object {
"description": "includes build/apartment number",
"minLength": 0,
"type": "string",
},
},
"type": "object",
},
Object {
"type": "number",
},
],
"maxLength": 10,
"minItems": 0,
"type": "array",
},
"email": Object {
"description": "User email address",
"example": "john.smith@example.com",
@ -966,6 +997,7 @@ try {
"format": "int32",
"type": "integer",
},
"minProperties": 2,
"type": "object",
},
},
@ -1903,6 +1935,46 @@ Object {
},
"schemas": Object {
"ApiResponse": Object {
"patternProperties": Object {
"^O_\\\\\\\\w+\\\\\\\\.[1-9]{2,4}$": Object {
"properties": Object {
"nestedProperty": Object {
"default": "lazy",
"description": "The measured skill for hunting",
"enum": Array [
"clueless",
"lazy",
"adventurous",
"aggressive",
],
"example": "adventurous",
"type": Array [
"string",
"boolean",
],
},
},
"type": "object",
},
"^S_\\\\\\\\w+\\\\\\\\.[1-9]{2,4}$": Object {
"description": "The measured skill for hunting",
"else": Object {
"maxLength": 10,
"minLength": 1,
},
"if": Object {
"x-displayName": "fieldName === 'status'",
},
"then": Object {
"enum": Array [
"success",
"failed",
],
"format": "url",
"type": "string",
},
},
},
"properties": Object {
"code": Object {
"format": "int32",
@ -1934,7 +2006,10 @@ Object {
"aggressive",
],
"example": "adventurous",
"type": "string",
"type": Array [
"string",
"boolean",
],
},
},
"required": Array [
@ -2086,6 +2161,16 @@ Object {
"friend": Object {
"$ref": "#/components/schemas/Pet",
},
"huntingSkill": Object {
"enum": Array [
0,
1,
2,
],
"type": Array [
"integer",
],
},
"id": Object {
"$ref": "#/components/schemas/Id",
"description": "Pet ID",
@ -2105,17 +2190,33 @@ Object {
},
"photoUrls": Object {
"description": "The list of URL to a cute photos featuring pet",
"else": Object {
"maxItems": 20,
"minItems": 1,
"type": Array [
"integer",
"null",
],
"x-displayName": "notString",
},
"if": Object {
"type": "string",
"x-displayName": "isString",
},
"items": Object {
"format": "url",
"type": "string",
},
"maxItems": 20,
"maxItems": 10,
"minItems": 1,
"then": Object {
"maxItems": 15,
"minItems": 1,
},
"type": Array [
"string",
"integer",
"null",
"array",
],
"xml": Object {
"name": "photoUrl",
@ -2173,7 +2274,51 @@ Object {
},
},
"User": Object {
"else": Object {
"required": Array [],
},
"if": Object {
"properties": Object {
"userStatus": Object {
"enum": Array [
10,
],
},
},
"title": "userStatus === 10",
},
"properties": Object {
"addresses": Object {
"items": Object {
"type": "string",
},
"maxLength": 10,
"minItems": 0,
"prefixItems": Array [
Object {
"properties": Object {
"city": Object {
"minLength": 0,
"type": "string",
},
"country": Object {
"minLength": 0,
"type": "string",
},
"street": Object {
"description": "includes build/apartment number",
"minLength": 0,
"type": "string",
},
},
"type": "object",
},
Object {
"type": "number",
},
],
"type": "array",
},
"email": Object {
"description": "User email address",
"example": "john.smith@example.com",
@ -2238,6 +2383,11 @@ Object {
"type": "string",
},
},
"then": Object {
"required": Array [
"phone",
],
},
"type": "object",
"xml": Object {
"name": "User",

View File

@ -277,7 +277,7 @@ describe('Utils', () => {
expect(isPrimitiveType(schema)).toEqual(true);
});
it('Should return false for array of string which include the null value', () => {
it('Should return true for array of string which include the null value', () => {
const schema = {
type: ['object', 'string', 'null'],
};
@ -553,7 +553,7 @@ describe('Utils', () => {
});
it('should have a humanized constraint when minItems and maxItems are the same', () => {
expect(humanizeConstraints(itemConstraintSchema(7, 7))).toContain('7 items');
expect(humanizeConstraints(itemConstraintSchema(7, 7))).toContain('= 7 items');
});
it('should have a humanized constraint when justMinItems is set, and it is equal to 1', () => {

View File

@ -107,7 +107,7 @@ export const mergeObjects = (target: any, ...sources: any[]): any => {
return mergeObjects(target, ...sources);
};
const isObject = (item: any): boolean => {
export const isObject = (item: unknown): item is Record<string, unknown> => {
return item !== null && typeof item === 'object';
};
@ -210,6 +210,10 @@ export function unescapeHTMLChars(str: string): string {
.replace(/&quot;/g, '"');
}
export function isArray(value: unknown): value is Array<any> {
export function isArray(value: unknown): value is any[] {
return Array.isArray(value);
}
export function isBoolean(value: unknown): value is boolean {
return typeof value === 'boolean';
}

View File

@ -16,7 +16,7 @@ import {
Referenced,
} from '../types';
import { IS_BROWSER } from './dom';
import { isNumeric, removeQueryString, resolveUrl, isArray } from './helpers';
import { isNumeric, removeQueryString, resolveUrl, isArray, isBoolean } from './helpers';
function isWildcardStatusCode(statusCode: string | number): statusCode is string {
return typeof statusCode === 'string' && /\dxx/i.test(statusCode);
@ -99,6 +99,7 @@ const schemaKeywordTypes = {
additionalProperties: 'object',
unevaluatedProperties: 'object',
properties: 'object',
patternProperties: 'object',
};
export function detectType(schema: OpenAPISchema): string {
@ -124,6 +125,10 @@ export function isPrimitiveType(
return false;
}
if ((schema.if && schema.then) || (schema.if && schema.else)) {
return false;
}
let isPrimitive = true;
const isArrayType = isArray(type);
@ -134,8 +139,13 @@ export function isPrimitiveType(
: schema.additionalProperties === undefined && schema.unevaluatedProperties === undefined;
}
if (isArray(schema.items) || isArray(schema.prefixItems)) {
return false;
}
if (
schema.items !== undefined &&
!isBoolean(schema.items) &&
(type === 'array' || (isArrayType && type?.includes('array')))
) {
isPrimitive = isPrimitiveType(schema.items, schema.items.type);
@ -418,7 +428,7 @@ function humanizeRangeConstraint(
let stringRange;
if (min !== undefined && max !== undefined) {
if (min === max) {
stringRange = `${min} ${description}`;
stringRange = `= ${min} ${description}`;
} else {
stringRange = `[ ${min} .. ${max} ] ${description}`;
}
@ -471,6 +481,15 @@ export function humanizeConstraints(schema: OpenAPISchema): string[] {
res.push(arrayRange);
}
const propertiesRange = humanizeRangeConstraint(
'properties',
schema.minProperties,
schema.maxProperties,
);
if (propertiesRange !== undefined) {
res.push(propertiesRange);
}
const multipleOfConstraint = humanizeMultipleOfConstraint(schema.multipleOf);
if (multipleOfConstraint !== undefined) {
res.push(multipleOfConstraint);
@ -596,7 +615,6 @@ export function normalizeServers(
});
}
export const SECURITY_DEFINITIONS_COMPONENT_NAME = 'security-definitions';
export const SECURITY_DEFINITIONS_JSX_NAME = 'SecurityDefinitions';
export const SCHEMA_DEFINITION_JSX_NAME = 'SchemaDefinition';