mirror of
https://github.com/Redocly/redoc.git
synced 2025-08-02 19:30:19 +03:00
Merge tag 'refs/tags/v2.0.0-rc.77' into sections-at-the-end
This commit is contained in:
commit
76af74e391
2
.github/workflows/e2e-tests.yml
vendored
2
.github/workflows/e2e-tests.yml
vendored
|
@ -7,6 +7,6 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- run: npm ci
|
- run: npm ci && npm ci --prefix cli
|
||||||
- run: npm run bundle
|
- run: npm run bundle
|
||||||
- run: npm run e2e
|
- run: npm run e2e
|
||||||
|
|
6
.github/workflows/main.yml
vendored
6
.github/workflows/main.yml
vendored
|
@ -30,7 +30,7 @@ jobs:
|
||||||
elif [[ $GITHUB_REF == refs/pull/* ]]; then
|
elif [[ $GITHUB_REF == refs/pull/* ]]; then
|
||||||
VERSION=pr-${{ github.event.number }}
|
VERSION=pr-${{ github.event.number }}
|
||||||
fi
|
fi
|
||||||
TAGS="${DOCKER_IMAGE}:${VERSION}"
|
TAGS="${DOCKER_IMAGE}:${VERSION},${DOCKER_IMAGE}:latest"
|
||||||
if [ "${{ github.event_name }}" = "push" ]; then
|
if [ "${{ github.event_name }}" = "push" ]; then
|
||||||
TAGS="$TAGS,${DOCKER_IMAGE}:sha-${GITHUB_SHA::8}"
|
TAGS="$TAGS,${DOCKER_IMAGE}:sha-${GITHUB_SHA::8}"
|
||||||
fi
|
fi
|
||||||
|
@ -38,7 +38,7 @@ jobs:
|
||||||
echo ::set-output name=tags::${TAGS}
|
echo ::set-output name=tags::${TAGS}
|
||||||
echo ::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ')
|
echo ::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ')
|
||||||
- name: Push to GitHub Packages
|
- name: Push to GitHub Packages
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
context: ./cli
|
context: ./cli
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
@ -69,7 +69,7 @@ jobs:
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./config/docker/Dockerfile
|
file: ./config/docker/Dockerfile
|
||||||
|
|
6
.github/workflows/publish-cli.yml
vendored
6
.github/workflows/publish-cli.yml
vendored
|
@ -21,7 +21,7 @@ jobs:
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
npm-${{ hashFiles('package-lock.json') }}
|
npm-${{ hashFiles('package-lock.json') }}
|
||||||
npm-
|
npm-
|
||||||
- run: npm ci
|
- run: npm ci && npm ci --prefix cli
|
||||||
- run: npm run bundle
|
- run: npm run bundle
|
||||||
- name: Store bundle artifact
|
- name: Store bundle artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
|
@ -42,7 +42,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- run: npm ci
|
- run: npm ci && npm ci --prefix cli
|
||||||
- name: Download bundled artifact
|
- name: Download bundled artifact
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
|
@ -65,7 +65,7 @@ jobs:
|
||||||
npm-${{ hashFiles('package-lock.json') }}
|
npm-${{ hashFiles('package-lock.json') }}
|
||||||
npm-
|
npm-
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci && npm ci --prefix cli
|
||||||
- name: Bundle
|
- name: Bundle
|
||||||
run: npm run compile:cli
|
run: npm run compile:cli
|
||||||
- name: Store bundle artifact
|
- name: Store bundle artifact
|
||||||
|
|
48
.github/workflows/publish.yml
vendored
48
.github/workflows/publish.yml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
npm-${{ hashFiles('package-lock.json') }}
|
npm-${{ hashFiles('package-lock.json') }}
|
||||||
npm-
|
npm-
|
||||||
- run: npm ci
|
- run: npm ci && npm ci --prefix cli
|
||||||
- run: npm run bundle
|
- run: npm run bundle
|
||||||
- name: Store bundle artifact
|
- name: Store bundle artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
|
@ -38,38 +38,13 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- run: npm ci
|
- run: npm ci && npm ci --prefix cli
|
||||||
- name: Download bundled artifact
|
- name: Download bundled artifact
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: bundles
|
name: bundles
|
||||||
path: bundles
|
path: bundles
|
||||||
- run: npm run e2e
|
- run: npm run e2e
|
||||||
# disable this for now
|
|
||||||
# deploy-demo:
|
|
||||||
# needs: [bundle, unit-tests, e2e-tests]
|
|
||||||
# runs-on: ubuntu-latest
|
|
||||||
# steps:
|
|
||||||
# - uses: actions/checkout@v3
|
|
||||||
# - name: Configure AWS Credentials
|
|
||||||
# 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: Install dependencies
|
|
||||||
# run: npm ci
|
|
||||||
# - name: Download bundled artifacts
|
|
||||||
# uses: actions/download-artifact@v3
|
|
||||||
# with:
|
|
||||||
# name: bundles
|
|
||||||
# path: bundles
|
|
||||||
# - name: Build package
|
|
||||||
# run: npm run build:demo
|
|
||||||
# - name: Deploy to S3 bucket
|
|
||||||
# run: npm run deploy:demo
|
|
||||||
# - name: Invalidate
|
|
||||||
# run: aws cloudfront create-invalidation --distribution-id ${{ secrets.CF_DEMO_DISTRIBUTION_ID }} --paths "/*"
|
|
||||||
publish:
|
publish:
|
||||||
name: Publish to NPM
|
name: Publish to NPM
|
||||||
needs: [bundle, unit-tests, e2e-tests]
|
needs: [bundle, unit-tests, e2e-tests]
|
||||||
|
@ -117,3 +92,22 @@ jobs:
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
- name: Publish to S3
|
- name: Publish to S3
|
||||||
run: npm run publish-cdn
|
run: npm run publish-cdn
|
||||||
|
|
||||||
|
invalidate-cache:
|
||||||
|
name: Clear cache
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [publish, publish-cdn]
|
||||||
|
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: Invalidate cache
|
||||||
|
run: ./scripts/invalidate-cache.sh
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
DISTRIBUTION: ${{ secrets.DISTRIBUTION }}
|
||||||
|
|
95
CHANGELOG.md
95
CHANGELOG.md
|
@ -1,3 +1,98 @@
|
||||||
|
# [2.0.0-rc.77](https://github.com/Redocly/redoc/compare/v2.0.0-rc.76...v2.0.0-rc.77) (2022-09-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add hard limit on deref depth to prevent crashes ([ddde105](https://github.com/Redocly/redoc/commit/ddde105acaf0a77b0bb5d13df5fd6180bc8169e9))
|
||||||
|
* do not use discriminator when specific schema was referenced in oneOf or anyOf ([#2153](https://github.com/Redocly/redoc/issues/2153)) ([6ac1e1e](https://github.com/Redocly/redoc/commit/6ac1e1eb183e97e2cd67ad14d8a39fac8289ebcc))
|
||||||
|
* hoistOneOf missing refs stack and improve allOf for same $ref ([bb325d0](https://github.com/Redocly/redoc/commit/bb325d0d285c4cf4ee7c6d70878d2dd0dc9c6ed7))
|
||||||
|
* latest docker cli tag ([#2140](https://github.com/Redocly/redoc/issues/2140)) ([8dc03eb](https://github.com/Redocly/redoc/commit/8dc03eb7ed262d6b1d460425ce43990710470845))
|
||||||
|
* markdown parent name ([#2062](https://github.com/Redocly/redoc/issues/2062)) ([da9ed0b](https://github.com/Redocly/redoc/commit/da9ed0b4d1a4070d326ecb472459f0ff916c6036))
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* feet: search feature to support path ([#2145](https://github.com/Redocly/redoc/issues/2145)) ([c52ee83f](https://github.com/Redocly/redoc/commit/c52ee83f77ccfc79137c85deafe8d93e68465d45))
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.76](https://github.com/Redocly/redoc/compare/v2.0.0-rc.75...v2.0.0-rc.76) (2022-08-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* "API Docs By Redocly" overlapping last element in sidebar ([#2132](https://github.com/Redocly/redoc/issues/2132)) ([c60c6f5](https://github.com/Redocly/redoc/commit/c60c6f58917563d57c0eef650b9dfcece2e15049))
|
||||||
|
* encoding issue in CDN responses ([#2130](https://github.com/Redocly/redoc/issues/2130)) ([7816902](https://github.com/Redocly/redoc/commit/781690284a45b2b8af9eb525757632d0d19ef453))
|
||||||
|
* Optional authentication not rendered properly ([#2117](https://github.com/Redocly/redoc/issues/2117)) ([#2134](https://github.com/Redocly/redoc/issues/2134)) ([efd5e09](https://github.com/Redocly/redoc/commit/efd5e09c907b36a3999f4c9c3165b6b2bdc1d536))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add clear cache for publish action ([#2129](https://github.com/Redocly/redoc/issues/2129)) ([d8093e3](https://github.com/Redocly/redoc/commit/d8093e3e2086874242eac82ddd202f35d5b8d558))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.75](https://github.com/Redocly/redoc/compare/v2.0.0-rc.74...v2.0.0-rc.75) (2022-08-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* duplication of title ([#2119](https://github.com/Redocly/redoc/issues/2119)) ([40ebfd2](https://github.com/Redocly/redoc/commit/40ebfd2d63758b37665e2e4447732f671811e2a5))
|
||||||
|
* handle error if security scopes is invalid ([#2113](https://github.com/Redocly/redoc/issues/2113)) ([428fd69](https://github.com/Redocly/redoc/commit/428fd6983dc257f524121d98aeb1c58b39cf81f7))
|
||||||
|
* publishing docker image to github packages ([#2115](https://github.com/Redocly/redoc/issues/2115)) ([250f6d1](https://github.com/Redocly/redoc/commit/250f6d12b2d31d2166990bd9cb83ca1c63509686))
|
||||||
|
* Redocly logo ([#2109](https://github.com/Redocly/redoc/issues/2109)) ([a35bb3f](https://github.com/Redocly/redoc/commit/a35bb3ff26bf10b0e54383222df283800d6ee2c8))
|
||||||
|
* search and navigate error ([cfd810f](https://github.com/Redocly/redoc/commit/cfd810fdf9d37862e07458fa1c3c04046e22f315))
|
||||||
|
* sibling for openapi 3.1 ([#2112](https://github.com/Redocly/redoc/issues/2112)) ([0b1a790](https://github.com/Redocly/redoc/commit/0b1a79009010f0640a3030093b7c0dcf8caa49e4))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add notification about new version available ([#2100](https://github.com/Redocly/redoc/issues/2100)) ([d6ca8cc](https://github.com/Redocly/redoc/commit/d6ca8cc53b9667f09ce8fef88dfac1039c562b78))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.74](https://github.com/Redocly/redoc/compare/v2.0.0-rc.73...v2.0.0-rc.74) (2022-07-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* invalid url when href is empty ([#2105](https://github.com/Redocly/redoc/issues/2105)) ([e5f0235](https://github.com/Redocly/redoc/commit/e5f02359851a3797283ee513d734ab8e27266b92))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.73](https://github.com/Redocly/redoc/compare/v2.0.0-rc.72...v2.0.0-rc.73) (2022-07-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add label API docs by Redocly ([#2099](https://github.com/Redocly/redoc/issues/2099)) ([dcdab83](https://github.com/Redocly/redoc/commit/dcdab838903a5d923c5e327d07d7743214769a61))
|
||||||
|
* add the latest tag for the CLI docker image ([#2087](https://github.com/Redocly/redoc/issues/2087)) ([80ecd0f](https://github.com/Redocly/redoc/commit/80ecd0f19746379b056bfb1b11950693f3dc3724))
|
||||||
|
* correct URLs of OperationModel servers for static site generation ([#2081](https://github.com/Redocly/redoc/issues/2081)) ([b1afd08](https://github.com/Redocly/redoc/commit/b1afd08bcf83770b537ed1eb9c90341de0162a1c))
|
||||||
|
* enum duplication values when schema uses a specific combination of oneOf and allOf([#2088](https://github.com/Redocly/redoc/issues/2088)) ([e411847](https://github.com/Redocly/redoc/commit/e4118479f69209c5dd09a2be0e978834dcd9eb8f))
|
||||||
|
* highlight text syntax ([#2069](https://github.com/Redocly/redoc/issues/2069)) ([4fc6aa0](https://github.com/Redocly/redoc/commit/4fc6aa0859c94e25fd30c4a4250455e44cc76488))
|
||||||
|
* merge reference for openapi 3.1 ([#2063](https://github.com/Redocly/redoc/issues/2063)) ([87541e4](https://github.com/Redocly/redoc/commit/87541e45dc2526696deb32a6350a14a44a709b54))
|
||||||
|
* nested patternProperties ([#2073](https://github.com/Redocly/redoc/issues/2073)) ([9920991](https://github.com/Redocly/redoc/commit/99209910806b85289a89fb3131049ed79118bc72))
|
||||||
|
* operation url in static page ([#2093](https://github.com/Redocly/redoc/issues/2093)) ([98eec19](https://github.com/Redocly/redoc/commit/98eec19647b63f3598ec30fdeb428f614cf93ad4))
|
||||||
|
* property with nested allOf ([#2083](https://github.com/Redocly/redoc/issues/2083)) ([7cc0500](https://github.com/Redocly/redoc/commit/7cc0500f3c1ddd1da17ee31278468207093f9281))
|
||||||
|
* recursion for boolean items ([#2097](https://github.com/Redocly/redoc/issues/2097)) ([a5804db](https://github.com/Redocly/redoc/commit/a5804db1ce60ee6d90db8a3b54138eb1ca420c6f))
|
||||||
|
* resolve dependency conflict in installing ([#2060](https://github.com/Redocly/redoc/issues/2060)) ([e26c8b2](https://github.com/Redocly/redoc/commit/e26c8b23d9b36abd5572bd0fe350d74a5cf65afb))
|
||||||
|
* restore old variant security injections ([#2075](https://github.com/Redocly/redoc/issues/2075)) ([1a1bc26](https://github.com/Redocly/redoc/commit/1a1bc26503c06b6a7022289e5b9353bd59e48a9a))
|
||||||
|
* rewrite recursive checks ([#2072](https://github.com/Redocly/redoc/issues/2072)) ([2970f95](https://github.com/Redocly/redoc/commit/2970f959cfa31cb4d5288ca23ca05cd34357dcec))
|
||||||
|
* Scrolling keeps rewriting url after a Redoc element was removed [#2051](https://github.com/Redocly/redoc/issues/2051) ([#2085](https://github.com/Redocly/redoc/issues/2085)) ([0045be0](https://github.com/Redocly/redoc/commit/0045be0b753b8fb7d8d58a4e511783a6ba858444))
|
||||||
|
* mis-nesting of aria roles on sidebar navigation ([#2050](https://github.com/Redocly/redoc/issues/2050)) ([7ca10da](https://github.com/Redocly/redoc/commit/7ca10daf12f2cac9fecf559b11f0f0c8bd21ae43))
|
||||||
|
* 404 on the documentation page ([#2092](https://github.com/Redocly/redoc/issues/2050)) ([17bb08](https://github.com/Redocly/redoc/commit/17bb08909a1734e6e59c83ce29f31ae7cf6fc784))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.72](https://github.com/Redocly/redoc/compare/v2.0.0-rc.71...v2.0.0-rc.72) (2022-06-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* handled style change in ServerUrl and ServersOverlay dynamically ([#1989](https://github.com/Redocly/redoc/issues/1989)) ([a366de4](https://github.com/Redocly/redoc/commit/a366de4cf67fb94baa33b7b5c311cc1f54a63e53))
|
||||||
|
* nested items with refs ([#2035](https://github.com/Redocly/redoc/issues/2035)) ([51127aa](https://github.com/Redocly/redoc/commit/51127aadc3e6b0f8e4066afb1c3b2ea6db453da2))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [2.0.0-rc.71](https://github.com/Redocly/redoc/compare/v2.0.0-rc.70...v2.0.0-rc.71) (2022-05-31)
|
# [2.0.0-rc.71](https://github.com/Redocly/redoc/compare/v2.0.0-rc.70...v2.0.0-rc.71) (2022-05-31)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -314,6 +314,12 @@ You can use all of the following options with the standalone version of the <red
|
||||||
* `backgroundColor`: '#263238'
|
* `backgroundColor`: '#263238'
|
||||||
* `width`: '40%'
|
* `width`: '40%'
|
||||||
* `textColor`: '#ffffff'
|
* `textColor`: '#ffffff'
|
||||||
|
* `servers`
|
||||||
|
* `overlay`
|
||||||
|
* `backgroundColor`: '#fafafa'
|
||||||
|
* `textColor`: '#263238'
|
||||||
|
* `url`
|
||||||
|
* `backgroundColor`: '#fff'
|
||||||
* `fab`
|
* `fab`
|
||||||
* `backgroundColor`: '#263238'
|
* `backgroundColor`: '#263238'
|
||||||
* `color`: '#ffffff'
|
* `color`: '#ffffff'
|
||||||
|
|
|
@ -14,7 +14,7 @@ The two following commands are available:
|
||||||
- `redoc-cli serve [spec]` - starts the server with `spec` rendered with ReDoc.
|
- `redoc-cli serve [spec]` - starts the server with `spec` rendered with ReDoc.
|
||||||
Supports a server-side rendering mode (`--ssr`)
|
Supports a server-side rendering mode (`--ssr`)
|
||||||
and can watch the spec (`--watch`) to automatically reload the page whenever it changes.\
|
and can watch the spec (`--watch`) to automatically reload the page whenever it changes.\
|
||||||
Deprecated. Use `npx @redocly/openapi-cli preview-docs [spec]`
|
Deprecated. Use `npx @redocly/cli preview-docs [spec]`
|
||||||
- `redoc-cli bundle [spec]` - bundles `spec` and Redoc into a **zero-dependency** HTML file.\
|
- `redoc-cli bundle [spec]` - bundles `spec` and Redoc into a **zero-dependency** HTML file.\
|
||||||
Deprecated. Use Use "build" command instead.
|
Deprecated. Use Use "build" command instead.
|
||||||
- `redoc-cli build [spec]` - build `spec` and Redoc into a **zero-dependency** HTML file.
|
- `redoc-cli build [spec]` - build `spec` and Redoc into a **zero-dependency** HTML file.
|
||||||
|
|
21
cli/index.ts
21
cli/index.ts
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
/* tslint:disable:no-implicit-dependencies */
|
/* tslint:disable:no-implicit-dependencies */
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import * as updateNotifier from 'update-notifier';
|
||||||
import { renderToString } from 'react-dom/server';
|
import { renderToString } from 'react-dom/server';
|
||||||
import { ServerStyleSheet } from 'styled-components';
|
import { ServerStyleSheet } from 'styled-components';
|
||||||
|
|
||||||
|
@ -111,6 +112,7 @@ const handlerForBuildCommand = async (argv: any) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
notifyUpdateCliVersion();
|
||||||
await bundle(argv.spec, config);
|
await bundle(argv.spec, config);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleError(e);
|
handleError(e);
|
||||||
|
@ -174,6 +176,7 @@ YargsParser.command(
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
notifyUpdateCliVersion();
|
||||||
await serve(argv.host as string, argv.port as number, argv.spec as string, config);
|
await serve(argv.host as string, argv.port as number, argv.spec as string, config);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleError(e);
|
handleError(e);
|
||||||
|
@ -182,7 +185,7 @@ YargsParser.command(
|
||||||
[
|
[
|
||||||
res => {
|
res => {
|
||||||
console.log(
|
console.log(
|
||||||
`\n⚠️ This command is deprecated. Use "npx @redocly/openapi-cli preview-docs petstore.yaml"\n`,
|
`\n⚠️ This command is deprecated. Use "npx @redocly/cli preview-docs petstore.yaml"\n`,
|
||||||
);
|
);
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
@ -340,6 +343,7 @@ async function getPageHTML(
|
||||||
const specUrl = redocOptions.specUrl || (isURL(pathToSpec) ? pathToSpec : undefined);
|
const specUrl = redocOptions.specUrl || (isURL(pathToSpec) ? pathToSpec : undefined);
|
||||||
const store = await createStore(spec, specUrl, redocOptions);
|
const store = await createStore(spec, specUrl, redocOptions);
|
||||||
const sheet = new ServerStyleSheet();
|
const sheet = new ServerStyleSheet();
|
||||||
|
// @ts-ignore
|
||||||
html = renderToString(sheet.collectStyles(React.createElement(Redoc, { store })));
|
html = renderToString(sheet.collectStyles(React.createElement(Redoc, { store })));
|
||||||
css = sheet.getStyleTags();
|
css = sheet.getStyleTags();
|
||||||
state = await store.toJS();
|
state = await store.toJS();
|
||||||
|
@ -367,7 +371,7 @@ async function getPageHTML(
|
||||||
</script>`,
|
</script>`,
|
||||||
redocHead: ssr
|
redocHead: ssr
|
||||||
? (cdn
|
? (cdn
|
||||||
? '<script src="https://unpkg.com/redoc@next/bundles/redoc.standalone.js"></script>'
|
? '<script src="https://unpkg.com/redoc@latest/bundles/redoc.standalone.js"></script>'
|
||||||
: `<script>${redocStandaloneSrc}</script>`) + css
|
: `<script>${redocStandaloneSrc}</script>`) + css
|
||||||
: '<script src="redoc.standalone.js"></script>',
|
: '<script src="redoc.standalone.js"></script>',
|
||||||
title: title || spec.info.title || 'ReDoc documentation',
|
title: title || spec.info.title || 'ReDoc documentation',
|
||||||
|
@ -467,3 +471,16 @@ function getObjectOrJSON(options) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function notifyUpdateCliVersion() {
|
||||||
|
const pkg = require('./package.json');
|
||||||
|
const notifier = updateNotifier({
|
||||||
|
pkg,
|
||||||
|
updateCheckInterval: 0,
|
||||||
|
shouldNotifyInNpmScript: true,
|
||||||
|
});
|
||||||
|
notifier.notify({
|
||||||
|
message:
|
||||||
|
'Run `{updateCommand}` to update.\nChangelog: https://github.com/Redocly/redoc/releases/tag/{latestVersion}',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
4144
cli/npm-shrinkwrap.json
generated
4144
cli/npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "redoc-cli",
|
"name": "redoc-cli",
|
||||||
"version": "0.13.14",
|
"version": "0.13.19",
|
||||||
"description": "ReDoc's Command Line Interface",
|
"description": "ReDoc's Command Line Interface",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"bin": "index.js",
|
"bin": "index.js",
|
||||||
|
@ -19,16 +19,15 @@
|
||||||
"node-libs-browser": "^2.2.1",
|
"node-libs-browser": "^2.2.1",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
"redoc": "2.0.0-rc.70",
|
"redoc": "2.0.0-rc.76",
|
||||||
"styled-components": "^5.3.0",
|
"styled-components": "^5.3.0",
|
||||||
|
"update-notifier": "^5.0.1",
|
||||||
"yargs": "^17.3.1"
|
"yargs": "^17.3.1"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chokidar": "^2.1.3",
|
|
||||||
"@types/handlebars": "^4.1.0",
|
|
||||||
"@types/mkdirp": "^1.0.1"
|
"@types/mkdirp": "^1.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1181,6 +1181,7 @@ components:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
|
type: number
|
||||||
description: Tag ID
|
description: Tag ID
|
||||||
$ref: '#/components/schemas/Id'
|
$ref: '#/components/schemas/Id'
|
||||||
name:
|
name:
|
||||||
|
|
|
@ -38,7 +38,7 @@ info:
|
||||||
OAuth2 - an open protocol to allow secure authorization in a simple
|
OAuth2 - an open protocol to allow secure authorization in a simple
|
||||||
and standard method from web, mobile and desktop applications.
|
and standard method from web, mobile and desktop applications.
|
||||||
|
|
||||||
<SecurityDefinitions />
|
<!-- ReDoc-Inject: <security-definitions> -->
|
||||||
|
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
title: Swagger Petstore
|
title: Swagger Petstore
|
||||||
|
|
|
@ -54,7 +54,7 @@ The CLI includes the following commands:
|
||||||
a reference to an OpenAPI definition. Options include:
|
a reference to an OpenAPI definition. Options include:
|
||||||
- `--ssr`: Implements a server-side rendering model.
|
- `--ssr`: Implements a server-side rendering model.
|
||||||
- `--watch`: Automatically reloads the server while you edit your OpenAPI definition.
|
- `--watch`: Automatically reloads the server while you edit your OpenAPI definition.
|
||||||
- `--options`: Customizes your output using [Redoc options](https://redocly.com/docs/api-reference-docs/configuration/).
|
- `--options`: Customizes your output using [Redoc functionality options](https://redocly.com/docs/api-reference-docs/configuration/functionality) or [Redoc theming options](https://redocly.com/docs/api-reference-docs/configuration/theming).
|
||||||
To add nested options, use dot notation.
|
To add nested options, use dot notation.
|
||||||
- **`redoc-cli build [spec]`:** Builds `spec` and Redoc into a zero-dependency HTML file. Options include:
|
- **`redoc-cli build [spec]`:** Builds `spec` and Redoc into a zero-dependency HTML file. Options include:
|
||||||
- `-t` or `--template`: Uses custom [Handlebars](https://handlebarsjs.com/) templates to render your OpenAPI definition.
|
- `-t` or `--template`: Uses custom [Handlebars](https://handlebarsjs.com/) templates to render your OpenAPI definition.
|
||||||
|
|
|
@ -59,7 +59,7 @@ For example:
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information on configuration options, refer to the
|
For more information on configuration options, refer to the
|
||||||
[Configuration options for Reference docs](https://redocly.com/docs/api-reference-docs/configuration/)
|
[Configuration options for Reference docs](https://redocly.com/docs/api-reference-docs/configuration/functionality/)
|
||||||
section of the documentation. Options available for Redoc are noted,
|
section of the documentation. Options available for Redoc are noted,
|
||||||
"Supported in Redoc CE".
|
"Supported in Redoc CE".
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ describe('Search', () => {
|
||||||
|
|
||||||
getSearchInput().type('{enter}', { force: true });
|
getSearchInput().type('{enter}', { force: true });
|
||||||
|
|
||||||
cy.contains('[role=navigation] [role=menuitem]', 'Introduction').should('have.class', 'active');
|
cy.contains('[role=menu] [role=menuitem]', 'Introduction').should('have.class', 'active');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should mark search results', () => {
|
it('should mark search results', () => {
|
||||||
|
@ -59,4 +59,20 @@ describe('Search', () => {
|
||||||
getSearchInput().type('xzss', { force: true });
|
getSearchInput().type('xzss', { force: true });
|
||||||
getSearchResults().should('exist').should('contain', 'No results found');
|
getSearchResults().should('exist').should('contain', 'No results found');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should allow search by path or keywords in path', () => {
|
||||||
|
getSearchInput().clear().type('uploadImage', { force: true });
|
||||||
|
cy.get('[role=search] [role=menuitem]')
|
||||||
|
.should('have.length', 1)
|
||||||
|
.first()
|
||||||
|
.should('contain', 'uploads an image');
|
||||||
|
|
||||||
|
getSearchInput()
|
||||||
|
.clear()
|
||||||
|
.type('/pet/{petId}/uploadImage', { force: true, parseSpecialCharSequences: false });
|
||||||
|
cy.get('[role=search] [role=menuitem]')
|
||||||
|
.should('have.length', 1)
|
||||||
|
.first()
|
||||||
|
.should('contain', 'uploads an image');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
860
package-lock.json
generated
860
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
29
package.json
29
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "redoc",
|
"name": "redoc",
|
||||||
"version": "2.0.0-rc.71",
|
"version": "2.0.0-rc.77",
|
||||||
"description": "ReDoc",
|
"description": "ReDoc",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -62,8 +62,8 @@
|
||||||
"pre-commit": "pretty-quick --staged && npm run lint"
|
"pre-commit": "pretty-quick --staged && npm run lint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cypress/webpack-preprocessor": "^5.9.0",
|
"@cypress/webpack-preprocessor": "^5.12.0",
|
||||||
"@hot-loader/react-dom": "^17.0.1",
|
"@hot-loader/react-dom": "^17.0.2",
|
||||||
"@size-limit/preset-app": "^7.0.4",
|
"@size-limit/preset-app": "^7.0.4",
|
||||||
"@types/chai": "^4.2.18",
|
"@types/chai": "^4.2.18",
|
||||||
"@types/dompurify": "^2.2.2",
|
"@types/dompurify": "^2.2.2",
|
||||||
|
@ -110,6 +110,7 @@
|
||||||
"license-checker": "^25.0.1",
|
"license-checker": "^25.0.1",
|
||||||
"lodash.noop": "^3.0.1",
|
"lodash.noop": "^3.0.1",
|
||||||
"mobx": "^6.3.2",
|
"mobx": "^6.3.2",
|
||||||
|
"outdent": "^0.8.0",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.3.2",
|
||||||
"pretty-quick": "^3.0.0",
|
"pretty-quick": "^3.0.0",
|
||||||
"raf": "^3.4.1",
|
"raf": "^3.4.1",
|
||||||
|
@ -123,10 +124,10 @@
|
||||||
"ts-jest": "^27.0.2",
|
"ts-jest": "^27.0.2",
|
||||||
"ts-loader": "^9.2.6",
|
"ts-loader": "^9.2.6",
|
||||||
"ts-node": "^10.0.0",
|
"ts-node": "^10.0.0",
|
||||||
|
"tslib": "^2.4.0",
|
||||||
"typescript": "~4.1.0",
|
"typescript": "~4.1.0",
|
||||||
"unfetch": "^4.2.0",
|
"unfetch": "^4.2.0",
|
||||||
"url-polyfill": "^1.1.12",
|
"webpack": "^5.50.1",
|
||||||
"webpack": "^5.38.1",
|
|
||||||
"webpack-cli": "^4.7.2",
|
"webpack-cli": "^4.7.2",
|
||||||
"webpack-dev-server": "^4.6.0",
|
"webpack-dev-server": "^4.6.0",
|
||||||
"webpack-node-externals": "^3.0.0",
|
"webpack-node-externals": "^3.0.0",
|
||||||
|
@ -140,7 +141,7 @@
|
||||||
"styled-components": "^4.1.1 || ^5.1.1"
|
"styled-components": "^4.1.1 || ^5.1.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@redocly/openapi-core": "^1.0.0-beta.97",
|
"@redocly/openapi-core": "^1.0.0-beta.104",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"decko": "^1.2.0",
|
"decko": "^1.2.0",
|
||||||
"dompurify": "^2.2.8",
|
"dompurify": "^2.2.8",
|
||||||
|
@ -152,7 +153,7 @@
|
||||||
"mobx-react": "^7.2.0",
|
"mobx-react": "^7.2.0",
|
||||||
"openapi-sampler": "^1.3.0",
|
"openapi-sampler": "^1.3.0",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"perfect-scrollbar": "^1.5.1",
|
"perfect-scrollbar": "^1.5.5",
|
||||||
"polished": "^4.1.3",
|
"polished": "^4.1.3",
|
||||||
"prismjs": "^1.27.0",
|
"prismjs": "^1.27.0",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
|
@ -167,6 +168,14 @@
|
||||||
{
|
{
|
||||||
"path": "./bundles/redoc.standalone.js",
|
"path": "./bundles/redoc.standalone.js",
|
||||||
"limit": "350 kB"
|
"limit": "350 kB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./bundles/redoc.lib.js",
|
||||||
|
"limit": "100 kB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./bundles/redoc.browser.lib.js",
|
||||||
|
"limit": "100 kB"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"jest": {
|
"jest": {
|
||||||
|
@ -186,10 +195,12 @@
|
||||||
"coveragePathIgnorePatterns": [
|
"coveragePathIgnorePatterns": [
|
||||||
"\\.d\\.ts$",
|
"\\.d\\.ts$",
|
||||||
"/benchmark/",
|
"/benchmark/",
|
||||||
"/node_modules/"
|
"/node_modules/",
|
||||||
|
"src/services/__tests__/models/helpers.ts"
|
||||||
],
|
],
|
||||||
"modulePathIgnorePatterns": [
|
"modulePathIgnorePatterns": [
|
||||||
"/benchmark/"
|
"/benchmark/",
|
||||||
|
"src/services/__tests__/models/helpers.ts"
|
||||||
],
|
],
|
||||||
"snapshotSerializers": [
|
"snapshotSerializers": [
|
||||||
"enzyme-to-json/serializer"
|
"enzyme-to-json/serializer"
|
||||||
|
|
24
scripts/invalidate-cache.sh
Executable file
24
scripts/invalidate-cache.sh
Executable file
|
@ -0,0 +1,24 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e # exit on error
|
||||||
|
|
||||||
|
echo jsdelivr clearing cache
|
||||||
|
curl -i -X POST https://purge.jsdelivr.net/ \
|
||||||
|
-H 'cache-control: no-cache' \
|
||||||
|
-H 'content-type: application/json' \
|
||||||
|
-d '{
|
||||||
|
"path": [
|
||||||
|
"npm/redoc@latest/bundles/redoc.browser.lib.js",
|
||||||
|
"npm/redoc@latest/bundles/redoc.lib.js",
|
||||||
|
"npm/redoc@latest/bundles/redoc.standalone.js"
|
||||||
|
]
|
||||||
|
}'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo start invalidate cloudfront
|
||||||
|
|
||||||
|
aws cloudfront create-invalidation --distribution-id $DISTRIBUTION --paths "/redoc/*"
|
||||||
|
|
||||||
|
echo Cache cleared successfully
|
||||||
|
|
||||||
|
exit 0
|
|
@ -8,11 +8,13 @@ VERSION=$(node scripts/version.js)
|
||||||
VERSION_TAG=v${VERSION:0:1}.x
|
VERSION_TAG=v${VERSION:0:1}.x
|
||||||
|
|
||||||
copy_to_s3 () {
|
copy_to_s3 () {
|
||||||
aws s3 cp bundles "s3://redocly-cdn/redoc/$1/bundles" --recursive "$2"
|
aws s3 cp --exclude "*" --include "*.js" --content-type "application/javascript; charset=utf-8" bundles "s3://redocly-cdn/redoc/$1/bundles" --recursive
|
||||||
aws s3 cp CHANGELOG.md "s3://redocly-cdn/redoc/$1/CHANGELOG.md" "$2"
|
aws s3 cp --exclude "*" --include "*.map" --content-type "application/json" bundles "s3://redocly-cdn/redoc/$1/bundles" --recursive
|
||||||
aws s3 cp LICENSE "s3://redocly-cdn/redoc/$1/LICENSE" "$2"
|
aws s3 cp --exclude "*" --include "*.txt" bundles "s3://redocly-cdn/redoc/$1/bundles" --recursive
|
||||||
aws s3 cp package.json "s3://redocly-cdn/redoc/$1/package.json" "$2"
|
aws s3 cp CHANGELOG.md "s3://redocly-cdn/redoc/$1/CHANGELOG.md"
|
||||||
aws s3 cp README.md "s3://redocly-cdn/redoc/$1/README.md" "$2"
|
aws s3 cp LICENSE "s3://redocly-cdn/redoc/$1/LICENSE"
|
||||||
|
aws s3 cp package.json "s3://redocly-cdn/redoc/$1/package.json"
|
||||||
|
aws s3 cp README.md "s3://redocly-cdn/redoc/$1/README.md"
|
||||||
}
|
}
|
||||||
|
|
||||||
if aws s3 ls "redocly-cdn/redoc/v$VERSION/" "$@"; then
|
if aws s3 ls "redocly-cdn/redoc/v$VERSION/" "$@"; then
|
||||||
|
@ -22,7 +24,7 @@ else
|
||||||
echo Releasing $VERSION
|
echo Releasing $VERSION
|
||||||
|
|
||||||
echo Uploading to S3 $VERSION
|
echo Uploading to S3 $VERSION
|
||||||
copy_to_s3 "v$VERSION" $@
|
copy_to_s3 "v$VERSION"
|
||||||
|
|
||||||
echo Uploading to S3 $VERSION_TAG
|
echo Uploading to S3 $VERSION_TAG
|
||||||
copy_to_s3 "$VERSION_TAG" $@
|
copy_to_s3 "$VERSION_TAG" $@
|
||||||
|
|
|
@ -8,36 +8,24 @@ export interface CopyButtonWrapperProps {
|
||||||
children: (props: { renderCopyButton: () => React.ReactNode }) => React.ReactNode;
|
children: (props: { renderCopyButton: () => React.ReactNode }) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CopyButtonWrapper extends React.PureComponent<
|
export const CopyButtonWrapper = (
|
||||||
CopyButtonWrapperProps,
|
props: CopyButtonWrapperProps & { tooltipShown?: boolean },
|
||||||
{ tooltipShown: boolean }
|
): JSX.Element => {
|
||||||
> {
|
const [tooltipShown, setTooltipShown] = React.useState(false);
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
tooltipShown: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
const copy = () => {
|
||||||
return this.props.children({ renderCopyButton: this.renderCopyButton });
|
|
||||||
}
|
|
||||||
|
|
||||||
copy = () => {
|
|
||||||
const content =
|
const content =
|
||||||
typeof this.props.data === 'string'
|
typeof props.data === 'string' ? props.data : JSON.stringify(props.data, null, 2);
|
||||||
? this.props.data
|
|
||||||
: JSON.stringify(this.props.data, null, 2);
|
|
||||||
ClipboardService.copyCustom(content);
|
ClipboardService.copyCustom(content);
|
||||||
this.showTooltip();
|
showTooltip();
|
||||||
};
|
};
|
||||||
|
|
||||||
renderCopyButton = () => {
|
const renderCopyButton = () => {
|
||||||
return (
|
return (
|
||||||
<button onClick={this.copy}>
|
<button onClick={copy}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={ClipboardService.isSupported() ? 'Copied' : 'Not supported in your browser'}
|
title={ClipboardService.isSupported() ? 'Copied' : 'Not supported in your browser'}
|
||||||
open={this.state.tooltipShown}
|
open={tooltipShown}
|
||||||
>
|
>
|
||||||
Copy
|
Copy
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -45,15 +33,12 @@ export class CopyButtonWrapper extends React.PureComponent<
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
showTooltip() {
|
const showTooltip = () => {
|
||||||
this.setState({
|
setTooltipShown(true);
|
||||||
tooltipShown: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.setState({
|
setTooltipShown(false);
|
||||||
tooltipShown: false,
|
|
||||||
});
|
|
||||||
}, 1500);
|
}, 1500);
|
||||||
}
|
};
|
||||||
}
|
return props.children({ renderCopyButton: renderCopyButton }) as JSX.Element;
|
||||||
|
};
|
||||||
|
|
|
@ -8,31 +8,29 @@ const directionMap = {
|
||||||
down: '0',
|
down: '0',
|
||||||
};
|
};
|
||||||
|
|
||||||
class IntShelfIcon extends React.PureComponent<{
|
const IntShelfIcon = (props: {
|
||||||
className?: string;
|
className?: string;
|
||||||
float?: 'left' | 'right';
|
float?: 'left' | 'right';
|
||||||
size?: string;
|
size?: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
direction: 'left' | 'right' | 'up' | 'down';
|
direction: 'left' | 'right' | 'up' | 'down';
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
}> {
|
}): JSX.Element => {
|
||||||
render() {
|
return (
|
||||||
return (
|
<svg
|
||||||
<svg
|
className={props.className}
|
||||||
className={this.props.className}
|
style={props.style}
|
||||||
style={this.props.style}
|
version="1.1"
|
||||||
version="1.1"
|
viewBox="0 0 24 24"
|
||||||
viewBox="0 0 24 24"
|
x="0"
|
||||||
x="0"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
y="0"
|
||||||
y="0"
|
aria-hidden="true"
|
||||||
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 points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 " />
|
</svg>
|
||||||
</svg>
|
);
|
||||||
);
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ShelfIcon = styled(IntShelfIcon)`
|
export const ShelfIcon = styled(IntShelfIcon)`
|
||||||
height: ${props => props.size || '18px'};
|
height: ${props => props.size || '18px'};
|
||||||
|
|
|
@ -17,20 +17,18 @@ export interface CallbackTitleProps {
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CallbackTitle extends React.PureComponent<CallbackTitleProps> {
|
export const CallbackTitle = (props: CallbackTitleProps) => {
|
||||||
render() {
|
const { name, opened, className, onClick, httpVerb, deprecated } = props;
|
||||||
const { name, opened, className, onClick, httpVerb, deprecated } = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CallbackTitleWrapper className={className} onClick={onClick || undefined}>
|
<CallbackTitleWrapper className={className} onClick={onClick || undefined}>
|
||||||
<OperationBadgeStyled type={httpVerb}>{shortenHTTPVerb(httpVerb)}</OperationBadgeStyled>
|
<OperationBadgeStyled type={httpVerb}>{shortenHTTPVerb(httpVerb)}</OperationBadgeStyled>
|
||||||
<ShelfIcon size={'1.5em'} direction={opened ? 'down' : 'right'} float={'left'} />
|
<ShelfIcon size={'1.5em'} direction={opened ? 'down' : 'right'} float={'left'} />
|
||||||
<CallbackName deprecated={deprecated}>{name}</CallbackName>
|
<CallbackName deprecated={deprecated}>{name}</CallbackName>
|
||||||
{deprecated ? <Badge type="warning"> {l('deprecated')} </Badge> : null}
|
{deprecated ? <Badge type="warning"> {l('deprecated')} </Badge> : null}
|
||||||
</CallbackTitleWrapper>
|
</CallbackTitleWrapper>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const CallbackTitleWrapper = styled.button`
|
const CallbackTitleWrapper = styled.button`
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|
|
@ -4,8 +4,8 @@ import * as React from 'react';
|
||||||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||||
import { AdvancedMarkdown } from '../Markdown/AdvancedMarkdown';
|
import { AdvancedMarkdown } from '../Markdown/AdvancedMarkdown';
|
||||||
import { H1, H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
|
import { H1, H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
|
||||||
import { ContentItemModel } from '../../services/MenuBuilder';
|
import type { ContentItemModel } from '../../services';
|
||||||
import { GroupModel, OperationModel } from '../../services/models';
|
import type { GroupModel, OperationModel } from '../../services/models';
|
||||||
import { Operation } from '../Operation/Operation';
|
import { Operation } from '../Operation/Operation';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
|
@ -79,7 +79,11 @@ export class SectionItem extends React.Component<ContentItemProps> {
|
||||||
</Header>
|
</Header>
|
||||||
</MiddlePanel>
|
</MiddlePanel>
|
||||||
</Row>
|
</Row>
|
||||||
<AdvancedMarkdown source={description || ''} htmlWrap={middlePanelWrap} />
|
<AdvancedMarkdown
|
||||||
|
parentId={this.props.item.id}
|
||||||
|
source={description || ''}
|
||||||
|
htmlWrap={middlePanelWrap}
|
||||||
|
/>
|
||||||
{externalDocs && (
|
{externalDocs && (
|
||||||
<Row>
|
<Row>
|
||||||
<MiddlePanel>
|
<MiddlePanel>
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { StyledComponent } from 'styled-components';
|
||||||
|
|
||||||
import { DropdownProps, MimeLabel, SimpleDropdown } from '../../common-elements/Dropdown';
|
import { DropdownProps, MimeLabel, SimpleDropdown } from '../../common-elements/Dropdown';
|
||||||
|
|
||||||
export interface DropdownOrLabelProps extends DropdownProps {
|
export interface DropdownOrLabelProps extends DropdownProps {
|
||||||
Label?: React.ComponentClass;
|
Label?: StyledComponent<any, any, GenericObject, never>;
|
||||||
Dropdown?: React.ComponentClass;
|
Dropdown?: StyledComponent<
|
||||||
|
React.NamedExoticComponent<DropdownProps>,
|
||||||
|
any,
|
||||||
|
{
|
||||||
|
fullWidth?: boolean | undefined;
|
||||||
|
},
|
||||||
|
never
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DropdownOrLabel(props: DropdownOrLabelProps): JSX.Element {
|
export function DropdownOrLabel(props: DropdownOrLabelProps): JSX.Element {
|
||||||
|
|
|
@ -59,8 +59,8 @@ export const ServersOverlay = styled.div<{ expanded: boolean }>`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
background: #fafafa;
|
background: ${props => props.theme.rightPanel.servers.overlay.backgroundColor};
|
||||||
color: #263238;
|
color: ${props => props.theme.rightPanel.servers.overlay.textColor};
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.33);
|
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.33);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -78,7 +78,7 @@ export const ServerItem = styled.div`
|
||||||
export const ServerUrl = styled.div`
|
export const ServerUrl = styled.div`
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
background: #fff;
|
background: ${props => props.theme.rightPanel.servers.url.backgroundColor};
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
color: ${props => props.theme.colors.primary.main};
|
color: ${props => props.theme.colors.primary.main};
|
||||||
> span {
|
> span {
|
||||||
|
|
|
@ -37,6 +37,6 @@ export class ErrorBoundary extends React.Component<
|
||||||
</ErrorWrapper>
|
</ErrorWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return React.Children.only(this.props.children);
|
return <React.Fragment>{React.Children.only(this.props.children)}</React.Fragment>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,17 +19,13 @@ const JsonViewerWrap = styled.div`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
class Json extends React.PureComponent<JsonProps> {
|
const Json = (props: JsonProps) => {
|
||||||
node: HTMLDivElement;
|
const [node, setNode] = React.useState<HTMLDivElement>();
|
||||||
|
|
||||||
render() {
|
const renderInner = ({ renderCopyButton }) => {
|
||||||
return <CopyButtonWrapper data={this.props.data}>{this.renderInner}</CopyButtonWrapper>;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderInner = ({ renderCopyButton }) => {
|
|
||||||
const showFoldingButtons =
|
const showFoldingButtons =
|
||||||
this.props.data &&
|
props.data &&
|
||||||
Object.values(this.props.data).some(value => typeof value === 'object' && value !== null);
|
Object.values(props.data).some(value => typeof value === 'object' && value !== null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<JsonViewerWrap>
|
<JsonViewerWrap>
|
||||||
|
@ -37,19 +33,19 @@ class Json extends React.PureComponent<JsonProps> {
|
||||||
{renderCopyButton()}
|
{renderCopyButton()}
|
||||||
{showFoldingButtons && (
|
{showFoldingButtons && (
|
||||||
<>
|
<>
|
||||||
<button onClick={this.expandAll}> Expand all </button>
|
<button onClick={expandAll}> Expand all </button>
|
||||||
<button onClick={this.collapseAll}> Collapse all </button>
|
<button onClick={collapseAll}> Collapse all </button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</SampleControls>
|
</SampleControls>
|
||||||
<OptionsContext.Consumer>
|
<OptionsContext.Consumer>
|
||||||
{options => (
|
{options => (
|
||||||
<PrismDiv
|
<PrismDiv
|
||||||
className={this.props.className}
|
className={props.className}
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
ref={node => (this.node = node!)}
|
ref={node => setNode(node!)}
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: jsonToHTML(this.props.data, options.jsonSampleExpandLevel),
|
__html: jsonToHTML(props.data, options.jsonSampleExpandLevel),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -58,8 +54,8 @@ class Json extends React.PureComponent<JsonProps> {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
expandAll = () => {
|
const expandAll = () => {
|
||||||
const elements = this.node.getElementsByClassName('collapsible');
|
const elements = node?.getElementsByClassName('collapsible');
|
||||||
for (const collapsed of Array.prototype.slice.call(elements)) {
|
for (const collapsed of Array.prototype.slice.call(elements)) {
|
||||||
const parentNode = collapsed.parentNode as Element;
|
const parentNode = collapsed.parentNode as Element;
|
||||||
parentNode.classList.remove('collapsed');
|
parentNode.classList.remove('collapsed');
|
||||||
|
@ -67,8 +63,8 @@ class Json extends React.PureComponent<JsonProps> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
collapseAll = () => {
|
const collapseAll = () => {
|
||||||
const elements = this.node.getElementsByClassName('collapsible');
|
const elements = node?.getElementsByClassName('collapsible');
|
||||||
// skip first item to avoid collapsing whole object/array
|
// skip first item to avoid collapsing whole object/array
|
||||||
const elementsArr = Array.prototype.slice.call(elements, 1);
|
const elementsArr = Array.prototype.slice.call(elements, 1);
|
||||||
|
|
||||||
|
@ -79,7 +75,7 @@ class Json extends React.PureComponent<JsonProps> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
collapseElement = (target: HTMLElement) => {
|
const collapseElement = (target: HTMLElement) => {
|
||||||
let collapsed;
|
let collapsed;
|
||||||
if (target.className === 'collapser') {
|
if (target.className === 'collapser') {
|
||||||
collapsed = target.parentElement!.getElementsByClassName('collapsible')[0];
|
collapsed = target.parentElement!.getElementsByClassName('collapsible')[0];
|
||||||
|
@ -93,26 +89,27 @@ class Json extends React.PureComponent<JsonProps> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
clickListener = (event: MouseEvent) => {
|
const clickListener = React.useCallback((event: MouseEvent) => {
|
||||||
this.collapseElement(event.target as HTMLElement);
|
collapseElement(event.target as HTMLElement);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
focusListener = (event: KeyboardEvent) => {
|
const focusListener = React.useCallback((event: KeyboardEvent) => {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
this.collapseElement(event.target as HTMLElement);
|
collapseElement(event.target as HTMLElement);
|
||||||
}
|
}
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
componentDidMount() {
|
React.useEffect(() => {
|
||||||
this.node!.addEventListener('click', this.clickListener);
|
node?.addEventListener('click', clickListener);
|
||||||
this.node!.addEventListener('focus', this.focusListener);
|
node?.addEventListener('focus', focusListener);
|
||||||
}
|
return () => {
|
||||||
|
node?.removeEventListener('click', clickListener);
|
||||||
|
node?.removeEventListener('focus', focusListener);
|
||||||
|
};
|
||||||
|
}, [clickListener, focusListener, node]);
|
||||||
|
|
||||||
componentWillUnmount() {
|
return <CopyButtonWrapper data={props.data}>{renderInner}</CopyButtonWrapper>;
|
||||||
this.node!.removeEventListener('click', this.clickListener);
|
};
|
||||||
this.node!.removeEventListener('focus', this.focusListener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const JsonViewer = styled(Json)`
|
export const JsonViewer = styled(Json)`
|
||||||
${jsonStyles};
|
${jsonStyles};
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { StoreConsumer } from '../StoreBuilder';
|
||||||
|
|
||||||
export interface AdvancedMarkdownProps extends BaseMarkdownProps {
|
export interface AdvancedMarkdownProps extends BaseMarkdownProps {
|
||||||
htmlWrap?: (part: JSX.Element) => JSX.Element;
|
htmlWrap?: (part: JSX.Element) => JSX.Element;
|
||||||
|
parentId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AdvancedMarkdown extends React.Component<AdvancedMarkdownProps> {
|
export class AdvancedMarkdown extends React.Component<AdvancedMarkdownProps> {
|
||||||
|
@ -28,7 +29,7 @@ export class AdvancedMarkdown extends React.Component<AdvancedMarkdownProps> {
|
||||||
throw new Error('When using components in markdown, store prop must be provided');
|
throw new Error('When using components in markdown, store prop must be provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderer = new MarkdownRenderer(options);
|
const renderer = new MarkdownRenderer(options, this.props.parentId);
|
||||||
const parts = renderer.renderMdWithComponents(source);
|
const parts = renderer.renderMdWithComponents(source);
|
||||||
|
|
||||||
if (!parts.length) {
|
if (!parts.length) {
|
||||||
|
@ -42,7 +43,8 @@ export class AdvancedMarkdown extends React.Component<AdvancedMarkdownProps> {
|
||||||
{ key: idx },
|
{ key: idx },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <part.component key={idx} {...{ ...part.props, ...part.propsSelector(store) }} />;
|
const PartComponent = part.component as React.FunctionComponent;
|
||||||
|
return <PartComponent key={idx} {...{ ...part.props, ...part.propsSelector(store) }} />;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
16
src/components/Schema/RecursiveSchema.tsx
Normal file
16
src/components/Schema/RecursiveSchema.tsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
import { RecursiveLabel, TypeName, TypeTitle } from '../../common-elements/fields';
|
||||||
|
import { l } from '../../services/Labels';
|
||||||
|
import type { SchemaProps } from '.';
|
||||||
|
|
||||||
|
export const RecursiveSchema = observer(({ schema }: SchemaProps) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<TypeName>{schema.displayType}</TypeName>
|
||||||
|
{schema.title && <TypeTitle> {schema.title} </TypeTitle>}
|
||||||
|
<RecursiveLabel> {l('recursive')} </RecursiveLabel>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
|
@ -1,7 +1,6 @@
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { RecursiveLabel, TypeName, TypeTitle } from '../../common-elements/fields';
|
|
||||||
import { FieldDetails } from '../Fields/FieldDetails';
|
import { FieldDetails } from '../Fields/FieldDetails';
|
||||||
|
|
||||||
import { FieldModel, SchemaModel } from '../../services/models';
|
import { FieldModel, SchemaModel } from '../../services/models';
|
||||||
|
@ -9,8 +8,8 @@ import { FieldModel, SchemaModel } from '../../services/models';
|
||||||
import { ArraySchema } from './ArraySchema';
|
import { ArraySchema } from './ArraySchema';
|
||||||
import { ObjectSchema } from './ObjectSchema';
|
import { ObjectSchema } from './ObjectSchema';
|
||||||
import { OneOfSchema } from './OneOfSchema';
|
import { OneOfSchema } from './OneOfSchema';
|
||||||
|
import { RecursiveSchema } from './RecursiveSchema';
|
||||||
|
|
||||||
import { l } from '../../services/Labels';
|
|
||||||
import { isArray } from '../../utils/helpers';
|
import { isArray } from '../../utils/helpers';
|
||||||
|
|
||||||
export interface SchemaOptions {
|
export interface SchemaOptions {
|
||||||
|
@ -36,13 +35,7 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
|
||||||
const { type, oneOf, discriminatorProp, isCircular } = schema;
|
const { type, oneOf, discriminatorProp, isCircular } = schema;
|
||||||
|
|
||||||
if (isCircular) {
|
if (isCircular) {
|
||||||
return (
|
return <RecursiveSchema schema={schema} />;
|
||||||
<div>
|
|
||||||
<TypeName>{schema.displayType}</TypeName>
|
|
||||||
{schema.title && <TypeTitle> {schema.title} </TypeTitle>}
|
|
||||||
<RecursiveLabel> {l('recursive')} </RecursiveLabel>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (discriminatorProp !== undefined) {
|
if (discriminatorProp !== undefined) {
|
||||||
|
@ -52,11 +45,14 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
const activeSchema = oneOf[schema.activeOneOf];
|
||||||
|
return activeSchema.isCircular ? (
|
||||||
|
<RecursiveSchema schema={activeSchema} />
|
||||||
|
) : (
|
||||||
<ObjectSchema
|
<ObjectSchema
|
||||||
{...rest}
|
{...rest}
|
||||||
level={level}
|
level={level}
|
||||||
schema={oneOf![schema.activeOneOf]}
|
schema={activeSchema}
|
||||||
discriminator={{
|
discriminator={{
|
||||||
fieldName: discriminatorProp,
|
fieldName: discriminatorProp,
|
||||||
parentSchema: schema,
|
parentSchema: schema,
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { IMenuItem } from '../../services/MenuStore';
|
import type { IMenuItem, SearchResult } from '../../services/types';
|
||||||
import { SearchStore } from '../../services/SearchStore';
|
import type { SearchStore } from '../../services/SearchStore';
|
||||||
|
import type { MarkerService } from '../../services/MarkerService';
|
||||||
|
|
||||||
import { MenuItem } from '../SideMenu/MenuItem';
|
import { MenuItem } from '../SideMenu/MenuItem';
|
||||||
|
|
||||||
import { MarkerService } from '../../services/MarkerService';
|
|
||||||
import { SearchResult } from '../../services/SearchWorker.worker';
|
|
||||||
|
|
||||||
import { OptionsContext } from '../OptionsProvider';
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
import { bind, debounce } from 'decko';
|
import { bind, debounce } from 'decko';
|
||||||
import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
|
import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
|
||||||
|
@ -134,12 +132,13 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { activeItemIdx } = this.state;
|
const { activeItemIdx } = this.state;
|
||||||
const results = this.state.results.map(res => ({
|
const results = this.state.results
|
||||||
item: this.props.getItemById(res.meta)!,
|
.filter(res => this.props.getItemById(res.meta))
|
||||||
score: res.score,
|
.map(res => ({
|
||||||
}));
|
item: this.props.getItemById(res.meta)!,
|
||||||
|
score: res.score,
|
||||||
results.sort((a, b) => b.score - a.score);
|
}))
|
||||||
|
.sort((a, b) => b.score - a.score);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SearchWrap role="search">
|
<SearchWrap role="search">
|
||||||
|
|
|
@ -13,7 +13,7 @@ export interface OAuthFlowProps {
|
||||||
export function OAuthFlowComponent(props: OAuthFlowProps) {
|
export function OAuthFlowComponent(props: OAuthFlowProps) {
|
||||||
const { type, flow, RequiredScopes } = props;
|
const { type, flow, RequiredScopes } = props;
|
||||||
const scopesNames = Object.keys(flow?.scopes || {});
|
const scopesNames = Object.keys(flow?.scopes || {});
|
||||||
console.log('rended');
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SecurityRow>
|
<SecurityRow>
|
||||||
|
|
|
@ -17,6 +17,8 @@ export function SecurityHeader(props: SecurityRequirementProps) {
|
||||||
const { security, showSecuritySchemeType, expanded } = props;
|
const { security, showSecuritySchemeType, expanded } = props;
|
||||||
|
|
||||||
const grouping = security.schemes.length > 1;
|
const grouping = security.schemes.length > 1;
|
||||||
|
if (security.schemes.length === 0)
|
||||||
|
return <SecurityRequirementOrWrap expanded={expanded}>None</SecurityRequirementOrWrap>;
|
||||||
return (
|
return (
|
||||||
<SecurityRequirementOrWrap expanded={expanded}>
|
<SecurityRequirementOrWrap expanded={expanded}>
|
||||||
{grouping && '('}
|
{grouping && '('}
|
||||||
|
|
|
@ -92,7 +92,7 @@ function getRequiredScopes(id: string, securities: SecurityRequirementModel[]):
|
||||||
let schemesLength = security.schemes.length;
|
let schemesLength = security.schemes.length;
|
||||||
while (schemesLength--) {
|
while (schemesLength--) {
|
||||||
const scheme = security.schemes[schemesLength];
|
const scheme = security.schemes[schemesLength];
|
||||||
if (scheme.id === id) {
|
if (scheme.id === id && Array.isArray(scheme.scopes)) {
|
||||||
allScopes.push(...scheme.scopes);
|
allScopes.push(...scheme.scopes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
18
src/components/SideMenu/Logo.tsx
Normal file
18
src/components/SideMenu/Logo.tsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
export default function RedoclyLogo(): JSX.Element | null {
|
||||||
|
const [isDisplay, setDisplay] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDisplay(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return isDisplay ? (
|
||||||
|
<img
|
||||||
|
alt={'redocly logo'}
|
||||||
|
onError={() => setDisplay(false)}
|
||||||
|
src={'https://cdn.redoc.ly/redoc/logo-mini.svg'}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
}
|
|
@ -2,13 +2,14 @@ import { observer } from 'mobx-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { ShelfIcon } from '../../common-elements/shelfs';
|
import { ShelfIcon } from '../../common-elements/shelfs';
|
||||||
import { IMenuItem, OperationModel } from '../../services';
|
import { OperationModel } from '../../services';
|
||||||
import { shortenHTTPVerb } from '../../utils/openapi';
|
import { shortenHTTPVerb } from '../../utils/openapi';
|
||||||
import { MenuItems } from './MenuItems';
|
import { MenuItems } from './MenuItems';
|
||||||
import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements';
|
import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements';
|
||||||
import { l } from '../../services/Labels';
|
import { l } from '../../services/Labels';
|
||||||
import { scrollIntoViewIfNeeded } from '../../utils';
|
import { scrollIntoViewIfNeeded } from '../../utils';
|
||||||
import { OptionsContext } from '../OptionsProvider';
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
|
import type { IMenuItem } from '../../services';
|
||||||
|
|
||||||
export interface MenuItemProps {
|
export interface MenuItemProps {
|
||||||
item: IMenuItem;
|
item: IMenuItem;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { IMenuItem } from '../../services';
|
import type { IMenuItem } from '../../services';
|
||||||
|
|
||||||
import { MenuItem } from './MenuItem';
|
import { MenuItem } from './MenuItem';
|
||||||
import { MenuItemUl } from './styled.elements';
|
import { MenuItemUl } from './styled.elements';
|
||||||
|
@ -26,7 +26,7 @@ export class MenuItems extends React.Component<MenuItemsProps> {
|
||||||
className={className}
|
className={className}
|
||||||
style={this.props.style}
|
style={this.props.style}
|
||||||
expanded={expanded}
|
expanded={expanded}
|
||||||
{...(root ? { role: 'navigation' } : {})}
|
{...(root ? { role: 'menu' } : {})}
|
||||||
>
|
>
|
||||||
{items.map((item, idx) => (
|
{items.map((item, idx) => (
|
||||||
<MenuItem key={idx} item={item} onActivate={this.props.onActivate} />
|
<MenuItem key={idx} item={item} onActivate={this.props.onActivate} />
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { IMenuItem, MenuStore } from '../../services/MenuStore';
|
import { MenuStore } from '../../services';
|
||||||
|
import type { IMenuItem } from '../../services';
|
||||||
import { OptionsContext } from '../OptionsProvider';
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
import { MenuItems } from './MenuItems';
|
import { MenuItems } from './MenuItems';
|
||||||
|
|
||||||
import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
|
import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
|
||||||
import { RedocAttribution } from './styled.elements';
|
import { RedocAttribution } from './styled.elements';
|
||||||
|
import RedoclyLogo from './Logo';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class SideMenu extends React.Component<{ menu: MenuStore; className?: string }> {
|
export class SideMenu extends React.Component<{ menu: MenuStore; className?: string }> {
|
||||||
|
@ -26,7 +28,8 @@ export class SideMenu extends React.Component<{ menu: MenuStore; className?: str
|
||||||
<MenuItems items={store.items} onActivate={this.activate} root={true} />
|
<MenuItems items={store.items} onActivate={this.activate} root={true} />
|
||||||
<RedocAttribution>
|
<RedocAttribution>
|
||||||
<a target="_blank" rel="noopener noreferrer" href="https://redocly.com/redoc/">
|
<a target="_blank" rel="noopener noreferrer" href="https://redocly.com/redoc/">
|
||||||
Documentation Powered by Redocly
|
<RedoclyLogo />
|
||||||
|
API docs by Redocly
|
||||||
</a>
|
</a>
|
||||||
</RedocAttribution>
|
</RedocAttribution>
|
||||||
</PerfectScrollbarWrap>
|
</PerfectScrollbarWrap>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { default as classnames } from 'classnames';
|
||||||
import { darken } from 'polished';
|
import { darken } from 'polished';
|
||||||
|
|
||||||
import { deprecatedCss, ShelfIcon } from '../../common-elements';
|
import { deprecatedCss, ShelfIcon } from '../../common-elements';
|
||||||
import styled, { css, ResolvedThemeInterface } from '../../styled-components';
|
import styled, { css, media, ResolvedThemeInterface } from '../../styled-components';
|
||||||
|
|
||||||
export const OperationBadge = styled.span.attrs((props: { type: string }) => ({
|
export const OperationBadge = styled.span.attrs((props: { type: string }) => ({
|
||||||
className: `operation-type ${props.type}`,
|
className: `operation-type ${props.type}`,
|
||||||
|
@ -84,6 +84,10 @@ export const MenuItemUl = styled.ul<{ expanded: boolean }>`
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
padding-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
& & {
|
& & {
|
||||||
font-size: 0.929em;
|
font-size: 0.929em;
|
||||||
}
|
}
|
||||||
|
@ -169,21 +173,33 @@ export const MenuItemTitle = styled.span<{ width?: string }>`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const RedocAttribution = styled.div`
|
export const RedocAttribution = styled.div`
|
||||||
${({ theme }) => `
|
${({ theme }) => css`
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
margin-top: ${theme.spacing.unit * 2}px;
|
margin-top: ${theme.spacing.unit * 2}px;
|
||||||
padding: 0 ${theme.spacing.unit * 4}px;
|
text-align: center;
|
||||||
text-align: left;
|
position: fixed;
|
||||||
|
width: ${theme.sidebar.width};
|
||||||
|
bottom: 0;
|
||||||
|
background: ${theme.sidebar.backgroundColor};
|
||||||
|
|
||||||
opacity: 0.7;
|
a,
|
||||||
|
a:visited,
|
||||||
a,
|
a:hover {
|
||||||
a:visited,
|
color: ${theme.sidebar.textColor} !important;
|
||||||
a:hover {
|
padding: ${theme.spacing.unit}px 0;
|
||||||
color: ${theme.sidebar.textColor} !important;
|
border-top: 1px solid ${darken(0.1, theme.sidebar.backgroundColor)};
|
||||||
border-top: 1px solid ${darken(0.1, theme.sidebar.backgroundColor)};
|
text-decoration: none;
|
||||||
padding: ${theme.spacing.unit}px 0;
|
display: flex;
|
||||||
display: block;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
`};
|
||||||
|
img {
|
||||||
|
width: 15px;
|
||||||
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
`};
|
|
||||||
|
${media.lessThan('small')`
|
||||||
|
width: 100%;
|
||||||
|
`};
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -9,24 +9,21 @@ export interface SourceCodeProps {
|
||||||
lang: string;
|
lang: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SourceCode extends React.PureComponent<SourceCodeProps> {
|
export const SourceCode = (props: SourceCodeProps) => {
|
||||||
render() {
|
const { source, lang } = props;
|
||||||
const { source, lang } = this.props;
|
return <StyledPre dangerouslySetInnerHTML={{ __html: highlight(source, lang) }} />;
|
||||||
return <StyledPre dangerouslySetInnerHTML={{ __html: highlight(source, lang) }} />;
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SourceCodeWithCopy extends React.Component<SourceCodeProps> {
|
export const SourceCodeWithCopy = (props: SourceCodeProps) => {
|
||||||
render() {
|
const { source, lang } = props;
|
||||||
return (
|
return (
|
||||||
<CopyButtonWrapper data={this.props.source}>
|
<CopyButtonWrapper data={source}>
|
||||||
{({ renderCopyButton }) => (
|
{({ renderCopyButton }) => (
|
||||||
<SampleControlsWrap>
|
<SampleControlsWrap>
|
||||||
<SampleControls>{renderCopyButton()}</SampleControls>
|
<SampleControls>{renderCopyButton()}</SampleControls>
|
||||||
<SourceCode lang={this.props.lang} source={this.props.source} />
|
<SourceCode lang={lang} source={source} />
|
||||||
</SampleControlsWrap>
|
</SampleControlsWrap>
|
||||||
)}
|
)}
|
||||||
</CopyButtonWrapper>
|
</CopyButtonWrapper>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
SecuritySchemesModel,
|
SecuritySchemesModel,
|
||||||
} from '../../services';
|
} from '../../services';
|
||||||
import { StoreProvider } from '../StoreBuilder';
|
import { StoreProvider } from '../StoreBuilder';
|
||||||
|
import { SecurityRequirementModel } from '../../services/models/SecurityRequirement';
|
||||||
import { SecurityRequirements } from '../SecurityRequirement/SecurityRequirement';
|
import { SecurityRequirements } from '../SecurityRequirement/SecurityRequirement';
|
||||||
import { withTheme } from '../testProviders';
|
import { withTheme } from '../testProviders';
|
||||||
import { SecurityDefs } from '../SecuritySchemes/SecuritySchemes';
|
import { SecurityDefs } from '../SecuritySchemes/SecuritySchemes';
|
||||||
|
@ -50,6 +51,20 @@ describe('SecurityRequirement', () => {
|
||||||
expect(component.html()).toMatchSnapshot();
|
expect(component.html()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should render 'None' when empty object in security open api", () => {
|
||||||
|
const options = new RedocNormalizedOptions({});
|
||||||
|
const parser = new OpenAPIParser(
|
||||||
|
{ openapi: '3.0', info: { title: 'test', version: '0' }, paths: {} },
|
||||||
|
undefined,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
const securityRequirement = [new SecurityRequirementModel({}, parser)];
|
||||||
|
const component = mount(
|
||||||
|
withTheme(<SecurityRequirements securities={securityRequirement} key={1} />),
|
||||||
|
);
|
||||||
|
expect(component.find('span').at(0).text()).toEqual('None');
|
||||||
|
});
|
||||||
|
|
||||||
it('should hide authDefinition', async () => {
|
it('should hide authDefinition', async () => {
|
||||||
const store = await createStore(simpleSecurityFixture, undefined, {
|
const store = await createStore(simpleSecurityFixture, undefined, {
|
||||||
hideSecuritySection: true,
|
hideSecuritySection: true,
|
||||||
|
|
|
@ -22,7 +22,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
"examples": undefined,
|
"examples": undefined,
|
||||||
"externalDocs": undefined,
|
"externalDocs": undefined,
|
||||||
"format": undefined,
|
"format": undefined,
|
||||||
"isCircular": undefined,
|
"isCircular": false,
|
||||||
"isPrimitive": false,
|
"isPrimitive": false,
|
||||||
"maxItems": undefined,
|
"maxItems": undefined,
|
||||||
"minItems": undefined,
|
"minItems": undefined,
|
||||||
|
@ -70,7 +70,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
"examples": undefined,
|
"examples": undefined,
|
||||||
"externalDocs": undefined,
|
"externalDocs": undefined,
|
||||||
"format": undefined,
|
"format": undefined,
|
||||||
"isCircular": undefined,
|
"isCircular": false,
|
||||||
"isPrimitive": true,
|
"isPrimitive": true,
|
||||||
"maxItems": undefined,
|
"maxItems": undefined,
|
||||||
"minItems": undefined,
|
"minItems": undefined,
|
||||||
|
@ -208,6 +208,15 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
},
|
},
|
||||||
"rightPanel": Object {
|
"rightPanel": Object {
|
||||||
"backgroundColor": "#263238",
|
"backgroundColor": "#263238",
|
||||||
|
"servers": Object {
|
||||||
|
"overlay": Object {
|
||||||
|
"backgroundColor": "#fafafa",
|
||||||
|
"textColor": "#263238",
|
||||||
|
},
|
||||||
|
"url": Object {
|
||||||
|
"backgroundColor": "#fff",
|
||||||
|
},
|
||||||
|
},
|
||||||
"textColor": "#ffffff",
|
"textColor": "#ffffff",
|
||||||
"width": "40%",
|
"width": "40%",
|
||||||
},
|
},
|
||||||
|
@ -292,6 +301,10 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
"type": "number",
|
"type": "number",
|
||||||
},
|
},
|
||||||
"readOnly": false,
|
"readOnly": false,
|
||||||
|
"refsStack": Array [
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Dog/properties/packSize",
|
||||||
|
],
|
||||||
"schema": Object {
|
"schema": Object {
|
||||||
"default": undefined,
|
"default": undefined,
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
@ -329,7 +342,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
"examples": undefined,
|
"examples": undefined,
|
||||||
"externalDocs": undefined,
|
"externalDocs": undefined,
|
||||||
"format": undefined,
|
"format": undefined,
|
||||||
"isCircular": undefined,
|
"isCircular": false,
|
||||||
"isPrimitive": true,
|
"isPrimitive": true,
|
||||||
"maxItems": undefined,
|
"maxItems": undefined,
|
||||||
"minItems": undefined,
|
"minItems": undefined,
|
||||||
|
@ -467,6 +480,15 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
},
|
},
|
||||||
"rightPanel": Object {
|
"rightPanel": Object {
|
||||||
"backgroundColor": "#263238",
|
"backgroundColor": "#263238",
|
||||||
|
"servers": Object {
|
||||||
|
"overlay": Object {
|
||||||
|
"backgroundColor": "#fafafa",
|
||||||
|
"textColor": "#263238",
|
||||||
|
},
|
||||||
|
"url": Object {
|
||||||
|
"backgroundColor": "#fff",
|
||||||
|
},
|
||||||
|
},
|
||||||
"textColor": "#ffffff",
|
"textColor": "#ffffff",
|
||||||
"width": "40%",
|
"width": "40%",
|
||||||
},
|
},
|
||||||
|
@ -549,11 +571,27 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
"rawSchema": Object {
|
"rawSchema": Object {
|
||||||
"default": undefined,
|
"default": undefined,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"x-refsStack": Array [
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"readOnly": false,
|
"readOnly": false,
|
||||||
|
"refsStack": Array [
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
"#/components/schemas/Dog/properties/type",
|
||||||
|
],
|
||||||
"schema": Object {
|
"schema": Object {
|
||||||
"default": undefined,
|
"default": undefined,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"x-refsStack": Array [
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"title": "",
|
"title": "",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -563,7 +601,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"format": undefined,
|
"format": undefined,
|
||||||
"isCircular": undefined,
|
"isCircular": false,
|
||||||
"isPrimitive": false,
|
"isPrimitive": false,
|
||||||
"maxItems": undefined,
|
"maxItems": undefined,
|
||||||
"minItems": undefined,
|
"minItems": undefined,
|
||||||
|
@ -701,6 +739,15 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
},
|
},
|
||||||
"rightPanel": Object {
|
"rightPanel": Object {
|
||||||
"backgroundColor": "#263238",
|
"backgroundColor": "#263238",
|
||||||
|
"servers": Object {
|
||||||
|
"overlay": Object {
|
||||||
|
"backgroundColor": "#fafafa",
|
||||||
|
"textColor": "#263238",
|
||||||
|
},
|
||||||
|
"url": Object {
|
||||||
|
"backgroundColor": "#fff",
|
||||||
|
},
|
||||||
|
},
|
||||||
"textColor": "#ffffff",
|
"textColor": "#ffffff",
|
||||||
"width": "40%",
|
"width": "40%",
|
||||||
},
|
},
|
||||||
|
@ -794,27 +841,38 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
"readOnly": false,
|
"readOnly": false,
|
||||||
|
"refsStack": Array [
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
],
|
||||||
"schema": Object {
|
"schema": Object {
|
||||||
"allOf": undefined,
|
"allOf": undefined,
|
||||||
|
"description": undefined,
|
||||||
"discriminator": Object {
|
"discriminator": Object {
|
||||||
"propertyName": "type",
|
"propertyName": "type",
|
||||||
},
|
},
|
||||||
"parentRefs": Array [
|
|
||||||
"#/components/schemas/Pet",
|
|
||||||
],
|
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
"packSize": Object {
|
"packSize": Object {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
},
|
},
|
||||||
"type": Object {
|
"type": Object {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"x-refsStack": Array [
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"readOnly": undefined,
|
||||||
"required": Array [
|
"required": Array [
|
||||||
"type",
|
"type",
|
||||||
],
|
],
|
||||||
"title": "Dog",
|
"title": "Dog",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"writeOnly": undefined,
|
||||||
|
"x-circular-ref": undefined,
|
||||||
|
"x-parentRefs": Array [
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"title": "Dog",
|
"title": "Dog",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -864,7 +922,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
"examples": undefined,
|
"examples": undefined,
|
||||||
"externalDocs": undefined,
|
"externalDocs": undefined,
|
||||||
"format": undefined,
|
"format": undefined,
|
||||||
"isCircular": undefined,
|
"isCircular": false,
|
||||||
"isPrimitive": true,
|
"isPrimitive": true,
|
||||||
"maxItems": undefined,
|
"maxItems": undefined,
|
||||||
"minItems": undefined,
|
"minItems": undefined,
|
||||||
|
@ -1002,6 +1060,15 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
},
|
},
|
||||||
"rightPanel": Object {
|
"rightPanel": Object {
|
||||||
"backgroundColor": "#263238",
|
"backgroundColor": "#263238",
|
||||||
|
"servers": Object {
|
||||||
|
"overlay": Object {
|
||||||
|
"backgroundColor": "#fafafa",
|
||||||
|
"textColor": "#263238",
|
||||||
|
},
|
||||||
|
"url": Object {
|
||||||
|
"backgroundColor": "#fff",
|
||||||
|
},
|
||||||
|
},
|
||||||
"textColor": "#ffffff",
|
"textColor": "#ffffff",
|
||||||
"width": "40%",
|
"width": "40%",
|
||||||
},
|
},
|
||||||
|
@ -1084,11 +1151,27 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
"rawSchema": Object {
|
"rawSchema": Object {
|
||||||
"default": undefined,
|
"default": undefined,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"x-refsStack": Array [
|
||||||
|
"#/components/schemas/Cat",
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"readOnly": false,
|
"readOnly": false,
|
||||||
|
"refsStack": Array [
|
||||||
|
"#/components/schemas/Cat",
|
||||||
|
"#/components/schemas/Cat",
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
"#/components/schemas/Cat",
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
"#/components/schemas/Cat/properties/type",
|
||||||
|
],
|
||||||
"schema": Object {
|
"schema": Object {
|
||||||
"default": undefined,
|
"default": undefined,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"x-refsStack": Array [
|
||||||
|
"#/components/schemas/Cat",
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"title": "",
|
"title": "",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -1123,7 +1206,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
"examples": undefined,
|
"examples": undefined,
|
||||||
"externalDocs": undefined,
|
"externalDocs": undefined,
|
||||||
"format": undefined,
|
"format": undefined,
|
||||||
"isCircular": undefined,
|
"isCircular": false,
|
||||||
"isPrimitive": true,
|
"isPrimitive": true,
|
||||||
"maxItems": undefined,
|
"maxItems": undefined,
|
||||||
"minItems": undefined,
|
"minItems": undefined,
|
||||||
|
@ -1261,6 +1344,15 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
},
|
},
|
||||||
"rightPanel": Object {
|
"rightPanel": Object {
|
||||||
"backgroundColor": "#263238",
|
"backgroundColor": "#263238",
|
||||||
|
"servers": Object {
|
||||||
|
"overlay": Object {
|
||||||
|
"backgroundColor": "#fafafa",
|
||||||
|
"textColor": "#263238",
|
||||||
|
},
|
||||||
|
"url": Object {
|
||||||
|
"backgroundColor": "#fff",
|
||||||
|
},
|
||||||
|
},
|
||||||
"textColor": "#ffffff",
|
"textColor": "#ffffff",
|
||||||
"width": "40%",
|
"width": "40%",
|
||||||
},
|
},
|
||||||
|
@ -1343,11 +1435,23 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
"rawSchema": Object {
|
"rawSchema": Object {
|
||||||
"default": undefined,
|
"default": undefined,
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
"x-refsStack": Array [
|
||||||
|
"#/components/schemas/Cat",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"readOnly": false,
|
"readOnly": false,
|
||||||
|
"refsStack": Array [
|
||||||
|
"#/components/schemas/Cat",
|
||||||
|
"#/components/schemas/Cat",
|
||||||
|
"#/components/schemas/Cat",
|
||||||
|
"#/components/schemas/Cat/properties/packSize",
|
||||||
|
],
|
||||||
"schema": Object {
|
"schema": Object {
|
||||||
"default": undefined,
|
"default": undefined,
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
"x-refsStack": Array [
|
||||||
|
"#/components/schemas/Cat",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"title": "",
|
"title": "",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
@ -1357,7 +1461,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"format": undefined,
|
"format": undefined,
|
||||||
"isCircular": undefined,
|
"isCircular": false,
|
||||||
"isPrimitive": false,
|
"isPrimitive": false,
|
||||||
"maxItems": undefined,
|
"maxItems": undefined,
|
||||||
"minItems": undefined,
|
"minItems": undefined,
|
||||||
|
@ -1495,6 +1599,15 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
},
|
},
|
||||||
"rightPanel": Object {
|
"rightPanel": Object {
|
||||||
"backgroundColor": "#263238",
|
"backgroundColor": "#263238",
|
||||||
|
"servers": Object {
|
||||||
|
"overlay": Object {
|
||||||
|
"backgroundColor": "#fafafa",
|
||||||
|
"textColor": "#263238",
|
||||||
|
},
|
||||||
|
"url": Object {
|
||||||
|
"backgroundColor": "#fff",
|
||||||
|
},
|
||||||
|
},
|
||||||
"textColor": "#ffffff",
|
"textColor": "#ffffff",
|
||||||
"width": "40%",
|
"width": "40%",
|
||||||
},
|
},
|
||||||
|
@ -1590,27 +1703,41 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
"readOnly": false,
|
"readOnly": false,
|
||||||
|
"refsStack": Array [
|
||||||
|
"#/components/schemas/Cat",
|
||||||
|
],
|
||||||
"schema": Object {
|
"schema": Object {
|
||||||
"allOf": undefined,
|
"allOf": undefined,
|
||||||
|
"description": undefined,
|
||||||
"discriminator": Object {
|
"discriminator": Object {
|
||||||
"propertyName": "type",
|
"propertyName": "type",
|
||||||
},
|
},
|
||||||
"parentRefs": Array [
|
|
||||||
"#/components/schemas/Pet",
|
|
||||||
],
|
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
"packSize": Object {
|
"packSize": Object {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
"x-refsStack": Array [
|
||||||
|
"#/components/schemas/Cat",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"type": Object {
|
"type": Object {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"x-refsStack": Array [
|
||||||
|
"#/components/schemas/Cat",
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"readOnly": undefined,
|
||||||
"required": Array [
|
"required": Array [
|
||||||
"type",
|
"type",
|
||||||
],
|
],
|
||||||
"title": "Cat",
|
"title": "Cat",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"writeOnly": undefined,
|
||||||
|
"x-circular-ref": undefined,
|
||||||
|
"x-parentRefs": Array [
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"title": "Cat",
|
"title": "Cat",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -1752,6 +1879,15 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
},
|
},
|
||||||
"rightPanel": Object {
|
"rightPanel": Object {
|
||||||
"backgroundColor": "#263238",
|
"backgroundColor": "#263238",
|
||||||
|
"servers": Object {
|
||||||
|
"overlay": Object {
|
||||||
|
"backgroundColor": "#fafafa",
|
||||||
|
"textColor": "#263238",
|
||||||
|
},
|
||||||
|
"url": Object {
|
||||||
|
"backgroundColor": "#fff",
|
||||||
|
},
|
||||||
|
},
|
||||||
"textColor": "#ffffff",
|
"textColor": "#ffffff",
|
||||||
"width": "40%",
|
"width": "40%",
|
||||||
},
|
},
|
||||||
|
@ -1846,6 +1982,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
"readOnly": false,
|
"readOnly": false,
|
||||||
|
"refsStack": Array [
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
],
|
||||||
"schema": Object {
|
"schema": Object {
|
||||||
"discriminator": Object {
|
"discriminator": Object {
|
||||||
"propertyName": "type",
|
"propertyName": "type",
|
||||||
|
@ -1912,7 +2051,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
"examples": undefined,
|
"examples": undefined,
|
||||||
"externalDocs": undefined,
|
"externalDocs": undefined,
|
||||||
"format": undefined,
|
"format": undefined,
|
||||||
"isCircular": undefined,
|
"isCircular": false,
|
||||||
"isPrimitive": true,
|
"isPrimitive": true,
|
||||||
"maxItems": undefined,
|
"maxItems": undefined,
|
||||||
"minItems": undefined,
|
"minItems": undefined,
|
||||||
|
@ -2050,6 +2189,15 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
},
|
},
|
||||||
"rightPanel": Object {
|
"rightPanel": Object {
|
||||||
"backgroundColor": "#263238",
|
"backgroundColor": "#263238",
|
||||||
|
"servers": Object {
|
||||||
|
"overlay": Object {
|
||||||
|
"backgroundColor": "#fafafa",
|
||||||
|
"textColor": "#263238",
|
||||||
|
},
|
||||||
|
"url": Object {
|
||||||
|
"backgroundColor": "#fff",
|
||||||
|
},
|
||||||
|
},
|
||||||
"textColor": "#ffffff",
|
"textColor": "#ffffff",
|
||||||
"width": "40%",
|
"width": "40%",
|
||||||
},
|
},
|
||||||
|
@ -2134,6 +2282,10 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
"type": "number",
|
"type": "number",
|
||||||
},
|
},
|
||||||
"readOnly": false,
|
"readOnly": false,
|
||||||
|
"refsStack": Array [
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Dog/properties/packSize",
|
||||||
|
],
|
||||||
"schema": Object {
|
"schema": Object {
|
||||||
"default": undefined,
|
"default": undefined,
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
@ -2171,7 +2323,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
"examples": undefined,
|
"examples": undefined,
|
||||||
"externalDocs": undefined,
|
"externalDocs": undefined,
|
||||||
"format": undefined,
|
"format": undefined,
|
||||||
"isCircular": undefined,
|
"isCircular": false,
|
||||||
"isPrimitive": true,
|
"isPrimitive": true,
|
||||||
"maxItems": undefined,
|
"maxItems": undefined,
|
||||||
"minItems": undefined,
|
"minItems": undefined,
|
||||||
|
@ -2309,6 +2461,15 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
},
|
},
|
||||||
"rightPanel": Object {
|
"rightPanel": Object {
|
||||||
"backgroundColor": "#263238",
|
"backgroundColor": "#263238",
|
||||||
|
"servers": Object {
|
||||||
|
"overlay": Object {
|
||||||
|
"backgroundColor": "#fafafa",
|
||||||
|
"textColor": "#263238",
|
||||||
|
},
|
||||||
|
"url": Object {
|
||||||
|
"backgroundColor": "#fff",
|
||||||
|
},
|
||||||
|
},
|
||||||
"textColor": "#ffffff",
|
"textColor": "#ffffff",
|
||||||
"width": "40%",
|
"width": "40%",
|
||||||
},
|
},
|
||||||
|
@ -2391,11 +2552,27 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
"rawSchema": Object {
|
"rawSchema": Object {
|
||||||
"default": undefined,
|
"default": undefined,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"x-refsStack": Array [
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"readOnly": false,
|
"readOnly": false,
|
||||||
|
"refsStack": Array [
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
"#/components/schemas/Dog/properties/type",
|
||||||
|
],
|
||||||
"schema": Object {
|
"schema": Object {
|
||||||
"default": undefined,
|
"default": undefined,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"x-refsStack": Array [
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"title": "",
|
"title": "",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -2405,7 +2582,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"format": undefined,
|
"format": undefined,
|
||||||
"isCircular": undefined,
|
"isCircular": false,
|
||||||
"isPrimitive": false,
|
"isPrimitive": false,
|
||||||
"maxItems": undefined,
|
"maxItems": undefined,
|
||||||
"minItems": undefined,
|
"minItems": undefined,
|
||||||
|
@ -2543,6 +2720,15 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
},
|
},
|
||||||
"rightPanel": Object {
|
"rightPanel": Object {
|
||||||
"backgroundColor": "#263238",
|
"backgroundColor": "#263238",
|
||||||
|
"servers": Object {
|
||||||
|
"overlay": Object {
|
||||||
|
"backgroundColor": "#fafafa",
|
||||||
|
"textColor": "#263238",
|
||||||
|
},
|
||||||
|
"url": Object {
|
||||||
|
"backgroundColor": "#fff",
|
||||||
|
},
|
||||||
|
},
|
||||||
"textColor": "#ffffff",
|
"textColor": "#ffffff",
|
||||||
"width": "40%",
|
"width": "40%",
|
||||||
},
|
},
|
||||||
|
@ -2636,27 +2822,38 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
"readOnly": false,
|
"readOnly": false,
|
||||||
|
"refsStack": Array [
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
],
|
||||||
"schema": Object {
|
"schema": Object {
|
||||||
"allOf": undefined,
|
"allOf": undefined,
|
||||||
|
"description": undefined,
|
||||||
"discriminator": Object {
|
"discriminator": Object {
|
||||||
"propertyName": "type",
|
"propertyName": "type",
|
||||||
},
|
},
|
||||||
"parentRefs": Array [
|
|
||||||
"#/components/schemas/Pet",
|
|
||||||
],
|
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
"packSize": Object {
|
"packSize": Object {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
},
|
},
|
||||||
"type": Object {
|
"type": Object {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"x-refsStack": Array [
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"readOnly": undefined,
|
||||||
"required": Array [
|
"required": Array [
|
||||||
"type",
|
"type",
|
||||||
],
|
],
|
||||||
"title": "Dog",
|
"title": "Dog",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"writeOnly": undefined,
|
||||||
|
"x-circular-ref": undefined,
|
||||||
|
"x-parentRefs": Array [
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"title": "Dog",
|
"title": "Dog",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -2700,7 +2897,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
||||||
"examples": undefined,
|
"examples": undefined,
|
||||||
"externalDocs": undefined,
|
"externalDocs": undefined,
|
||||||
"format": undefined,
|
"format": undefined,
|
||||||
"isCircular": undefined,
|
"isCircular": false,
|
||||||
"isPrimitive": true,
|
"isPrimitive": true,
|
||||||
"maxItems": undefined,
|
"maxItems": undefined,
|
||||||
"minItems": undefined,
|
"minItems": undefined,
|
||||||
|
@ -2712,6 +2909,10 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
||||||
"type": "number",
|
"type": "number",
|
||||||
},
|
},
|
||||||
"readOnly": false,
|
"readOnly": false,
|
||||||
|
"refsStack": Array [
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Dog/properties/packSize",
|
||||||
|
],
|
||||||
"schema": Object {
|
"schema": Object {
|
||||||
"default": undefined,
|
"default": undefined,
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
@ -2757,7 +2958,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
||||||
"examples": undefined,
|
"examples": undefined,
|
||||||
"externalDocs": undefined,
|
"externalDocs": undefined,
|
||||||
"format": undefined,
|
"format": undefined,
|
||||||
"isCircular": undefined,
|
"isCircular": false,
|
||||||
"isPrimitive": true,
|
"isPrimitive": true,
|
||||||
"maxItems": undefined,
|
"maxItems": undefined,
|
||||||
"minItems": undefined,
|
"minItems": undefined,
|
||||||
|
@ -2767,11 +2968,27 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
||||||
"rawSchema": Object {
|
"rawSchema": Object {
|
||||||
"default": undefined,
|
"default": undefined,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"x-refsStack": Array [
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"readOnly": false,
|
"readOnly": false,
|
||||||
|
"refsStack": Array [
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
"#/components/schemas/Dog/properties/type",
|
||||||
|
],
|
||||||
"schema": Object {
|
"schema": Object {
|
||||||
"default": undefined,
|
"default": undefined,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"x-refsStack": Array [
|
||||||
|
"#/components/schemas/Dog",
|
||||||
|
"#/components/schemas/Pet",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"title": "",
|
"title": "",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
@ -1,32 +1,26 @@
|
||||||
import { Lambda, observe } from 'mobx';
|
import { Lambda, observe } from 'mobx';
|
||||||
|
|
||||||
import { OpenAPISpec } from '../types';
|
import type { OpenAPISpec } from '../types';
|
||||||
import { loadAndBundleSpec } from '../utils/loadAndBundleSpec';
|
import { loadAndBundleSpec } from '../utils/loadAndBundleSpec';
|
||||||
import { history } from './HistoryService';
|
import { history } from './HistoryService';
|
||||||
import { MarkerService } from './MarkerService';
|
import { MarkerService } from './MarkerService';
|
||||||
import { MenuStore } from './MenuStore';
|
import { MenuStore } from './MenuStore';
|
||||||
import { SpecStore } from './models';
|
import { SpecStore } from './models';
|
||||||
import { RedocNormalizedOptions, RedocRawOptions } from './RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||||
|
import type { RedocRawOptions } from './RedocNormalizedOptions';
|
||||||
import { ScrollService } from './ScrollService';
|
import { ScrollService } from './ScrollService';
|
||||||
import { SearchStore } from './SearchStore';
|
import { SearchStore } from './SearchStore';
|
||||||
|
|
||||||
import { SchemaDefinition } from '../components/SchemaDefinition/SchemaDefinition';
|
import { SchemaDefinition } from '../components/SchemaDefinition/SchemaDefinition';
|
||||||
import { SecurityDefs } from '../components/SecuritySchemes/SecuritySchemes';
|
import { SecurityDefs } from '../components/SecuritySchemes/SecuritySchemes';
|
||||||
import { SCHEMA_DEFINITION_JSX_NAME, SECURITY_DEFINITIONS_JSX_NAME } from '../utils/openapi';
|
import {
|
||||||
|
SCHEMA_DEFINITION_JSX_NAME,
|
||||||
|
SECURITY_DEFINITIONS_JSX_NAME,
|
||||||
|
OLD_SECURITY_DEFINITIONS_JSX_NAME,
|
||||||
|
} from '../utils/openapi';
|
||||||
|
|
||||||
import { IS_BROWSER } from '../utils';
|
import { IS_BROWSER } from '../utils';
|
||||||
|
import type { StoreState } from './types';
|
||||||
export interface StoreState {
|
|
||||||
menu: {
|
|
||||||
activeItemIdx: number;
|
|
||||||
};
|
|
||||||
spec: {
|
|
||||||
url?: string;
|
|
||||||
data: any;
|
|
||||||
};
|
|
||||||
searchIndex: any;
|
|
||||||
options: RedocRawOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createStore(
|
export async function createStore(
|
||||||
spec: object,
|
spec: object,
|
||||||
|
@ -160,6 +154,12 @@ const DEFAULT_OPTIONS: RedocRawOptions = {
|
||||||
securitySchemes: store.spec.securitySchemes,
|
securitySchemes: store.spec.securitySchemes,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
[OLD_SECURITY_DEFINITIONS_JSX_NAME]: {
|
||||||
|
component: SecurityDefs,
|
||||||
|
propsSelector: (store: AppStore) => ({
|
||||||
|
securitySchemes: store.spec.securitySchemes,
|
||||||
|
}),
|
||||||
|
},
|
||||||
[SCHEMA_DEFINITION_JSX_NAME]: {
|
[SCHEMA_DEFINITION_JSX_NAME]: {
|
||||||
component: SchemaDefinition,
|
component: SchemaDefinition,
|
||||||
propsSelector: (store: AppStore) => ({
|
propsSelector: (store: AppStore) => ({
|
||||||
|
|
|
@ -1,25 +1,4 @@
|
||||||
export interface LabelsConfig {
|
import type { LabelsConfig, LabelsConfigRaw } from './types';
|
||||||
enum: string;
|
|
||||||
enumSingleValue: string;
|
|
||||||
enumArray: string;
|
|
||||||
default: string;
|
|
||||||
deprecated: string;
|
|
||||||
example: string;
|
|
||||||
examples: string;
|
|
||||||
recursive: string;
|
|
||||||
arrayOf: string;
|
|
||||||
webhook: string;
|
|
||||||
const: string;
|
|
||||||
noResultsFound: string;
|
|
||||||
download: string;
|
|
||||||
downloadSpecification: string;
|
|
||||||
responses: string;
|
|
||||||
callbackResponses: string;
|
|
||||||
requestSamples: string;
|
|
||||||
responseSamples: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type LabelsConfigRaw = Partial<LabelsConfig>;
|
|
||||||
|
|
||||||
const labels: LabelsConfig = {
|
const labels: LabelsConfig = {
|
||||||
enum: 'Enum',
|
enum: 'Enum',
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import * as React from 'react';
|
|
||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
|
|
||||||
import { highlight, safeSlugify, unescapeHTMLChars } from '../utils';
|
import { highlight, safeSlugify, unescapeHTMLChars } from '../utils';
|
||||||
import { AppStore } from './AppStore';
|
|
||||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||||
|
import type { MarkdownHeading, MDXComponentMeta } from './types';
|
||||||
|
|
||||||
const renderer = new marked.Renderer();
|
const renderer = new marked.Renderer();
|
||||||
|
|
||||||
|
@ -22,20 +21,6 @@ export const MDX_COMPONENT_REGEXP = '(?:^ {0,3}<({component})([\\s\\S]*?)>([\\s\
|
||||||
|
|
||||||
export const COMPONENT_REGEXP = '(?:' + LEGACY_REGEXP + '|' + MDX_COMPONENT_REGEXP + ')';
|
export const COMPONENT_REGEXP = '(?:' + LEGACY_REGEXP + '|' + MDX_COMPONENT_REGEXP + ')';
|
||||||
|
|
||||||
export interface MDXComponentMeta {
|
|
||||||
component: React.ComponentType;
|
|
||||||
propsSelector: (store?: AppStore) => any;
|
|
||||||
props?: object;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MarkdownHeading {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
level: number;
|
|
||||||
items?: MarkdownHeading[];
|
|
||||||
description?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildComponentComment(name: string) {
|
export function buildComponentComment(name: string) {
|
||||||
return `<!-- ReDoc-Inject: <${name}> -->`;
|
return `<!-- ReDoc-Inject: <${name}> -->`;
|
||||||
}
|
}
|
||||||
|
@ -61,7 +46,8 @@ export class MarkdownRenderer {
|
||||||
private headingEnhanceRenderer: marked.Renderer;
|
private headingEnhanceRenderer: marked.Renderer;
|
||||||
private originalHeadingRule: typeof marked.Renderer.prototype.heading;
|
private originalHeadingRule: typeof marked.Renderer.prototype.heading;
|
||||||
|
|
||||||
constructor(public options?: RedocNormalizedOptions) {
|
constructor(public options?: RedocNormalizedOptions, public parentId?: string) {
|
||||||
|
this.parentId = parentId;
|
||||||
this.parser = new marked.Parser();
|
this.parser = new marked.Parser();
|
||||||
this.headingEnhanceRenderer = new marked.Renderer();
|
this.headingEnhanceRenderer = new marked.Renderer();
|
||||||
this.originalHeadingRule = this.headingEnhanceRenderer.heading.bind(
|
this.originalHeadingRule = this.headingEnhanceRenderer.heading.bind(
|
||||||
|
@ -77,8 +63,10 @@ export class MarkdownRenderer {
|
||||||
parentId?: string,
|
parentId?: string,
|
||||||
): MarkdownHeading {
|
): MarkdownHeading {
|
||||||
name = unescapeHTMLChars(name);
|
name = unescapeHTMLChars(name);
|
||||||
const item = {
|
const item: MarkdownHeading = {
|
||||||
id: parentId ? `${parentId}/${safeSlugify(name)}` : `section/${safeSlugify(name)}`,
|
id: parentId
|
||||||
|
? `${parentId}/${safeSlugify(name)}`
|
||||||
|
: `${this.parentId || 'section'}/${safeSlugify(name)}`,
|
||||||
name,
|
name,
|
||||||
level,
|
level,
|
||||||
items: [],
|
items: [],
|
||||||
|
@ -102,7 +90,7 @@ export class MarkdownRenderer {
|
||||||
attachHeadingsDescriptions(rawText: string) {
|
attachHeadingsDescriptions(rawText: string) {
|
||||||
const buildRegexp = (heading: MarkdownHeading) => {
|
const buildRegexp = (heading: MarkdownHeading) => {
|
||||||
return new RegExp(
|
return new RegExp(
|
||||||
`##?\\s+${heading.name.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}\s*(\n|\r\n)`,
|
`##?\\s+${heading.name.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}\s*(\n|\r\n|$|\s*)`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,41 +1,12 @@
|
||||||
import {
|
import type { OpenAPISpec, OpenAPIPaths } from '../types';
|
||||||
OpenAPIOperation,
|
|
||||||
OpenAPIParameter,
|
|
||||||
OpenAPISpec,
|
|
||||||
OpenAPITag,
|
|
||||||
Referenced,
|
|
||||||
OpenAPIServer,
|
|
||||||
OpenAPIPaths,
|
|
||||||
} from '../types';
|
|
||||||
import { isOperationName, JsonPointer, alphabeticallyByProp } from '../utils';
|
import { isOperationName, JsonPointer, alphabeticallyByProp } from '../utils';
|
||||||
import { MarkdownRenderer } from './MarkdownRenderer';
|
import { MarkdownRenderer } from './MarkdownRenderer';
|
||||||
import { GroupModel, OperationModel } from './models';
|
import { GroupModel, OperationModel } from './models';
|
||||||
import { OpenAPIParser } from './OpenAPIParser';
|
import type { OpenAPIParser } from './OpenAPIParser';
|
||||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
import type { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||||
|
import type { ContentItemModel, TagGroup, TagInfo, TagsInfoMap } from './types';
|
||||||
export type TagInfo = OpenAPITag & {
|
|
||||||
operations: ExtendedOpenAPIOperation[];
|
|
||||||
used?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ExtendedOpenAPIOperation = {
|
|
||||||
pointer: string;
|
|
||||||
pathName: string;
|
|
||||||
httpVerb: string;
|
|
||||||
pathParameters: Array<Referenced<OpenAPIParameter>>;
|
|
||||||
pathServers: Array<OpenAPIServer> | undefined;
|
|
||||||
isWebhook: boolean;
|
|
||||||
} & OpenAPIOperation;
|
|
||||||
|
|
||||||
export type TagsInfoMap = Record<string, TagInfo>;
|
|
||||||
|
|
||||||
export interface TagGroup {
|
|
||||||
name: string;
|
|
||||||
tags: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GROUP_DEPTH = 0;
|
export const GROUP_DEPTH = 0;
|
||||||
export type ContentItemModel = GroupModel | OperationModel;
|
|
||||||
|
|
||||||
export class MenuBuilder {
|
export class MenuBuilder {
|
||||||
/**
|
/**
|
||||||
|
@ -87,7 +58,7 @@ export class MenuBuilder {
|
||||||
initialDepth: number,
|
initialDepth: number,
|
||||||
options: RedocNormalizedOptions,
|
options: RedocNormalizedOptions,
|
||||||
): ContentItemModel[] {
|
): ContentItemModel[] {
|
||||||
const renderer = new MarkdownRenderer(options);
|
const renderer = new MarkdownRenderer(options, parent?.id);
|
||||||
const headings = renderer.extractHeadings(description || '');
|
const headings = renderer.extractHeadings(description || '');
|
||||||
|
|
||||||
if (headings.length && parent && parent.description) {
|
if (headings.length && parent && parent.description) {
|
||||||
|
@ -256,7 +227,7 @@ export class MenuBuilder {
|
||||||
for (const operationName of operations) {
|
for (const operationName of operations) {
|
||||||
const operationInfo = path[operationName];
|
const operationInfo = path[operationName];
|
||||||
if (path.$ref) {
|
if (path.$ref) {
|
||||||
const resolvedPaths = parser.deref<OpenAPIPaths>(path as OpenAPIPaths);
|
const { resolved: resolvedPaths } = parser.deref<OpenAPIPaths>(path as OpenAPIPaths);
|
||||||
getTags(parser, { [pathName]: resolvedPaths }, isWebhook);
|
getTags(parser, { [pathName]: resolvedPaths }, isWebhook);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,15 @@
|
||||||
import { action, observable, makeObservable } from 'mobx';
|
import { action, observable, makeObservable } from 'mobx';
|
||||||
import { querySelector } from '../utils/dom';
|
import { querySelector } from '../utils/dom';
|
||||||
import { SpecStore } from './models';
|
import { escapeHTMLAttrChars, flattenByProp, SECURITY_SCHEMES_SECTION_PREFIX } from '../utils';
|
||||||
|
|
||||||
import { history as historyInst, HistoryService } from './HistoryService';
|
import { history as historyInst, HistoryService } from './HistoryService';
|
||||||
import { ScrollService } from './ScrollService';
|
|
||||||
|
|
||||||
import { escapeHTMLAttrChars, flattenByProp, SECURITY_SCHEMES_SECTION_PREFIX } from '../utils';
|
|
||||||
import { GROUP_DEPTH } from './MenuBuilder';
|
import { GROUP_DEPTH } from './MenuBuilder';
|
||||||
|
|
||||||
export type MenuItemGroupType = 'group' | 'tag' | 'section';
|
import type { SpecStore } from './models';
|
||||||
export type MenuItemType = MenuItemGroupType | 'operation';
|
import type { ScrollService } from './ScrollService';
|
||||||
|
import type { IMenuItem } from './types';
|
||||||
|
|
||||||
/** Generic interface for MenuItems */
|
/** Generic interface for MenuItems */
|
||||||
export interface IMenuItem {
|
|
||||||
id: string;
|
|
||||||
absoluteIdx?: number;
|
|
||||||
name: string;
|
|
||||||
sidebarLabel: string;
|
|
||||||
description?: string;
|
|
||||||
depth: number;
|
|
||||||
active: boolean;
|
|
||||||
expanded: boolean;
|
|
||||||
items: IMenuItem[];
|
|
||||||
parent?: IMenuItem;
|
|
||||||
deprecated?: boolean;
|
|
||||||
type: MenuItemType;
|
|
||||||
topMargin?: boolean;
|
|
||||||
|
|
||||||
deactivate(): void;
|
|
||||||
activate(): void;
|
|
||||||
|
|
||||||
collapse(): void;
|
|
||||||
expand(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SECTION_ATTR = 'data-section-id';
|
export const SECTION_ATTR = 'data-section-id';
|
||||||
|
|
||||||
|
|
|
@ -1,45 +1,29 @@
|
||||||
import { OpenAPIRef, OpenAPISchema, OpenAPISpec, Referenced } from '../types';
|
import type { OpenAPIRef, OpenAPISchema, OpenAPISpec } from '../types';
|
||||||
|
import { IS_BROWSER, getDefinitionName } from '../utils/';
|
||||||
import { isArray, isBoolean, IS_BROWSER } from '../utils/';
|
|
||||||
import { JsonPointer } from '../utils/JsonPointer';
|
import { JsonPointer } from '../utils/JsonPointer';
|
||||||
import { getDefinitionName, isNamedDefinition } from '../utils/openapi';
|
|
||||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||||
|
import type { MergedOpenAPISchema } from './types';
|
||||||
|
|
||||||
export type MergedOpenAPISchema = OpenAPISchema & { parentRefs?: string[] };
|
const MAX_DEREF_DEPTH = 999; // prevent circular detection crashes by adding hard limit on deref depth
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class to keep track of visited references to avoid
|
|
||||||
* endless recursion because of circular refs
|
|
||||||
*/
|
|
||||||
class RefCounter {
|
|
||||||
_counter = {};
|
|
||||||
|
|
||||||
reset(): void {
|
|
||||||
this._counter = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
visit(ref: string): void {
|
|
||||||
this._counter[ref] = this._counter[ref] ? this._counter[ref] + 1 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
exit(ref: string): void {
|
|
||||||
this._counter[ref] = this._counter[ref] && this._counter[ref] - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
visited(ref: string): boolean {
|
|
||||||
return !!this._counter[ref];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads and keeps spec. Provides raw spec operations
|
* Loads and keeps spec. Provides raw spec operations
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export function pushRef(stack: string[], ref?: string): string[] {
|
||||||
|
return ref && stack[stack.length - 1] !== ref ? [...stack, ref] : stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function concatRefStacks(base: string[], stack?: string[]): string[] {
|
||||||
|
return stack ? base.concat(stack) : base;
|
||||||
|
}
|
||||||
|
|
||||||
export class OpenAPIParser {
|
export class OpenAPIParser {
|
||||||
specUrl?: string;
|
specUrl?: string;
|
||||||
spec: OpenAPISpec;
|
spec: OpenAPISpec;
|
||||||
|
|
||||||
private _refCounter: RefCounter = new RefCounter();
|
private readonly allowMergeRefs: boolean = false;
|
||||||
private allowMergeRefs: boolean = false;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
spec: OpenAPISpec,
|
spec: OpenAPISpec,
|
||||||
|
@ -51,13 +35,13 @@ export class OpenAPIParser {
|
||||||
this.spec = spec;
|
this.spec = spec;
|
||||||
this.allowMergeRefs = spec.openapi.startsWith('3.1');
|
this.allowMergeRefs = spec.openapi.startsWith('3.1');
|
||||||
|
|
||||||
const href = IS_BROWSER ? window.location.href : undefined;
|
const href = IS_BROWSER ? window.location.href : '';
|
||||||
if (typeof specUrl === 'string') {
|
if (typeof specUrl === 'string') {
|
||||||
this.specUrl = new URL(specUrl, href).href;
|
this.specUrl = href ? new URL(specUrl, href).href : specUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(spec: any) {
|
validate(spec: GenericObject): void {
|
||||||
if (spec.openapi === undefined) {
|
if (spec.openapi === undefined) {
|
||||||
throw new Error('Document must be valid OpenAPI 3.0.0 definition');
|
throw new Error('Document must be valid OpenAPI 3.0.0 definition');
|
||||||
}
|
}
|
||||||
|
@ -86,101 +70,92 @@ export class OpenAPIParser {
|
||||||
/**
|
/**
|
||||||
* checks if the object is OpenAPI reference (contains $ref property)
|
* checks if the object is OpenAPI reference (contains $ref property)
|
||||||
*/
|
*/
|
||||||
isRef(obj: any): obj is OpenAPIRef {
|
isRef<T extends unknown>(obj: OpenAPIRef | T): obj is OpenAPIRef {
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
obj = <OpenAPIRef>obj;
|
||||||
return obj.$ref !== undefined && obj.$ref !== null;
|
return obj.$ref !== undefined && obj.$ref !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* resets visited endpoints. should be run after
|
|
||||||
*/
|
|
||||||
resetVisited() {
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
// check in dev mode
|
|
||||||
for (const k in this._refCounter._counter) {
|
|
||||||
if (this._refCounter._counter[k] > 0) {
|
|
||||||
console.warn('Not exited reference: ' + k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._refCounter = new RefCounter();
|
|
||||||
}
|
|
||||||
|
|
||||||
exitRef<T>(ref: Referenced<T>) {
|
|
||||||
if (!this.isRef(ref)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._refCounter.exit(ref.$ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve given reference object or return as is if it is not a reference
|
* Resolve given reference object or return as is if it is not a reference
|
||||||
* @param obj object to dereference
|
* @param obj object to dereference
|
||||||
* @param forceCircular whether to dereference even if it is circular ref
|
* @param forceCircular whether to dereference even if it is circular ref
|
||||||
|
* @param mergeAsAllOf
|
||||||
*/
|
*/
|
||||||
deref<T extends object>(obj: OpenAPIRef | T, forceCircular = false, mergeAsAllOf = false): T {
|
deref<T extends unknown>(
|
||||||
|
obj: OpenAPIRef | T,
|
||||||
|
baseRefsStack: string[] = [],
|
||||||
|
mergeAsAllOf = false,
|
||||||
|
): { resolved: T; refsStack: string[] } {
|
||||||
|
// this can be set by all of when it mergers props from different sources
|
||||||
|
const objRefsStack = obj?.['x-refsStack'];
|
||||||
|
baseRefsStack = concatRefStacks(baseRefsStack, objRefsStack);
|
||||||
|
|
||||||
if (this.isRef(obj)) {
|
if (this.isRef(obj)) {
|
||||||
const schemaName = getDefinitionName(obj.$ref);
|
const schemaName = getDefinitionName(obj.$ref);
|
||||||
if (schemaName && this.options.ignoreNamedSchemas.has(schemaName)) {
|
if (schemaName && this.options.ignoreNamedSchemas.has(schemaName)) {
|
||||||
return { type: 'object', title: schemaName } as T;
|
return { resolved: { type: 'object', title: schemaName } as T, refsStack: baseRefsStack };
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolved = this.byRef<T>(obj.$ref)!;
|
let resolved = this.byRef<T>(obj.$ref);
|
||||||
const visited = this._refCounter.visited(obj.$ref);
|
if (!resolved) {
|
||||||
this._refCounter.visit(obj.$ref);
|
throw new Error(`Failed to resolve $ref "${obj.$ref}"`);
|
||||||
if (visited && !forceCircular) {
|
|
||||||
// circular reference detected
|
|
||||||
// tslint:disable-next-line
|
|
||||||
return Object.assign({}, resolved, { 'x-circular-ref': true });
|
|
||||||
}
|
}
|
||||||
// deref again in case one more $ref is here
|
|
||||||
let result = resolved;
|
let refsStack = baseRefsStack;
|
||||||
if (this.isRef(resolved)) {
|
if (baseRefsStack.includes(obj.$ref) || baseRefsStack.length > MAX_DEREF_DEPTH) {
|
||||||
result = this.deref(resolved, false, mergeAsAllOf);
|
resolved = Object.assign({}, resolved, { 'x-circular-ref': true });
|
||||||
this.exitRef(resolved);
|
} else if (this.isRef(resolved)) {
|
||||||
|
const res = this.deref(resolved, baseRefsStack, mergeAsAllOf);
|
||||||
|
refsStack = res.refsStack;
|
||||||
|
resolved = res.resolved;
|
||||||
}
|
}
|
||||||
return this.allowMergeRefs ? this.mergeRefs(obj, resolved, mergeAsAllOf) : result;
|
|
||||||
|
refsStack = pushRef(baseRefsStack, obj.$ref);
|
||||||
|
resolved = this.allowMergeRefs ? this.mergeRefs(obj, resolved, mergeAsAllOf) : resolved;
|
||||||
|
|
||||||
|
return { resolved, refsStack };
|
||||||
}
|
}
|
||||||
return obj;
|
return {
|
||||||
|
resolved: obj,
|
||||||
|
refsStack: concatRefStacks(baseRefsStack, objRefsStack),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
shallowDeref<T extends unknown>(obj: OpenAPIRef | T): T {
|
mergeRefs<T extends unknown>(ref: OpenAPIRef, resolved: T, mergeAsAllOf: boolean): T {
|
||||||
if (this.isRef(obj)) {
|
|
||||||
const schemaName = getDefinitionName(obj.$ref);
|
|
||||||
if (schemaName && this.options.ignoreNamedSchemas.has(schemaName)) {
|
|
||||||
return { type: 'object', title: schemaName } as T;
|
|
||||||
}
|
|
||||||
const resolved = this.byRef<T>(obj.$ref);
|
|
||||||
return this.allowMergeRefs ? this.mergeRefs(obj, resolved, false) : (resolved as T);
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
mergeRefs(ref, resolved, mergeAsAllOf: boolean) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { $ref, ...rest } = ref;
|
const { $ref, ...rest } = ref;
|
||||||
const keys = Object.keys(rest);
|
const keys = Object.keys(rest);
|
||||||
if (keys.length === 0) {
|
if (keys.length === 0) {
|
||||||
if (this.isRef(resolved)) {
|
|
||||||
return this.shallowDeref(resolved);
|
|
||||||
}
|
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
mergeAsAllOf &&
|
mergeAsAllOf &&
|
||||||
keys.some(k => k !== 'description' && k !== 'title' && k !== 'externalDocs')
|
keys.some(
|
||||||
|
k =>
|
||||||
|
![
|
||||||
|
'description',
|
||||||
|
'title',
|
||||||
|
'externalDocs',
|
||||||
|
'x-refsStack',
|
||||||
|
'x-parentRefs',
|
||||||
|
'readOnly',
|
||||||
|
'writeOnly',
|
||||||
|
].includes(k),
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
|
const { description, title, readOnly, writeOnly, ...restSchema } = rest as OpenAPISchema;
|
||||||
return {
|
return {
|
||||||
allOf: [rest, resolved],
|
allOf: [{ description, title, readOnly, writeOnly }, resolved, restSchema],
|
||||||
};
|
} as T;
|
||||||
} else {
|
} else {
|
||||||
// small optimization
|
// small optimization
|
||||||
return {
|
return {
|
||||||
...resolved,
|
...(resolved as GenericObject),
|
||||||
...rest,
|
...rest,
|
||||||
};
|
} as T;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,18 +164,18 @@ export class OpenAPIParser {
|
||||||
* @param schema schema with allOF
|
* @param schema schema with allOF
|
||||||
* @param $ref pointer of the schema
|
* @param $ref pointer of the schema
|
||||||
* @param forceCircular whether to dereference children even if it is a circular ref
|
* @param forceCircular whether to dereference children even if it is a circular ref
|
||||||
|
* @param used$Refs
|
||||||
*/
|
*/
|
||||||
mergeAllOf(
|
mergeAllOf(
|
||||||
schema: OpenAPISchema,
|
schema: MergedOpenAPISchema,
|
||||||
$ref?: string,
|
$ref: string | undefined,
|
||||||
forceCircular: boolean = false,
|
refsStack: string[],
|
||||||
used$Refs = new Set<string>(),
|
|
||||||
): MergedOpenAPISchema {
|
): MergedOpenAPISchema {
|
||||||
if ($ref) {
|
if (schema['x-circular-ref']) {
|
||||||
used$Refs.add($ref);
|
return schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
schema = this.hoistOneOfs(schema);
|
schema = this.hoistOneOfs(schema, refsStack);
|
||||||
|
|
||||||
if (schema.allOf === undefined) {
|
if (schema.allOf === undefined) {
|
||||||
return schema;
|
return schema;
|
||||||
|
@ -208,8 +183,8 @@ export class OpenAPIParser {
|
||||||
|
|
||||||
let receiver: MergedOpenAPISchema = {
|
let receiver: MergedOpenAPISchema = {
|
||||||
...schema,
|
...schema,
|
||||||
|
'x-parentRefs': [],
|
||||||
allOf: undefined,
|
allOf: undefined,
|
||||||
parentRefs: [],
|
|
||||||
title: schema.title || getDefinitionName($ref),
|
title: schema.title || getDefinitionName($ref),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -221,36 +196,49 @@ export class OpenAPIParser {
|
||||||
receiver.items = { ...receiver.items };
|
receiver.items = { ...receiver.items };
|
||||||
}
|
}
|
||||||
|
|
||||||
const allOfSchemas = schema.allOf
|
const allOfSchemas = uniqByPropIncludeMissing(
|
||||||
.map(subSchema => {
|
schema.allOf
|
||||||
if (subSchema && subSchema.$ref && used$Refs.has(subSchema.$ref)) {
|
.map((subSchema: OpenAPISchema) => {
|
||||||
return undefined;
|
const { resolved, refsStack: subRefsStack } = this.deref(subSchema, refsStack, true);
|
||||||
}
|
|
||||||
|
|
||||||
const resolved = this.deref(subSchema, forceCircular, true);
|
const subRef = subSchema.$ref || undefined;
|
||||||
const subRef = subSchema.$ref || undefined;
|
const subMerged = this.mergeAllOf(resolved, subRef, subRefsStack);
|
||||||
const subMerged = this.mergeAllOf(resolved, subRef, forceCircular, used$Refs);
|
if (subMerged['x-circular-ref'] && subMerged.allOf) {
|
||||||
receiver.parentRefs!.push(...(subMerged.parentRefs || []));
|
// if mergeAllOf is circular and still contains allOf, we should ignore it
|
||||||
return {
|
return undefined;
|
||||||
$ref: subRef,
|
}
|
||||||
schema: subMerged,
|
if (subRef) {
|
||||||
};
|
// collect information for implicit descriminator lookup
|
||||||
})
|
receiver['x-parentRefs']?.push(...(subMerged['x-parentRefs'] || []), subRef);
|
||||||
.filter(child => child !== undefined) as Array<{
|
}
|
||||||
$ref: string | undefined;
|
return {
|
||||||
schema: MergedOpenAPISchema;
|
$ref: subRef,
|
||||||
}>;
|
refsStack: pushRef(subRefsStack, subRef),
|
||||||
|
schema: subMerged,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(child => child !== undefined) as Array<{
|
||||||
|
schema: MergedOpenAPISchema;
|
||||||
|
refsStack: string[];
|
||||||
|
$ref?: string;
|
||||||
|
}>,
|
||||||
|
'$ref',
|
||||||
|
);
|
||||||
|
|
||||||
for (const { $ref: subSchemaRef, schema: subSchema } of allOfSchemas) {
|
for (const { schema: subSchema, refsStack: subRefsStack } of allOfSchemas) {
|
||||||
const {
|
const {
|
||||||
type,
|
type,
|
||||||
enum: enumProperty,
|
enum: enumProperty,
|
||||||
properties,
|
properties,
|
||||||
items,
|
items,
|
||||||
required,
|
required,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
readOnly,
|
||||||
|
writeOnly,
|
||||||
oneOf,
|
oneOf,
|
||||||
anyOf,
|
anyOf,
|
||||||
title,
|
'x-circular-ref': isCircular,
|
||||||
...otherConstraints
|
...otherConstraints
|
||||||
} = subSchema;
|
} = subSchema;
|
||||||
|
|
||||||
|
@ -268,49 +256,57 @@ export class OpenAPIParser {
|
||||||
|
|
||||||
if (enumProperty !== undefined) {
|
if (enumProperty !== undefined) {
|
||||||
if (Array.isArray(enumProperty) && Array.isArray(receiver.enum)) {
|
if (Array.isArray(enumProperty) && Array.isArray(receiver.enum)) {
|
||||||
receiver.enum = [...enumProperty, ...receiver.enum];
|
receiver.enum = Array.from(new Set([...enumProperty, ...receiver.enum]));
|
||||||
} else {
|
} else {
|
||||||
receiver.enum = enumProperty;
|
receiver.enum = enumProperty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (properties !== undefined) {
|
if (properties !== undefined && typeof properties === 'object') {
|
||||||
receiver.properties = receiver.properties || {};
|
receiver.properties = receiver.properties || {};
|
||||||
for (const prop in properties) {
|
for (const prop in properties) {
|
||||||
|
const propRefsStack = concatRefStacks(subRefsStack, properties[prop]?.['x-refsStack']);
|
||||||
if (!receiver.properties[prop]) {
|
if (!receiver.properties[prop]) {
|
||||||
receiver.properties[prop] = properties[prop];
|
receiver.properties[prop] = {
|
||||||
} else {
|
...properties[prop],
|
||||||
|
'x-refsStack': propRefsStack,
|
||||||
|
} as MergedOpenAPISchema;
|
||||||
|
} else if (!isCircular) {
|
||||||
// merge inner properties
|
// merge inner properties
|
||||||
const mergedProp = this.mergeAllOf(
|
const mergedProp = this.mergeAllOf(
|
||||||
{ allOf: [receiver.properties[prop], properties[prop]] },
|
{
|
||||||
|
allOf: [
|
||||||
|
receiver.properties[prop],
|
||||||
|
{ ...properties[prop], 'x-refsStack': propRefsStack } as any,
|
||||||
|
],
|
||||||
|
'x-refsStack': propRefsStack,
|
||||||
|
},
|
||||||
$ref + '/properties/' + prop,
|
$ref + '/properties/' + prop,
|
||||||
|
propRefsStack,
|
||||||
);
|
);
|
||||||
receiver.properties[prop] = mergedProp;
|
receiver.properties[prop] = mergedProp;
|
||||||
this.exitParents(mergedProp); // every prop resolution should have separate recursive stack
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (items !== undefined) {
|
if (items !== undefined && !isCircular) {
|
||||||
const receiverItems = isBoolean(receiver.items)
|
const receiverItems =
|
||||||
? { items: receiver.items }
|
typeof receiver.items === 'boolean'
|
||||||
: receiver.items
|
? {}
|
||||||
? (Object.assign({}, receiver.items) as OpenAPISchema)
|
: (Object.assign({}, receiver.items) as OpenAPISchema);
|
||||||
: {};
|
const subSchemaItems =
|
||||||
const subSchemaItems = isBoolean(items)
|
typeof subSchema.items === 'boolean'
|
||||||
? { items }
|
? {}
|
||||||
: (Object.assign({}, items) as OpenAPISchema);
|
: (Object.assign({}, subSchema.items) as OpenAPISchema);
|
||||||
// merge inner properties
|
// merge inner properties
|
||||||
receiver.items = this.mergeAllOf(
|
receiver.items = this.mergeAllOf(
|
||||||
{ allOf: [receiverItems, subSchemaItems] },
|
{
|
||||||
|
allOf: [receiverItems, subSchemaItems],
|
||||||
|
},
|
||||||
$ref + '/items',
|
$ref + '/items',
|
||||||
|
subRefsStack,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (required !== undefined) {
|
|
||||||
receiver.required = (receiver.required || []).concat(required);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oneOf !== undefined) {
|
if (oneOf !== undefined) {
|
||||||
receiver.oneOf = oneOf;
|
receiver.oneOf = oneOf;
|
||||||
}
|
}
|
||||||
|
@ -319,18 +315,21 @@ export class OpenAPIParser {
|
||||||
receiver.anyOf = anyOf;
|
receiver.anyOf = anyOf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (required !== undefined) {
|
||||||
|
receiver.required = [...(receiver.required || []), ...required];
|
||||||
|
}
|
||||||
|
|
||||||
// merge rest of constraints
|
// merge rest of constraints
|
||||||
// TODO: do more intelligent merge
|
// TODO: do more intelligent merge
|
||||||
receiver = { ...receiver, title: receiver.title || title, ...otherConstraints };
|
receiver = {
|
||||||
|
...receiver,
|
||||||
if (subSchemaRef) {
|
title: receiver.title || title,
|
||||||
receiver.parentRefs!.push(subSchemaRef);
|
description: receiver.description || description,
|
||||||
if (receiver.title === undefined && isNamedDefinition(subSchemaRef)) {
|
readOnly: receiver.readOnly !== undefined ? receiver.readOnly : readOnly,
|
||||||
// this is not so correct behaviour. commented out for now
|
writeOnly: receiver.writeOnly !== undefined ? receiver.writeOnly : writeOnly,
|
||||||
// ref: https://github.com/Redocly/redoc/issues/601
|
'x-circular-ref': receiver['x-circular-ref'] || isCircular,
|
||||||
// receiver.title = JsonPointer.baseName(subSchemaRef);
|
...otherConstraints,
|
||||||
}
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return receiver;
|
return receiver;
|
||||||
|
@ -345,10 +344,12 @@ export class OpenAPIParser {
|
||||||
const res: Record<string, string[]> = {};
|
const res: Record<string, string[]> = {};
|
||||||
const schemas = (this.spec.components && this.spec.components.schemas) || {};
|
const schemas = (this.spec.components && this.spec.components.schemas) || {};
|
||||||
for (const defName in schemas) {
|
for (const defName in schemas) {
|
||||||
const def = this.deref(schemas[defName]);
|
const { resolved: def } = this.deref(schemas[defName]);
|
||||||
if (
|
if (
|
||||||
def.allOf !== undefined &&
|
def.allOf !== undefined &&
|
||||||
def.allOf.find(obj => obj.$ref !== undefined && $refs.indexOf(obj.$ref) > -1)
|
def.allOf.find(
|
||||||
|
(obj: OpenAPISchema) => obj.$ref !== undefined && $refs.indexOf(obj.$ref) > -1,
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
res['#/components/schemas/' + defName] = [def['x-discriminator-value'] || defName];
|
res['#/components/schemas/' + defName] = [def['x-discriminator-value'] || defName];
|
||||||
}
|
}
|
||||||
|
@ -356,13 +357,7 @@ export class OpenAPIParser {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
exitParents(shema: MergedOpenAPISchema) {
|
private hoistOneOfs(schema: OpenAPISchema, refsStack: string[]) {
|
||||||
for (const parent$ref of shema.parentRefs || []) {
|
|
||||||
this.exitRef({ $ref: parent$ref });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private hoistOneOfs(schema: OpenAPISchema) {
|
|
||||||
if (schema.allOf === undefined) {
|
if (schema.allOf === undefined) {
|
||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
|
@ -370,19 +365,15 @@ export class OpenAPIParser {
|
||||||
const allOf = schema.allOf;
|
const allOf = schema.allOf;
|
||||||
for (let i = 0; i < allOf.length; i++) {
|
for (let i = 0; i < allOf.length; i++) {
|
||||||
const sub = allOf[i];
|
const sub = allOf[i];
|
||||||
if (isArray(sub.oneOf)) {
|
if (Array.isArray(sub.oneOf)) {
|
||||||
const beforeAllOf = allOf.slice(0, i);
|
const beforeAllOf = allOf.slice(0, i);
|
||||||
const afterAllOf = allOf.slice(i + 1);
|
const afterAllOf = allOf.slice(i + 1);
|
||||||
return {
|
return {
|
||||||
oneOf: sub.oneOf.map(part => {
|
oneOf: sub.oneOf.map((part: OpenAPISchema) => {
|
||||||
const merged = this.mergeAllOf({
|
return {
|
||||||
allOf: [...beforeAllOf, part, ...afterAllOf],
|
allOf: [...beforeAllOf, part, ...afterAllOf],
|
||||||
});
|
'x-refsStack': refsStack,
|
||||||
|
};
|
||||||
// each oneOf should be independent so exiting all the parent refs
|
|
||||||
// otherwise it will cause false-positive recursive detection
|
|
||||||
this.exitParents(merged);
|
|
||||||
return merged;
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -391,3 +382,15 @@ export class OpenAPIParser {
|
||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unique array by property, missing properties are included
|
||||||
|
*/
|
||||||
|
function uniqByPropIncludeMissing<T extends object>(arr: T[], prop: keyof T): T[] {
|
||||||
|
const seen = new Set();
|
||||||
|
return arr.filter(item => {
|
||||||
|
const k = item[prop];
|
||||||
|
if (!k) return true;
|
||||||
|
return k && !seen.has(k) && seen.add(k);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -2,14 +2,9 @@ import defaultTheme, { ResolvedThemeInterface, resolveTheme, ThemeInterface } fr
|
||||||
import { querySelector } from '../utils/dom';
|
import { querySelector } from '../utils/dom';
|
||||||
import { isArray, isNumeric, mergeObjects } from '../utils/helpers';
|
import { isArray, isNumeric, mergeObjects } from '../utils/helpers';
|
||||||
|
|
||||||
import { LabelsConfigRaw, setRedocLabels } from './Labels';
|
import { setRedocLabels } from './Labels';
|
||||||
import { MDXComponentMeta } from './MarkdownRenderer';
|
import { SideNavStyleEnum } from './types';
|
||||||
|
import type { LabelsConfigRaw, MDXComponentMeta } from './types';
|
||||||
export enum SideNavStyleEnum {
|
|
||||||
SummaryOnly = 'summary-only',
|
|
||||||
PathOnly = 'path-only',
|
|
||||||
IdOnly = 'id-only',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RedocRawOptions {
|
export interface RedocRawOptions {
|
||||||
theme?: ThemeInterface;
|
theme?: ThemeInterface;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { bind } from 'decko';
|
||||||
import * as EventEmitter from 'eventemitter3';
|
import * as EventEmitter from 'eventemitter3';
|
||||||
|
|
||||||
import { IS_BROWSER, querySelector, Throttle } from '../utils';
|
import { IS_BROWSER, querySelector, Throttle } from '../utils';
|
||||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
import type { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||||
|
|
||||||
const EVENT = 'scroll';
|
const EVENT = 'scroll';
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { IS_BROWSER } from '../utils/';
|
import { IS_BROWSER } from '../utils/';
|
||||||
import { IMenuItem } from './MenuStore';
|
import type { IMenuItem } from './types';
|
||||||
import { OperationModel } from './models';
|
import type { OperationModel } from './models';
|
||||||
|
|
||||||
import Worker from './SearchWorker.worker';
|
import Worker from './SearchWorker.worker';
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ export class SearchStore<T> {
|
||||||
const recurse = items => {
|
const recurse = items => {
|
||||||
items.forEach(group => {
|
items.forEach(group => {
|
||||||
if (group.type !== 'group') {
|
if (group.type !== 'group') {
|
||||||
this.add(group.name, group.description || '', group.id);
|
this.add(group.name, (group.description || '').concat(' ', group.path || ''), group.id);
|
||||||
}
|
}
|
||||||
recurse(group.items);
|
recurse(group.items);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import * as lunr from 'lunr';
|
import * as lunr from 'lunr';
|
||||||
|
import type { SearchResult } from './types';
|
||||||
|
|
||||||
/* just for better typings */
|
/* just for better typings */
|
||||||
export default class Worker {
|
export default class Worker {
|
||||||
|
@ -11,17 +12,6 @@ export default class Worker {
|
||||||
fromExternalJS = fromExternalJS;
|
fromExternalJS = fromExternalJS;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchDocument {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SearchResult<T = string> {
|
|
||||||
meta: T;
|
|
||||||
score: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
let store: any[] = [];
|
let store: any[] = [];
|
||||||
|
|
||||||
lunr.tokenizer.separator = /\s+/;
|
lunr.tokenizer.separator = /\s+/;
|
||||||
|
@ -47,7 +37,10 @@ function initEmpty() {
|
||||||
|
|
||||||
initEmpty();
|
initEmpty();
|
||||||
|
|
||||||
const expandTerm = term => '*' + lunr.stemmer(new lunr.Token(term, {})) + '*';
|
const expandTerm = term => {
|
||||||
|
const token = lunr.trimmer(new lunr.Token(term, {}));
|
||||||
|
return '*' + lunr.stemmer(token) + '*';
|
||||||
|
};
|
||||||
|
|
||||||
export function add<T>(title: string, description: string, meta?: T) {
|
export function add<T>(title: string, description: string, meta?: T) {
|
||||||
const ref = store.push(meta) - 1;
|
const ref = store.push(meta) - 1;
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { OpenAPIExternalDocumentation, OpenAPIPath, OpenAPISpec, Referenced } from '../types';
|
import type { OpenAPIExternalDocumentation, OpenAPIPath, OpenAPISpec, Referenced } from '../types';
|
||||||
|
|
||||||
import { ContentItemModel, MenuBuilder } from './MenuBuilder';
|
import { MenuBuilder } from './MenuBuilder';
|
||||||
import { ApiInfoModel } from './models/ApiInfo';
|
import { ApiInfoModel } from './models/ApiInfo';
|
||||||
import { WebhookModel } from './models/Webhook';
|
import { WebhookModel } from './models/Webhook';
|
||||||
import { SecuritySchemesModel } from './models/SecuritySchemes';
|
import { SecuritySchemesModel } from './models/SecuritySchemes';
|
||||||
import { OpenAPIParser } from './OpenAPIParser';
|
import { OpenAPIParser } from './OpenAPIParser';
|
||||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
import type { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||||
|
import type { ContentItemModel } from './types';
|
||||||
/**
|
/**
|
||||||
* Store that contains all the specification related information in the form of tree
|
* Store that contains all the specification related information in the form of tree
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { MarkdownRenderer, MDXComponentMeta } from '../MarkdownRenderer';
|
import type { MDXComponentMeta } from '../types';
|
||||||
|
import { MarkdownRenderer } from '../MarkdownRenderer';
|
||||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
|
|
||||||
const TestComponent = () => null;
|
const TestComponent = () => null;
|
||||||
|
@ -96,4 +97,22 @@ describe('Markdown renderer', () => {
|
||||||
expect(part.component).toBe(TestComponent);
|
expect(part.component).toBe(TestComponent);
|
||||||
expect(part.props).toEqual({ children: ' Test Test ' });
|
expect(part.props).toEqual({ children: ' Test Test ' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should properly extract title from text', () => {
|
||||||
|
const rawTexts = ['text before\n# Test', 'text before\n # Test', 'text before\n# Test\n'];
|
||||||
|
rawTexts.forEach(text => {
|
||||||
|
const headings = renderer.extractHeadings(text);
|
||||||
|
expect(headings).toHaveLength(1);
|
||||||
|
expect(headings[0].name).toEqual('Test');
|
||||||
|
expect(headings[0].description).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
const rawTexts2 = ['# Test \n text after', '# Test \ntext after'];
|
||||||
|
rawTexts2.forEach(text => {
|
||||||
|
const headings = renderer.extractHeadings(text);
|
||||||
|
expect(headings).toHaveLength(1);
|
||||||
|
expect(headings[0].name).toEqual('Test');
|
||||||
|
expect(headings[0].description).toEqual('text after');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -41,14 +41,14 @@ describe('Models', () => {
|
||||||
expect(schema.title).toEqual('Foo');
|
expect(schema.title).toEqual('Foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should merge oneOff to inside allOff', () => {
|
test('should merge oneOf to inside allOff', () => {
|
||||||
// TODO: should hoist
|
// TODO: should hoist
|
||||||
const spec = require('./fixtures/mergeAllOf.json');
|
const spec = require('./fixtures/mergeAllOf.json');
|
||||||
parser = new OpenAPIParser(spec, undefined, opts);
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
const schema = parser.mergeAllOf(spec.components.schemas.Case4);
|
const schema = parser.mergeAllOf(spec.components.schemas.Case4);
|
||||||
expect(schema.title).toEqual('Foo');
|
expect(schema.title).toEqual('Foo');
|
||||||
expect(schema.parentRefs).toHaveLength(1);
|
expect(schema['x-parentRefs']).toHaveLength(1);
|
||||||
expect(schema.parentRefs[0]).toEqual('#/components/schemas/Ref');
|
expect(schema['x-parentRefs'][0]).toEqual('#/components/schemas/Ref');
|
||||||
expect(schema.oneOf).toEqual([{ title: 'Bar' }, { title: 'Baz' }]);
|
expect(schema.oneOf).toEqual([{ title: 'Bar' }, { title: 'Baz' }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ describe('Models', () => {
|
||||||
description: 'Overriden description',
|
description: 'Overriden description',
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(parser.shallowDeref(schemaOrRef)).toMatchSnapshot();
|
expect(parser.deref(schemaOrRef)).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should correct resolve double $ref if no need sibling', () => {
|
test('should correct resolve double $ref if no need sibling', () => {
|
||||||
|
@ -70,7 +70,7 @@ describe('Models', () => {
|
||||||
$ref: '#/components/schemas/Parent',
|
$ref: '#/components/schemas/Parent',
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(parser.deref(schemaOrRef, false, true)).toMatchSnapshot();
|
expect(parser.deref(schemaOrRef, [], true)).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,12 +2,17 @@
|
||||||
|
|
||||||
exports[`Models Schema should correct resolve double $ref if no need sibling 1`] = `
|
exports[`Models Schema should correct resolve double $ref if no need sibling 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"properties": Object {
|
"refsStack": Array [
|
||||||
"test": Object {
|
"#/components/schemas/Parent",
|
||||||
"type": "string",
|
],
|
||||||
|
"resolved": Object {
|
||||||
|
"properties": Object {
|
||||||
|
"test": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
"type": "object",
|
||||||
},
|
},
|
||||||
"type": "object",
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -15,84 +20,84 @@ exports[`Models Schema should hoist oneOfs when mergin allOf 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"oneOf": Array [
|
"oneOf": Array [
|
||||||
Object {
|
Object {
|
||||||
"oneOf": Array [
|
"allOf": Array [
|
||||||
Object {
|
Object {
|
||||||
"allOf": undefined,
|
|
||||||
"parentRefs": Array [],
|
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
"extra": Object {
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"password": Object {
|
|
||||||
"description": "The user's password",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"username": Object {
|
"username": Object {
|
||||||
"description": "The user's name",
|
"description": "The user's name",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"title": undefined,
|
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"allOf": undefined,
|
|
||||||
"parentRefs": Array [],
|
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
"extra": Object {
|
"extra": Object {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
"mobile": Object {
|
|
||||||
"description": "The user's mobile",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"username": Object {
|
|
||||||
"description": "The user's name",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"title": undefined,
|
},
|
||||||
|
Object {
|
||||||
|
"oneOf": Array [
|
||||||
|
Object {
|
||||||
|
"properties": Object {
|
||||||
|
"password": Object {
|
||||||
|
"description": "The user's password",
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"properties": Object {
|
||||||
|
"mobile": Object {
|
||||||
|
"description": "The user's mobile",
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"x-refsStack": undefined,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"oneOf": Array [
|
"allOf": Array [
|
||||||
Object {
|
Object {
|
||||||
"allOf": undefined,
|
|
||||||
"parentRefs": Array [],
|
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
"email": Object {
|
"email": Object {
|
||||||
"description": "The user's email",
|
"description": "The user's email",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
"extra": Object {
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"password": Object {
|
|
||||||
"description": "The user's password",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"title": undefined,
|
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"allOf": undefined,
|
|
||||||
"parentRefs": Array [],
|
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
"email": Object {
|
|
||||||
"description": "The user's email",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"extra": Object {
|
"extra": Object {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
"mobile": Object {
|
|
||||||
"description": "The user's mobile",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"title": undefined,
|
},
|
||||||
|
Object {
|
||||||
|
"oneOf": Array [
|
||||||
|
Object {
|
||||||
|
"properties": Object {
|
||||||
|
"password": Object {
|
||||||
|
"description": "The user's password",
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"properties": Object {
|
||||||
|
"mobile": Object {
|
||||||
|
"description": "The user's mobile",
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"x-refsStack": undefined,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -100,7 +105,12 @@ Object {
|
||||||
|
|
||||||
exports[`Models Schema should override description from $ref of the referenced component, when sibling description exists 1`] = `
|
exports[`Models Schema should override description from $ref of the referenced component, when sibling description exists 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"description": "Overriden description",
|
"refsStack": Array [
|
||||||
"type": "object",
|
"#/components/schemas/Test",
|
||||||
|
],
|
||||||
|
"resolved": Object {
|
||||||
|
"description": "Overriden description",
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -25,6 +25,26 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"nestedObjectProp": {
|
||||||
|
"type": "object",
|
||||||
|
"patternProperties": {
|
||||||
|
".*": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nestedArrayProp": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"patternProperties": {
|
||||||
|
".*": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,6 +133,21 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Case6": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/Tag"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Case7": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"array_field": {
|
||||||
|
"$ref": "#/components/schemas/AnyArray"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Cat": {
|
"Cat": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -148,6 +163,33 @@
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Tag": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"description": "Tag ID",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Id"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "tag name",
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"readOnly": true
|
||||||
|
},
|
||||||
|
"AnyArray": {
|
||||||
|
"type": "array",
|
||||||
|
"items": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,6 +133,21 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Case6": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/Tag"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Case7": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"array_field": {
|
||||||
|
"$ref": "#/components/schemas/AnyArray"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Cat": {
|
"Cat": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -148,6 +163,33 @@
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Tag": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"description": "Tag ID",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Id"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"description": "tag name",
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"readOnly": true
|
||||||
|
},
|
||||||
|
"AnyArray": {
|
||||||
|
"type": "array",
|
||||||
|
"items": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,30 @@ describe('Models', () => {
|
||||||
expect(info.summary).toEqual('Test summary\nsome text\n## Heading\n test');
|
expect(info.summary).toEqual('Test summary\nsome text\n## Heading\n test');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should correctly populate description when 2nd line is started by white space', () => {
|
||||||
|
parser.spec = {
|
||||||
|
openapi: '3.0.0',
|
||||||
|
info: {
|
||||||
|
description: 'text before\n # Test',
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const info = new ApiInfoModel(parser);
|
||||||
|
expect(info.description).toEqual('text before\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should correctly populate description when 2nd line is only white space', () => {
|
||||||
|
parser.spec = {
|
||||||
|
openapi: '3.0.0',
|
||||||
|
info: {
|
||||||
|
description: 'text before\n \n # Test',
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const info = new ApiInfoModel(parser);
|
||||||
|
expect(info.description).toEqual('text before\n');
|
||||||
|
});
|
||||||
|
|
||||||
test('should correctly populate license identifier', () => {
|
test('should correctly populate license identifier', () => {
|
||||||
parser.spec = {
|
parser.spec = {
|
||||||
openapi: '3.1.0',
|
openapi: '3.1.0',
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { parseYaml } from '@redocly/openapi-core';
|
||||||
|
import { outdent } from 'outdent';
|
||||||
import { RequestBodyModel } from '../../models/RequestBody';
|
import { RequestBodyModel } from '../../models/RequestBody';
|
||||||
import { OpenAPIParser } from '../../OpenAPIParser';
|
import { OpenAPIParser } from '../../OpenAPIParser';
|
||||||
import { RedocNormalizedOptions } from '../../RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from '../../RedocNormalizedOptions';
|
||||||
|
@ -23,5 +25,55 @@ describe('Models', () => {
|
||||||
expect(consoleError).not.toHaveBeenCalled();
|
expect(consoleError).not.toHaveBeenCalled();
|
||||||
expect(req).toEqual({ description: '', required: false });
|
expect(req).toEqual({ description: '', required: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should have correct field data when it includes allOf', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
paths:
|
||||||
|
/user:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- user
|
||||||
|
summary: Create user
|
||||||
|
description: This can only be done by the logged in user.
|
||||||
|
operationId: createUser
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/User'
|
||||||
|
description: Created user object
|
||||||
|
required: true
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
User:
|
||||||
|
allOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: correct description name
|
||||||
|
readOnly: false
|
||||||
|
writeOnly: false
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/NameField'
|
||||||
|
NameField:
|
||||||
|
description: name description
|
||||||
|
readOnly: true
|
||||||
|
writeOnly: true
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const req = new RequestBodyModel({
|
||||||
|
parser,
|
||||||
|
infoOrRef: spec.paths['/user'].post.requestBody,
|
||||||
|
options: opts,
|
||||||
|
isEvent: false,
|
||||||
|
});
|
||||||
|
const nameField = req.content?.mediaTypes[0].schema?.fields?.[0];
|
||||||
|
expect(nameField?.schema.readOnly).toBe(false);
|
||||||
|
expect(nameField?.schema.writeOnly).toBe(false);
|
||||||
|
expect(nameField?.description).toMatchInlineSnapshot(`"correct description name"`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import { parseYaml } from '@redocly/openapi-core';
|
||||||
|
import { outdent } from 'outdent';
|
||||||
|
|
||||||
import { ResponseModel } from '../../models/Response';
|
import { ResponseModel } from '../../models/Response';
|
||||||
import { OpenAPIParser } from '../../OpenAPIParser';
|
import { OpenAPIParser } from '../../OpenAPIParser';
|
||||||
import { RedocNormalizedOptions } from '../../RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from '../../RedocNormalizedOptions';
|
||||||
|
@ -53,5 +56,81 @@ describe('Models', () => {
|
||||||
expect(Object.keys(resp.extensions).length).toEqual(1);
|
expect(Object.keys(resp.extensions).length).toEqual(1);
|
||||||
expect(resp.extensions['x-example']).toEqual({ a: 1 });
|
expect(resp.extensions['x-example']).toEqual({ a: 1 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should get correct sibling in responses for openapi 3.1', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.1.0
|
||||||
|
paths:
|
||||||
|
/test:
|
||||||
|
get:
|
||||||
|
operationId: test
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Overridden description
|
||||||
|
$ref: "#/components/responses/Successful"
|
||||||
|
components:
|
||||||
|
responses:
|
||||||
|
Successful:
|
||||||
|
description: successful operation
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
successful:
|
||||||
|
type: boolean
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const code = '200';
|
||||||
|
const responseModel = new ResponseModel({
|
||||||
|
parser: parser,
|
||||||
|
code: code,
|
||||||
|
defaultAsError: false,
|
||||||
|
infoOrRef: spec.paths['/test'].get.responses[code],
|
||||||
|
options: opts,
|
||||||
|
isEvent: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(responseModel.summary).toBe('Overridden description');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not override description in responses for openapi 3.0', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
paths:
|
||||||
|
/test:
|
||||||
|
get:
|
||||||
|
operationId: test
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Overridden description
|
||||||
|
$ref: "#/components/responses/Successful"
|
||||||
|
components:
|
||||||
|
responses:
|
||||||
|
Successful:
|
||||||
|
description: successful operation
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
successful:
|
||||||
|
type: boolean
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const code = '200';
|
||||||
|
const responseModel = new ResponseModel({
|
||||||
|
parser: parser,
|
||||||
|
code: code,
|
||||||
|
defaultAsError: false,
|
||||||
|
infoOrRef: spec.paths['/test'].get.responses[code],
|
||||||
|
options: opts,
|
||||||
|
isEvent: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(responseModel.summary).toBe('successful operation');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
730
src/services/__tests__/models/Schema.circular.test.ts
Normal file
730
src/services/__tests__/models/Schema.circular.test.ts
Normal file
|
@ -0,0 +1,730 @@
|
||||||
|
import outdent from 'outdent';
|
||||||
|
import { parseYaml } from '@redocly/openapi-core';
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
import { SchemaModel } from '../../models';
|
||||||
|
import { OpenAPIParser } from '../../OpenAPIParser';
|
||||||
|
import { RedocNormalizedOptions } from '../../RedocNormalizedOptions';
|
||||||
|
|
||||||
|
import { circularDetailsPrinter, printSchema } from './helpers';
|
||||||
|
|
||||||
|
const opts = new RedocNormalizedOptions({}) as RedocNormalizedOptions;
|
||||||
|
|
||||||
|
describe('Models', () => {
|
||||||
|
describe.only('Schema Circular tracking', () => {
|
||||||
|
let parser;
|
||||||
|
|
||||||
|
expect.addSnapshotSerializer({
|
||||||
|
test: val => typeof val === 'string',
|
||||||
|
print: v => v as string,
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should detect circular for array nested in allOf', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
a: { $ref: '#/components/schemas/Schema' }
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
spec.components.schemas.Schema,
|
||||||
|
'#/components/schemas/Schema',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(
|
||||||
|
`a: <object> !circular`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not detect circular refs when ref used multiple times across allOf', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Foo:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
foo: { type: string }
|
||||||
|
Schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/Foo'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
foobar: { $ref: '#/components/schemas/Foo' }
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(parser, spec.components.schemas.Schema, '', opts);
|
||||||
|
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||||
|
foo: <string>
|
||||||
|
foobar:
|
||||||
|
foo: <string>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should detect circular for array with self-reference', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Array:
|
||||||
|
type: "array"
|
||||||
|
items: { "$ref": "#/components/schemas/Array" }
|
||||||
|
Schema:
|
||||||
|
allOf: [{ "$ref": "#/components/schemas/Array" }]
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
spec.components.schemas.Schema,
|
||||||
|
'#/components/schemas/Schema',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(
|
||||||
|
`[<array> !circular]`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should detect circular for object nested in allOf', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Object:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/Object'
|
||||||
|
- type: "object"
|
||||||
|
properties: { "a": { "$ref": "#/components/schemas/Object" } }
|
||||||
|
Schema:
|
||||||
|
allOf: [{ "$ref": "#/components/schemas/Object" }]
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
spec.components.schemas.Schema,
|
||||||
|
'#/components/schemas/Schema',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(
|
||||||
|
`a: <any> !circular`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not detect circular for base DTO case', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
BaseDTO:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id: {type: string}
|
||||||
|
BaseB:
|
||||||
|
type: object
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/BaseDTO'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
fieldB: { type: string }
|
||||||
|
BaseA:
|
||||||
|
type: object
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/BaseDTO'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
b: { $ref: '#/components/schemas/BaseB' }
|
||||||
|
fieldA: { type: string }
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(parser, spec.components.schemas.BaseA, '', opts);
|
||||||
|
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||||
|
id: <string>
|
||||||
|
b:
|
||||||
|
id: <string>
|
||||||
|
fieldB: <string>
|
||||||
|
fieldA: <string>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should detect circular ref for self referencing discriminator', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
SelfComponentDto:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
self:
|
||||||
|
type: object
|
||||||
|
discriminator:
|
||||||
|
propertyName: schemaId
|
||||||
|
mapping:
|
||||||
|
title: '#/components/schemas/SelfComponentDto'
|
||||||
|
oneOf:
|
||||||
|
- $ref: '#/components/schemas/SelfComponentDto'
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(parser, spec.components.schemas.SelfComponentDto, '', opts);
|
||||||
|
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||||
|
self: oneOf
|
||||||
|
title ->
|
||||||
|
self: oneOf
|
||||||
|
title -> <object> !circular
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should detect circular with nested oneOf hoisting', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Node:
|
||||||
|
type: 'object'
|
||||||
|
allOf:
|
||||||
|
- oneOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
parent:
|
||||||
|
$ref: '#/components/schemas/Node'
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(parser, spec.components.schemas.Node, '', opts);
|
||||||
|
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||||
|
oneOf
|
||||||
|
object ->
|
||||||
|
parent: oneOf
|
||||||
|
object ->
|
||||||
|
parent: <object> !circular
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should detect simple props recursion', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
PropRecursion:
|
||||||
|
properties:
|
||||||
|
children:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
a:
|
||||||
|
$ref: '#/components/schemas/PropRecursion'
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(parser, spec.components.schemas.PropRecursion, '', opts);
|
||||||
|
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||||
|
children:
|
||||||
|
a:
|
||||||
|
children:
|
||||||
|
a: <object> !circular
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should detect recursion for props with type array', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
PropsRecursion:
|
||||||
|
properties:
|
||||||
|
children:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/PropsRecursion'
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(parser, spec.components.schemas.PropsRecursion, '', opts);
|
||||||
|
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||||
|
children: [
|
||||||
|
children: [<object> !circular]
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should detect and ignore allOf recursion', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Parent:
|
||||||
|
$ref: '#/components/schemas/Child'
|
||||||
|
Child:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/Parent'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
a:
|
||||||
|
type: string
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
spec.components.schemas.Child,
|
||||||
|
'#/components/schemas/Child',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`a: <string>`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should detect and ignore allOf recursion in nested prop', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
ExternalReference:
|
||||||
|
type: object
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/CompanyReference'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
externalId: { type: string }
|
||||||
|
CompanyReference:
|
||||||
|
type: object
|
||||||
|
required: [ guid, externalId ]
|
||||||
|
properties:
|
||||||
|
guid: { type: string }
|
||||||
|
nestedRecursive: { $ref: '#/components/schemas/ExternalReference' }
|
||||||
|
Entity:
|
||||||
|
type: object
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/ExternalReference'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
directRecursive: { $ref: '#/components/schemas/ExternalReference' }
|
||||||
|
selfRecursive: { $ref: '#/components/schemas/Entity' }
|
||||||
|
anotherField: { $ref: '#/components/schemas/AnotherEntity' }
|
||||||
|
AnotherEntity:
|
||||||
|
type: object
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/CompanyReference'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
someField: { type: number }
|
||||||
|
anotherSelfRecursive: { $ref: '#/components/schemas/AnotherEntity' }
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
spec.components.schemas.Entity,
|
||||||
|
'#/components/schemas/Entity',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: this has a little issue with too early detection in anotherField -> nestedRecursive
|
||||||
|
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||||
|
guid*: <string>
|
||||||
|
nestedRecursive: <object> !circular
|
||||||
|
externalId*: <string>
|
||||||
|
directRecursive:
|
||||||
|
guid*: <string>
|
||||||
|
nestedRecursive: <object> !circular
|
||||||
|
externalId*: <string>
|
||||||
|
selfRecursive: <object> !circular
|
||||||
|
anotherField:
|
||||||
|
guid*: <string>
|
||||||
|
nestedRecursive: <object> !circular
|
||||||
|
someField: <number>
|
||||||
|
anotherSelfRecursive: <object> !circular
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should detect and ignore allOf with discriminator recursion', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Pet:
|
||||||
|
type: object
|
||||||
|
required: [ petType ]
|
||||||
|
discriminator:
|
||||||
|
propertyName: petType
|
||||||
|
mapping:
|
||||||
|
cat: '#/components/schemas/Cat'
|
||||||
|
dog: '#/components/schemas/Dog'
|
||||||
|
properties:
|
||||||
|
category: { $ref: '#/components/schemas/Category' }
|
||||||
|
status: { type: string }
|
||||||
|
friend:
|
||||||
|
allOf: [{ $ref: '#/components/schemas/Pet' }]
|
||||||
|
petType: { type: string }
|
||||||
|
Cat:
|
||||||
|
description: A representation of a cat
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/Pet'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
huntingSkill: { type: string }
|
||||||
|
Dog:
|
||||||
|
description: A representation of a dog
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/Pet'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
packSize: { type: integer }
|
||||||
|
Category:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name: { type: string }
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
spec.components.schemas.Pet,
|
||||||
|
'#/components/schemas/Pet',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||||
|
oneOf
|
||||||
|
cat ->
|
||||||
|
category:
|
||||||
|
name: <string>
|
||||||
|
status: <string>
|
||||||
|
friend: <object> !circular
|
||||||
|
petType*: <string>
|
||||||
|
huntingSkill: <string>
|
||||||
|
dog ->
|
||||||
|
category:
|
||||||
|
name: <string>
|
||||||
|
status: <string>
|
||||||
|
friend: <object> !circular
|
||||||
|
petType*: <string>
|
||||||
|
packSize: <integer>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should detect and recursion on the right level with array of discriminators', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Pet:
|
||||||
|
type: object
|
||||||
|
required: [ petType ]
|
||||||
|
discriminator:
|
||||||
|
propertyName: petType
|
||||||
|
mapping:
|
||||||
|
cat: '#/components/schemas/Cat'
|
||||||
|
dog: '#/components/schemas/Dog'
|
||||||
|
properties:
|
||||||
|
category: { $ref: '#/components/schemas/Category' }
|
||||||
|
status: { type: string }
|
||||||
|
friend:
|
||||||
|
allOf: [{ $ref: '#/components/schemas/Pet' }]
|
||||||
|
petType: { type: string }
|
||||||
|
Cat:
|
||||||
|
description: A representation of a cat
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/Pet'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
huntingSkill: { type: string }
|
||||||
|
Dog:
|
||||||
|
description: A representation of a dog
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/Pet'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
packSize: { type: integer }
|
||||||
|
Category:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name: { type: string }
|
||||||
|
Response:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Pet'
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
spec.components.schemas.Response,
|
||||||
|
'#/components/schemas/Response',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
oneOf
|
||||||
|
cat ->
|
||||||
|
category:
|
||||||
|
name: <string>
|
||||||
|
status: <string>
|
||||||
|
friend: <object> !circular
|
||||||
|
petType*: <string>
|
||||||
|
huntingSkill: <string>
|
||||||
|
dog ->
|
||||||
|
category:
|
||||||
|
name: <string>
|
||||||
|
status: <string>
|
||||||
|
friend: <object> !circular
|
||||||
|
petType*: <string>
|
||||||
|
packSize: <integer>
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should detect and recursion with discriminator and oneOf', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
User:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pet:
|
||||||
|
oneOf:
|
||||||
|
- $ref: '#/components/schemas/Pet'
|
||||||
|
Pet:
|
||||||
|
type: object
|
||||||
|
required: [ petType ]
|
||||||
|
discriminator:
|
||||||
|
propertyName: petType
|
||||||
|
mapping:
|
||||||
|
cat: '#/components/schemas/Cat'
|
||||||
|
dog: '#/components/schemas/Dog'
|
||||||
|
properties:
|
||||||
|
category: { $ref: '#/components/schemas/Category' }
|
||||||
|
status: { type: string }
|
||||||
|
friend:
|
||||||
|
allOf: [{ $ref: '#/components/schemas/Pet' }]
|
||||||
|
petType: { type: string }
|
||||||
|
Cat:
|
||||||
|
description: A representation of a cat
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/Pet'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
huntingSkill: { type: string }
|
||||||
|
Dog:
|
||||||
|
description: A representation of a dog
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/Pet'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
packSize: { type: integer }
|
||||||
|
Category:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name: { type: string }
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
spec.components.schemas.User,
|
||||||
|
'#/components/schemas/User',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||||
|
pet: oneOf
|
||||||
|
Pet -> oneOf
|
||||||
|
cat ->
|
||||||
|
category:
|
||||||
|
name: <string>
|
||||||
|
status: <string>
|
||||||
|
friend: <object> !circular
|
||||||
|
petType*: <string>
|
||||||
|
huntingSkill: <string>
|
||||||
|
dog ->
|
||||||
|
category:
|
||||||
|
name: <string>
|
||||||
|
status: <string>
|
||||||
|
friend: <object> !circular
|
||||||
|
petType*: <string>
|
||||||
|
packSize: <integer>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should detect and recursion with nested oneOf', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Tag:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
description: Tag name
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
items:
|
||||||
|
oneOf:
|
||||||
|
- $ref: "#/components/schemas/Tag"
|
||||||
|
User:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pet:
|
||||||
|
oneOf:
|
||||||
|
- $ref: '#/components/schemas/Tag'
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
spec.components.schemas.User,
|
||||||
|
'#/components/schemas/User',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||||
|
pet: oneOf
|
||||||
|
Tag ->
|
||||||
|
name: <string> (Tag name)
|
||||||
|
items: oneOf
|
||||||
|
Tag -> <object> !circular
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not use discriminator for direct schemas refs in oneOf/anyOf', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Parent:
|
||||||
|
type: object
|
||||||
|
discriminator:
|
||||||
|
propertyName: type
|
||||||
|
mapping:
|
||||||
|
foo: '#/components/schemas/Foo'
|
||||||
|
bar: '#/components/schemas/Bar'
|
||||||
|
baz: '#/components/schemas/Baz'
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
Foo:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/Parent'
|
||||||
|
- type: object
|
||||||
|
Bar:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/Parent'
|
||||||
|
- type: object
|
||||||
|
Baz:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/Parent'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
nested:
|
||||||
|
anyOf:
|
||||||
|
- $ref: '#/components/schemas/Foo'
|
||||||
|
- $ref: '#/components/schemas/Bar'
|
||||||
|
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
spec.components.schemas.Parent,
|
||||||
|
'#/components/schemas/Parent',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||||
|
oneOf
|
||||||
|
foo ->
|
||||||
|
type: <string>
|
||||||
|
bar ->
|
||||||
|
type: <string>
|
||||||
|
baz ->
|
||||||
|
type: <string>
|
||||||
|
nested: oneOf
|
||||||
|
Foo ->
|
||||||
|
type: <string>
|
||||||
|
Bar ->
|
||||||
|
type: <string>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should detect and recursion with nested oneOf case same schema', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Test:
|
||||||
|
allOf:
|
||||||
|
- type: object
|
||||||
|
required:
|
||||||
|
- "@relations"
|
||||||
|
properties:
|
||||||
|
"@relations":
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
A:
|
||||||
|
$ref: "#/components/schemas/A"
|
||||||
|
- type: object
|
||||||
|
required:
|
||||||
|
- "@relations"
|
||||||
|
properties:
|
||||||
|
"@relations":
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
A:
|
||||||
|
$ref: "#/components/schemas/A"
|
||||||
|
A:
|
||||||
|
type: object
|
||||||
|
description: Description
|
||||||
|
properties:
|
||||||
|
B:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
oneOf:
|
||||||
|
- type: object
|
||||||
|
- title: tableLookup
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
fallback:
|
||||||
|
type: array
|
||||||
|
default: []
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/A/properties/B"
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
spec.components.schemas.Test,
|
||||||
|
'#/components/schemas/Test',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(printSchema(schema, circularDetailsPrinter)).toMatchInlineSnapshot(`
|
||||||
|
@relations*:
|
||||||
|
A:
|
||||||
|
B: [
|
||||||
|
oneOf
|
||||||
|
object -> <object>
|
||||||
|
tableLookup ->
|
||||||
|
fallback: [
|
||||||
|
[
|
||||||
|
oneOf
|
||||||
|
object -> <object>
|
||||||
|
tableLookup ->
|
||||||
|
fallback: [<array> !circular]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,7 +1,11 @@
|
||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
import { parseYaml } from '@redocly/openapi-core';
|
||||||
|
import { outdent } from 'outdent';
|
||||||
|
import { MediaTypeModel } from '../../models';
|
||||||
import { SchemaModel } from '../../models/Schema';
|
import { SchemaModel } from '../../models/Schema';
|
||||||
import { OpenAPIParser } from '../../OpenAPIParser';
|
import { OpenAPIParser } from '../../OpenAPIParser';
|
||||||
import { RedocNormalizedOptions } from '../../RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from '../../RedocNormalizedOptions';
|
||||||
|
import { enumDetailsPrinter, printSchema } from './helpers';
|
||||||
|
|
||||||
const opts = new RedocNormalizedOptions({});
|
const opts = new RedocNormalizedOptions({});
|
||||||
|
|
||||||
|
@ -107,11 +111,22 @@ describe('Models', () => {
|
||||||
const spec = require('../fixtures/3.1/patternProperties.json');
|
const spec = require('../fixtures/3.1/patternProperties.json');
|
||||||
parser = new OpenAPIParser(spec, undefined, opts);
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
const schema = new SchemaModel(parser, spec.components.schemas.Patterns, '', 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).toHaveLength(4);
|
||||||
expect(schema.fields![0].schema.type).toEqual('string');
|
expect(schema.fields![0].kind).toEqual('field');
|
||||||
expect(schema.fields![1].kind).toEqual('patternProperties');
|
expect(schema.fields![0].name).toEqual('nestedObjectProp');
|
||||||
expect(schema.fields![1].schema.type).toEqual('object');
|
expect(schema.fields![0].schema.type).toEqual('object');
|
||||||
|
expect(schema.fields![0].schema.fields![0].kind).toEqual('patternProperties');
|
||||||
|
|
||||||
|
expect(schema.fields).toHaveLength(4);
|
||||||
|
expect(schema.fields![1].kind).toEqual('field');
|
||||||
|
expect(schema.fields![1].name).toEqual('nestedArrayProp');
|
||||||
|
expect(schema.fields![1].schema.items!.fields![0].kind).toEqual('patternProperties');
|
||||||
|
|
||||||
|
expect(schema.fields![2].kind).toEqual('patternProperties');
|
||||||
|
expect(schema.fields![2].schema.type).toEqual('string');
|
||||||
|
expect(schema.fields![3].kind).toEqual('patternProperties');
|
||||||
|
expect(schema.fields![3].schema.type).toEqual('object');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('type array', () => {
|
describe('type array', () => {
|
||||||
|
@ -212,6 +227,340 @@ describe('Models', () => {
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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.Case6, '', opts);
|
||||||
|
expect(schema.type).toBe('array');
|
||||||
|
expect(schema.typePrefix).toBe('Array of ');
|
||||||
|
expect(schema.items?.fields).toHaveLength(2);
|
||||||
|
expect(schema.items?.pointer).toEqual('#/components/schemas/Tag');
|
||||||
|
expect(schema.isPrimitive).toBe(false);
|
||||||
|
expect(schema.items?.isPrimitive).toBe(false);
|
||||||
|
expect(schema.minItems).toBe(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test.each(eachArray)(
|
||||||
|
'schemaDefinition should resolve items with boolean type',
|
||||||
|
specFixture => {
|
||||||
|
const spec = require(specFixture);
|
||||||
|
const parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(parser, spec.components.schemas.Case7, '', opts);
|
||||||
|
expect(schema.fields?.[0].schema?.type).toBe('array');
|
||||||
|
expect(schema.fields?.[0].schema?.typePrefix).toBe('Array of ');
|
||||||
|
expect(schema.fields?.[0].schema.items?.displayType).toBe('any');
|
||||||
|
expect(schema?.fields).toHaveLength(1);
|
||||||
|
expect(schema.fields?.[0].schema.pointer).toEqual('#/components/schemas/AnyArray');
|
||||||
|
expect(schema.fields?.[0].schema.isPrimitive).toBe(true);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should get correct fields data if it includes allOf', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
User:
|
||||||
|
allOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: correct description name
|
||||||
|
readOnly: false
|
||||||
|
writeOnly: false
|
||||||
|
allOf:
|
||||||
|
- '#/components/schemas/NameField'
|
||||||
|
NameField:
|
||||||
|
type: object
|
||||||
|
description: name description
|
||||||
|
readOnly: true
|
||||||
|
writeOnly: false
|
||||||
|
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
spec.components.schemas.User,
|
||||||
|
'#/components/schemas/User',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
const fieldSchema = schema.fields?.[0].schema;
|
||||||
|
expect(fieldSchema?.readOnly).toBe(false);
|
||||||
|
expect(fieldSchema?.writeOnly).toBe(false);
|
||||||
|
expect(printSchema(schema)).toMatchInlineSnapshot(`
|
||||||
|
"name: <string> (correct description name)
|
||||||
|
allOf: <any>"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
describe('enum values', () => {
|
||||||
|
test('should get correct fields enum fields without duplication', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
StringField: { type: string, title: StringField, enum: [A, B, C] }
|
||||||
|
FieldA: { type: string, title: FieldA, enum: [A1, A2, A3] }
|
||||||
|
FieldB: { type: string, title: FieldB, enum: [B1, B2, B3] }
|
||||||
|
FieldC: { type: string, title: FieldC, enum: [C1, C2, C3] }
|
||||||
|
ObjectWithAllOf:
|
||||||
|
title: StringFilter
|
||||||
|
type: object
|
||||||
|
allOf:
|
||||||
|
- properties:
|
||||||
|
type: { type: string, enum: [STRING] }
|
||||||
|
field: { $ref: '#/components/schemas/StringField' }
|
||||||
|
required: [type, field, values]
|
||||||
|
- oneOf:
|
||||||
|
- properties:
|
||||||
|
field: { type: string, enum: [A] }
|
||||||
|
values: { type: array, items: { $ref: '#/components/schemas/FieldA' } }
|
||||||
|
- properties:
|
||||||
|
field: { type: string, enum: [B] }
|
||||||
|
values: { type: array, items: { $ref: '#/components/schemas/FieldB' } }
|
||||||
|
- properties:
|
||||||
|
field: { type: string, enum: [C] }
|
||||||
|
values: { type: array, items: { $ref: '#/components/schemas/FieldC' } }
|
||||||
|
ObjectWithOneOf:
|
||||||
|
title: StringFilter
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
type: { type: string, enum: [STRING] }
|
||||||
|
field: { $ref: '#/components/schemas/StringField' }
|
||||||
|
required: [type, field, values]
|
||||||
|
oneOf:
|
||||||
|
- properties:
|
||||||
|
field: { type: string, enum: [A] }
|
||||||
|
values: { type: array, items: { $ref: '#/components/schemas/FieldA' } }
|
||||||
|
- properties:
|
||||||
|
field: { type: string, enum: [B] }
|
||||||
|
values: { type: array, items: { $ref: '#/components/schemas/FieldB' } }
|
||||||
|
- properties:
|
||||||
|
field: { type: string, enum: [C] }
|
||||||
|
values: { type: array, items: { $ref: '#/components/schemas/FieldC' } }
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schemaWithOneOf = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
spec.components.schemas.ObjectWithOneOf,
|
||||||
|
'#/components/schemas/ObjectWithOneOf',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
expect(printSchema(schemaWithOneOf, enumDetailsPrinter)).toMatchInlineSnapshot(`
|
||||||
|
"oneOf
|
||||||
|
StringFilter ->
|
||||||
|
field*: <string>enum: [A,B,C]
|
||||||
|
values*: [<string>enum: [A1,A2,A3]]
|
||||||
|
type*: <string>enum: [STRING]
|
||||||
|
StringFilter ->
|
||||||
|
field*: <string>enum: [A,B,C]
|
||||||
|
values*: [<string>enum: [B1,B2,B3]]
|
||||||
|
type*: <string>enum: [STRING]
|
||||||
|
StringFilter ->
|
||||||
|
field*: <string>enum: [A,B,C]
|
||||||
|
values*: [<string>enum: [C1,C2,C3]]
|
||||||
|
type*: <string>enum: [STRING]"
|
||||||
|
`);
|
||||||
|
|
||||||
|
const schemaWithAllOf = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
spec.components.schemas.ObjectWithAllOf,
|
||||||
|
'#/components/schemas/ObjectWithAllOf',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
expect(printSchema(schemaWithAllOf, enumDetailsPrinter)).toMatchInlineSnapshot(`
|
||||||
|
"oneOf
|
||||||
|
object ->
|
||||||
|
type*: <string>enum: [STRING]
|
||||||
|
field*: <string>enum: [A,B,C]
|
||||||
|
values*: [<string>enum: [A1,A2,A3]]
|
||||||
|
object ->
|
||||||
|
type*: <string>enum: [STRING]
|
||||||
|
field*: <string>enum: [B,A,C]
|
||||||
|
values*: [<string>enum: [B1,B2,B3]]
|
||||||
|
object ->
|
||||||
|
type*: <string>enum: [STRING]
|
||||||
|
field*: <string>enum: [C,A,B]
|
||||||
|
values*: [<string>enum: [C1,C2,C3]]"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should get correct fields enum limits', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
StringField: { type: string, title: StringField, enum: [A, B, C] }
|
||||||
|
FieldA: { type: string, title: FieldA, enum: [A1, A2, A3] }
|
||||||
|
FieldB: { type: string, title: FieldB, enum: [B1, B2, B3] }
|
||||||
|
FieldC: { type: string, title: FieldC, enum: [C1, C2, C3] }
|
||||||
|
ObjectWithAllOf:
|
||||||
|
title: StringFilter
|
||||||
|
type: object
|
||||||
|
allOf:
|
||||||
|
- properties:
|
||||||
|
type: { type: string, enum: [STRING] }
|
||||||
|
required: [type, field, values]
|
||||||
|
- oneOf:
|
||||||
|
- properties:
|
||||||
|
field: { type: string, enum: [A] }
|
||||||
|
values: { type: array, items: { $ref: '#/components/schemas/FieldA' } }
|
||||||
|
- properties:
|
||||||
|
field: { type: string, enum: [B] }
|
||||||
|
values: { type: array, items: { $ref: '#/components/schemas/FieldB' } }
|
||||||
|
- properties:
|
||||||
|
field: { type: string, enum: [C] }
|
||||||
|
values: { type: array, items: { $ref: '#/components/schemas/FieldC' } }
|
||||||
|
ObjectWithOneOf:
|
||||||
|
title: StringFilter
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
type: { type: string, enum: [STRING] }
|
||||||
|
required: [type, field, values]
|
||||||
|
oneOf:
|
||||||
|
- properties:
|
||||||
|
field: { type: string, enum: [A] }
|
||||||
|
values: { type: array, items: { $ref: '#/components/schemas/FieldA' } }
|
||||||
|
- properties:
|
||||||
|
field: { type: string, enum: [B] }
|
||||||
|
values: { type: array, items: { $ref: '#/components/schemas/FieldB' } }
|
||||||
|
- properties:
|
||||||
|
field: { type: string, enum: [C] }
|
||||||
|
values: { type: array, items: { $ref: '#/components/schemas/FieldC' } }
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schemaWithOneOf = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
spec.components.schemas.ObjectWithOneOf,
|
||||||
|
'#/components/schemas/ObjectWithOneOf',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
expect(printSchema(schemaWithOneOf, enumDetailsPrinter)).toMatchInlineSnapshot(`
|
||||||
|
"oneOf
|
||||||
|
StringFilter ->
|
||||||
|
field*: <string>enum: [A]
|
||||||
|
values*: [<string>enum: [A1,A2,A3]]
|
||||||
|
type*: <string>enum: [STRING]
|
||||||
|
StringFilter ->
|
||||||
|
field*: <string>enum: [B]
|
||||||
|
values*: [<string>enum: [B1,B2,B3]]
|
||||||
|
type*: <string>enum: [STRING]
|
||||||
|
StringFilter ->
|
||||||
|
field*: <string>enum: [C]
|
||||||
|
values*: [<string>enum: [C1,C2,C3]]
|
||||||
|
type*: <string>enum: [STRING]"
|
||||||
|
`);
|
||||||
|
|
||||||
|
const schemaWithAllOf = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
spec.components.schemas.ObjectWithAllOf,
|
||||||
|
'#/components/schemas/ObjectWithAllOf',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
expect(printSchema(schemaWithAllOf, enumDetailsPrinter)).toMatchInlineSnapshot(`
|
||||||
|
"oneOf
|
||||||
|
object ->
|
||||||
|
type*: <string>enum: [STRING]
|
||||||
|
field*: <string>enum: [A]
|
||||||
|
values*: [<string>enum: [A1,A2,A3]]
|
||||||
|
object ->
|
||||||
|
type*: <string>enum: [STRING]
|
||||||
|
field*: <string>enum: [B]
|
||||||
|
values*: [<string>enum: [B1,B2,B3]]
|
||||||
|
object ->
|
||||||
|
type*: <string>enum: [STRING]
|
||||||
|
field*: <string>enum: [C]
|
||||||
|
values*: [<string>enum: [C1,C2,C3]]"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should get correct sibling inside schema type for openapi 3.1', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.1.0
|
||||||
|
paths:
|
||||||
|
/test:
|
||||||
|
get:
|
||||||
|
operationId: test
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
testAttr:
|
||||||
|
description: Overridden description
|
||||||
|
type: string
|
||||||
|
$ref: '#/components/schemas/Test'
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Test:
|
||||||
|
type: object
|
||||||
|
description: Refed description
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const name = 'application/json';
|
||||||
|
const mediaType = new MediaTypeModel(
|
||||||
|
parser,
|
||||||
|
name,
|
||||||
|
true,
|
||||||
|
spec.paths['/test'].get.responses['200'].content[name],
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(printSchema(mediaType?.schema as any)).toMatchInlineSnapshot(
|
||||||
|
`"testAttr: <string> (Overridden description)"`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not override schema in openapi 3.0', () => {
|
||||||
|
const spec = parseYaml(outdent`
|
||||||
|
openapi: 3.0.0
|
||||||
|
paths:
|
||||||
|
/test:
|
||||||
|
get:
|
||||||
|
operationId: test
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
testAttr:
|
||||||
|
type: string
|
||||||
|
description: Overridden description
|
||||||
|
$ref: '#/components/schemas/Test'
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Test:
|
||||||
|
type: object
|
||||||
|
description: Refed description
|
||||||
|
`) as any;
|
||||||
|
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const name = 'application/json';
|
||||||
|
const mediaType = new MediaTypeModel(
|
||||||
|
parser,
|
||||||
|
name,
|
||||||
|
true,
|
||||||
|
spec.paths['/test'].get.responses['200'].content[name],
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(printSchema(mediaType?.schema as any)).toMatchInlineSnapshot(
|
||||||
|
`"testAttr: <object> (Refed description)"`,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,19 +4,27 @@ exports[`Models Schema schemaDefinition should resolve field with conditional op
|
||||||
Object {
|
Object {
|
||||||
"allOf": undefined,
|
"allOf": undefined,
|
||||||
"default": undefined,
|
"default": undefined,
|
||||||
|
"description": undefined,
|
||||||
"items": Object {
|
"items": Object {
|
||||||
"allOf": undefined,
|
"allOf": undefined,
|
||||||
|
"description": undefined,
|
||||||
"format": "url",
|
"format": "url",
|
||||||
"parentRefs": Array [],
|
"readOnly": undefined,
|
||||||
"title": undefined,
|
"title": undefined,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"writeOnly": undefined,
|
||||||
|
"x-circular-ref": undefined,
|
||||||
|
"x-parentRefs": Array [],
|
||||||
},
|
},
|
||||||
"maxItems": 20,
|
"maxItems": 20,
|
||||||
"minItems": 1,
|
"minItems": 1,
|
||||||
"parentRefs": Array [],
|
"readOnly": undefined,
|
||||||
"title": "isString",
|
"title": "isString",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"writeOnly": undefined,
|
||||||
|
"x-circular-ref": undefined,
|
||||||
"x-displayName": "isString",
|
"x-displayName": "isString",
|
||||||
|
"x-parentRefs": Array [],
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -24,32 +32,40 @@ exports[`Models Schema schemaDefinition should resolve field with conditional op
|
||||||
Object {
|
Object {
|
||||||
"allOf": undefined,
|
"allOf": undefined,
|
||||||
"default": undefined,
|
"default": undefined,
|
||||||
|
"description": undefined,
|
||||||
"items": Object {
|
"items": Object {
|
||||||
"allOf": undefined,
|
"allOf": undefined,
|
||||||
|
"description": undefined,
|
||||||
"format": "url",
|
"format": "url",
|
||||||
"parentRefs": Array [],
|
"readOnly": undefined,
|
||||||
"title": undefined,
|
"title": undefined,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"writeOnly": undefined,
|
||||||
|
"x-circular-ref": undefined,
|
||||||
|
"x-parentRefs": Array [],
|
||||||
},
|
},
|
||||||
"maxItems": 10,
|
"maxItems": 10,
|
||||||
"minItems": 1,
|
"minItems": 1,
|
||||||
"parentRefs": Array [],
|
|
||||||
"pattern": "\\\\d+",
|
"pattern": "\\\\d+",
|
||||||
|
"readOnly": undefined,
|
||||||
"title": "notString",
|
"title": "notString",
|
||||||
"type": Array [
|
"type": Array [
|
||||||
"string",
|
"string",
|
||||||
"integer",
|
"integer",
|
||||||
"null",
|
"null",
|
||||||
],
|
],
|
||||||
|
"writeOnly": undefined,
|
||||||
|
"x-circular-ref": undefined,
|
||||||
"x-displayName": "notString",
|
"x-displayName": "notString",
|
||||||
|
"x-parentRefs": Array [],
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Models Schema schemaDefinition should resolve schema with conditional operators 1`] = `
|
exports[`Models Schema schemaDefinition should resolve schema with conditional operators 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"allOf": undefined,
|
"allOf": undefined,
|
||||||
|
"description": undefined,
|
||||||
"maxItems": 2,
|
"maxItems": 2,
|
||||||
"parentRefs": Array [],
|
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
"test": Object {
|
"test": Object {
|
||||||
"allOf": undefined,
|
"allOf": undefined,
|
||||||
|
@ -59,32 +75,46 @@ Object {
|
||||||
],
|
],
|
||||||
"items": Object {
|
"items": Object {
|
||||||
"allOf": undefined,
|
"allOf": undefined,
|
||||||
|
"description": undefined,
|
||||||
"format": "url",
|
"format": "url",
|
||||||
"parentRefs": Array [],
|
"readOnly": undefined,
|
||||||
"title": undefined,
|
"title": undefined,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"writeOnly": undefined,
|
||||||
|
"x-circular-ref": undefined,
|
||||||
|
"x-parentRefs": Array [],
|
||||||
},
|
},
|
||||||
"maxItems": 20,
|
"maxItems": 20,
|
||||||
"minItems": 1,
|
"minItems": 1,
|
||||||
"parentRefs": Array [],
|
"readOnly": undefined,
|
||||||
"title": undefined,
|
"title": undefined,
|
||||||
"type": Array [
|
"type": Array [
|
||||||
"string",
|
"string",
|
||||||
"integer",
|
"integer",
|
||||||
"null",
|
"null",
|
||||||
],
|
],
|
||||||
|
"writeOnly": undefined,
|
||||||
|
"x-circular-ref": undefined,
|
||||||
|
"x-parentRefs": Array [],
|
||||||
|
"x-refsStack": Array [
|
||||||
|
"/oneOf/0",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"readOnly": undefined,
|
||||||
"title": "=== 10",
|
"title": "=== 10",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"writeOnly": undefined,
|
||||||
|
"x-circular-ref": undefined,
|
||||||
|
"x-parentRefs": Array [],
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Models Schema schemaDefinition should resolve schema with conditional operators 2`] = `
|
exports[`Models Schema schemaDefinition should resolve schema with conditional operators 2`] = `
|
||||||
Object {
|
Object {
|
||||||
"allOf": undefined,
|
"allOf": undefined,
|
||||||
|
"description": undefined,
|
||||||
"maxItems": 20,
|
"maxItems": 20,
|
||||||
"parentRefs": Array [],
|
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
"test": Object {
|
"test": Object {
|
||||||
"description": "The list of URL to a cute photos featuring pet",
|
"description": "The list of URL to a cute photos featuring pet",
|
||||||
|
@ -99,9 +129,16 @@ Object {
|
||||||
"integer",
|
"integer",
|
||||||
"null",
|
"null",
|
||||||
],
|
],
|
||||||
|
"x-refsStack": Array [
|
||||||
|
"/oneOf/1",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"readOnly": undefined,
|
||||||
"title": "case 2",
|
"title": "case 2",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"writeOnly": undefined,
|
||||||
|
"x-circular-ref": undefined,
|
||||||
|
"x-parentRefs": Array [],
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
80
src/services/__tests__/models/helpers.ts
Normal file
80
src/services/__tests__/models/helpers.ts
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import type { SchemaModel } from '../../models';
|
||||||
|
|
||||||
|
function printType(type: string | string[]): string {
|
||||||
|
return `<${type}>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function printDescription(description: string | string[]): string {
|
||||||
|
return description ? ` (${description})` : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function circularDetailsPrinter(schema: SchemaModel): string {
|
||||||
|
return schema.isCircular ? ' !circular' : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function enumDetailsPrinter(schema: SchemaModel): string {
|
||||||
|
return schema.enum ? `enum: [${schema.enum.toString()}]` : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function printSchema(
|
||||||
|
schema: SchemaModel,
|
||||||
|
detailsPrinter: (schema: SchemaModel) => string = () => '',
|
||||||
|
identLevel = 0,
|
||||||
|
inline = false,
|
||||||
|
): string {
|
||||||
|
if (!schema) return '';
|
||||||
|
const ident = ' '.repeat(identLevel);
|
||||||
|
|
||||||
|
if (schema.isPrimitive || schema.isCircular) {
|
||||||
|
if (schema.type === 'array' && schema.items) {
|
||||||
|
return `${inline ? ' ' : ident}[${printType(schema.items.type)}${detailsPrinter(
|
||||||
|
schema.items,
|
||||||
|
)}]${printDescription(schema.items.description)}`;
|
||||||
|
} else {
|
||||||
|
return `${inline ? ' ' : ident}${printType(schema.displayType)}${detailsPrinter(
|
||||||
|
schema,
|
||||||
|
)}${printDescription(schema.description)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.oneOf) {
|
||||||
|
return (
|
||||||
|
`${inline ? ' ' : ident}oneOf\n` +
|
||||||
|
schema.oneOf
|
||||||
|
.map(sub => {
|
||||||
|
return (
|
||||||
|
`${ident} ${sub.title || sub.displayType} ->` +
|
||||||
|
printSchema(sub, detailsPrinter, identLevel + 2, true)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.join('\n')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.fields) {
|
||||||
|
const prefix = inline ? '\n' : '';
|
||||||
|
return (
|
||||||
|
prefix +
|
||||||
|
schema.fields
|
||||||
|
.map(f => {
|
||||||
|
return `${ident}${f.name}${f.required ? '*' : ''}:${printSchema(
|
||||||
|
f.schema,
|
||||||
|
detailsPrinter,
|
||||||
|
identLevel + 1,
|
||||||
|
true,
|
||||||
|
)}`;
|
||||||
|
})
|
||||||
|
.join('\n')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.items) {
|
||||||
|
return (
|
||||||
|
`${inline ? ' ' : ident}[\n` +
|
||||||
|
printSchema(schema.items, detailsPrinter, identLevel) +
|
||||||
|
`\n${inline ? ident.slice(0, -2) : ident}]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ' error';
|
||||||
|
}
|
|
@ -11,3 +11,4 @@ export * from './RedocNormalizedOptions';
|
||||||
export * from './MenuBuilder';
|
export * from './MenuBuilder';
|
||||||
export * from './SearchStore';
|
export * from './SearchStore';
|
||||||
export * from './MarkerService';
|
export * from './MarkerService';
|
||||||
|
export * from './types';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { OpenAPIContact, OpenAPIInfo, OpenAPILicense } from '../../types';
|
import type { OpenAPIContact, OpenAPIInfo, OpenAPILicense } from '../../types';
|
||||||
import { IS_BROWSER } from '../../utils/';
|
import { IS_BROWSER } from '../../utils/';
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
|
|
||||||
export class ApiInfoModel implements OpenAPIInfo {
|
export class ApiInfoModel implements OpenAPIInfo {
|
||||||
|
@ -24,7 +24,7 @@ export class ApiInfoModel implements OpenAPIInfo {
|
||||||
this.description = parser.spec.info.description || '';
|
this.description = parser.spec.info.description || '';
|
||||||
this.summary = parser.spec.info.summary || '';
|
this.summary = parser.spec.info.summary || '';
|
||||||
|
|
||||||
const firstHeadingLinePos = this.description.search(/^##?\s+/m);
|
const firstHeadingLinePos = this.description.search(/^\s*##?\s+/m);
|
||||||
if (firstHeadingLinePos > -1) {
|
if (firstHeadingLinePos > -1) {
|
||||||
this.description = this.description.substring(0, firstHeadingLinePos);
|
this.description = this.description.substring(0, firstHeadingLinePos);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { action, observable, makeObservable } from 'mobx';
|
import { action, observable, makeObservable } from 'mobx';
|
||||||
|
|
||||||
import { OpenAPICallback, Referenced } from '../../types';
|
|
||||||
import { isOperationName, JsonPointer } from '../../utils';
|
import { isOperationName, JsonPointer } from '../../utils';
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
|
||||||
import { OperationModel } from './Operation';
|
import { OperationModel } from './Operation';
|
||||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||||
|
import type { OpenAPICallback, Referenced } from '../../types';
|
||||||
|
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
|
|
||||||
export class CallbackModel {
|
export class CallbackModel {
|
||||||
@observable
|
@observable
|
||||||
|
@ -23,8 +23,7 @@ export class CallbackModel {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
const paths = parser.deref<OpenAPICallback>(infoOrRef);
|
const { resolved: paths } = parser.deref<OpenAPICallback>(infoOrRef);
|
||||||
parser.exitRef(infoOrRef);
|
|
||||||
|
|
||||||
for (const pathName of Object.keys(paths)) {
|
for (const pathName of Object.keys(paths)) {
|
||||||
const path = paths[pathName];
|
const path = paths[pathName];
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { OpenAPIEncoding, OpenAPIExample, Referenced } from '../../types';
|
import type { OpenAPIEncoding, OpenAPIExample, Referenced } from '../../types';
|
||||||
import { isFormUrlEncoded, isJsonLike, urlFormEncodePayload } from '../../utils/openapi';
|
import { isFormUrlEncoded, isJsonLike, urlFormEncodePayload } from '../../utils/openapi';
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||||
|
|
||||||
const externalExamplesCache: { [url: string]: Promise<any> } = {};
|
const externalExamplesCache: { [url: string]: Promise<any> } = {};
|
||||||
|
|
||||||
|
@ -16,14 +16,13 @@ export class ExampleModel {
|
||||||
public mime: string,
|
public mime: string,
|
||||||
encoding?: { [field: string]: OpenAPIEncoding },
|
encoding?: { [field: string]: OpenAPIEncoding },
|
||||||
) {
|
) {
|
||||||
const example = parser.deref(infoOrRef);
|
const { resolved: example } = parser.deref(infoOrRef);
|
||||||
this.value = example.value;
|
this.value = example.value;
|
||||||
this.summary = example.summary;
|
this.summary = example.summary;
|
||||||
this.description = example.description;
|
this.description = example.description;
|
||||||
if (example.externalValue) {
|
if (example.externalValue) {
|
||||||
this.externalValueUrl = new URL(example.externalValue, parser.specUrl).href;
|
this.externalValueUrl = new URL(example.externalValue, parser.specUrl).href;
|
||||||
}
|
}
|
||||||
parser.exitRef(infoOrRef);
|
|
||||||
|
|
||||||
if (isFormUrlEncoded(mime) && this.value && typeof this.value === 'object') {
|
if (isFormUrlEncoded(mime) && this.value && typeof this.value === 'object') {
|
||||||
this.value = urlFormEncodePayload(this.value, encoding);
|
this.value = urlFormEncodePayload(this.value, encoding);
|
||||||
|
@ -35,7 +34,7 @@ export class ExampleModel {
|
||||||
return Promise.resolve(undefined);
|
return Promise.resolve(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (externalExamplesCache[this.externalValueUrl]) {
|
if (this.externalValueUrl in externalExamplesCache) {
|
||||||
return externalExamplesCache[this.externalValueUrl];
|
return externalExamplesCache[this.externalValueUrl];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import { action, observable, makeObservable } from 'mobx';
|
import { action, observable, makeObservable } from 'mobx';
|
||||||
|
|
||||||
import {
|
import type {
|
||||||
OpenAPIParameter,
|
OpenAPIParameter,
|
||||||
OpenAPIParameterLocation,
|
OpenAPIParameterLocation,
|
||||||
OpenAPIParameterStyle,
|
OpenAPIParameterStyle,
|
||||||
Referenced,
|
Referenced,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
|
|
||||||
import { extractExtensions } from '../../utils/openapi';
|
import { extractExtensions } from '../../utils/openapi';
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||||
import { SchemaModel } from './Schema';
|
import { SchemaModel } from './Schema';
|
||||||
import { ExampleModel } from './Example';
|
import { ExampleModel } from './Example';
|
||||||
import { isArray, mapValues } from '../../utils/helpers';
|
import { isArray, mapValues } from '../../utils/helpers';
|
||||||
|
@ -64,10 +64,11 @@ export class FieldModel {
|
||||||
infoOrRef: Referenced<OpenAPIParameter> & { name?: string; kind?: string },
|
infoOrRef: Referenced<OpenAPIParameter> & { name?: string; kind?: string },
|
||||||
pointer: string,
|
pointer: string,
|
||||||
options: RedocNormalizedOptions,
|
options: RedocNormalizedOptions,
|
||||||
|
refsStack?: string[],
|
||||||
) {
|
) {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
|
|
||||||
const info = parser.deref<OpenAPIParameter>(infoOrRef);
|
const { resolved: info } = parser.deref<OpenAPIParameter>(infoOrRef);
|
||||||
this.kind = infoOrRef.kind || 'field';
|
this.kind = infoOrRef.kind || 'field';
|
||||||
this.name = infoOrRef.name || info.name;
|
this.name = infoOrRef.name || info.name;
|
||||||
this.in = info.in;
|
this.in = info.in;
|
||||||
|
@ -80,7 +81,7 @@ export class FieldModel {
|
||||||
fieldSchema = info.content[serializationMime] && info.content[serializationMime].schema;
|
fieldSchema = info.content[serializationMime] && info.content[serializationMime].schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.schema = new SchemaModel(parser, fieldSchema || {}, pointer, options);
|
this.schema = new SchemaModel(parser, fieldSchema || {}, pointer, options, false, refsStack);
|
||||||
this.description =
|
this.description =
|
||||||
info.description === undefined ? this.schema.description || '' : info.description;
|
info.description === undefined ? this.schema.description || '' : info.description;
|
||||||
this.example = info.example || this.schema.example;
|
this.example = info.example || this.schema.example;
|
||||||
|
@ -110,7 +111,6 @@ export class FieldModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.deprecated = info.deprecated === undefined ? !!this.schema.deprecated : info.deprecated;
|
this.deprecated = info.deprecated === undefined ? !!this.schema.deprecated : info.deprecated;
|
||||||
parser.exitRef(infoOrRef);
|
|
||||||
|
|
||||||
if (options.showExtensions) {
|
if (options.showExtensions) {
|
||||||
this.extensions = extractExtensions(info, options.showExtensions);
|
this.extensions = extractExtensions(info, options.showExtensions);
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { action, observable, makeObservable } from 'mobx';
|
import { action, observable, makeObservable } from 'mobx';
|
||||||
|
|
||||||
import { OpenAPIExternalDocumentation, OpenAPITag } from '../../types';
|
import type { OpenAPIExternalDocumentation, OpenAPITag } from '../../types';
|
||||||
import { safeSlugify } from '../../utils';
|
import { safeSlugify } from '../../utils';
|
||||||
import { MarkdownHeading, MarkdownRenderer } from '../MarkdownRenderer';
|
import { MarkdownRenderer } from '../MarkdownRenderer';
|
||||||
import { ContentItemModel } from '../MenuBuilder';
|
import type { ContentItemModel, IMenuItem, MarkdownHeading, MenuItemGroupType } from '../types';
|
||||||
import { IMenuItem, MenuItemGroupType } from '../MenuStore';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Operations Group model ready to be used by components
|
* Operations Group model ready to be used by components
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { action, computed, observable, makeObservable } from 'mobx';
|
import { action, computed, observable, makeObservable } from 'mobx';
|
||||||
|
|
||||||
import { OpenAPIMediaType } from '../../types';
|
import type { OpenAPIMediaType } from '../../types';
|
||||||
import { MediaTypeModel } from './MediaType';
|
import { MediaTypeModel } from './MediaType';
|
||||||
|
|
||||||
import { mergeSimilarMediaTypes } from '../../utils';
|
import { mergeSimilarMediaTypes } from '../../utils';
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MediaContent model ready to be sued by React components
|
* MediaContent model ready to be sued by React components
|
||||||
|
@ -34,7 +34,6 @@ export class MediaContentModel {
|
||||||
this.mediaTypes = Object.keys(info).map(name => {
|
this.mediaTypes = Object.keys(info).map(name => {
|
||||||
const mime = info[name];
|
const mime = info[name];
|
||||||
// reset deref cache just in case something is left there
|
// reset deref cache just in case something is left there
|
||||||
parser.resetVisited();
|
|
||||||
return new MediaTypeModel(parser, name, isRequestType, mime, options);
|
return new MediaTypeModel(parser, name, isRequestType, mime, options);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import * as Sampler from 'openapi-sampler';
|
import * as Sampler from 'openapi-sampler';
|
||||||
|
|
||||||
import { OpenAPIMediaType } from '../../types';
|
import type { OpenAPIMediaType } from '../../types';
|
||||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
import { SchemaModel } from './Schema';
|
import { SchemaModel } from './Schema';
|
||||||
|
|
||||||
import { isJsonLike, mapValues } from '../../utils';
|
import { isJsonLike, mapValues } from '../../utils';
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||||
import { ExampleModel } from './Example';
|
import { ExampleModel } from './Example';
|
||||||
|
|
||||||
export class MediaTypeModel {
|
export class MediaTypeModel {
|
||||||
|
@ -40,7 +40,7 @@ export class MediaTypeModel {
|
||||||
this.examples = {
|
this.examples = {
|
||||||
default: new ExampleModel(
|
default: new ExampleModel(
|
||||||
parser,
|
parser,
|
||||||
{ value: parser.shallowDeref(info.example) },
|
{ value: parser.deref(info.example).resolved },
|
||||||
name,
|
name,
|
||||||
info.encoding,
|
info.encoding,
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
import { action, observable, makeObservable } from 'mobx';
|
import { action, observable, makeObservable } from 'mobx';
|
||||||
|
|
||||||
import { IMenuItem } from '../MenuStore';
|
|
||||||
import { GroupModel } from './Group.model';
|
|
||||||
import { SecurityRequirementModel } from './SecurityRequirement';
|
|
||||||
|
|
||||||
import { OpenAPIExternalDocumentation, OpenAPIServer, OpenAPIXCodeSample } from '../../types';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
extractExtensions,
|
extractExtensions,
|
||||||
getOperationSummary,
|
getOperationSummary,
|
||||||
|
@ -17,15 +11,20 @@ import {
|
||||||
sortByField,
|
sortByField,
|
||||||
sortByRequired,
|
sortByRequired,
|
||||||
} from '../../utils';
|
} from '../../utils';
|
||||||
import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder';
|
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import { GroupModel } from './Group.model';
|
||||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
import { SecurityRequirementModel } from './SecurityRequirement';
|
||||||
import { CallbackModel } from './Callback';
|
import { CallbackModel } from './Callback';
|
||||||
import { FieldModel } from './Field';
|
import { FieldModel } from './Field';
|
||||||
import { MediaContentModel } from './MediaContent';
|
|
||||||
import { RequestBodyModel } from './RequestBody';
|
import { RequestBodyModel } from './RequestBody';
|
||||||
import { ResponseModel } from './Response';
|
import { ResponseModel } from './Response';
|
||||||
import { SideNavStyleEnum } from '../RedocNormalizedOptions';
|
import { SideNavStyleEnum } from '../types';
|
||||||
|
|
||||||
|
import type { OpenAPIExternalDocumentation, OpenAPIServer, OpenAPIXCodeSample } from '../../types';
|
||||||
|
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||||
|
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
|
import type { MediaContentModel } from './MediaContent';
|
||||||
|
import type { ContentItemModel, ExtendedOpenAPIOperation, IMenuItem } from '../types';
|
||||||
|
|
||||||
export interface XPayloadSample {
|
export interface XPayloadSample {
|
||||||
lang: 'payload';
|
lang: 'payload';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { OpenAPIRequestBody, Referenced } from '../../types';
|
import type { OpenAPIRequestBody, Referenced } from '../../types';
|
||||||
|
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
import { MediaContentModel } from './MediaContent';
|
import { MediaContentModel } from './MediaContent';
|
||||||
import { getContentWithLegacyExamples } from '../../utils';
|
import { getContentWithLegacyExamples } from '../../utils';
|
||||||
|
|
||||||
|
@ -19,10 +19,9 @@ export class RequestBodyModel {
|
||||||
|
|
||||||
constructor({ parser, infoOrRef, options, isEvent }: RequestBodyProps) {
|
constructor({ parser, infoOrRef, options, isEvent }: RequestBodyProps) {
|
||||||
const isRequest = !isEvent;
|
const isRequest = !isEvent;
|
||||||
const info = parser.deref(infoOrRef);
|
const { resolved: info } = parser.deref(infoOrRef);
|
||||||
this.description = info.description || '';
|
this.description = info.description || '';
|
||||||
this.required = !!info.required;
|
this.required = !!info.required;
|
||||||
parser.exitRef(infoOrRef);
|
|
||||||
|
|
||||||
const mediaContent = getContentWithLegacyExamples(info);
|
const mediaContent = getContentWithLegacyExamples(info);
|
||||||
if (mediaContent !== undefined) {
|
if (mediaContent !== undefined) {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { action, observable, makeObservable } from 'mobx';
|
import { action, observable, makeObservable } from 'mobx';
|
||||||
|
|
||||||
import { OpenAPIResponse, Referenced } from '../../types';
|
import type { OpenAPIResponse, Referenced } from '../../types';
|
||||||
|
|
||||||
import { getStatusCodeType, extractExtensions } from '../../utils';
|
import { getStatusCodeType, extractExtensions } from '../../utils';
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
import { FieldModel } from './Field';
|
import { FieldModel } from './Field';
|
||||||
import { MediaContentModel } from './MediaContent';
|
import { MediaContentModel } from './MediaContent';
|
||||||
|
|
||||||
|
@ -41,8 +41,7 @@ export class ResponseModel {
|
||||||
|
|
||||||
this.expanded = options.expandResponses === 'all' || options.expandResponses[code];
|
this.expanded = options.expandResponses === 'all' || options.expandResponses[code];
|
||||||
|
|
||||||
const info = parser.deref(infoOrRef);
|
const { resolved: info } = parser.deref(infoOrRef);
|
||||||
parser.exitRef(infoOrRef);
|
|
||||||
this.code = code;
|
this.code = code;
|
||||||
if (info.content !== undefined) {
|
if (info.content !== undefined) {
|
||||||
this.content = new MediaContentModel(parser, info.content, isRequest, options);
|
this.content = new MediaContentModel(parser, info.content, isRequest, options);
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { action, observable, makeObservable } from 'mobx';
|
import { action, observable, makeObservable } from 'mobx';
|
||||||
|
|
||||||
import { OpenAPIExternalDocumentation, OpenAPISchema, Referenced } from '../../types';
|
import type { OpenAPIExternalDocumentation, OpenAPISchema, Referenced } from '../../types';
|
||||||
|
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
import { pushRef } from '../OpenAPIParser';
|
||||||
|
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
import { FieldModel } from './Field';
|
import { FieldModel } from './Field';
|
||||||
|
|
||||||
import { MergedOpenAPISchema } from '../';
|
import { MergedOpenAPISchema } from '../types';
|
||||||
import {
|
import {
|
||||||
detectType,
|
detectType,
|
||||||
extractExtensions,
|
extractExtensions,
|
||||||
|
@ -80,18 +81,19 @@ export class SchemaModel {
|
||||||
pointer: string,
|
pointer: string,
|
||||||
private options: RedocNormalizedOptions,
|
private options: RedocNormalizedOptions,
|
||||||
isChild: boolean = false,
|
isChild: boolean = false,
|
||||||
|
private refsStack: string[] = [],
|
||||||
) {
|
) {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
|
|
||||||
this.pointer = schemaOrRef.$ref || pointer || '';
|
this.pointer = schemaOrRef.$ref || pointer || '';
|
||||||
this.rawSchema = parser.deref(schemaOrRef, false, true);
|
|
||||||
this.schema = parser.mergeAllOf(this.rawSchema, this.pointer, isChild);
|
|
||||||
|
|
||||||
|
const { resolved, refsStack: newRefsStack } = parser.deref(schemaOrRef, refsStack, true);
|
||||||
|
this.refsStack = pushRef(newRefsStack, this.pointer);
|
||||||
|
this.rawSchema = resolved;
|
||||||
|
|
||||||
|
this.schema = parser.mergeAllOf(this.rawSchema, this.pointer, this.refsStack);
|
||||||
this.init(parser, isChild);
|
this.init(parser, isChild);
|
||||||
|
|
||||||
parser.exitRef(schemaOrRef);
|
|
||||||
parser.exitParents(this.schema);
|
|
||||||
|
|
||||||
if (options.showExtensions) {
|
if (options.showExtensions) {
|
||||||
this.extensions = extractExtensions(this.schema, options.showExtensions);
|
this.extensions = extractExtensions(this.schema, options.showExtensions);
|
||||||
}
|
}
|
||||||
|
@ -112,7 +114,7 @@ export class SchemaModel {
|
||||||
|
|
||||||
init(parser: OpenAPIParser, isChild: boolean) {
|
init(parser: OpenAPIParser, isChild: boolean) {
|
||||||
const schema = this.schema;
|
const schema = this.schema;
|
||||||
this.isCircular = schema['x-circular-ref'];
|
this.isCircular = !!schema['x-circular-ref'];
|
||||||
|
|
||||||
this.title =
|
this.title =
|
||||||
schema.title || (isNamedDefinition(this.pointer) && JsonPointer.baseName(this.pointer)) || '';
|
schema.title || (isNamedDefinition(this.pointer) && JsonPointer.baseName(this.pointer)) || '';
|
||||||
|
@ -189,16 +191,18 @@ export class SchemaModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasType('object')) {
|
if (this.hasType('object')) {
|
||||||
this.fields = buildFields(parser, schema, this.pointer, this.options);
|
this.fields = buildFields(parser, schema, this.pointer, this.options, this.refsStack);
|
||||||
} else if (this.hasType('array')) {
|
} else if (this.hasType('array')) {
|
||||||
if (isArray(schema.items) || isArray(schema.prefixItems)) {
|
if (isArray(schema.items) || isArray(schema.prefixItems)) {
|
||||||
this.fields = buildFields(parser, schema, this.pointer, this.options);
|
this.fields = buildFields(parser, schema, this.pointer, this.options, this.refsStack);
|
||||||
} else if (isObject(schema.items)) {
|
} else if (schema.items) {
|
||||||
this.items = new SchemaModel(
|
this.items = new SchemaModel(
|
||||||
parser,
|
parser,
|
||||||
schema.items as OpenAPISchema,
|
schema.items as OpenAPISchema,
|
||||||
this.pointer + '/items',
|
this.pointer + '/items',
|
||||||
this.options,
|
this.options,
|
||||||
|
false,
|
||||||
|
this.refsStack,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +213,8 @@ export class SchemaModel {
|
||||||
this.displayFormat = this.items?.format || '';
|
this.displayFormat = this.items?.format || '';
|
||||||
this.typePrefix = this.items?.typePrefix || '' + l('arrayOf');
|
this.typePrefix = this.items?.typePrefix || '' + l('arrayOf');
|
||||||
this.title = this.title || this.items?.title || '';
|
this.title = this.title || this.items?.title || '';
|
||||||
this.isPrimitive = this.items?.isPrimitive || this.isPrimitive;
|
this.isPrimitive =
|
||||||
|
this.items?.isPrimitive !== undefined ? this.items?.isPrimitive : this.isPrimitive;
|
||||||
|
|
||||||
if (this.example === undefined && this.items?.example !== undefined) {
|
if (this.example === undefined && this.items?.example !== undefined) {
|
||||||
this.example = [this.items.example];
|
this.example = [this.items.example];
|
||||||
|
@ -230,9 +235,9 @@ export class SchemaModel {
|
||||||
|
|
||||||
private initOneOf(oneOf: OpenAPISchema[], parser: OpenAPIParser) {
|
private initOneOf(oneOf: OpenAPISchema[], parser: OpenAPIParser) {
|
||||||
this.oneOf = oneOf!.map((variant, idx) => {
|
this.oneOf = oneOf!.map((variant, idx) => {
|
||||||
const derefVariant = parser.deref(variant, false, true);
|
const { resolved: derefVariant, refsStack } = parser.deref(variant, this.refsStack, true);
|
||||||
|
|
||||||
const merged = parser.mergeAllOf(derefVariant, this.pointer + '/oneOf/' + idx);
|
const merged = parser.mergeAllOf(derefVariant, this.pointer + '/oneOf/' + idx, refsStack);
|
||||||
|
|
||||||
// try to infer title
|
// try to infer title
|
||||||
const title =
|
const title =
|
||||||
|
@ -248,16 +253,15 @@ export class SchemaModel {
|
||||||
...merged,
|
...merged,
|
||||||
title,
|
title,
|
||||||
allOf: [{ ...this.schema, oneOf: undefined, anyOf: undefined }],
|
allOf: [{ ...this.schema, oneOf: undefined, anyOf: undefined }],
|
||||||
|
// if specific child schemas are listed in oneOf/anyOf, they are not supposed to be discriminated
|
||||||
|
discriminator: derefVariant.allOf ? undefined : merged.discriminator,
|
||||||
} as OpenAPISchema,
|
} as OpenAPISchema,
|
||||||
this.pointer + '/oneOf/' + idx,
|
variant.$ref || this.pointer + '/oneOf/' + idx,
|
||||||
this.options,
|
this.options,
|
||||||
|
false,
|
||||||
|
refsStack,
|
||||||
);
|
);
|
||||||
|
|
||||||
parser.exitRef(variant);
|
|
||||||
// each oneOf should be independent so exiting all the parent refs
|
|
||||||
// otherwise it will cause false-positive recursive detection
|
|
||||||
parser.exitParents(merged);
|
|
||||||
|
|
||||||
return schema;
|
return schema;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -279,16 +283,11 @@ export class SchemaModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private initDiscriminator(
|
private initDiscriminator(schema: OpenAPISchema, parser: OpenAPIParser) {
|
||||||
schema: OpenAPISchema & {
|
|
||||||
parentRefs?: string[];
|
|
||||||
},
|
|
||||||
parser: OpenAPIParser,
|
|
||||||
) {
|
|
||||||
const discriminator = getDiscriminator(schema)!;
|
const discriminator = getDiscriminator(schema)!;
|
||||||
this.discriminatorProp = discriminator.propertyName;
|
this.discriminatorProp = discriminator.propertyName;
|
||||||
const implicitInversedMapping = parser.findDerived([
|
const implicitInversedMapping = parser.findDerived([
|
||||||
...(schema.parentRefs || []),
|
...(this.schema['x-parentRefs'] || []),
|
||||||
this.pointer,
|
this.pointer,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -371,7 +370,14 @@ export class SchemaModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.oneOf = refs.map(({ $ref, name }) => {
|
this.oneOf = refs.map(({ $ref, name }) => {
|
||||||
const innerSchema = new SchemaModel(parser, parser.byRef($ref)!, $ref, this.options, true);
|
const innerSchema = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
{ $ref },
|
||||||
|
$ref,
|
||||||
|
this.options,
|
||||||
|
true,
|
||||||
|
this.refsStack.slice(0, -1),
|
||||||
|
);
|
||||||
innerSchema.title = name;
|
innerSchema.title = name;
|
||||||
return innerSchema;
|
return innerSchema;
|
||||||
});
|
});
|
||||||
|
@ -404,6 +410,8 @@ export class SchemaModel {
|
||||||
} as OpenAPISchema,
|
} as OpenAPISchema,
|
||||||
this.pointer + '/oneOf/' + idx,
|
this.pointer + '/oneOf/' + idx,
|
||||||
this.options,
|
this.options,
|
||||||
|
false,
|
||||||
|
this.refsStack,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
this.oneOfType = 'One of';
|
this.oneOfType = 'One of';
|
||||||
|
@ -415,6 +423,7 @@ function buildFields(
|
||||||
schema: OpenAPISchema,
|
schema: OpenAPISchema,
|
||||||
$ref: string,
|
$ref: string,
|
||||||
options: RedocNormalizedOptions,
|
options: RedocNormalizedOptions,
|
||||||
|
refsStack: string[],
|
||||||
): FieldModel[] {
|
): FieldModel[] {
|
||||||
const props = schema.properties || schema.prefixItems || schema.items || {};
|
const props = schema.properties || schema.prefixItems || schema.items || {};
|
||||||
const patternProps = schema.patternProperties || {};
|
const patternProps = schema.patternProperties || {};
|
||||||
|
@ -446,6 +455,7 @@ function buildFields(
|
||||||
},
|
},
|
||||||
$ref + '/properties/' + fieldName,
|
$ref + '/properties/' + fieldName,
|
||||||
options,
|
options,
|
||||||
|
refsStack,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -478,6 +488,7 @@ function buildFields(
|
||||||
},
|
},
|
||||||
`${$ref}/patternProperties/${fieldName}`,
|
`${$ref}/patternProperties/${fieldName}`,
|
||||||
options,
|
options,
|
||||||
|
refsStack,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -497,6 +508,7 @@ function buildFields(
|
||||||
},
|
},
|
||||||
$ref + '/additionalProperties',
|
$ref + '/additionalProperties',
|
||||||
options,
|
options,
|
||||||
|
refsStack,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -508,6 +520,7 @@ function buildFields(
|
||||||
fieldsCount: fields.length,
|
fieldsCount: fields.length,
|
||||||
$ref,
|
$ref,
|
||||||
options,
|
options,
|
||||||
|
refsStack,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -520,12 +533,14 @@ function buildAdditionalItems({
|
||||||
fieldsCount,
|
fieldsCount,
|
||||||
$ref,
|
$ref,
|
||||||
options,
|
options,
|
||||||
|
refsStack,
|
||||||
}: {
|
}: {
|
||||||
parser: OpenAPIParser;
|
parser: OpenAPIParser;
|
||||||
schema?: OpenAPISchema | OpenAPISchema[] | boolean;
|
schema?: OpenAPISchema | OpenAPISchema[] | boolean;
|
||||||
fieldsCount: number;
|
fieldsCount: number;
|
||||||
$ref: string;
|
$ref: string;
|
||||||
options: RedocNormalizedOptions;
|
options: RedocNormalizedOptions;
|
||||||
|
refsStack: string[];
|
||||||
}) {
|
}) {
|
||||||
if (isBoolean(schema)) {
|
if (isBoolean(schema)) {
|
||||||
return schema
|
return schema
|
||||||
|
@ -538,6 +553,7 @@ function buildAdditionalItems({
|
||||||
},
|
},
|
||||||
`${$ref}/additionalItems`,
|
`${$ref}/additionalItems`,
|
||||||
options,
|
options,
|
||||||
|
refsStack,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
@ -555,6 +571,7 @@ function buildAdditionalItems({
|
||||||
},
|
},
|
||||||
`${$ref}/additionalItems`,
|
`${$ref}/additionalItems`,
|
||||||
options,
|
options,
|
||||||
|
refsStack,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
@ -570,6 +587,7 @@ function buildAdditionalItems({
|
||||||
},
|
},
|
||||||
`${$ref}/additionalItems`,
|
`${$ref}/additionalItems`,
|
||||||
options,
|
options,
|
||||||
|
refsStack,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { OpenAPISecurityRequirement, OpenAPISecurityScheme } from '../../types';
|
import type { OpenAPISecurityRequirement, OpenAPISecurityScheme } from '../../types';
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||||
|
|
||||||
export interface SecurityScheme extends OpenAPISecurityScheme {
|
export interface SecurityScheme extends OpenAPISecurityScheme {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -16,7 +16,7 @@ export class SecurityRequirementModel {
|
||||||
|
|
||||||
this.schemes = Object.keys(requirement || {})
|
this.schemes = Object.keys(requirement || {})
|
||||||
.map(id => {
|
.map(id => {
|
||||||
const scheme = parser.deref(schemes[id]);
|
const { resolved: scheme } = parser.deref(schemes[id]);
|
||||||
const scopes = requirement[id] || [];
|
const scopes = requirement[id] || [];
|
||||||
|
|
||||||
if (!scheme) {
|
if (!scheme) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { OpenAPISecurityScheme, Referenced } from '../../types';
|
import type { OpenAPISecurityScheme, Referenced } from '../../types';
|
||||||
import { SECURITY_SCHEMES_SECTION_PREFIX } from '../../utils';
|
import { SECURITY_SCHEMES_SECTION_PREFIX } from '../../utils';
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||||
|
|
||||||
export class SecuritySchemeModel {
|
export class SecuritySchemeModel {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -24,7 +24,7 @@ export class SecuritySchemeModel {
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(parser: OpenAPIParser, id: string, scheme: Referenced<OpenAPISecurityScheme>) {
|
constructor(parser: OpenAPIParser, id: string, scheme: Referenced<OpenAPISecurityScheme>) {
|
||||||
const info = parser.deref(scheme);
|
const { resolved: info } = parser.deref(scheme);
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.sectionId = SECURITY_SCHEMES_SECTION_PREFIX + id;
|
this.sectionId = SECURITY_SCHEMES_SECTION_PREFIX + id;
|
||||||
this.type = info.type;
|
this.type = info.type;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { OpenAPIPath, Referenced } from '../../types';
|
import type { OpenAPIPath, Referenced } from '../../types';
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||||
import { OperationModel } from './Operation';
|
import { OperationModel } from './Operation';
|
||||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
import { isOperationName } from '../..';
|
import { isOperationName } from '../..';
|
||||||
|
|
||||||
export class WebhookModel {
|
export class WebhookModel {
|
||||||
|
@ -12,8 +12,7 @@ export class WebhookModel {
|
||||||
options: RedocNormalizedOptions,
|
options: RedocNormalizedOptions,
|
||||||
infoOrRef?: Referenced<OpenAPIPath>,
|
infoOrRef?: Referenced<OpenAPIPath>,
|
||||||
) {
|
) {
|
||||||
const webhooks = parser.deref<OpenAPIPath>(infoOrRef || {});
|
const { resolved: webhooks } = parser.deref<OpenAPIPath>(infoOrRef || {});
|
||||||
parser.exitRef(infoOrRef);
|
|
||||||
this.initWebhooks(parser, webhooks, options);
|
this.initWebhooks(parser, webhooks, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
132
src/services/types.ts
Normal file
132
src/services/types.ts
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
import {
|
||||||
|
OpenAPIOperation,
|
||||||
|
OpenAPIParameter,
|
||||||
|
OpenAPISchema,
|
||||||
|
OpenAPIServer,
|
||||||
|
OpenAPITag,
|
||||||
|
Referenced,
|
||||||
|
} from '../types';
|
||||||
|
import { AppStore } from './AppStore';
|
||||||
|
import { GroupModel } from './models';
|
||||||
|
import { OperationModel } from './models/Operation';
|
||||||
|
import { RedocRawOptions } from './RedocNormalizedOptions';
|
||||||
|
|
||||||
|
export interface StoreState {
|
||||||
|
menu: {
|
||||||
|
activeItemIdx: number;
|
||||||
|
};
|
||||||
|
spec: {
|
||||||
|
url?: string;
|
||||||
|
data: any;
|
||||||
|
};
|
||||||
|
searchIndex: any;
|
||||||
|
options: RedocRawOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LabelsConfig {
|
||||||
|
enum: string;
|
||||||
|
enumSingleValue: string;
|
||||||
|
enumArray: string;
|
||||||
|
default: string;
|
||||||
|
deprecated: string;
|
||||||
|
example: string;
|
||||||
|
examples: string;
|
||||||
|
recursive: string;
|
||||||
|
arrayOf: string;
|
||||||
|
webhook: string;
|
||||||
|
const: string;
|
||||||
|
noResultsFound: string;
|
||||||
|
download: string;
|
||||||
|
downloadSpecification: string;
|
||||||
|
responses: string;
|
||||||
|
callbackResponses: string;
|
||||||
|
requestSamples: string;
|
||||||
|
responseSamples: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LabelsConfigRaw = Partial<LabelsConfig>;
|
||||||
|
|
||||||
|
export interface MDXComponentMeta {
|
||||||
|
component: React.ComponentType;
|
||||||
|
propsSelector: (store?: AppStore) => any;
|
||||||
|
props?: object;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MarkdownHeading {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
level: number;
|
||||||
|
items?: MarkdownHeading[];
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ContentItemModel = GroupModel | OperationModel;
|
||||||
|
|
||||||
|
export type TagInfo = OpenAPITag & {
|
||||||
|
operations: ExtendedOpenAPIOperation[];
|
||||||
|
used?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ExtendedOpenAPIOperation = {
|
||||||
|
pointer: string;
|
||||||
|
pathName: string;
|
||||||
|
httpVerb: string;
|
||||||
|
pathParameters: Array<Referenced<OpenAPIParameter>>;
|
||||||
|
pathServers: Array<OpenAPIServer> | undefined;
|
||||||
|
isWebhook: boolean;
|
||||||
|
} & OpenAPIOperation;
|
||||||
|
|
||||||
|
export type TagsInfoMap = Record<string, TagInfo>;
|
||||||
|
|
||||||
|
export interface TagGroup {
|
||||||
|
name: string;
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MenuItemGroupType = 'group' | 'tag' | 'section';
|
||||||
|
export type MenuItemType = MenuItemGroupType | 'operation';
|
||||||
|
|
||||||
|
export interface IMenuItem {
|
||||||
|
id: string;
|
||||||
|
absoluteIdx?: number;
|
||||||
|
name: string;
|
||||||
|
sidebarLabel: string;
|
||||||
|
description?: string;
|
||||||
|
depth: number;
|
||||||
|
active: boolean;
|
||||||
|
expanded: boolean;
|
||||||
|
items: IMenuItem[];
|
||||||
|
parent?: IMenuItem;
|
||||||
|
deprecated?: boolean;
|
||||||
|
type: MenuItemType;
|
||||||
|
topMargin?: boolean;
|
||||||
|
|
||||||
|
deactivate(): void;
|
||||||
|
activate(): void;
|
||||||
|
|
||||||
|
collapse(): void;
|
||||||
|
expand(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchDocument {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchResult<T = string> {
|
||||||
|
meta: T;
|
||||||
|
score: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SideNavStyleEnum {
|
||||||
|
SummaryOnly = 'summary-only',
|
||||||
|
PathOnly = 'path-only',
|
||||||
|
IdOnly = 'id-only',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MergedOpenAPISchema = OpenAPISchema & {
|
||||||
|
'x-refsStack'?: string[];
|
||||||
|
'x-parentRefs'?: string[];
|
||||||
|
'x-circular-ref'?: boolean;
|
||||||
|
};
|
|
@ -1,11 +1,12 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { hydrate as hydrateComponent, render } from 'react-dom';
|
import { hydrate as hydrateComponent, render, unmountComponentAtNode } from 'react-dom';
|
||||||
import { configure } from 'mobx';
|
import { configure } from 'mobx';
|
||||||
|
|
||||||
import { Redoc, RedocStandalone } from './components/';
|
import { Redoc, RedocStandalone } from './components/';
|
||||||
import { AppStore, StoreState } from './services/AppStore';
|
import { AppStore } from './services/AppStore';
|
||||||
import { debugTime, debugTimeEnd } from './utils/debug';
|
import { debugTime, debugTimeEnd } from './utils/debug';
|
||||||
import { querySelector } from './utils/dom';
|
import { querySelector } from './utils/dom';
|
||||||
|
import type { StoreState } from './services';
|
||||||
|
|
||||||
configure({
|
configure({
|
||||||
useProxies: 'ifavailable',
|
useProxies: 'ifavailable',
|
||||||
|
@ -73,6 +74,12 @@ export function init(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function destroy(element: Element | null = querySelector('redoc')): void {
|
||||||
|
if (element) {
|
||||||
|
unmountComponentAtNode(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function hydrate(
|
export function hydrate(
|
||||||
state: StoreState,
|
state: StoreState,
|
||||||
element: Element | null = querySelector('redoc'),
|
element: Element | null = querySelector('redoc'),
|
||||||
|
|
20
src/theme.ts
20
src/theme.ts
|
@ -164,6 +164,15 @@ const defaultTheme: ThemeInterface = {
|
||||||
backgroundColor: '#263238',
|
backgroundColor: '#263238',
|
||||||
width: '40%',
|
width: '40%',
|
||||||
textColor: '#ffffff',
|
textColor: '#ffffff',
|
||||||
|
servers: {
|
||||||
|
overlay: {
|
||||||
|
backgroundColor: '#fafafa',
|
||||||
|
textColor: '#263238',
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
codeBlock: {
|
codeBlock: {
|
||||||
backgroundColor: ({ rightPanel }) => darken(0.1, rightPanel.backgroundColor),
|
backgroundColor: ({ rightPanel }) => darken(0.1, rightPanel.backgroundColor),
|
||||||
|
@ -231,6 +240,16 @@ export interface FontSettings {
|
||||||
color: string;
|
color: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Servers {
|
||||||
|
overlay: {
|
||||||
|
backgroundColor: string;
|
||||||
|
textColor: string;
|
||||||
|
};
|
||||||
|
url: {
|
||||||
|
backgroundColor: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface ResolvedThemeInterface {
|
export interface ResolvedThemeInterface {
|
||||||
spacing: {
|
spacing: {
|
||||||
unit: number;
|
unit: number;
|
||||||
|
@ -350,6 +369,7 @@ export interface ResolvedThemeInterface {
|
||||||
backgroundColor: string;
|
backgroundColor: string;
|
||||||
textColor: string;
|
textColor: string;
|
||||||
width: string;
|
width: string;
|
||||||
|
servers: Servers;
|
||||||
};
|
};
|
||||||
codeBlock: {
|
codeBlock: {
|
||||||
backgroundColor: string;
|
backgroundColor: string;
|
||||||
|
|
|
@ -40,7 +40,10 @@ export interface OpenAPIPaths {
|
||||||
[path: string]: OpenAPIPath;
|
[path: string]: OpenAPIPath;
|
||||||
}
|
}
|
||||||
export interface OpenAPIRef {
|
export interface OpenAPIRef {
|
||||||
|
'x-refsStack'?: string[];
|
||||||
$ref: string;
|
$ref: string;
|
||||||
|
summary?: string;
|
||||||
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Referenced<T> = OpenAPIRef | T;
|
export type Referenced<T> = OpenAPIRef | T;
|
||||||
|
|
|
@ -513,7 +513,7 @@ Petstore offers two forms of authentication:
|
||||||
OAuth2 - an open protocol to allow secure authorization in a simple
|
OAuth2 - an open protocol to allow secure authorization in a simple
|
||||||
and standard method from web, mobile and desktop applications.
|
and standard method from web, mobile and desktop applications.
|
||||||
|
|
||||||
<SecurityDefinitions />
|
<!-- ReDoc-Inject: <security-definitions> -->
|
||||||
",
|
",
|
||||||
"license": Object {
|
"license": Object {
|
||||||
"name": "Apache 2.0",
|
"name": "Apache 2.0",
|
||||||
|
@ -2261,6 +2261,7 @@ Object {
|
||||||
"id": Object {
|
"id": Object {
|
||||||
"$ref": "#/components/schemas/Id",
|
"$ref": "#/components/schemas/Id",
|
||||||
"description": "Tag ID",
|
"description": "Tag ID",
|
||||||
|
"type": "number",
|
||||||
},
|
},
|
||||||
"name": Object {
|
"name": Object {
|
||||||
"description": "Tag name",
|
"description": "Tag name",
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
humanizeNumberRange,
|
humanizeNumberRange,
|
||||||
getContentWithLegacyExamples,
|
getContentWithLegacyExamples,
|
||||||
getDefinitionName,
|
getDefinitionName,
|
||||||
|
langFromMime,
|
||||||
} from '../';
|
} from '../';
|
||||||
|
|
||||||
import { FieldModel, OpenAPIParser, RedocNormalizedOptions } from '../../services';
|
import { FieldModel, OpenAPIParser, RedocNormalizedOptions } from '../../services';
|
||||||
|
@ -424,6 +425,24 @@ describe('Utils', () => {
|
||||||
expect(res).toEqual([{ url: 'https://base.com/sandbox/test', description: 'test' }]);
|
expect(res).toEqual([{ url: 'https://base.com/sandbox/test', description: 'test' }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should remove query string and hash from url', () => {
|
||||||
|
const originalWindow = { ...window };
|
||||||
|
const windowSpy: jest.SpyInstance = jest.spyOn(global, 'window', 'get');
|
||||||
|
windowSpy.mockImplementation(() => ({
|
||||||
|
...originalWindow,
|
||||||
|
location: {
|
||||||
|
...originalWindow.location,
|
||||||
|
href: 'https://base.com/subpath/?param=value#tag',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
const res = normalizeServers(undefined, [
|
||||||
|
{
|
||||||
|
url: 'sandbox/test',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(res).toEqual([{ url: 'https://base.com/subpath/sandbox/test', description: '' }]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should expand variables', () => {
|
it('should expand variables', () => {
|
||||||
const servers = normalizeServers('', [
|
const servers = normalizeServers('', [
|
||||||
{
|
{
|
||||||
|
@ -1320,4 +1339,18 @@ describe('Utils', () => {
|
||||||
expect(getDefinitionName()).toBeUndefined();
|
expect(getDefinitionName()).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('langFromMime', () => {
|
||||||
|
test('should return correct lang name from content type', () => {
|
||||||
|
expect(langFromMime('application/xml')).toEqual('xml');
|
||||||
|
expect(langFromMime('application/x-xml')).toEqual('xml');
|
||||||
|
expect(langFromMime('application/csv')).toEqual('csv');
|
||||||
|
expect(langFromMime('application/x-csv')).toEqual('csv');
|
||||||
|
expect(langFromMime('text/plain')).toEqual('tex');
|
||||||
|
expect(langFromMime('text/x-plain')).toEqual('tex');
|
||||||
|
expect(langFromMime('application/plain')).toEqual('tex');
|
||||||
|
|
||||||
|
expect(langFromMime('text/some-type')).toEqual('clike');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -179,10 +179,11 @@ export function titleize(text: string) {
|
||||||
return text.charAt(0).toUpperCase() + text.slice(1);
|
return text.charAt(0).toUpperCase() + text.slice(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeQueryString(serverUrl: string): string {
|
export function removeQueryStringAndHash(serverUrl: string): string {
|
||||||
try {
|
try {
|
||||||
const url = parseURL(serverUrl);
|
const url = parseURL(serverUrl);
|
||||||
url.search = '';
|
url.search = '';
|
||||||
|
url.hash = '';
|
||||||
return url.toString();
|
return url.toString();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// when using with redoc-cli serverUrl can be empty resulting in crash
|
// when using with redoc-cli serverUrl can be empty resulting in crash
|
||||||
|
|
|
@ -21,6 +21,7 @@ import 'prismjs/components/prism-scala.js';
|
||||||
import 'prismjs/components/prism-sql.js';
|
import 'prismjs/components/prism-sql.js';
|
||||||
import 'prismjs/components/prism-swift.js';
|
import 'prismjs/components/prism-swift.js';
|
||||||
import 'prismjs/components/prism-yaml.js';
|
import 'prismjs/components/prism-yaml.js';
|
||||||
|
import 'prismjs/components/prism-csv.js';
|
||||||
|
|
||||||
const DEFAULT_LANG = 'clike';
|
const DEFAULT_LANG = 'clike';
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
Referenced,
|
Referenced,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { IS_BROWSER } from './dom';
|
import { IS_BROWSER } from './dom';
|
||||||
import { isNumeric, removeQueryString, resolveUrl, isArray, isBoolean } from './helpers';
|
import { isNumeric, removeQueryStringAndHash, resolveUrl, isArray, isBoolean } from './helpers';
|
||||||
|
|
||||||
function isWildcardStatusCode(statusCode: string | number): statusCode is string {
|
function isWildcardStatusCode(statusCode: string | number): statusCode is string {
|
||||||
return typeof statusCode === 'string' && /\dxx/i.test(statusCode);
|
return typeof statusCode === 'string' && /\dxx/i.test(statusCode);
|
||||||
|
@ -121,6 +121,10 @@ export function isPrimitiveType(
|
||||||
schema: OpenAPISchema,
|
schema: OpenAPISchema,
|
||||||
type: string | string[] | undefined = schema.type,
|
type: string | string[] | undefined = schema.type,
|
||||||
) {
|
) {
|
||||||
|
if (schema['x-circular-ref']) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (schema.oneOf !== undefined || schema.anyOf !== undefined) {
|
if (schema.oneOf !== undefined || schema.anyOf !== undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -136,7 +140,9 @@ export function isPrimitiveType(
|
||||||
isPrimitive =
|
isPrimitive =
|
||||||
schema.properties !== undefined
|
schema.properties !== undefined
|
||||||
? Object.keys(schema.properties).length === 0
|
? Object.keys(schema.properties).length === 0
|
||||||
: schema.additionalProperties === undefined && schema.unevaluatedProperties === undefined;
|
: schema.additionalProperties === undefined &&
|
||||||
|
schema.unevaluatedProperties === undefined &&
|
||||||
|
schema.patternProperties === undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isArray(schema.items) || isArray(schema.prefixItems)) {
|
if (isArray(schema.items) || isArray(schema.prefixItems)) {
|
||||||
|
@ -395,6 +401,15 @@ export function langFromMime(contentType: string): string {
|
||||||
if (contentType.search(/xml/i) !== -1) {
|
if (contentType.search(/xml/i) !== -1) {
|
||||||
return 'xml';
|
return 'xml';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (contentType.search(/csv/i) !== -1) {
|
||||||
|
return 'csv';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentType.search(/plain/i) !== -1) {
|
||||||
|
return 'tex';
|
||||||
|
}
|
||||||
|
|
||||||
return 'clike';
|
return 'clike';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -541,13 +556,13 @@ export function mergeParams(
|
||||||
): Array<Referenced<OpenAPIParameter>> {
|
): Array<Referenced<OpenAPIParameter>> {
|
||||||
const operationParamNames = {};
|
const operationParamNames = {};
|
||||||
operationParams.forEach(param => {
|
operationParams.forEach(param => {
|
||||||
param = parser.shallowDeref(param);
|
({ resolved: param } = parser.deref(param));
|
||||||
operationParamNames[param.name + '_' + param.in] = true;
|
operationParamNames[param.name + '_' + param.in] = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// filter out path params overridden by operation ones with the same name
|
// filter out path params overridden by operation ones with the same name
|
||||||
pathParams = pathParams.filter(param => {
|
pathParams = pathParams.filter(param => {
|
||||||
param = parser.shallowDeref(param);
|
({ resolved: param } = parser.deref(param));
|
||||||
return !operationParamNames[param.name + '_' + param.in];
|
return !operationParamNames[param.name + '_' + param.in];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -591,7 +606,7 @@ export function normalizeServers(
|
||||||
return href.endsWith('.html') ? dirname(href) : href;
|
return href.endsWith('.html') ? dirname(href) : href;
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseUrl = specUrl === undefined ? removeQueryString(getHref()) : dirname(specUrl);
|
const baseUrl = specUrl === undefined ? removeQueryStringAndHash(getHref()) : dirname(specUrl);
|
||||||
|
|
||||||
if (servers.length === 0) {
|
if (servers.length === 0) {
|
||||||
// Behaviour defined in OpenAPI spec: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#openapi-object
|
// Behaviour defined in OpenAPI spec: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#openapi-object
|
||||||
|
@ -616,6 +631,7 @@ export function normalizeServers(
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SECURITY_DEFINITIONS_JSX_NAME = 'SecurityDefinitions';
|
export const SECURITY_DEFINITIONS_JSX_NAME = 'SecurityDefinitions';
|
||||||
|
export const OLD_SECURITY_DEFINITIONS_JSX_NAME = 'security-definitions';
|
||||||
export const SCHEMA_DEFINITION_JSX_NAME = 'SchemaDefinition';
|
export const SCHEMA_DEFINITION_JSX_NAME = 'SchemaDefinition';
|
||||||
|
|
||||||
export let SECURITY_SCHEMES_SECTION_PREFIX = 'section/Authentication/';
|
export let SECURITY_SCHEMES_SECTION_PREFIX = 'section/Authentication/';
|
||||||
|
@ -632,6 +648,8 @@ export const shortenHTTPVerb = verb =>
|
||||||
export function isRedocExtension(key: string): boolean {
|
export function isRedocExtension(key: string): boolean {
|
||||||
const redocExtensions = {
|
const redocExtensions = {
|
||||||
'x-circular-ref': true,
|
'x-circular-ref': true,
|
||||||
|
'x-parentRefs': true,
|
||||||
|
'x-refsStack': true,
|
||||||
'x-code-samples': true, // deprecated
|
'x-code-samples': true, // deprecated
|
||||||
'x-codeSamples': true,
|
'x-codeSamples': true,
|
||||||
'x-displayName': true,
|
'x-displayName': true,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user