mirror of
https://github.com/Redocly/redoc.git
synced 2025-08-03 03:40:23 +03:00
Merge tag 'v2.0.0-rc.57' into sections-at-the-end
This commit is contained in:
commit
042150c643
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: 'type: bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Minimal reproducible OpenAPI snippet(if possible)**
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the problem to be solved**
|
||||
A clear and concise description of what problem to be solved
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
13
.github/pull_request_template.md
vendored
Normal file
13
.github/pull_request_template.md
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
## What/Why/How?
|
||||
|
||||
## Reference
|
||||
|
||||
## Testing
|
||||
|
||||
## Screenshots (optional)
|
||||
|
||||
## Check yourself
|
||||
|
||||
- [ ] Code is linted
|
||||
- [ ] Tested
|
||||
- [ ] All new/updated code is covered with tests
|
6
.github/sync.yml
vendored
Normal file
6
.github/sync.yml
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
group:
|
||||
- files:
|
||||
- source: docs/
|
||||
dest: docs/redoc
|
||||
repos: |
|
||||
Redocly/docs
|
42
.github/workflows/demo-deploy-s3.yml
vendored
42
.github/workflows/demo-deploy-s3.yml
vendored
|
@ -1,42 +0,0 @@
|
|||
name: Redoc demo CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v[0-9]*.[0-9]*.[0-9]*
|
||||
|
||||
jobs:
|
||||
build-and-unit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- run: npm ci
|
||||
- run: npm run bundle
|
||||
- run: npm test
|
||||
deploy:
|
||||
needs: build-and-unit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: cache node modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: |
|
||||
npm-${{ hashFiles('package-lock.json') }}
|
||||
npm-
|
||||
- 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: 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 "/*"
|
12
.github/workflows/e2e-tests.yml
vendored
Normal file
12
.github/workflows/e2e-tests.yml
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
name: Tests e2e
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build-and-e2e:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: npm ci
|
||||
- run: npm run bundle
|
||||
- run: npm run e2e
|
45
.github/workflows/main.yml
vendored
Normal file
45
.github/workflows/main.yml
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
name: Publish Docker image
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker image to GitHub Packages
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v2
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Prepare
|
||||
id: prep
|
||||
run: |
|
||||
DOCKER_IMAGE=ghcr.io/redocly/redoc/cli
|
||||
VERSION=edge
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
VERSION=${GITHUB_REF#refs/tags/}
|
||||
elif [[ $GITHUB_REF == refs/heads/* ]]; then
|
||||
VERSION=$(echo ${GITHUB_REF#refs/heads/} | sed -r 's#/+#-#g')
|
||||
elif [[ $GITHUB_REF == refs/pull/* ]]; then
|
||||
VERSION=pr-${{ github.event.number }}
|
||||
fi
|
||||
TAGS="${DOCKER_IMAGE}:${VERSION}"
|
||||
if [ "${{ github.event_name }}" = "push" ]; then
|
||||
TAGS="$TAGS,${DOCKER_IMAGE}:sha-${GITHUB_SHA::8}"
|
||||
fi
|
||||
echo ::set-output name=version::${VERSION}
|
||||
echo ::set-output name=tags::${TAGS}
|
||||
echo ::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ')
|
||||
- name: Push to GitHub Packages
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: ./cli
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.prep.outputs.tags }}
|
120
.github/workflows/publish-cli.yml
vendored
Normal file
120
.github/workflows/publish-cli.yml
vendored
Normal file
|
@ -0,0 +1,120 @@
|
|||
name: Publish cli
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
|
||||
jobs:
|
||||
bundle:
|
||||
needs: [ check-version-cli ]
|
||||
if: needs.check-version-cli.outputs.changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: |
|
||||
npm-${{ hashFiles('package-lock.json') }}
|
||||
npm-
|
||||
- run: npm ci
|
||||
- run: npm run bundle
|
||||
- name: Store bundle artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: bundles-cli
|
||||
path: bundles
|
||||
retention-days: 1
|
||||
unit-tests:
|
||||
needs: [ check-version-cli ]
|
||||
if: needs.check-version-cli.outputs.changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
e2e-tests:
|
||||
needs: [ bundle ]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- run: npm ci
|
||||
- name: Download bundled artifact
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: bundles-cli
|
||||
path: bundles
|
||||
- run: npm run e2e
|
||||
bundle-cli:
|
||||
needs: [ check-version-cli ]
|
||||
if: needs.check-version-cli.outputs.changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: |
|
||||
npm-${{ hashFiles('package-lock.json') }}
|
||||
npm-
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Bundle
|
||||
run: npm run compile:cli
|
||||
- name: Store bundle artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: cli
|
||||
path: cli
|
||||
retention-days: 1
|
||||
check-version-cli:
|
||||
name: Check Version
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
changed: ${{ steps.check.outputs.changed }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v2
|
||||
- name: Check if version has been updated
|
||||
id: check
|
||||
uses: EndBug/version-check@v2.0.1
|
||||
with:
|
||||
file-name: ./cli/package.json
|
||||
file-url: https://unpkg.com/redoc-cli/package.json
|
||||
static-checking: localIsNew
|
||||
publish-cli:
|
||||
needs: [bundle-cli, unit-tests, e2e-tests]
|
||||
if: needs.check-version-cli.outputs.changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: "14.x"
|
||||
- uses: actions/checkout@v2
|
||||
- name: Download cli bundled artifact
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: cli
|
||||
path: cli
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: |
|
||||
npm-${{ hashFiles('package-lock.json') }}
|
||||
npm-
|
||||
- name: Publish to NPM
|
||||
run: cd cli/ && npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
102
.github/workflows/publish.yml
vendored
Normal file
102
.github/workflows/publish.yml
vendored
Normal file
|
@ -0,0 +1,102 @@
|
|||
name: Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v[0-9]*.[0-9]*.[0-9]*
|
||||
|
||||
jobs:
|
||||
bundle:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: |
|
||||
npm-${{ hashFiles('package-lock.json') }}
|
||||
npm-
|
||||
- run: npm ci
|
||||
- run: npm run bundle
|
||||
- name: Store bundle artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: bundles
|
||||
path: bundles
|
||||
retention-days: 1
|
||||
unit-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
e2e-tests:
|
||||
needs: [bundle]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- run: npm ci
|
||||
- name: Download bundled artifact
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: bundles
|
||||
path: bundles
|
||||
- run: npm run e2e
|
||||
# disable this for now
|
||||
# deploy-demo:
|
||||
# needs: [bundle, unit-tests, e2e-tests]
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v1
|
||||
# - 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@v2
|
||||
# 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:
|
||||
needs: [bundle, unit-tests, e2e-tests]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: "14.x"
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
- uses: actions/checkout@v2
|
||||
- name: Download bundled artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: bundles
|
||||
path: bundles
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: |
|
||||
npm-${{ hashFiles('package-lock.json') }}
|
||||
npm-
|
||||
- name: Before deploy
|
||||
run: npm ci && npm run declarations
|
||||
- name: Publish to NPM
|
||||
run: npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
- name: After script
|
||||
run: cat ./coverage/lcov.info | coveralls
|
18
.github/workflows/sync.yml
vendored
Normal file
18
.github/workflows/sync.yml
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
name: Sync Files
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@master
|
||||
- name: Run GitHub File Sync
|
||||
uses: Redocly/repo-file-sync-action@master
|
||||
with:
|
||||
GH_PAT: ${{ secrets.GH_PAT }}
|
||||
COMMIT_PREFIX: "sync:"
|
||||
SKIP_PR: true
|
2
.github/workflows/unit-tests.yml
vendored
2
.github/workflows/unit-tests.yml
vendored
|
@ -9,4 +9,4 @@ jobs:
|
|||
- uses: actions/checkout@v1
|
||||
- run: npm ci
|
||||
- run: npm run bundle
|
||||
- run: npm test
|
||||
- run: npm test
|
32
.travis.yml
32
.travis.yml
|
@ -1,32 +0,0 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- '10'
|
||||
cache:
|
||||
directories:
|
||||
- "~/.cache"
|
||||
env:
|
||||
global:
|
||||
- GH_REF: github.com/Redocly/redoc.git
|
||||
- GIT_AUTHOR_EMAIL: redoc-bot@users.noreply.github.com
|
||||
- GIT_AUTHOR_NAME: RedocBot
|
||||
- secure: apiavCfCQngL9Een1m7MIXMf3bqO3rY4YY59TMBl/yFKi80CEsHPHhgVUkl6hC+aM5PeBt/vgjh37rHMX31j/pcSZ4Z8SO/4Bwr36iHfhSxSEuAQog8P07qWqH7wYYWGIVmF682stgl0fYF+GN92sx/6edFVzsWVECf2G7imtICKSTbhKGm3Dhn2JwGnhD7eyfgZ33omgiaswumdu0xABoXDfqSZR+16fC4Ap5rhv3fXO9ndvRNy1STn376nT+my6e86UrQL4aS/S+HNHgIe1BUs+5cOp6Jgw6t0ie7phY0EAiECsRxy9K4e3Dctv9m6+Wma4+vy65MS0zGyrqey6oyV4l827sCOjrD1qcqc9bX6FlMSouVoNfE4ZjINNAbgigTaiLSoDSPcf5I5smkkM2ezzFOMSZwZxNdaNL2LKb97vc8m/ZUkv0sKZyT7oqVL7aJweEivsSHj5l2KR8Z7XrVB1y2eI6GvyTSa/d+CL4dSRzjh8+IRN047YBrdTKD5IkdT0upfoBu14WPUfFmLKxX+iMCslXRWb6kwojhrWNYmZvL65KRAzJ6+eIPDG/W5QUOpYyYT77bLlBQjVo6NmVvl9v3HMECq9CHH0ivKFBGPiKMOx7cJkTax3FuyznOW2WCXB9kTb5Zk9toaiNlSp9L6ll/h2Eyxa6n6sWUgmmM=
|
||||
- secure: vVRg9BKGBwF2MbXQnEccFL+XW0/7RaBmge9k7jbGYScBwkP3XjnQ/Xaj0cvTz2CM2EqXsbpwfvr4Jo+enW/E3MGy5RiEzv5hUe/jIFRR0gfAFbZxSTvg5xiFhTDffqQk0fncO4jXu+wPO5lZ2CMRWzyXz3i1MZhjMcAgoDr1+TRss/EGXLNHxr2RM88tpUW0fV2prIRoyGqhCgnYZtrm7hmr41Ej+itg1MqZLml/Rjkt3KsNgI+z0O5Qn3QSAO8GtPZqeftQxAjevOmxZGcssxY8EJvqbjAujr4y51WncXpEmCRPSY2J9R5+fkgZurqwnJapbQpjwKYemok3ps7EHg2gWkAlmPdQO4LKpbffGkM/o5b+8+HdIuQZugsSWQD9hUSftTAFLcfA1isi7V2lHE1m8bX/vk9zIyDdcPSwIaFe9y+w3PexwFmTjPLq+nia/UY2kARFZMEIFAJby6gkA70DcAJ50QOM86InJu5DSzGbIssgTGAXCn0TPPyGveaurVLw8x61j3yh8LDF46gUHey3rqv6WjpCM9h/vg7X/gq5ve/5Q2KHscUKfs/sA53Mt7qPeqRZY1QCaaRjzqJO/ZraHqWWeKmPKaWhPGR0kYEnkvB+K9GZ+HNSWCltjCO4SJ1xeEl7CRqQxAwdiMATF5SKqyiC+bn5oc35mFgbRF8=
|
||||
- secure: ela1tn4wkJQZ8O4iv+4pIZi5cebxeCStVF1tEUe6qa6WWgJYVXmS2tEv3QQ36NUBFrP58Y6yl10XguPnvj/2BCqcZI4FUBHh3BfiBoUtXxDCVKI5LtlniNiOFGUwfzEeYka8T51zFlcUXSCCaxHkRZbmBsIzeJ39UwTi5fy0qwLv9GgL0czhwm8I8sZ8gyWdGmqpXNFEsb9JP4ZA3mw2qpWkGpGAqQPD9XSCkU3LmX1/ltwsBMAgGYKLLo7vU8d5KV2c8L1Gnxfl6BvfmqUD/dsas/1rnk08rU2nez5ekuQa2tJRkDLOv8bqvrGRLjHSUa3yPuisC6SsDGSU7/3DcozZyYsz7WQ6WI8tYabyjqqeJTF1N8a5T3IbZaZNV1J4JHOO9Cb/y7gIg4edANg6tbe7MzZpdEPRBnw6OkdTdirpNsWQ/jnfpY1hn6mraQZz/q8yaz3W21NjbBJhVnvfh5gWLKQ3YAAziCBhmmrThFhUu0czz+G920MuFo477TBcxvlrE7CaNJ0Q6yYkDehEPOv3jvEs1QVHPwuRrlaLTbBhrlTICKZ58gdX30O8N4i0Xgp/v6qrC03bplnMQc8E/uC61wcVLJixnlZVp8FODpUvPjsxVFkpuNSOIAaiqcERmoiPXx05Epzmr78hjU5rYCx/1MmVoeB4gs9YO+4guD4=
|
||||
addons:
|
||||
chrome: stable
|
||||
apt:
|
||||
packages:
|
||||
- libgconf-2-4
|
||||
before_script: npm run bundle
|
||||
script: npm test && ([ "${TRAVIS_PULL_REQUEST}" = "false" ] && npm run e2e-ci || npm
|
||||
run e2e)
|
||||
after_script: cat ./coverage/lcov.info | coveralls
|
||||
before_deploy: npm run compile:cli && npm run declarations
|
||||
deploy:
|
||||
- provider: npm
|
||||
skip_cleanup: true
|
||||
email: gotsijroman@gmail.com
|
||||
tag: next
|
||||
api_key: "$NPM_TOKEN"
|
||||
on:
|
||||
tags: true
|
87
CHANGELOG.md
87
CHANGELOG.md
|
@ -1,3 +1,90 @@
|
|||
# [2.0.0-rc.57](https://github.com/Redocly/redoc/compare/v2.0.0-rc.56...v2.0.0-rc.57) (2021-10-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix deref logic for oas3.1 ([#1767](https://github.com/Redocly/redoc/issues/1767)) ([4fb9c83](https://github.com/Redocly/redoc/commit/4fb9c835256b9e44bcecabde7baf0f0f3e5beb3f))
|
||||
* improve publish action scripts ([#1729](https://github.com/Redocly/redoc/issues/1729)) ([952c05c](https://github.com/Redocly/redoc/commit/952c05c6b4b95fe6082611fed9e2f0913272b904))
|
||||
* No match scenario in search ([#1667](https://github.com/Redocly/redoc/issues/1667)) ([352a851](https://github.com/Redocly/redoc/commit/352a8518576dfb6b240ec41212a64f1c7312ab67))
|
||||
* OpenAPI 3.1: Missing description when $ref used [#1727](https://github.com/Redocly/redoc/issues/1727) ([fe6909e](https://github.com/Redocly/redoc/commit/fe6909ed80dd6053b48c30f63a2460614bf957a9))
|
||||
* OpenAPI 3.1: Missing description when $ref used [#1727](https://github.com/Redocly/redoc/issues/1727) ([35f7787](https://github.com/Redocly/redoc/commit/35f77878de7d1dd250040771f17757a5a6ce85f9))
|
||||
* Redoc spelling ([c87600d](https://github.com/Redocly/redoc/commit/c87600d520f037d291169b44b5803a35af16b5a5))
|
||||
* Schema for events incorrectly omits readOnly and includes writeOnly ([#1720](https://github.com/Redocly/redoc/issues/1720) [#1540](https://github.com/Redocly/redoc/issues/1540)) ([a8e0c29](https://github.com/Redocly/redoc/commit/a8e0c296852661dec1dcad2388d7589f9e0d3609))
|
||||
* scrolling to the first item ([#1753](https://github.com/Redocly/redoc/issues/1753)) ([bccd213](https://github.com/Redocly/redoc/commit/bccd21394ef79940c2efbe24a0d866c7af103d94))
|
||||
* The number of items in the array in the array is incorrect [#1762](https://github.com/Redocly/redoc/issues/1762) ([#1763](https://github.com/Redocly/redoc/issues/1763)) ([3b8d644](https://github.com/Redocly/redoc/commit/3b8d6441bd9978b849a53021d40fd4fe150272ea))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add q/kdb+ syntax highlighting ([#1605](https://github.com/Redocly/redoc/issues/1605)) ([43451ba](https://github.com/Redocly/redoc/commit/43451ba4cd24270b8629a967d3fd2ce2eed8912e))
|
||||
* new option generatedPayloadSamplesMaxDepth ([#1642](https://github.com/Redocly/redoc/issues/1642)) ([bd9390a](https://github.com/Redocly/redoc/commit/bd9390a5bfc5458c06121110db33968a20fcebe4))
|
||||
|
||||
|
||||
|
||||
# [2.0.0-rc.56](https://github.com/Redocly/redoc/compare/v2.0.0-rc.53...v2.0.0-rc.56) (2021-08-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* handle empty object in security array ([#1678](https://github.com/Redocly/redoc/issues/1678)) ([9e1ea70](https://github.com/Redocly/redoc/commit/9e1ea703e56a71567b13d0d22e2d69945a22de4d))
|
||||
* hideLoading options in redoc standalone ([#1709](https://github.com/Redocly/redoc/issues/1709)) ([6a52a16](https://github.com/Redocly/redoc/commit/6a52a16d5b75a2955da7217c4a264f0fa8e98c89))
|
||||
* improve openapi 3.1 ([#1700](https://github.com/Redocly/redoc/issues/1700)) ([cd2d6f7](https://github.com/Redocly/redoc/commit/cd2d6f76e87c8385786a9c8e51c0d11c79d9707c))
|
||||
- show contentEncoding on fields
|
||||
- crash with OpenAPI 3.1 type as array of strings in requestBody
|
||||
- nullable label not shown
|
||||
* nullable object's fields were missing ([#1721](https://github.com/Redocly/redoc/issues/1721)) ([ddf297b](https://github.com/Redocly/redoc/commit/ddf297b11269ef515bd62771912a5609721d5e39))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add github action to build docker images and push to ghcr.io on release ([#1614](https://github.com/Redocly/redoc/issues/1614)) ([919a5f0](https://github.com/Redocly/redoc/commit/919a5f02fb94ca869011d5eaf63ee71b61b60150))
|
||||
* add yaml highlight ([#1684](https://github.com/Redocly/redoc/issues/1684)) ([d724440](https://github.com/Redocly/redoc/commit/d72444008533623c87f238fe8758b1dd518b89eb))
|
||||
* added localization for some labels ([#1675](https://github.com/Redocly/redoc/issues/1675)) ([ec50858](https://github.com/Redocly/redoc/commit/ec50858ec47af08c5fe553266fe3c209fba97eae))
|
||||
|
||||
|
||||
# [2.0.0-rc.55](https://github.com/Redocly/redoc/compare/v2.0.0-rc.54...v2.0.0-rc.55) (2021-07-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* broken linkify ([3df72fb](https://github.com/Redocly/redoc/commit/3df72fb99ff24fb9a551565b7568d96f8614ed6f)), closes [#1655](https://github.com/Redocly/redoc/issues/1655)
|
||||
* fix accidentally removed onLoaded ([b41a8b4](https://github.com/Redocly/redoc/commit/b41a8b4ac714084dc25de7914fa1f99386e907e2)), closes [#1656](https://github.com/Redocly/redoc/issues/1656)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* added git folder sync config ([a69f0fb](https://github.com/Redocly/redoc/commit/a69f0fb00986a04c812ab273711e8f3501b98139))
|
||||
|
||||
|
||||
|
||||
# [2.0.0-rc.54](https://github.com/Redocly/redoc/compare/v2.0.0-rc.53...v2.0.0-rc.54) (2021-06-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* added missing semicolon to styling ([#1578](https://github.com/Redocly/redoc/issues/1578)) ([dfc4cf1](https://github.com/Redocly/redoc/commit/dfc4cf1caa131aa7bc6da6d489e3a8425d800326))
|
||||
* parse json theme string for standalone tag ([#1492](https://github.com/Redocly/redoc/issues/1492)) ([d7a0a4d](https://github.com/Redocly/redoc/commit/d7a0a4da17241dd9c089202dba76a8312248616e))
|
||||
* right absolute path for load and bundle definition ([#1579](https://github.com/Redocly/redoc/issues/1579)) ([ab2d57a](https://github.com/Redocly/redoc/commit/ab2d57a5a2ac5df007d76be0d664f3fb5f909566))
|
||||
* use operation path if operation summary/description is not provided ([#1596](https://github.com/Redocly/redoc/issues/1596)) ([4b072be](https://github.com/Redocly/redoc/commit/4b072be8d1c0dc4f1fa627168eebaed0a0213e08)), closes [#1270](https://github.com/Redocly/redoc/issues/1270)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add basic support OpenAPI 3.1 ([#1622](https://github.com/Redocly/redoc/issues/1622)) ([823be24](https://github.com/Redocly/redoc/commit/823be24b313c3a2445df7e0801a0cc79c20bacd1))
|
||||
* merge refs oas 3.1 ([#1640](https://github.com/Redocly/redoc/issues/1640)) ([f4ea368](https://github.com/Redocly/redoc/commit/f4ea368f78a693fd70d48b5e0e5ffce3560432f4))
|
||||
|
||||
|
||||
|
||||
# [2.0.0-rc.51](https://github.com/Redocly/redoc/compare/v2.0.0-rc.50...v2.0.0-rc.51) (2021-04-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use openapi-core to bundle definition instead of json-schema-ref-parser ([5033946](https://github.com/Redocly/redoc/commit/503394655da2aac544e278796098cba93d9194b9)),
|
||||
closes: [#1506](https://github.com/Redocly/redoc/issues/1506), [#1478](https://github.com/Redocly/redoc/issues/1478)
|
||||
* add disable-google-font parameter to serve command in cli ([c7bbef5](https://github.com/Redocly/redoc/commit/c7bbef515524095e957729eac35a5b7a97619b55)), closes [#1501](https://github.com/Redocly/redoc/issues/1501)
|
||||
|
||||
|
||||
|
||||
# [2.0.0-rc.50](https://github.com/Redocly/redoc/compare/v2.0.0-rc.49...v2.0.0-rc.50) (2021-02-15)
|
||||
|
||||
|
||||
|
|
278
README.md
278
README.md
|
@ -1,93 +1,161 @@
|
|||
<div align="center">
|
||||
<img alt="ReDoc logo" src="https://raw.githubusercontent.com/Redocly/redoc/master/docs/images/redoc-logo.png" width="400px" />
|
||||
<img alt="Redoc logo" src="https://raw.githubusercontent.com/Redocly/redoc/master//docs/images/redoc.png" width="400px" />
|
||||
|
||||
**OpenAPI/Swagger-generated API Reference Documentation**
|
||||
# Generate interactive API documentation from OpenAPI definitions
|
||||
|
||||
[](https://travis-ci.com/Redocly/redoc) [](https://coveralls.io/github/Redocly/redoc?branch=master) [](https://david-dm.org/Redocly/redoc) [](https://david-dm.org/Redocly/redoc#info=devDependencies) [](https://www.npmjs.com/package/redoc) [](https://github.com/Redocly/redoc/blob/master/LICENSE)
|
||||
|
||||
[](https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js) [](https://www.npmjs.com/package/redoc) [](https://www.jsdelivr.com/package/npm/redoc) [](https://hub.docker.com/r/redocly/redoc/)
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
**This is README for `2.0` version of ReDoc (React based). README for `1.x` version is on the branch [v1.x](https://github.com/Redocly/redoc/tree/v1.x)**
|
||||
**This is the README for the `2.x` version of Redoc (React-based).**
|
||||
**The README for the `1.x` version is on the [v1.x](https://github.com/Redocly/redoc/tree/v1.x) branch**
|
||||
|
||||
## About Redoc
|
||||
|
||||

|
||||
Redoc is an open-source tool for generating documentation from OpenAPI (fka Swagger) definitions.
|
||||
|
||||
## [Live demo](http://redocly.github.io/redoc/)
|
||||
By default Redoc offers a three-panel, responsive layout:
|
||||
|
||||
[<img alt="Deploy to Github" src="http://i.imgur.com/YZmaqk3.png" height="60px">](https://github.com/Rebilly/generator-openapi-repo#generator-openapi-repo--) [<img alt="ReDoc as a service" src="http://i.imgur.com/edqdCv6.png" height="60px">](https://redoc.ly) [<img alt="Customization services" src="http://i.imgur.com/c4sUF7M.png" height="60px">](https://redoc.ly/#services)
|
||||
- The left panel contains a search bar and navigation menu.
|
||||
- The central panel contains the documentation.
|
||||
- The right panel contains request and response examples.
|
||||
|
||||

|
||||
|
||||
## Live demo
|
||||
|
||||
If you want to see how Redoc will render your OpenAPI definition,
|
||||
you can try it out online at https://redocly.github.io/redoc/.
|
||||
|
||||
A version of the Swagger Petstore API is displayed by default.
|
||||
To test it with your own OpenAPI definition,
|
||||
enter the URL for your definition and select **TRY IT**.
|
||||
|
||||
## Redoc vs. Reference vs. Portals
|
||||
|
||||
Redoc is Redocly's community-edition product. Looking for something more?
|
||||
Checkout the following feature comparison of Redocly's premium products versus Redoc:
|
||||
|
||||
| Features | Redoc | Reference | Portals |
|
||||
|------------------------------|:---------:|:---------:|:-----------:|
|
||||
| **Specs** | | | |
|
||||
| Swagger 2.0 | √ | √ | √ |
|
||||
| OpenAPI 3.0 | √ | √ | √ |
|
||||
| OpenAPI 3.1 | √ (basic) | √ | √ |
|
||||
| | | | |
|
||||
| **Theming** | | | |
|
||||
| Fonts/colors | √ | √ | √ |
|
||||
| Extra theme options | | √ | √ |
|
||||
| | | | |
|
||||
| **Performance** | | | |
|
||||
| Pagination | | √ | √ |
|
||||
| Search (enhanced) | | √ | √ |
|
||||
| Search (server-side) | | | √ |
|
||||
| | | | |
|
||||
| **Multiple APIs** | | | |
|
||||
| Multiple versions | | √ | √ |
|
||||
| Multiple APIs | | | √ |
|
||||
| API catalog | | | √ |
|
||||
| | | | |
|
||||
| **Additional features** | | | |
|
||||
| Try-it console | | √ | √ |
|
||||
| Automated code samples | | √ | √ |
|
||||
| Deep links | | √ | √ |
|
||||
| More SEO control | | | √ |
|
||||
| Contextual docs | | | √ |
|
||||
| Landing pages | | | √ |
|
||||
| React hooks for more control | | | √ |
|
||||
| Personalization | | | √ |
|
||||
| Analytics integrations | | | √ |
|
||||
| Feedback | | | Coming Soon |
|
||||
|
||||
Refer to the Redocly's documentation for more information on these products:
|
||||
|
||||
- [Portals](https://redoc.ly/docs/developer-portal/introduction/)
|
||||
- [Reference](https://redoc.ly/docs/api-reference-docs/getting-started/)
|
||||
- [Redoc](https://redoc.ly/docs/redoc/quickstart/intro/)
|
||||
|
||||
## Features
|
||||
- Extremely easy deployment
|
||||
- [redoc-cli](https://github.com/Redocly/redoc/blob/master/cli/README.md) with ability to bundle your docs into **zero-dependency** HTML file
|
||||
- Server Side Rendering ready
|
||||
- The widest OpenAPI v2.0 features support (yes, it supports even `discriminator`) <br>
|
||||

|
||||
- OpenAPI 3.0 support
|
||||
- Neat **interactive** documentation for nested objects <br>
|
||||

|
||||
- Code samples support (via vendor extension) <br>
|
||||

|
||||
- Responsive three-panel design with menu/scrolling synchronization
|
||||
- Integrate API Introduction into side menu - ReDoc takes advantage of markdown headings from OpenAPI description field. It pulls them into side menu and also supports deep linking.
|
||||
- High-level grouping in side-menu via [`x-tagGroups`](docs/redoc-vendor-extensions.md#x-tagGroups) vendor extension
|
||||
- Simple integration with `create-react-app` ([sample](https://github.com/APIs-guru/create-react-app-redoc))
|
||||
- Branding/customizations via [`theme` option](#redoc-options-object)
|
||||
- [Multiple deployment options](https://redoc.ly/docs/redoc/quickstart/intro/)
|
||||
- [Server-side rendering (SSR) ready](https://redoc.ly/docs/redoc/quickstart/cli/#redoc-cli-commands)
|
||||
- Ability to integrate your API introduction into the side menu
|
||||
- [Simple integration with `create-react-app`](https://redoc.ly/docs/redoc/quickstart/react/)
|
||||
|
||||
## Roadmap
|
||||
- [x] ~~[OpenAPI v3.0 support](https://github.com/Redocly/redoc/issues/312)~~
|
||||
- [x] ~~performance optimizations~~
|
||||
- [x] ~~better navigation (menu improvements + search)~~
|
||||
- [x] ~~React rewrite~~
|
||||
- [x] ~~docs pre-rendering (performance and SEO)~~
|
||||
- [ ] ability to simple branding/styling
|
||||
- [ ] built-in API Console
|
||||
[Example repo](https://github.com/APIs-guru/create-react-app-redoc)
|
||||
- [Command-line interface to bundle your docs into a **zero-dependency** HTML file](https://redoc.ly/docs/redoc/quickstart/cli/)
|
||||
- Neat **interactive** documentation for nested objects <br>
|
||||

|
||||
|
||||
## Customization options
|
||||
[<img alt="Customization services" src="http://i.imgur.com/c4sUF7M.png" height="60px">](https://redoc.ly/#services)
|
||||
- High-level grouping in side-menu with the [`x-tagGroups`](https://redoc.ly/docs/api-reference-docs/specification-extensions/x-tag-groups/) specification extension
|
||||
- Branding/customizations using the [`theme` option](https://redoc.ly/docs/api-reference-docs/configuration/theming/)
|
||||
|
||||
## Support
|
||||
- OpenAPI v3.0 support
|
||||
- Basic OpenAPI v3.1 support
|
||||
- Broad OpenAPI v2.0 feature support (yes, it supports even `discriminator`) <br>
|
||||

|
||||
- Code samples support (via vendor extension) <br>
|
||||

|
||||
|
||||
## Releases
|
||||
**Important:** all the 2.x releases are deployed to npm and can be used via jsdeliver:
|
||||
- particular release, e.g. `v2.0.0-alpha.15`: https://cdn.jsdelivr.net/npm/redoc@2.0.0-alpha.17/bundles/redoc.standalone.js
|
||||
**Important:** all the 2.x releases are deployed to npm and can be used with jsdeliver:
|
||||
- particular release, for example, `v2.0.0-alpha.15`: https://cdn.jsdelivr.net/npm/redoc@2.0.0-alpha.17/bundles/redoc.standalone.js
|
||||
- `next` release: https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js
|
||||
|
||||
Additionally, all the 1.x releases are hosted on our GitHub Pages-based CDN **(deprecated)**:
|
||||
- particular release, e.g. `v1.2.0`: https://rebilly.github.io/ReDoc/releases/v1.2.0/redoc.min.js
|
||||
- particular release, for example `v1.2.0`: https://rebilly.github.io/ReDoc/releases/v1.2.0/redoc.min.js
|
||||
- `v1.x.x` release: https://rebilly.github.io/ReDoc/releases/v1.x.x/redoc.min.js
|
||||
- `latest` release: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js - it will point to latest 1.x.x release since 2.x releases are not hosted on this CDN but on unpkg.
|
||||
|
||||
## Version Guidance
|
||||
| ReDoc Release | OpenAPI Specification |
|
||||
| Redoc Release | OpenAPI Specification |
|
||||
|:--------------|:----------------------|
|
||||
| 2.0.0-alpha.54| 3.1, 3.0.x, 2.0 |
|
||||
| 2.0.0-alpha.x | 3.0, 2.0 |
|
||||
| 1.19.x | 2.0 |
|
||||
| 1.18.x | 2.0 |
|
||||
| 1.17.x | 2.0 |
|
||||
|
||||
## Some Real-life usages
|
||||
## Showcase
|
||||
- [Rebilly](https://api-reference.rebilly.com/)
|
||||
- [Docker Engine](https://docs.docker.com/engine/api/v1.25/)
|
||||
- [Zuora](https://www.zuora.com/developer/api-reference/)
|
||||
- [Discourse](http://docs.discourse.org)
|
||||
- [Commbox](https://www.commbox.io/api/)
|
||||
- [APIs.guru](https://apis.guru/api-doc/)
|
||||
- [FastAPI](https://github.com/tiangolo/fastapi)
|
||||
- [BoxKnight](https://www.docs.boxknight.com/)
|
||||
|
||||
## Lint OpenAPI definitions
|
||||
|
||||
Redocly's OpenAPI CLI is an open source command-line tool that you can use to lint
|
||||
your OpenAPI definition. Linting helps you to catch errors and inconsistencies in your
|
||||
OpenAPI definition before publishing.
|
||||
|
||||
Refer to [Lint configuration](https://redoc.ly/docs/cli/guides/lint/) in the OpenAPI documentation for more information.
|
||||
|
||||
## Deployment
|
||||
|
||||
### TL;DR
|
||||
### TL;DR final code example
|
||||
|
||||
To render your OpenAPI definition using Redoc, use the following HTML code sample and
|
||||
replace the `spec-url` attribute with the url or local file address to your definition.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>ReDoc</title>
|
||||
<title>Redoc</title>
|
||||
<!-- needed for adaptive design -->
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||
|
||||
<!--
|
||||
ReDoc doesn't change outer page styles
|
||||
Redoc doesn't change outer page styles
|
||||
-->
|
||||
<style>
|
||||
body {
|
||||
|
@ -98,117 +166,31 @@ Additionally, all the 1.x releases are hosted on our GitHub Pages-based CDN **(d
|
|||
</head>
|
||||
<body>
|
||||
<redoc spec-url='http://petstore.swagger.io/v2/swagger.json'></redoc>
|
||||
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"> </script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
That's all folks!
|
||||
|
||||
**IMPORTANT NOTE:** if you work with untrusted user spec, use `untrusted-spec` [option](#redoc-options-object) to prevent XSS security risks.
|
||||
|
||||
### 1. Install ReDoc (skip this step for CDN)
|
||||
Install using [npm](https://docs.npmjs.com/getting-started/what-is-npm):
|
||||
|
||||
npm i redoc
|
||||
|
||||
or using [yarn](https://yarnpkg.com):
|
||||
|
||||
yarn add redoc
|
||||
|
||||
### 2. Reference redoc script in HTML
|
||||
For **CDN**:
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js"> </script>
|
||||
```
|
||||
|
||||
For npm:
|
||||
```html
|
||||
<script src="node_modules/redoc/bundles/redoc.standalone.js"> </script>
|
||||
```
|
||||
For step-by-step instructions for how to get started using Redoc
|
||||
to render your OpenAPI definition, refer to the
|
||||
[**Redoc quickstart guide**](https://redoc.ly/docs/redoc/quickstart/intro/).
|
||||
|
||||
### 3. Add `<redoc>` element to your page
|
||||
```html
|
||||
<redoc spec-url="url/to/your/spec"></redoc>
|
||||
```
|
||||
See [**IE11 Support Notes**](docs/usage-with-ie11.md) for information on
|
||||
IE support for Redoc.
|
||||
|
||||
### 4. Enjoy :smile:
|
||||
|
||||
|
||||
## Usage as a React component
|
||||
|
||||
Install peer dependencies required by ReDoc if you don't have them installed already:
|
||||
|
||||
npm i react react-dom mobx styled-components core-js
|
||||
|
||||
Import `RedocStandalone` component from 'redoc' module:
|
||||
|
||||
```js
|
||||
import { RedocStandalone } from 'redoc';
|
||||
```
|
||||
|
||||
and use it somewhere in your component:
|
||||
|
||||
```js
|
||||
<RedocStandalone specUrl="url/to/your/spec"/>
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```js
|
||||
<RedocStandalone spec={/* spec as an object */}/>
|
||||
```
|
||||
|
||||
Also you can pass options:
|
||||
|
||||
```js
|
||||
<RedocStandalone
|
||||
specUrl="http://rebilly.github.io/RebillyAPI/openapi.json"
|
||||
options={{
|
||||
nativeScrollbars: true,
|
||||
theme: { colors: { primary: { main: '#dd5522' } } },
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
Here are detailed [options docs](#redoc-options-object).
|
||||
|
||||
You can also specify `onLoaded` callback which will be called each time Redoc has been fully rendered or when error occurs (with an error as the first argument). *NOTE*: It may be called multiply times if you change component properties
|
||||
|
||||
```js
|
||||
<RedocStandalone
|
||||
specUrl="http://rebilly.github.io/RebillyAPI/openapi.json"
|
||||
onLoaded={error => {
|
||||
if (!error) {
|
||||
console.log('Yay!');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
[**IE11 Support Notes**](docs/usage-with-ie11.md)
|
||||
|
||||
## The Docker way
|
||||
|
||||
ReDoc is available as pre-built Docker image in official [Docker Hub repository](https://hub.docker.com/r/redocly/redoc/). You may simply pull & run it:
|
||||
|
||||
docker pull redocly/redoc
|
||||
docker run -p 8080:80 redocly/redoc
|
||||
|
||||
Also you may rewrite some predefined environment variables defined in [Dockerfile](./config/docker/Dockerfile). By default ReDoc starts with demo Petstore spec located at `http://petstore.swagger.io/v2/swagger.json`, but you may change this URL using environment variable `SPEC_URL`:
|
||||
|
||||
docker run -p 8080:80 -e SPEC_URL=https://api.example.com/openapi.json redocly/redoc
|
||||
|
||||
## ReDoc CLI
|
||||
|
||||
[See here](https://github.com/Redocly/redoc/blob/master/cli/README.md)
|
||||
## Redoc CLI
|
||||
For more information on Redoc's commmand-line interface, refer to
|
||||
[**Using the Redoc CLI**](https://redoc.ly/docs/redoc/quickstart/cli/).
|
||||
|
||||
## Configuration
|
||||
|
||||
### Security Definition location
|
||||
You can inject Security Definitions widget into any place of your specification `description`. Check out details [here](docs/security-definitions-injection.md).
|
||||
You can inject the Security Definitions widget into any place in your definition `description`.
|
||||
For more information, refer to [Security definitions injection](docs/security-definitions-injection.md).
|
||||
|
||||
### Swagger vendor extensions
|
||||
ReDoc makes use of the following [vendor extensions](https://swagger.io/specification/#specificationExtensions):
|
||||
### OpenAPI specification extensions
|
||||
Redoc uses the following [specification extensions](https://swagger.io/specification/#specificationExtensions):
|
||||
* [`x-logo`](docs/redoc-vendor-extensions.md#x-logo) - is used to specify API logo
|
||||
* [`x-traitTag`](docs/redoc-vendor-extensions.md#x-traitTag) - useful for handling out common things like Pagination, Rate-Limits, etc
|
||||
* [`x-codeSamples`](docs/redoc-vendor-extensions.md#x-codeSamples) - specify operation code samples
|
||||
|
@ -224,11 +206,12 @@ ReDoc makes use of the following [vendor extensions](https://swagger.io/specific
|
|||
* [`x-explicitMappingOnly`](docs/redoc-vendor-extensions.md#x-explicitMappingOnly) - In Schemas, display a more descriptive property name in objects with additionalProperties when viewing the property list with an object
|
||||
|
||||
### `<redoc>` options object
|
||||
You can use all of the following options with standalone version on <redoc> tag by kebab-casing them, e.g. `scrollYOffset` becomes `scroll-y-offset` and `expandResponses` becomes `expand-responses`.
|
||||
You can use all of the following options with the standalone version of the <redoc> tag by kebab-casing them. For example, `scrollYOffset` becomes `scroll-y-offset`, and `expandResponses` becomes `expand-responses`.
|
||||
|
||||
* `disableSearch` - disable search indexing and search box.
|
||||
* `expandDefaultServerVariables` - enable expanding default server variables, default `false`.
|
||||
* `expandResponses` - specify which responses to expand by default by response codes. Values should be passed as comma-separated list without spaces e.g. `expandResponses="200,201"`. Special value `"all"` expands all responses by default. Be careful: this option can slow-down documentation rendering time.
|
||||
* `generatedPayloadSamplesMaxDepth` - set the maximum render depth for JSON payload samples (responses and request body). The default value is `10`.
|
||||
* `maxDisplayedEnumValues` - display only specified number of enum values. hide rest values under spoiler.
|
||||
* `hideDownloadButton` - do not show "Download" spec button. **THIS DOESN'T MAKE YOUR SPEC PRIVATE**, it just hides the button.
|
||||
* `hideHostname` - if set, the protocol and hostname is not shown in the operation definition.
|
||||
|
@ -314,23 +297,6 @@ You can use all of the following options with standalone version on <redoc> tag
|
|||
* `width`: '40%'
|
||||
* `textColor`: '#ffffff'
|
||||
|
||||
## Advanced usage of standalone version
|
||||
Instead of adding `spec-url` attribute to the `<redoc>` element you can initialize ReDoc via globally exposed `Redoc` object:
|
||||
```js
|
||||
Redoc.init(specOrSpecUrl, options, element, callback?)
|
||||
```
|
||||
|
||||
- `specOrSpecUrl` is either JSON object with specification or an URL to the spec in `JSON` or `YAML` format
|
||||
- `options` [options object](#redoc-options-object)
|
||||
- `element` DOM element to put ReDoc into
|
||||
- `callback` (optional) - callback to be called after Redoc has been fully rendered. It is also called also on errors with error as the first argument
|
||||
|
||||
```js
|
||||
Redoc.init('http://petstore.swagger.io/v2/swagger.json', {
|
||||
scrollYOffset: 50
|
||||
}, document.getElementById('redoc-container'))
|
||||
```
|
||||
|
||||
-----------
|
||||
## Development
|
||||
see [CONTRIBUTING.md](.github/CONTRIBUTING.md)
|
||||
|
|
|
@ -3,20 +3,29 @@
|
|||
**[ReDoc](https://github.com/Redocly/redoc)'s Command Line Interface**
|
||||
|
||||
## Installation
|
||||
You can use redoc cli by installing `redoc-cli` globally or using [npx](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b).
|
||||
|
||||
You can use `redoc-cli` by installing [the package](https://www.npmjs.com/package/redoc-cli) globally,
|
||||
or using [npx](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b).
|
||||
|
||||
## Usage
|
||||
|
||||
Two following commands are available:
|
||||
The two following commands are available:
|
||||
|
||||
- `redoc-cli serve [spec]` - starts the server with `spec` rendered with ReDoc. Supports SSR mode (`--ssr`) and can watch the spec (`--watch`)
|
||||
- `redoc-cli bundle [spec]` - bundles spec and ReDoc into **zero-dependency** HTML file.
|
||||
- `redoc-cli serve [spec]` - starts the server with `spec` rendered with ReDoc.
|
||||
Supports a server-side rendering mode (`--ssr`),
|
||||
and can watch the spec (`--watch`) to automatically reload the page whenever it changes.
|
||||
- `redoc-cli bundle [spec]` - bundles `spec` and ReDoc into a **zero-dependency** HTML file.
|
||||
|
||||
Some examples:
|
||||
|
||||
- Bundle with main color changed to `orange`: <br> `$ redoc-cli bundle [spec] --options.theme.colors.primary.main=orange`
|
||||
- Serve with `nativeScrollbars` option set to true: <br> `$ redoc-cli serve [spec] --options.nativeScrollbars`
|
||||
- Bundle using custom template (check [default template](https://github.com/Redocly/redoc/blob/master/cli/template.hbs) for reference): <br> `$ redoc-cli bundle [spec] -t custom.hbs`
|
||||
- Bundle using custom template and add custom `templateOptions`: <br> `$ redoc-cli bundle [spec] -t custom.hbs --templateOptions.metaDescription "Page meta description"`
|
||||
- Bundle with the main color changed to `orange`:<br/>
|
||||
`$ redoc-cli bundle [spec] --options.theme.colors.primary.main=orange`
|
||||
- Serve with the `nativeScrollbars` option set to true:<br/>
|
||||
`$ redoc-cli serve [spec] --options.nativeScrollbars`
|
||||
- Bundle using a custom [Handlebars](https://handlebarsjs.com/) template
|
||||
(check the [default template](https://github.com/Redocly/redoc/blob/master/cli/template.hbs) for an example):<br/>
|
||||
`$ redoc-cli bundle [spec] -t custom.hbs`
|
||||
- Bundle using a custom template and add custom `templateOptions`:<br/>
|
||||
`$ redoc-cli bundle [spec] -t custom.hbs --templateOptions.metaDescription "Page meta description"`
|
||||
|
||||
For more details run `redoc-cli --help`.
|
||||
For more details, run `redoc-cli --help`.
|
||||
|
|
16
cli/index.ts
16
cli/index.ts
|
@ -72,6 +72,12 @@ YargsParser.command(
|
|||
type: 'boolean',
|
||||
});
|
||||
|
||||
yargs.options('disable-google-font', {
|
||||
describe: 'Disable Google Font',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
});
|
||||
|
||||
yargs.demandOption('spec');
|
||||
return yargs;
|
||||
},
|
||||
|
@ -80,13 +86,12 @@ YargsParser.command(
|
|||
ssr: argv.ssr as boolean,
|
||||
title: argv.title as string,
|
||||
watch: argv.watch as boolean,
|
||||
disableGoogleFont: argv.disableGoogleFont as boolean,
|
||||
templateFileName: argv.template as string,
|
||||
templateOptions: argv.templateOptions || {},
|
||||
redocOptions: getObjectOrJSON(argv.options),
|
||||
};
|
||||
|
||||
console.log(config);
|
||||
|
||||
try {
|
||||
await serve(argv.port as number, argv.spec as string, config);
|
||||
} catch (e) {
|
||||
|
@ -163,9 +168,8 @@ YargsParser.command(
|
|||
}).argv;
|
||||
|
||||
async function serve(port: number, pathToSpec: string, options: Options = {}) {
|
||||
let spec = await loadAndBundleSpec(pathToSpec);
|
||||
let spec = await loadAndBundleSpec(isURL(pathToSpec) ? pathToSpec : resolve(pathToSpec));
|
||||
let pageHTML = await getPageHTML(spec, pathToSpec, options);
|
||||
|
||||
const server = createServer((request, response) => {
|
||||
console.time('GET ' + request.url);
|
||||
if (request.url === '/redoc.standalone.js') {
|
||||
|
@ -211,7 +215,7 @@ async function serve(port: number, pathToSpec: string, options: Options = {}) {
|
|||
|
||||
const handlePath = async _path => {
|
||||
try {
|
||||
spec = await loadAndBundleSpec(pathToSpec);
|
||||
spec = await loadAndBundleSpec(resolve(pathToSpec));
|
||||
pageHTML = await getPageHTML(spec, pathToSpec, options);
|
||||
log('Updated successfully');
|
||||
} catch (e) {
|
||||
|
@ -238,7 +242,7 @@ async function serve(port: number, pathToSpec: string, options: Options = {}) {
|
|||
|
||||
async function bundle(pathToSpec, options: Options = {}) {
|
||||
const start = Date.now();
|
||||
const spec = await loadAndBundleSpec(pathToSpec);
|
||||
const spec = await loadAndBundleSpec(isURL(pathToSpec) ? pathToSpec : resolve(pathToSpec));
|
||||
const pageHTML = await getPageHTML(spec, pathToSpec, { ...options, ssr: true });
|
||||
|
||||
mkdirp.sync(dirname(options.output!));
|
||||
|
|
3509
cli/npm-shrinkwrap.json
generated
3509
cli/npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "redoc-cli",
|
||||
"version": "0.10.4",
|
||||
"version": "0.12.3",
|
||||
"description": "ReDoc's Command Line Interface",
|
||||
"main": "index.js",
|
||||
"bin": "index.js",
|
||||
|
@ -8,21 +8,20 @@
|
|||
"author": "Roman Hotsiy <gotsijroman@gmail.com>",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chokidar": "^3.4.1",
|
||||
"handlebars": "^4.7.6",
|
||||
"chokidar": "^3.5.1",
|
||||
"handlebars": "^4.7.7",
|
||||
"isarray": "^2.0.5",
|
||||
"mkdirp": "^1.0.4",
|
||||
"mobx": "^6.0.4",
|
||||
"mobx": "^6.3.2",
|
||||
"node-libs-browser": "^2.2.1",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"redoc": "2.0.0-rc.50",
|
||||
"styled-components": "^5.1.1",
|
||||
"tslib": "^2.0.0",
|
||||
"yargs": "^15.4.1"
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"redoc": "2.0.0-rc.56",
|
||||
"styled-components": "^5.3.0",
|
||||
"yargs": "^17.0.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
|
|
@ -16,7 +16,8 @@ RUN npm ci --no-optional --ignore-scripts
|
|||
|
||||
# copy only required for the build files
|
||||
COPY src /build/src
|
||||
COPY webpack.config.ts tsconfig.json custom.d.ts /build/
|
||||
COPY webpack.config.ts tsconfig.json custom.d.ts /build/
|
||||
COPY config/webpack-utils.ts /build/config/
|
||||
COPY typings/styled-patch.d.ts /build/typings/styled-patch.d.ts
|
||||
|
||||
RUN npm run bundle:standalone
|
||||
|
|
46
config/webpack-utils.ts
Normal file
46
config/webpack-utils.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import * as webpack from 'webpack';
|
||||
|
||||
export function getBabelLoader({useBuiltIns, hot}: {useBuiltIns: boolean, hot?: boolean}) {
|
||||
return {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
babelrc: false,
|
||||
sourceType: 'unambiguous',
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
useBuiltIns: useBuiltIns ? 'usage' : false,
|
||||
corejs: 3,
|
||||
exclude: ['transform-typeof-symbol'],
|
||||
targets: 'defaults',
|
||||
modules: false,
|
||||
},
|
||||
],
|
||||
['@babel/preset-react', { development: false, runtime: 'classic' }],
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
plugins: [
|
||||
['@babel/plugin-proposal-decorators', { legacy: true }],
|
||||
['@babel/plugin-proposal-class-properties', { loose: false }],
|
||||
[
|
||||
'@babel/plugin-transform-runtime',
|
||||
{
|
||||
corejs: false,
|
||||
helpers: true,
|
||||
// eslint-disable-next-line import/no-internal-modules
|
||||
version: require('@babel/runtime/package.json').version,
|
||||
regenerator: true,
|
||||
},
|
||||
],
|
||||
'@babel/plugin-proposal-optional-chaining',
|
||||
'@babel/plugin-proposal-nullish-coalescing-operator',
|
||||
hot ? 'react-hot-loader/babel' : undefined,
|
||||
].filter(Boolean)
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function webpackIgnore(regexp) {
|
||||
return new webpack.NormalModuleReplacementPlugin(regexp, require.resolve('lodash/noop.js'));
|
||||
}
|
|
@ -31,7 +31,7 @@ const DropDownList = styled.ul`
|
|||
list-style: none;
|
||||
margin: 4px 0 0 0;
|
||||
padding: 5px 0;
|
||||
font-family: 'Lato';
|
||||
font-family: Roboto,sans-serif;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
|
|
|
@ -5,19 +5,21 @@ import { resolve as urlResolve } from 'url';
|
|||
import { RedocStandalone } from '../src';
|
||||
import ComboBox from './ComboBox';
|
||||
|
||||
const DEFAULT_SPEC = 'openapi.yaml';
|
||||
const NEW_VERSION_SPEC = 'openapi-3-1.yaml';
|
||||
|
||||
const demos = [
|
||||
{ value: NEW_VERSION_SPEC, label: 'Petstore OpenAPI 3.1' },
|
||||
{ value: 'https://api.apis.guru/v2/specs/instagram.com/1.0.0/swagger.yaml', label: 'Instagram' },
|
||||
{
|
||||
value: 'https://api.apis.guru/v2/specs/googleapis.com/calendar/v3/openapi.yaml',
|
||||
label: 'Google Calendar',
|
||||
},
|
||||
{ value: 'https://api.apis.guru/v2/specs/slack.com/1.5.0/openapi.yaml', label: 'Slack' },
|
||||
{ value: 'https://api.apis.guru/v2/specs/zoom.us/2.0.0/swagger.yaml', label: 'Zoom.us' },
|
||||
{ value: 'https://api.apis.guru/v2/specs/slack.com/1.7.0/openapi.yaml', label: 'Slack' },
|
||||
{ value: 'https://api.apis.guru/v2/specs/zoom.us/2.0.0/openapi.yaml', label: 'Zoom.us' },
|
||||
{ value: 'https://docs.graphhopper.com/openapi.json', label: 'GraphHopper' },
|
||||
];
|
||||
|
||||
const DEFAULT_SPEC = 'openapi.yaml';
|
||||
|
||||
class DemoApp extends React.Component<
|
||||
{},
|
||||
{ specUrl: string; dropdownOpen: boolean; cors: boolean }
|
||||
|
@ -45,6 +47,9 @@ class DemoApp extends React.Component<
|
|||
}
|
||||
|
||||
handleChange = (url: string) => {
|
||||
if (url === NEW_VERSION_SPEC) {
|
||||
this.setState({ cors: false })
|
||||
}
|
||||
this.setState({
|
||||
specUrl: url,
|
||||
});
|
||||
|
@ -72,7 +77,7 @@ class DemoApp extends React.Component<
|
|||
let proxiedUrl = specUrl;
|
||||
if (specUrl !== DEFAULT_SPEC) {
|
||||
proxiedUrl = cors
|
||||
? '\\\\cors.apis.guru/' + urlResolve(window.location.href, specUrl)
|
||||
? '\\\\cors.redoc.ly/' + urlResolve(window.location.href, specUrl)
|
||||
: specUrl;
|
||||
}
|
||||
return (
|
||||
|
@ -149,7 +154,7 @@ const Heading = styled.nav`
|
|||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: 'Lato';
|
||||
font-family: Roboto, sans-serif;
|
||||
`;
|
||||
|
||||
const Logo = styled.img`
|
||||
|
|
1261
demo/openapi-3-1.yaml
Normal file
1261
demo/openapi-3-1.yaml
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -377,7 +377,9 @@ paths:
|
|||
application/xml:
|
||||
schema:
|
||||
type: array
|
||||
maxItems: 999
|
||||
items:
|
||||
maxItems: 111
|
||||
$ref: '#/components/schemas/Pet'
|
||||
'400':
|
||||
description: Invalid tag value
|
||||
|
@ -1193,7 +1195,7 @@ x-webhooks:
|
|||
summary: New pet
|
||||
description: Information about a new pet in the systems
|
||||
operationId: newPet
|
||||
tags:
|
||||
tags:
|
||||
- pet
|
||||
requestBody:
|
||||
content:
|
||||
|
@ -1202,4 +1204,4 @@ x-webhooks:
|
|||
$ref: "#/components/schemas/Pet"
|
||||
responses:
|
||||
"200":
|
||||
description: Return a 200 status to indicate that the data was received successfully
|
||||
description: Return a 200 status to indicate that the data was received successfully
|
||||
|
|
|
@ -1,21 +1,7 @@
|
|||
import * as React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
// tslint:disable-next-line
|
||||
import { AppContainer } from 'react-hot-loader';
|
||||
// import DevTools from 'mobx-react-devtools';
|
||||
|
||||
import { Redoc, RedocProps } from '../../src/components/Redoc/Redoc';
|
||||
import { AppStore } from '../../src/services/AppStore';
|
||||
import { RedocRawOptions } from '../../src/services/RedocNormalizedOptions';
|
||||
import { loadAndBundleSpec } from '../../src/utils/loadAndBundleSpec';
|
||||
|
||||
const renderRoot = (props: RedocProps) =>
|
||||
render(
|
||||
<AppContainer>
|
||||
<Redoc {...props} />
|
||||
</AppContainer>,
|
||||
document.getElementById('example'),
|
||||
);
|
||||
import type { RedocRawOptions } from '../../src/services/RedocNormalizedOptions';
|
||||
import RedocStandalone from './hot';
|
||||
|
||||
const big = window.location.search.indexOf('big') > -1;
|
||||
const swagger = window.location.search.indexOf('swagger') > -1;
|
||||
|
@ -25,30 +11,6 @@ const userUrl = window.location.search.match(/url=(.*)$/);
|
|||
const specUrl =
|
||||
(userUrl && userUrl[1]) || (swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml');
|
||||
|
||||
let store;
|
||||
const options: RedocRawOptions = { nativeScrollbars: false, maxDisplayedEnumValues: 3 };
|
||||
|
||||
async function init() {
|
||||
const spec = await loadAndBundleSpec(specUrl);
|
||||
store = new AppStore(spec, specUrl, options);
|
||||
renderRoot({ store });
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
if (module.hot) {
|
||||
const reload = (reloadStore = false) => async () => {
|
||||
if (reloadStore) {
|
||||
// create a new Store
|
||||
store.dispose();
|
||||
|
||||
const state = await store.toJS();
|
||||
store = AppStore.fromJS(state);
|
||||
}
|
||||
|
||||
renderRoot({ store });
|
||||
};
|
||||
|
||||
module.hot.accept(['../../src/components/Redoc/Redoc'], reload());
|
||||
module.hot.accept(['../../src/services/AppStore'], reload(true));
|
||||
}
|
||||
render(<RedocStandalone specUrl={specUrl} options={options} />, document.getElementById('example'));
|
||||
|
|
10
demo/playground/hot.tsx
Normal file
10
demo/playground/hot.tsx
Normal file
|
@ -0,0 +1,10 @@
|
|||
import * as React from 'react';
|
||||
// eslint-disable-next-line import/no-internal-modules
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import { RedocStandalone as RedocStandaloneOrig, RedocStandaloneProps } from '../../src';
|
||||
|
||||
const RedocStandalone = function (props: RedocStandaloneProps) {
|
||||
return <RedocStandaloneOrig {...props} />;
|
||||
}
|
||||
|
||||
export default hot(RedocStandalone);
|
|
@ -1,14 +1,12 @@
|
|||
import { renderToString } from 'react-dom/server';
|
||||
import * as React from 'react';
|
||||
import { ServerStyleSheet } from 'styled-components';
|
||||
// @ts-ignore
|
||||
import { Redoc, createStore } from '../../';
|
||||
import { readFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
const yaml = require('yaml-js');
|
||||
const yaml = require('js-yaml');
|
||||
const http = require('http');
|
||||
const url = require('url');
|
||||
const fs = require('fs');
|
||||
|
||||
const PORT = 9999;
|
||||
|
@ -18,8 +16,8 @@ const server = http.createServer(async (request, response) => {
|
|||
if (request.url === '/redoc.standalone.js') {
|
||||
fs.createReadStream('bundles/redoc.standalone.js', 'utf8').pipe(response);
|
||||
} else if (request.url === '/') {
|
||||
const spec = yaml.load(readFileSync(resolve(__dirname, '../openapi.yaml')));
|
||||
let store = await createStore(spec, 'path/to/spec.yaml');
|
||||
const spec = yaml.load(readFileSync(resolve(__dirname, '../openapi.yaml'), 'utf-8'));
|
||||
const store = await createStore(spec, 'path/to/spec.yaml');
|
||||
|
||||
const sheet = new ServerStyleSheet();
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import * as CopyWebpackPlugin from 'copy-webpack-plugin';
|
||||
import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||
import * as HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import { compact } from 'lodash';
|
||||
import { resolve } from 'path';
|
||||
import * as webpack from 'webpack';
|
||||
import { getBabelLoader, webpackIgnore } from '../config/webpack-utils';
|
||||
|
||||
const VERSION = JSON.stringify(require('../package.json').version);
|
||||
const REVISION = JSON.stringify(
|
||||
|
@ -14,38 +14,6 @@ function root(filename) {
|
|||
return resolve(__dirname + '/' + filename);
|
||||
}
|
||||
|
||||
const tsLoader = (env) => ({
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
compilerOptions: {
|
||||
module: env.bench ? 'esnext' : 'es2015',
|
||||
declaration: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const babelLoader = () => ({
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
generatorOpts: {
|
||||
decoratorsBeforeExport: true,
|
||||
},
|
||||
plugins: compact([
|
||||
['@babel/plugin-syntax-typescript', { isTSX: true }],
|
||||
['@babel/plugin-syntax-decorators', { legacy: true }],
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-syntax-jsx',
|
||||
]),
|
||||
},
|
||||
});
|
||||
|
||||
const babelHotLoader = {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
plugins: ['react-hot-loader/babel'],
|
||||
},
|
||||
};
|
||||
|
||||
export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) => ({
|
||||
entry: [
|
||||
root('../src/polyfills.ts'),
|
||||
|
@ -57,6 +25,7 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
|||
: 'index.tsx',
|
||||
),
|
||||
],
|
||||
target: 'web',
|
||||
output: {
|
||||
filename: 'redoc-demo.bundle.js',
|
||||
path: root('dist'),
|
||||
|
@ -69,10 +38,17 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
|||
port: 9090,
|
||||
disableHostCheck: true,
|
||||
stats: 'minimal',
|
||||
hot: true,
|
||||
},
|
||||
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js', '.json'],
|
||||
fallback: {
|
||||
path: require.resolve('path-browserify'),
|
||||
http: false,
|
||||
fs: false,
|
||||
os: false,
|
||||
},
|
||||
alias:
|
||||
mode !== 'production'
|
||||
? {
|
||||
|
@ -81,10 +57,6 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
|||
: {},
|
||||
},
|
||||
|
||||
node: {
|
||||
fs: 'empty',
|
||||
},
|
||||
|
||||
performance: false,
|
||||
|
||||
externals: {
|
||||
|
@ -97,16 +69,26 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
|||
|
||||
module: {
|
||||
rules: [
|
||||
{ enforce: 'pre', test: /\.js$/, loader: 'source-map-loader' },
|
||||
{ test: [/\.eot$/, /\.gif$/, /\.woff$/, /\.svg$/, /\.ttf$/], use: 'null-loader' },
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: compact([
|
||||
mode !== 'production' ? babelHotLoader : undefined,
|
||||
tsLoader(env),
|
||||
babelLoader(),
|
||||
]),
|
||||
exclude: [/node_modules/],
|
||||
use: [getBabelLoader({useBuiltIns: true, hot: true} )],
|
||||
exclude: {
|
||||
and: [/node_modules/],
|
||||
not: {
|
||||
or: [
|
||||
/swagger2openapi/,
|
||||
/reftools/,
|
||||
/openapi-sampler/,
|
||||
/mobx/,
|
||||
/oas-resolver/,
|
||||
/oas-kit-common/,
|
||||
/oas-schema-walker/,
|
||||
/\@redocly\/openapi-core/,
|
||||
/colorette/,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
|
@ -117,29 +99,18 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /node_modules\/(swagger2openapi|reftools|oas-resolver|oas-kit-common|oas-schema-walker)\/.*\.js$/,
|
||||
use: {
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
transpileOnly: true,
|
||||
instance: 'ts2js-transpiler-only',
|
||||
compilerOptions: {
|
||||
allowJs: true,
|
||||
declaration: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
__REDOC_VERSION__: VERSION,
|
||||
__REDOC_REVISION__: REVISION,
|
||||
'process.env': '{}',
|
||||
'process.platform': '"browser"',
|
||||
'process.stdout': 'null',
|
||||
}),
|
||||
new webpack.NamedModulesPlugin(),
|
||||
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||
// new webpack.NamedModulesPlugin(),
|
||||
// new webpack.optimize.ModuleConcatenationPlugin(),
|
||||
new HtmlWebpackPlugin({
|
||||
template: env.playground
|
||||
? 'demo/playground/index.html'
|
||||
|
@ -147,16 +118,12 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
|||
? 'benchmark/index.html'
|
||||
: 'demo/index.html',
|
||||
}),
|
||||
new ForkTsCheckerWebpackPlugin(),
|
||||
ignore(/js-yaml\/dumper\.js$/),
|
||||
ignore(/json-schema-ref-parser\/lib\/dereference\.js/),
|
||||
ignore(/^\.\/SearchWorker\.worker$/),
|
||||
new ForkTsCheckerWebpackPlugin({ logger: { infrastructure: 'silent', issues: 'console' } }),
|
||||
webpackIgnore(/js-yaml\/dumper\.js$/),
|
||||
webpackIgnore(/json-schema-ref-parser\/lib\/dereference\.js/),
|
||||
webpackIgnore(/^\.\/SearchWorker\.worker$/),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: ['demo/openapi.yaml'],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
function ignore(regexp) {
|
||||
return new webpack.NormalModuleReplacementPlugin(regexp, require.resolve('lodash/noop.js'));
|
||||
}
|
||||
|
|
BIN
docs/images/redoc.png
Normal file
BIN
docs/images/redoc.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.9 KiB |
112
docs/quickstart/cli.md
Normal file
112
docs/quickstart/cli.md
Normal file
|
@ -0,0 +1,112 @@
|
|||
---
|
||||
title: Using the Redoc CLI
|
||||
---
|
||||
|
||||
# Using the Redoc CLI
|
||||
|
||||
With Redoc's command-line interface you can bundle your OpenAPI definition and API documentation
|
||||
(made with Redoc) into a zero-dependency HTML file and locally render your
|
||||
OpenAPI definition with Redoc.
|
||||
|
||||
## Step 1 - Install Redoc CLI
|
||||
|
||||
You can install the `redoc-cli` package globally using one of the following package managers:
|
||||
|
||||
- [npm](https://docs.npmjs.com/about-npm)
|
||||
- [yarn](https://classic.yarnpkg.com/en/docs/getting-started)
|
||||
|
||||
Or you can install `redoc-cli` using [npx](https://www.freecodecamp.org/news/npm-vs-npx-whats-the-difference/).
|
||||
|
||||
### Install Redoc CLI with yarn
|
||||
|
||||
To install the `redoc-cli` package globally with yarn:
|
||||
|
||||
```bash
|
||||
yarn global add redoc-cli
|
||||
```
|
||||
|
||||
### Install Redoc with npm
|
||||
|
||||
To install the `redoc-cli` package globally with npm:
|
||||
|
||||
```bash
|
||||
npm i -g redoc-cli
|
||||
```
|
||||
|
||||
### Install with `npx`
|
||||
|
||||
To install the `redoc-cli` package locally with `npx`, navigate to your project
|
||||
directory in your terminal, then use the following command:
|
||||
|
||||
```bash
|
||||
npx redoc-cli
|
||||
```
|
||||
|
||||
## Step 2 - Use the CLI
|
||||
|
||||
### Redoc CLI commands
|
||||
|
||||
The CLI includes the following commands:
|
||||
|
||||
- **`redoc-cli serve [spec]`:** Starts a local server with Redoc. You must include the required parameter, spec, which is
|
||||
a reference to an OpenAPI definition. Options include:
|
||||
- `--ssr`: Implements a server-side rendering model.
|
||||
- `--watch`: Automatically reloads the server while you edit your OpenAPI definition.
|
||||
- `--options`: Customizes your output using [Redoc options](https://redoc.ly/docs/api-reference-docs/configuration/).
|
||||
To add nested options, use dot notation.
|
||||
- **`redoc-cli bundle [spec]`:** Bundles `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.
|
||||
- `--templateOptions`: Adds template options you want to pass to your
|
||||
custom Handlebars template. To add options, use dot notation.
|
||||
- **`--help`:** Prints help text for the Redoc CLI commands and options.
|
||||
- **`--version`:** Prints the version of the `redoc-cli` package you have installed.
|
||||
|
||||
### Redoc CLI examples
|
||||
|
||||
#### Bundle
|
||||
|
||||
Bundle with the main color changed to `orange`:
|
||||
|
||||
```bash
|
||||
redoc-cli bundle openapi.yaml --options.theme.colors.primary.main=orange
|
||||
```
|
||||
|
||||
Bundle using a custom Handlebars template and add custom `templateOptions`:
|
||||
|
||||
```bash
|
||||
redoc-cli bundle http://petstore.swagger.io/v2/swagger.json -t custom.hbs --templateOptions.metaDescription "Page meta description"
|
||||
```
|
||||
|
||||
Sample Handlebars template:
|
||||
|
||||
```handlebars
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf8" />
|
||||
<title>{{title}}</title>
|
||||
<!-- needed for adaptive design -->
|
||||
<meta description="{{{templateOptions.metaDescription}}}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
{{{redocHead}}}
|
||||
{{#unless disableGoogleFont}}<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">{{/unless}}
|
||||
</head>
|
||||
<body>
|
||||
{{{redocHTML}}}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
#### Serve
|
||||
|
||||
Serve with the `nativeScrollbars` option set to `true`:
|
||||
|
||||
```bash
|
||||
redoc-cli serve openapi/dist.yaml --options.nativeScrollbars
|
||||
```
|
39
docs/quickstart/docker.md
Normal file
39
docs/quickstart/docker.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
title: Using the Redoc Docker image
|
||||
---
|
||||
|
||||
# Using the Redoc Docker image
|
||||
|
||||
Redoc is available as a pre-built Docker image in [Docker Hub](https://hub.docker.com/r/redocly/redoc/).
|
||||
|
||||
If you have [Docker](https://docs.docker.com/get-docker/) installed, pull the image with the following command:
|
||||
|
||||
```docker
|
||||
docker pull redocly/redoc
|
||||
```
|
||||
|
||||
Then run the image with the following command:
|
||||
|
||||
```docker
|
||||
docker run -p 8080:80 redocly/redoc
|
||||
```
|
||||
|
||||
The preview starts on port 8080, based on the port used in the command,
|
||||
and can be accessed at `http://localhost:8080`.
|
||||
To exit the preview, use `control+C`.
|
||||
|
||||
By default Redoc starts with a demo Swagger Petstore OpenAPI definition located at
|
||||
http://petstore.swagger.io/v2/swagger.json. You can update this URL using
|
||||
the environment variable `SPEC_URL`.
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
docker run -p 8080:80 -e SPEC_URL=https://api.example.com/openapi.json redocly/redoc
|
||||
```
|
||||
|
||||
## Using a Dockerfile
|
||||
|
||||
You can also create a Dockerfile with some predefined environment variables. Check out
|
||||
a sample [Dockerfile](https://github.com/Redocly/redoc/blob/master/config/docker/Dockerfile)
|
||||
in our code repo.
|
214
docs/quickstart/html.md
Normal file
214
docs/quickstart/html.md
Normal file
|
@ -0,0 +1,214 @@
|
|||
---
|
||||
title: Using the Redoc HTML element
|
||||
---
|
||||
|
||||
# Using the Redoc HTML element
|
||||
|
||||
## TL;DR final code example
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Redoc</title>
|
||||
<!-- needed for adaptive design -->
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||
|
||||
<!--
|
||||
Redoc doesn't change outer page styles
|
||||
-->
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<redoc spec-url='http://petstore.swagger.io/v2/swagger.json'></redoc>
|
||||
<script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"> </script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
:::attention Running Redoc locally requires an HTTP server
|
||||
Loading local OpenAPI definitions is impossible without running a web server because of issues with
|
||||
[same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) and
|
||||
other security reasons.
|
||||
:::
|
||||
|
||||
### Running Redoc locally
|
||||
|
||||
If you want to view your Redoc output locally, you can simulate an HTTP server.
|
||||
|
||||
#### Using Redocly OpenAPI CLI
|
||||
|
||||
Redocly OpenAPI CLI is an open source command-line tool that includes a command
|
||||
for simulating an HTTP server to provide a preview of your OpenAPI definition locally.
|
||||
|
||||
If you have [OpenAPI CLI](https://redoc.ly/docs/cli/#installation-and-usage) installed, `cd` into your
|
||||
project directory and run the following command:
|
||||
|
||||
```bash
|
||||
openapi preview-docs openapi.yaml
|
||||
```
|
||||
|
||||
By default, without providing a port, the preview starts on port 8080, and can be accessed at `http://localhost:8080`.
|
||||
To exit the preview, use `control+C`.
|
||||
|
||||
#### Using Python
|
||||
|
||||
If you have [Python 3](https://www.python.org/downloads/) installed, `cd` into your
|
||||
project directory and run the following command:
|
||||
|
||||
```python
|
||||
python3 -m http.server
|
||||
```
|
||||
|
||||
If you have [Python 2](https://www.python.org/downloads/) installed, `cd` into your
|
||||
project directory and run the following command:
|
||||
|
||||
```python
|
||||
python -m SimpleHTTPServer 8000
|
||||
```
|
||||
|
||||
The output after entering the command provides the local URL where the preview can be accessed.
|
||||
To exit the preview, use `control-C`.
|
||||
|
||||
#### Using Node.js
|
||||
|
||||
If you have [Node.js](https://nodejs.org/en/download/) installed, install `http-server`
|
||||
using the following npm command:
|
||||
|
||||
```bash
|
||||
npm install -g http-server
|
||||
```
|
||||
|
||||
Then, `cd` into your project directory and run the following command:
|
||||
|
||||
```node
|
||||
http-server
|
||||
```
|
||||
|
||||
The output after entering the command provides the local URL where the preview can be accessed.
|
||||
To exit the preview, use `control-C`.
|
||||
|
||||
## Step 1 - Install Redoc
|
||||
|
||||
You can install Redoc using one of the following package managers:
|
||||
|
||||
- [npm](https://docs.npmjs.com/about-npm)
|
||||
- [yarn](https://classic.yarnpkg.com/en/docs/getting-started)
|
||||
|
||||
:::attention Initialize your package manager
|
||||
If you do not have a `package.json` file in your project directory,
|
||||
you need to add one by initializing npm or yarn in your project. Use the command `npm init` for npm,
|
||||
or `yarn init` for yarn. These initialization commands will lead you through the process
|
||||
of creating a `package.json` file in your project.
|
||||
|
||||
For more information, see
|
||||
[Creating a package.json file](https://docs.npmjs.com/creating-a-package-json-file)
|
||||
in the npm documentation or [Yarn init](https://classic.yarnpkg.com/en/docs/cli/init/)
|
||||
in the yarn documentation.
|
||||
|
||||
:::
|
||||
|
||||
### Install Redoc with yarn
|
||||
|
||||
After navigating to your project directory in your terminal, use the following command:
|
||||
|
||||
```bash
|
||||
yarn add redoc
|
||||
```
|
||||
|
||||
### Install Redoc with npm
|
||||
|
||||
After navigating to your project directory in your terminal, use the following command:
|
||||
|
||||
```bash
|
||||
npm i redoc
|
||||
```
|
||||
|
||||
## Step 2 - Reference the Redoc script
|
||||
|
||||
You can reference the Redoc script using either a link to the files hosted on a CDN
|
||||
or the files located in your `node modules` folder.
|
||||
|
||||
### CDN link
|
||||
|
||||
To reference the Redoc script with a CDN link:
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"> </script>
|
||||
```
|
||||
|
||||
### Node modules link
|
||||
|
||||
To reference the Redoc script with a node modules link:
|
||||
|
||||
```html
|
||||
<script src="node_modules/redoc/bundles/redoc.standalone.js"> </script>
|
||||
```
|
||||
|
||||
## Step 3 - Add the <redoc> element
|
||||
|
||||
You can add the <redoc> element to your HTML page and reference your OpenAPI
|
||||
definition using the `spec-url` attribute, or you can initialize Redoc using
|
||||
a globally exposed Redoc object.
|
||||
|
||||
### Using the `spec-url` attribute
|
||||
|
||||
To add the <redoc> element with the `spec-url` attribute:
|
||||
|
||||
```html
|
||||
<redoc spec-url="url/to/your/spec"></redoc>
|
||||
```
|
||||
|
||||
#### Examples
|
||||
|
||||
```html
|
||||
<redoc spec-url="http://petstore.swagger.io/v2/swagger.json"></redoc>
|
||||
```
|
||||
|
||||
You can also use a local file (JSON or YAML) in your project, for instance:
|
||||
|
||||
```html
|
||||
<redoc spec-url="dist.json"></redoc>
|
||||
```
|
||||
|
||||
### Using a Redoc object
|
||||
|
||||
To add the <redoc> element with a globally exposed Redoc object:
|
||||
|
||||
```js
|
||||
Redoc.init(specOrSpecUrl, options, element, callback)
|
||||
```
|
||||
- `specOrSpecUrl`: Either a JSON object with the OpenAPI definition or a URL to the
|
||||
definition in JSON or YAML format.
|
||||
- `options`: See [options object](https://redoc.ly/docs/api-reference-docs/configuration/) reference.
|
||||
- `element`: DOM element Redoc will be inserted into.
|
||||
- `callback`(optional): Callback to be called after Redoc has been fully rendered.
|
||||
It is also called on errors with `error` as the first argument.
|
||||
|
||||
#### Examples
|
||||
|
||||
```html
|
||||
<script>
|
||||
Redoc.init('http://petstore.swagger.io/v2/swagger.json', {
|
||||
scrollYOffset: 50
|
||||
}, document.getElementById('redoc-container'))
|
||||
</script>
|
||||
```
|
||||
|
||||
You can also use a local file (JSON or YAML) in your project, for instance:
|
||||
|
||||
```html
|
||||
<script>
|
||||
Redoc.init('dist.yaml', {
|
||||
scrollYOffset: 50
|
||||
}, document.getElementById('redoc-container'))
|
||||
</script>
|
||||
```
|
44
docs/quickstart/intro.md
Normal file
44
docs/quickstart/intro.md
Normal file
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
title: Redoc quickstart guide
|
||||
---
|
||||
|
||||
# Redoc quickstart guide
|
||||
|
||||
This guide includes step-by-step instructions for how to get started using
|
||||
Redoc to render your OpenAPI definition.
|
||||
|
||||
Redoc offers multiple options for rendering your OpenAPI definition.
|
||||
You should select the option that best fits your needs.
|
||||
|
||||
The following options are supported:
|
||||
|
||||
- **[Live demo](https://redocly.github.io/redoc/):**
|
||||
The live demo offers a fast way to see how your OpenAPI will render with Redoc.
|
||||
- **[HTML element](./html.md):**
|
||||
Using the HTML element works well for typical website deployments.
|
||||
- **[React component](./react.md):**
|
||||
Using the React component is an option for users with a React-based application.
|
||||
- **[Docker image](./docker.md):**
|
||||
Using the Docker image works in a container-based deployment.
|
||||
- **[CLI](./cli.md):**
|
||||
Using the CLI is an option for users who prefer to use a command-line interface.
|
||||
|
||||
## Before you start
|
||||
|
||||
You will need an OpenAPI definition. For testing purposes, you can use one of the following sample OpenAPI definitions:
|
||||
- OpenAPI 3.0
|
||||
- [Rebilly Users OpenAPI Definition](https://raw.githubusercontent.com/Rebilly/api-definitions/main/openapi/users.yaml)
|
||||
- [Swagger Petstore Sample OpenAPI Definition](https://petstore3.swagger.io/api/v3/openapi.json)
|
||||
- OpenAPI 2.0
|
||||
- [Thingful OpenAPI Definition](https://raw.githubusercontent.com/thingful/openapi-spec/master/spec/swagger.yaml)
|
||||
- [Fitbit Plus OpenAPI Definition](https://raw.githubusercontent.com/TwineHealth/TwineDeveloperDocs/master/spec/swagger.yaml)
|
||||
|
||||
For more information on the OpenAPI specification, refer to the [Learning OpenAPI 3](https://redoc.ly/docs/resources/learning-openapi/)
|
||||
section in the documentation.
|
||||
|
||||
## Live demo online
|
||||
|
||||
If you want to see how ReDoc will render your OpenAPI definition, you can try it out online at https://redocly.github.io/redoc/.
|
||||
|
||||
A version of the Swagger Petstore API is displayed by default. To test it with your own OpenAPI definition, enter the URL for your
|
||||
definition and select the **TRY IT** button.
|
78
docs/quickstart/react.md
Normal file
78
docs/quickstart/react.md
Normal file
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
title: Using the Redoc React component
|
||||
---
|
||||
|
||||
# Using the Redoc React component
|
||||
|
||||
## Before you start
|
||||
|
||||
Install the following dependencies required by Redoc if you do not already have them installed:
|
||||
|
||||
- `react`
|
||||
- `react-dom`
|
||||
- `mobx`
|
||||
- `styled-components`
|
||||
- `core-js`
|
||||
|
||||
If you have npm installed, you can install these dependencies using the following command:
|
||||
|
||||
```js
|
||||
npm i react react-dom mobx styled-components core-js
|
||||
```
|
||||
|
||||
## Step 1 - Import the `RedocStandalone` component
|
||||
|
||||
```js
|
||||
import { RedocStandalone } from 'redoc';
|
||||
```
|
||||
|
||||
## Step 2 - Use the component
|
||||
|
||||
You can either link to your OpenAPI definition with a URL, using the following format:
|
||||
|
||||
```react
|
||||
<RedocStandalone specUrl="url/to/your/spec"/>
|
||||
```
|
||||
|
||||
Or you can pass your OpenAPI definition as an object, using the following format:
|
||||
|
||||
```js
|
||||
<RedocStandalone spec={/* spec as an object */}/>
|
||||
```
|
||||
|
||||
## Optional - Pass options
|
||||
|
||||
Options can be passed into the RedocStandalone component to alter how it renders.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
<RedocStandalone
|
||||
specUrl="http://petstore.swagger.io/v2/swagger.json"
|
||||
options={{
|
||||
nativeScrollbars: true,
|
||||
theme: { colors: { primary: { main: '#dd5522' } } },
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
For more information on configuration options, refer to the
|
||||
[Configuration options for Reference docs](https://redoc.ly/docs/api-reference-docs/configuration/)
|
||||
section of the documentation. Options available for Redoc are noted,
|
||||
"Supported in Redoc CE".
|
||||
|
||||
## Optional - Specify `onLoaded` callback
|
||||
|
||||
You can also specify the `onLoaded` callback, which is called each time Redoc
|
||||
is fully rendered or when an error occurs (with an error as the first argument).
|
||||
|
||||
```js
|
||||
<RedocStandalone
|
||||
specUrl="http://petstore.swagger.io/v2/swagger.json"
|
||||
onLoaded={error => {
|
||||
if (!error) {
|
||||
console.log('Yay!');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
```
|
13
docs/sidebars.yaml
Normal file
13
docs/sidebars.yaml
Normal file
|
@ -0,0 +1,13 @@
|
|||
redoc:
|
||||
- group: Quickstart
|
||||
expanded: false
|
||||
page: redoc/quickstart/intro.md
|
||||
pages:
|
||||
- label: HTML element
|
||||
page: redoc/quickstart/html.md
|
||||
- label: React component
|
||||
page: redoc/quickstart/react.md
|
||||
- label: Docker image
|
||||
page: redoc/quickstart/docker.md
|
||||
- label: Command-line interface
|
||||
page: redoc/quickstart/cli.md
|
|
@ -25,6 +25,29 @@ describe('Menu', () => {
|
|||
.should('be.visible');
|
||||
});
|
||||
|
||||
it('should sync active menu items while scroll back and scroll again', () => {
|
||||
cy.contains('h2', 'Add a new pet to the store')
|
||||
.scrollIntoView()
|
||||
.wait(100)
|
||||
.get('[role=menuitem].active')
|
||||
.children()
|
||||
.last()
|
||||
.should('have.text', 'Add a new pet to the store')
|
||||
.should('be.visible');
|
||||
|
||||
cy.contains('h1', 'Swagger Petstore')
|
||||
.scrollIntoView()
|
||||
.wait(100)
|
||||
|
||||
cy.contains('h1', 'Introduction')
|
||||
.scrollIntoView()
|
||||
.wait(100)
|
||||
.get('[role=menuitem].active')
|
||||
.should('have.text', 'Introduction');
|
||||
|
||||
cy.url().should('include', '#section/Introduction');
|
||||
});
|
||||
|
||||
it('should update URL hash when clicking on menu items', () => {
|
||||
cy.contains('[role=menuitem].-depth1', 'pet').click({ force: true });
|
||||
cy.location('hash').should('equal', '#tag/pet');
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// tslint:disable:no-implicit-dependencies
|
||||
import * as yaml from 'yaml-js';
|
||||
import * as yaml from 'js-yaml';
|
||||
|
||||
async function loadSpec(url: string): Promise<any> {
|
||||
const spec = await (await fetch(url)).text();
|
||||
|
|
|
@ -53,4 +53,10 @@ describe('Search', () => {
|
|||
getSearchInput().type('int', { force: true });
|
||||
cy.get('[data-markjs]').should('exist');
|
||||
});
|
||||
|
||||
it('should show proper message when no search results are found', () => {
|
||||
getSearchResults().should('not.exist');
|
||||
getSearchInput().type('xzss', {force: true});
|
||||
getSearchResults().should('exist').should('contain', 'No results found');
|
||||
})
|
||||
});
|
||||
|
|
|
@ -16,5 +16,6 @@ describe('Standalone bundle test', () => {
|
|||
}
|
||||
|
||||
baseCheck('OAS3 mode', 'e2e/standalone.html');
|
||||
baseCheck('OAS3.1 mode', 'e2e/standalone-3-1.html');
|
||||
baseCheck('OAS2 compatibility mode', 'e2e/standalone-compatibility.html');
|
||||
});
|
||||
|
|
8
e2e/standalone-3-1.html
Normal file
8
e2e/standalone-3-1.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
|
||||
<body>
|
||||
<redoc spec-url="../demo/openapi-3-1.yaml" native-scrollbars></redoc>
|
||||
<script src="../bundles/redoc.standalone.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
36535
package-lock.json
generated
36535
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
171
package.json
171
package.json
|
@ -1,11 +1,15 @@
|
|||
{
|
||||
"name": "redoc",
|
||||
"version": "2.0.0-rc.50",
|
||||
"version": "2.0.0-rc.57",
|
||||
"description": "ReDoc",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/Redocly/redoc"
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults",
|
||||
"ie 11"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=6.9",
|
||||
"npm": ">=3.0.0"
|
||||
|
@ -25,26 +29,27 @@
|
|||
"main": "bundles/redoc.lib.js",
|
||||
"types": "typings/index.d.ts",
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server --mode=development --env.playground --hot --config demo/webpack.config.ts",
|
||||
"start:prod": "webpack-dev-server --env.playground --mode=production --config demo/webpack.config.ts",
|
||||
"start:benchmark": "webpack-dev-server --mode=production --env.bench --config demo/webpack.config.ts",
|
||||
"start": "webpack serve --mode=development --env playground --hot --config demo/webpack.config.ts",
|
||||
"start:prod": "webpack serve --env playground --mode=production --config demo/webpack.config.ts",
|
||||
"start:benchmark": "webpack serve --mode=production --env.bench --config demo/webpack.config.ts",
|
||||
"test": "npm run lint && npm run unit && npm run license-check",
|
||||
"unit": "jest --coverage",
|
||||
"e2e": "cypress run",
|
||||
"e2e-ci": "cypress run --record",
|
||||
"bundlesize": "bundlesize",
|
||||
"ts-check": "tsc --noEmit --skipLibCheck",
|
||||
"cy:open": "cypress open",
|
||||
"bundle:clean": "rimraf bundles",
|
||||
"bundle:standalone": "webpack --env.standalone --mode=production",
|
||||
"bundle:standalone": "webpack --env production --env standalone --mode=production",
|
||||
"bundle:lib": "webpack --mode=production && npm run declarations",
|
||||
"bundle": "npm run bundle:clean && npm run bundle:lib && npm run bundle:standalone",
|
||||
"declarations": "tsc --emitDeclarationOnly -p tsconfig.lib.json && cp -R src/types typings/",
|
||||
"stats": "webpack --env.standalone --json --profile --mode=production > stats.json",
|
||||
"stats": "webpack --env production --env standalone --json --profile --mode=production > stats.json",
|
||||
"prettier": "prettier --write \"cli/index.ts\" \"src/**/*.{ts,tsx}\"",
|
||||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1",
|
||||
"lint": "eslint 'src/**/*.{js,ts,tsx}'",
|
||||
"benchmark": "node ./benchmark/benchmark.js",
|
||||
"start:demo": "webpack-dev-server --hot --config demo/webpack.config.ts --mode=development",
|
||||
"start:demo": "webpack serve --hot --config demo/webpack.config.ts --mode=development",
|
||||
"compile:cli": "tsc custom.d.ts cli/index.ts --target es6 --module commonjs --types yargs",
|
||||
"build:demo": "webpack --mode=production --config demo/webpack.config.ts",
|
||||
"deploy:demo": "aws s3 sync demo/dist s3://production-redoc-demo --acl=public-read",
|
||||
|
@ -52,120 +57,128 @@
|
|||
"docker:build": "docker build -f config/docker/Dockerfile -t redoc ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.10.5",
|
||||
"@babel/plugin-syntax-decorators": "^7.10.4",
|
||||
"@babel/core": "^7.14.3",
|
||||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
||||
"@babel/plugin-proposal-decorators": "^7.14.2",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.14.4",
|
||||
"@babel/plugin-syntax-decorators": "^7.12.13",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-syntax-jsx": "^7.10.4",
|
||||
"@babel/plugin-syntax-typescript": "^7.10.4",
|
||||
"@cypress/webpack-preprocessor": "^5.4.2",
|
||||
"@hot-loader/react-dom": "^16.12.0",
|
||||
"@types/chai": "^4.2.12",
|
||||
"@types/dompurify": "^2.0.2",
|
||||
"@babel/plugin-transform-runtime": "^7.14.3",
|
||||
"@babel/preset-env": "^7.14.4",
|
||||
"@babel/preset-react": "^7.13.13",
|
||||
"@babel/preset-typescript": "^7.13.0",
|
||||
"@cypress/webpack-preprocessor": "^5.9.0",
|
||||
"@hot-loader/react-dom": "^17.0.1",
|
||||
"@types/chai": "^4.2.18",
|
||||
"@types/dompurify": "^2.2.2",
|
||||
"@types/enzyme": "^3.10.5",
|
||||
"@types/enzyme-to-json": "^1.5.3",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/json-pointer": "^1.0.30",
|
||||
"@types/lodash": "^4.14.158",
|
||||
"@types/lodash": "^4.14.170",
|
||||
"@types/lunr": "^2.3.3",
|
||||
"@types/node": "^15.6.1",
|
||||
"@types/mark.js": "^8.11.5",
|
||||
"@types/marked": "^1.1.0",
|
||||
"@types/prismjs": "^1.16.1",
|
||||
"@types/prismjs": "^1.16.5",
|
||||
"@types/prop-types": "^15.7.3",
|
||||
"@types/react": "^16.9.43",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react": "^17.0.8",
|
||||
"@types/react-dom": "^17.0.5",
|
||||
"@types/react-tabs": "^2.3.2",
|
||||
"@types/styled-components": "^5.1.1",
|
||||
"@types/tapable": "^1.0.6",
|
||||
"@types/webpack": "^4.41.21",
|
||||
"@types/webpack-env": "^1.15.2",
|
||||
"@types/yargs": "^15.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "^3.7.0",
|
||||
"@typescript-eslint/parser": "^3.7.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-plugin-styled-components": "^1.10.7",
|
||||
"@types/tapable": "^2.2.2",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"@types/webpack-env": "^1.16.0",
|
||||
"@types/yargs": "^17.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.26.0",
|
||||
"@typescript-eslint/parser": "^4.26.0",
|
||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-plugin-styled-components": "^1.12.0",
|
||||
"beautify-benchmark": "^0.2.4",
|
||||
"bundlesize": "^0.18.0",
|
||||
"bundlesize": "^0.18.1",
|
||||
"conventional-changelog-cli": "^2.0.34",
|
||||
"copy-webpack-plugin": "^6.0.3",
|
||||
"core-js": "^3.6.5",
|
||||
"copy-webpack-plugin": "^9.0.0",
|
||||
"core-js": "^3.13.1",
|
||||
"coveralls": "^3.1.0",
|
||||
"css-loader": "^3.6.0",
|
||||
"cypress": "^4.11.0",
|
||||
"css-loader": "^5.2.6",
|
||||
"cypress": "^7.4.0",
|
||||
"enzyme": "^3.11.0",
|
||||
"enzyme-adapter-react-16": "^1.15.2",
|
||||
"enzyme-to-json": "^3.5.0",
|
||||
"eslint": "^7.5.0",
|
||||
"eslint-plugin-import": "^2.22.0",
|
||||
"eslint-plugin-react": "^7.20.3",
|
||||
"fork-ts-checker-webpack-plugin": "^5.0.11",
|
||||
"html-webpack-plugin": "^4.3.0",
|
||||
"jest": "^26.1.0",
|
||||
"enzyme-to-json": "^3.6.2",
|
||||
"eslint": "^7.27.0",
|
||||
"eslint-plugin-import": "^2.23.4",
|
||||
"eslint-plugin-react": "^7.24.0",
|
||||
"fork-ts-checker-webpack-plugin": "^6.2.10",
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"jest": "^27.0.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"lodash": "^4.17.19",
|
||||
"mobx": "^6.0.4",
|
||||
"prettier": "^2.0.5",
|
||||
"lodash": "^4.17.21",
|
||||
"mobx": "^6.3.2",
|
||||
"prettier": "^2.3.0",
|
||||
"raf": "^3.4.1",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-hot-loader": "^4.12.21",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-hot-loader": "^4.13.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"shelljs": "^0.8.4",
|
||||
"source-map-loader": "^1.0.1",
|
||||
"style-loader": "^1.2.1",
|
||||
"styled-components": "^5.1.1",
|
||||
"ts-jest": "^26.1.3",
|
||||
"style-loader": "^2.0.0",
|
||||
"styled-components": "^5.3.0",
|
||||
"ts-jest": "^27.0.2",
|
||||
"ts-loader": "^8.0.1",
|
||||
"ts-node": "^8.10.2",
|
||||
"typescript": "^3.9.7",
|
||||
"unfetch": "^4.1.0",
|
||||
"url-polyfill": "^1.1.10",
|
||||
"webpack": "^4.44.0",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"webpack-node-externals": "^2.5.0",
|
||||
"workerize-loader": "^1.3.0",
|
||||
"yaml-js": "^0.2.3"
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "~4.1.0",
|
||||
"unfetch": "^4.2.0",
|
||||
"url-polyfill": "^1.1.12",
|
||||
"webpack": "^5.38.1",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-node-externals": "^3.0.0",
|
||||
"workerize-loader": "github:redocly/workerize-loader#webpack-5-dist"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"core-js": "^3.1.4",
|
||||
"mobx": "^6.0.4",
|
||||
"react": "^16.8.4",
|
||||
"react-dom": "^16.8.4",
|
||||
"react": "^16.8.4 || ^17.0.0",
|
||||
"react-dom": "^16.8.4 || ^17.0.0",
|
||||
"styled-components": "^4.1.1 || ^5.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.14.0",
|
||||
"@redocly/openapi-core": "^1.0.0-beta.54",
|
||||
"@redocly/react-dropdown-aria": "^2.0.11",
|
||||
"@types/node": "^13.11.1",
|
||||
"classnames": "^2.2.6",
|
||||
"classnames": "^2.3.1",
|
||||
"decko": "^1.2.0",
|
||||
"dompurify": "^2.0.12",
|
||||
"eventemitter3": "^4.0.4",
|
||||
"json-pointer": "^0.6.0",
|
||||
"json-schema-ref-parser": "^6.1.0",
|
||||
"lunr": "2.3.8",
|
||||
"dompurify": "^2.2.8",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"json-pointer": "^0.6.1",
|
||||
"lunr": "^2.3.9",
|
||||
"mark.js": "^8.11.1",
|
||||
"marked": "^0.7.0",
|
||||
"memoize-one": "~5.1.1",
|
||||
"mobx-react": "^7.0.5",
|
||||
"openapi-sampler": "^1.0.0-beta.18",
|
||||
"perfect-scrollbar": "^1.4.0",
|
||||
"polished": "^3.6.5",
|
||||
"prismjs": "^1.22.0",
|
||||
"memoize-one": "^5.2.1",
|
||||
"mobx-react": "^7.2.0",
|
||||
"openapi-sampler": "^1.0.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"perfect-scrollbar": "^1.5.1",
|
||||
"polished": "^4.1.3",
|
||||
"prismjs": "^1.24.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-tabs": "^3.1.1",
|
||||
"slugify": "^1.4.4",
|
||||
"react-tabs": "^3.2.2",
|
||||
"slugify": "~1.4.7",
|
||||
"stickyfill": "^1.1.1",
|
||||
"swagger2openapi": "^6.2.1",
|
||||
"tslib": "^2.0.0",
|
||||
"swagger2openapi": "^7.0.6",
|
||||
"url-template": "^2.0.8"
|
||||
},
|
||||
"bundlesize": [
|
||||
{
|
||||
"path": "./bundles/redoc.standalone.js",
|
||||
"maxSize": "300 kB"
|
||||
"maxSize": "350 kB"
|
||||
}
|
||||
],
|
||||
"jest": {
|
||||
"testEnvironment": "jsdom",
|
||||
"setupFilesAfterEnv": [
|
||||
"<rootDir>/src/setupTests.ts"
|
||||
],
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import { renderToString } from 'react-dom/server';
|
||||
import * as yaml from 'yaml-js';
|
||||
import * as yaml from 'js-yaml';
|
||||
import { createStore, Redoc } from '../';
|
||||
|
||||
import { readFileSync } from 'fs';
|
||||
|
@ -10,7 +10,7 @@ import { resolve } from 'path';
|
|||
|
||||
describe('SSR', () => {
|
||||
it('should render in SSR mode', async () => {
|
||||
const spec = yaml.load(readFileSync(resolve(__dirname, '../../demo/openapi.yaml')));
|
||||
const spec = yaml.load(readFileSync(resolve(__dirname, '../../demo/openapi.yaml'), 'utf-8'));
|
||||
const store = await createStore(spec, '');
|
||||
expect(() => {
|
||||
renderToString(<Redoc store={store} />);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* tslint:disable:no-implicit-dependencies */
|
||||
import { mount } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
import * as yaml from 'yaml-js';
|
||||
import * as yaml from 'js-yaml';
|
||||
|
||||
import { readFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
@ -11,7 +11,7 @@ import { Loading, RedocStandalone } from '../components/';
|
|||
describe('Components', () => {
|
||||
describe('RedocStandalone', () => {
|
||||
test('should show loading first', () => {
|
||||
const spec = yaml.load(readFileSync(resolve(__dirname, '../../demo/openapi.yaml')));
|
||||
const spec = yaml.load(readFileSync(resolve(__dirname, '../../demo/openapi.yaml'), 'utf-8'));
|
||||
|
||||
const inst = mount(<RedocStandalone spec={spec} options={{}} />);
|
||||
expect(inst.find(Loading)).toHaveLength(1);
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// import { transparentize } from 'polished';
|
||||
|
||||
import styled, { extensionsHook, media } from '../styled-components';
|
||||
import { deprecatedCss } from './mixins';
|
||||
|
||||
|
|
|
@ -61,11 +61,6 @@ export const RecursiveLabel = styled(FieldLabel)`
|
|||
font-size: 13px;
|
||||
`;
|
||||
|
||||
export const NullableLabel = styled(FieldLabel)`
|
||||
color: #0e7c86;
|
||||
font-size: 13px;
|
||||
`;
|
||||
|
||||
export const PatternLabel = styled(FieldLabel)`
|
||||
color: #0e7c86;
|
||||
&::before,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import { StoreConsumer } from '../components/StoreBuilder';
|
||||
import { StoreContext } from '../components/StoreBuilder';
|
||||
import styled, { css } from '../styled-components';
|
||||
|
||||
import { HistoryService } from '../services';
|
||||
|
||||
// tslint:disable-next-line
|
||||
export const linkifyMixin = className => css`
|
||||
export const linkifyMixin = (className) => css`
|
||||
${className} {
|
||||
cursor: pointer;
|
||||
margin-left: -20px;
|
||||
|
@ -33,36 +33,41 @@ export const linkifyMixin = className => css`
|
|||
}
|
||||
`;
|
||||
|
||||
const isModifiedEvent = event =>
|
||||
const isModifiedEvent = (event) =>
|
||||
!!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
|
||||
|
||||
export class Link extends React.Component<{ to: string; className?: string; children?: any }> {
|
||||
navigate = (history: HistoryService, event) => {
|
||||
if (
|
||||
!event.defaultPrevented && // onClick prevented default
|
||||
event.button === 0 && // ignore everything but left clicks
|
||||
!isModifiedEvent(event) // ignore clicks with modifier keys
|
||||
) {
|
||||
event.preventDefault();
|
||||
history.replace(this.props.to);
|
||||
}
|
||||
};
|
||||
export function Link(props: { to: string; className?: string; children?: any }) {
|
||||
const store = React.useContext(StoreContext);
|
||||
const clickHandler = React.useCallback(
|
||||
(event: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
if (!store) return;
|
||||
navigate(store.menu.history, event, props.to);
|
||||
},
|
||||
[store],
|
||||
);
|
||||
|
||||
render() {
|
||||
return (
|
||||
<StoreConsumer>
|
||||
{store => (
|
||||
<a
|
||||
className={this.props.className}
|
||||
href={store!.menu.history.linkForId(this.props.to)}
|
||||
onClick={this.navigate.bind(this, store!.menu.history)}
|
||||
aria-label={this.props.to}
|
||||
>
|
||||
{this.props.children}
|
||||
</a>
|
||||
)}
|
||||
</StoreConsumer>
|
||||
);
|
||||
if (!store) return null;
|
||||
|
||||
return (
|
||||
<a
|
||||
className={props.className}
|
||||
href={store!.menu.history.linkForId(props.to)}
|
||||
onClick={clickHandler}
|
||||
aria-label={props.to}
|
||||
>
|
||||
{props.children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function navigate(history: HistoryService, event: React.MouseEvent<HTMLAnchorElement>, to: string) {
|
||||
if (
|
||||
!event.defaultPrevented && // onClick prevented default
|
||||
event.button === 0 && // ignore everything but left clicks
|
||||
!isModifiedEvent(event) // ignore clicks with modifier keys
|
||||
) {
|
||||
event.preventDefault();
|
||||
history.replace(to);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
InfoSpanBox,
|
||||
InfoSpanBoxWrap,
|
||||
} from './styled.elements';
|
||||
import { l } from '../../services/Labels';
|
||||
|
||||
export interface ApiInfoProps {
|
||||
store: AppStore;
|
||||
|
@ -38,7 +39,7 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
|||
const license =
|
||||
(info.license && (
|
||||
<InfoSpan>
|
||||
License: <a href={info.license.url}>{info.license.name}</a>
|
||||
License: {info.license.identifier ? info.license.identifier : (<a href={info.license.url}>{info.license.name}</a>)}
|
||||
</InfoSpan>
|
||||
)) ||
|
||||
null;
|
||||
|
@ -79,14 +80,14 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
|||
</ApiHeader>
|
||||
{!hideDownloadButton && (
|
||||
<p>
|
||||
Download OpenAPI specification:
|
||||
{l('downloadSpecification')}:
|
||||
<DownloadButton
|
||||
download={downloadFilename || true}
|
||||
target="_blank"
|
||||
href={downloadLink}
|
||||
onClick={this.handleDownloadClick}
|
||||
>
|
||||
Download
|
||||
{l('download')}
|
||||
</DownloadButton>
|
||||
</p>
|
||||
)}
|
||||
|
@ -100,7 +101,8 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
|||
)) ||
|
||||
null}
|
||||
</StyledMarkdownBlock>
|
||||
<Markdown source={store.spec.info.description} data-role="redoc-description" />
|
||||
<Markdown source={store.spec.info.summary} data-role="redoc-summary"/>
|
||||
<Markdown source={store.spec.info.description} data-role="redoc-description"/>
|
||||
{externalDocs && <ExternalDocumentation externalDocs={externalDocs} />}
|
||||
</MiddlePanel>
|
||||
</Row>
|
||||
|
|
|
@ -17,4 +17,5 @@ const Link = styled.a`
|
|||
display: inline-block;
|
||||
`;
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
export const LinkWrap = url => Component => <Link href={url}>{Component}</Link>;
|
||||
|
|
|
@ -8,7 +8,7 @@ import { RedocRawOptions } from '../../services/RedocNormalizedOptions';
|
|||
|
||||
export interface EnumValuesProps {
|
||||
values: string[];
|
||||
type: string;
|
||||
type: string | string[];
|
||||
}
|
||||
|
||||
export interface EnumValuesState {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import {
|
||||
NullableLabel,
|
||||
PatternLabel,
|
||||
RecursiveLabel,
|
||||
TypeFormat,
|
||||
|
@ -60,7 +59,7 @@ export class FieldDetails extends React.PureComponent<FieldProps, { patternShown
|
|||
} else {
|
||||
const label = l('example') + ':';
|
||||
const raw = !!field.in;
|
||||
renderedExamples = <FieldDetail label={label} value={getSerializedValue(field, field.example)} raw={raw} />;
|
||||
renderedExamples = <FieldDetail label={label} value={getSerializedValue(field, field.example)} raw={raw} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,9 +76,22 @@ export class FieldDetails extends React.PureComponent<FieldProps, { patternShown
|
|||
>{' '}
|
||||
</TypeFormat>
|
||||
)}
|
||||
{schema.contentEncoding && (
|
||||
<TypeFormat>
|
||||
{' '}<
|
||||
{schema.contentEncoding}
|
||||
>{' '}
|
||||
</TypeFormat>
|
||||
)}
|
||||
{schema.contentMediaType && (
|
||||
<TypeFormat>
|
||||
{' '}<
|
||||
{schema.contentMediaType}
|
||||
>{' '}
|
||||
</TypeFormat>
|
||||
)}
|
||||
{schema.title && !hideSchemaTitles && <TypeTitle> ({schema.title}) </TypeTitle>}
|
||||
<ConstraintsView constraints={schema.constraints} />
|
||||
{schema.nullable && <NullableLabel> {l('nullable')} </NullableLabel>}
|
||||
{schema.pattern && !hideSchemaPattern && (
|
||||
<>
|
||||
<PatternLabel>
|
||||
|
@ -112,6 +124,7 @@ export class FieldDetails extends React.PureComponent<FieldProps, { patternShown
|
|||
<ExternalDocumentation externalDocs={schema.externalDocs} compact={true} />
|
||||
)}
|
||||
{(renderDiscriminatorSwitch && renderDiscriminatorSwitch(this.props)) || null}
|
||||
{field.const && (<FieldDetail label={l('const') + ':'} value={field.const} />) || null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ export const StyledMarkdownBlock = styled(
|
|||
padding: ${props => props.theme.spacing.unit * 4}px;
|
||||
overflow-x: auto;
|
||||
line-height: normal;
|
||||
border-radius: 0px
|
||||
border-radius: 0px;
|
||||
border: 1px solid rgba(38, 50, 56, 0.1);
|
||||
|
||||
code {
|
||||
|
|
|
@ -69,13 +69,14 @@ function DropdownWithinHeader(props) {
|
|||
|
||||
export function BodyContent(props: { content: MediaContentModel; description?: string }): JSX.Element {
|
||||
const { content, description } = props;
|
||||
const { isRequestType } = content;
|
||||
return (
|
||||
<MediaTypesSwitch content={content} renderDropdown={DropdownWithinHeader}>
|
||||
{({ schema }) => {
|
||||
return (
|
||||
<>
|
||||
{description !== undefined && <Markdown source={description} />}
|
||||
<Schema skipReadOnly={true} key="schema" schema={schema} />
|
||||
<Schema skipReadOnly={isRequestType} key="schema" schema={schema} />
|
||||
</>
|
||||
);
|
||||
}}
|
||||
|
|
|
@ -39,7 +39,7 @@ export class Redoc extends React.Component<RedocProps> {
|
|||
const store = this.props.store;
|
||||
return (
|
||||
<ThemeProvider theme={options.theme}>
|
||||
<StoreProvider value={this.props.store}>
|
||||
<StoreProvider value={store}>
|
||||
<OptionsProvider value={options}>
|
||||
<RedocWrap className="redoc-wrap">
|
||||
<StickyResponsiveSidebar menu={menu} className="menu-content">
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import * as PropTypes from 'prop-types';
|
||||
import * as React from 'react';
|
||||
|
||||
import { RedocNormalizedOptions, RedocRawOptions } from '../services/RedocNormalizedOptions';
|
||||
import { argValueToBoolean, RedocNormalizedOptions, RedocRawOptions } from '../services/RedocNormalizedOptions';
|
||||
import { ErrorBoundary } from './ErrorBoundary';
|
||||
import { Loading } from './Loading/Loading';
|
||||
import { Redoc } from './Redoc/Redoc';
|
||||
|
@ -14,47 +13,23 @@ export interface RedocStandaloneProps {
|
|||
onLoaded?: (e?: Error) => any;
|
||||
}
|
||||
|
||||
export class RedocStandalone extends React.PureComponent<RedocStandaloneProps> {
|
||||
static propTypes = {
|
||||
spec: (props, _, componentName) => {
|
||||
if (!props.spec && !props.specUrl) {
|
||||
return new Error(
|
||||
`One of props 'spec' or 'specUrl' was not specified in '${componentName}'.`,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
export const RedocStandalone = function (props: RedocStandaloneProps) {
|
||||
const { spec, specUrl, options = {}, onLoaded } = props;
|
||||
const hideLoading = argValueToBoolean(options.hideLoading, false);
|
||||
|
||||
specUrl: (props, _, componentName) => {
|
||||
if (!props.spec && !props.specUrl) {
|
||||
return new Error(
|
||||
`One of props 'spec' or 'specUrl' was not specified in '${componentName}'.`,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
options: PropTypes.any,
|
||||
onLoaded: PropTypes.any,
|
||||
};
|
||||
const normalizedOpts = new RedocNormalizedOptions(options);
|
||||
|
||||
render() {
|
||||
const { spec, specUrl, options = {}, onLoaded } = this.props;
|
||||
const hideLoading = options.hideLoading !== undefined;
|
||||
|
||||
const normalizedOpts = new RedocNormalizedOptions(options);
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<StoreBuilder spec={spec} specUrl={specUrl} options={options} onLoaded={onLoaded}>
|
||||
{({ loading, store }) =>
|
||||
!loading ? (
|
||||
<Redoc store={store!} />
|
||||
) : hideLoading ? null : (
|
||||
<Loading color={normalizedOpts.theme.colors.primary.main} />
|
||||
)
|
||||
}
|
||||
</StoreBuilder>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<StoreBuilder spec={spec} specUrl={specUrl} options={options} onLoaded={onLoaded}>
|
||||
{({ loading, store }) =>
|
||||
!loading ? (
|
||||
<Redoc store={store!} />
|
||||
) : hideLoading ? null : (
|
||||
<Loading color={normalizedOpts.theme.colors.primary.main} />
|
||||
)
|
||||
}
|
||||
</StoreBuilder>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { SourceCodeWithCopy } from '../SourceCode/SourceCode';
|
|||
|
||||
import { RightPanelHeader, Tab, TabList, TabPanel, Tabs } from '../../common-elements';
|
||||
import { OptionsContext } from '../OptionsProvider';
|
||||
import { l } from '../../services/Labels';
|
||||
|
||||
export interface RequestSamplesProps {
|
||||
operation: OperationModel;
|
||||
|
@ -26,7 +27,7 @@ export class RequestSamples extends React.Component<RequestSamplesProps> {
|
|||
return (
|
||||
(hasSamples && (
|
||||
<div>
|
||||
<RightPanelHeader> Request samples </RightPanelHeader>
|
||||
<RightPanelHeader> {l('requestSamples')} </RightPanelHeader>
|
||||
|
||||
<Tabs defaultIndex={0}>
|
||||
<TabList hidden={hideTabList}>
|
||||
|
|
|
@ -5,6 +5,7 @@ import { OperationModel } from '../../services/models';
|
|||
|
||||
import { RightPanelHeader, Tab, TabList, TabPanel, Tabs } from '../../common-elements';
|
||||
import { PayloadSamples } from '../PayloadSamples/PayloadSamples';
|
||||
import { l } from '../../services/Labels';
|
||||
|
||||
export interface ResponseSamplesProps {
|
||||
operation: OperationModel;
|
||||
|
@ -23,7 +24,7 @@ export class ResponseSamples extends React.Component<ResponseSamplesProps> {
|
|||
return (
|
||||
(responses.length > 0 && (
|
||||
<div>
|
||||
<RightPanelHeader> Response samples </RightPanelHeader>
|
||||
<RightPanelHeader> {l('responseSamples')} </RightPanelHeader>
|
||||
|
||||
<Tabs defaultIndex={0}>
|
||||
<TabList>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import { l } from '../../services/Labels';
|
||||
import { ResponseModel } from '../../services/models';
|
||||
import styled from '../../styled-components';
|
||||
import { ResponseView } from './Response';
|
||||
|
@ -26,7 +27,7 @@ export class ResponsesList extends React.PureComponent<ResponseListProps> {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<ResponsesHeader>{isCallback ? 'Callback responses' : 'Responses'}</ResponsesHeader>
|
||||
<ResponsesHeader>{isCallback ? l('callbackResponses') : l('responses')}</ResponsesHeader>
|
||||
{responses.map(response => {
|
||||
return <ResponseView key={response.code} response={response} />;
|
||||
})}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// import { transparentize } from 'polished';
|
||||
|
||||
import { UnderlinedHeader } from '../../common-elements';
|
||||
import styled from '../../styled-components';
|
||||
import { ResponseTitle } from './ResponseTitle';
|
||||
|
|
|
@ -4,7 +4,8 @@ import { Schema, SchemaProps } from './Schema';
|
|||
|
||||
import { ArrayClosingLabel, ArrayOpenningLabel } from '../../common-elements';
|
||||
import styled from '../../styled-components';
|
||||
import {humanizeConstraints} from "../../utils";
|
||||
import { humanizeConstraints } from '../../utils';
|
||||
import { TypeName } from '../../common-elements/fields';
|
||||
|
||||
const PaddedSchema = styled.div`
|
||||
padding-left: ${({ theme }) => theme.spacing.unit * 2}px;
|
||||
|
@ -12,17 +13,22 @@ const PaddedSchema = styled.div`
|
|||
|
||||
export class ArraySchema extends React.PureComponent<SchemaProps> {
|
||||
render() {
|
||||
const itemsSchema = this.props.schema.items!;
|
||||
const itemConstraintSchema = (
|
||||
min: number | undefined = undefined,
|
||||
max: number | undefined = undefined,
|
||||
) => ({ type: 'array', minItems: min, maxItems: max });
|
||||
const schema = this.props.schema;
|
||||
const itemsSchema = schema.items;
|
||||
|
||||
const minMaxItems = humanizeConstraints(itemConstraintSchema(itemsSchema.schema.minItems, itemsSchema.schema.maxItems));
|
||||
const minMaxItems = schema.minItems === undefined && schema.maxItems === undefined ?
|
||||
'' :
|
||||
`(${humanizeConstraints(schema)})`;
|
||||
|
||||
if (schema.displayType && !itemsSchema && !minMaxItems.length) {
|
||||
return (<div>
|
||||
<TypeName>{schema.displayType}</TypeName>
|
||||
</div>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ArrayOpenningLabel> Array ({minMaxItems})</ArrayOpenningLabel>
|
||||
<ArrayOpenningLabel> Array {minMaxItems}</ArrayOpenningLabel>
|
||||
<PaddedSchema>
|
||||
<Schema {...this.props} schema={itemsSchema} />
|
||||
</PaddedSchema>
|
||||
|
|
|
@ -63,14 +63,13 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
|
|||
return <OneOfSchema schema={schema} {...this.props} />;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'object':
|
||||
if (schema.fields?.length) {
|
||||
return <ObjectSchema {...(this.props as any)} />;
|
||||
}
|
||||
break;
|
||||
case 'array':
|
||||
return <ArraySchema {...(this.props as any)} />;
|
||||
const types = Array.isArray(type) ? type : [type];
|
||||
if (types.includes('object')) {
|
||||
if (schema.fields?.length) {
|
||||
return <ObjectSchema {...(this.props as any)} />;
|
||||
}
|
||||
} else if (types.includes('array')) {
|
||||
return <ArraySchema {...(this.props as any)} />;
|
||||
}
|
||||
|
||||
// TODO: maybe adjust FieldDetails to accept schema
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
SearchResultsBox,
|
||||
SearchWrap,
|
||||
} from './styled.elements';
|
||||
import { l } from '../../services/Labels';
|
||||
|
||||
export interface SearchBoxProps {
|
||||
search: SearchStore<string>;
|
||||
|
@ -28,6 +29,7 @@ export interface SearchBoxProps {
|
|||
|
||||
export interface SearchBoxState {
|
||||
results: SearchResult[];
|
||||
noResults: boolean;
|
||||
term: string;
|
||||
activeItemIdx: number;
|
||||
}
|
||||
|
@ -39,6 +41,7 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
|
|||
super(props);
|
||||
this.state = {
|
||||
results: [],
|
||||
noResults: false,
|
||||
term: '',
|
||||
activeItemIdx: -1,
|
||||
};
|
||||
|
@ -47,6 +50,7 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
|
|||
clearResults(term: string) {
|
||||
this.setState({
|
||||
results: [],
|
||||
noResults: false,
|
||||
term,
|
||||
});
|
||||
this.props.marker.unmark();
|
||||
|
@ -55,6 +59,7 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
|
|||
clear = () => {
|
||||
this.setState({
|
||||
results: [],
|
||||
noResults: false,
|
||||
term: '',
|
||||
activeItemIdx: -1,
|
||||
});
|
||||
|
@ -95,6 +100,7 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
|
|||
setResults(results: SearchResult[], term: string) {
|
||||
this.setState({
|
||||
results,
|
||||
noResults: results.length === 0
|
||||
});
|
||||
this.props.marker.mark(term);
|
||||
}
|
||||
|
@ -166,6 +172,9 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
|
|||
</SearchResultsBox>
|
||||
</PerfectScrollbarWrap>
|
||||
)}
|
||||
{this.state.term && this.state.noResults ? (
|
||||
<SearchResultsBox data-role="search:results">{l('noResultsFound')}</SearchResultsBox>
|
||||
) : null}
|
||||
</SearchWrap>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// import { transparentize } from 'polished';
|
||||
import * as React from 'react';
|
||||
|
||||
import styled, { media } from '../../styled-components';
|
||||
|
@ -8,8 +7,8 @@ import { SecurityRequirementModel } from '../../services/models/SecurityRequirem
|
|||
import { linksCss } from '../Markdown/styled.elements';
|
||||
|
||||
const ScopeName = styled.code`
|
||||
font-size: ${props => props.theme.typography.code.fontSize};
|
||||
font-family: ${props => props.theme.typography.code.fontFamily};
|
||||
font-size: ${(props) => props.theme.typography.code.fontSize};
|
||||
font-family: ${(props) => props.theme.typography.code.fontFamily};
|
||||
border: 1px solid ${({ theme }) => theme.colors.border.dark};
|
||||
margin: 0 3px;
|
||||
padding: 0.2em;
|
||||
|
@ -67,18 +66,22 @@ export class SecurityRequirement extends React.PureComponent<SecurityRequirement
|
|||
const security = this.props.security;
|
||||
return (
|
||||
<SecurityRequirementOrWrap>
|
||||
{security.schemes.map(scheme => {
|
||||
return (
|
||||
<SecurityRequirementAndWrap key={scheme.id}>
|
||||
<Link to={scheme.sectionId}>{scheme.id}</Link>
|
||||
{scheme.scopes.length > 0 && ' ('}
|
||||
{scheme.scopes.map(scope => (
|
||||
<ScopeName key={scope}>{scope}</ScopeName>
|
||||
))}
|
||||
{scheme.scopes.length > 0 && ') '}
|
||||
</SecurityRequirementAndWrap>
|
||||
);
|
||||
})}
|
||||
{security.schemes.length ? (
|
||||
security.schemes.map((scheme) => {
|
||||
return (
|
||||
<SecurityRequirementAndWrap key={scheme.id}>
|
||||
<Link to={scheme.sectionId}>{scheme.id}</Link>
|
||||
{scheme.scopes.length > 0 && ' ('}
|
||||
{scheme.scopes.map((scope) => (
|
||||
<ScopeName key={scope}>{scope}</ScopeName>
|
||||
))}
|
||||
{scheme.scopes.length > 0 && ') '}
|
||||
</SecurityRequirementAndWrap>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<SecurityRequirementAndWrap>None</SecurityRequirementAndWrap>
|
||||
)}
|
||||
</SecurityRequirementOrWrap>
|
||||
);
|
||||
}
|
||||
|
@ -89,7 +92,7 @@ const AuthHeaderColumn = styled.div`
|
|||
`;
|
||||
|
||||
const SecuritiesColumn = styled.div`
|
||||
width: ${props => props.theme.schema.defaultDetailsWidth};
|
||||
width: ${(props) => props.theme.schema.defaultDetailsWidth};
|
||||
${media.lessThan('small')`
|
||||
margin-top: 10px;
|
||||
`}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// import { observe } from 'mobx';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as classnames from 'classnames';
|
||||
import { default as classnames } from 'classnames';
|
||||
import { darken } from 'polished';
|
||||
|
||||
import { deprecatedCss, ShelfIcon } from '../../common-elements';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as memoize from 'memoize-one/dist/memoize-one.cjs'; // fixme: https://github.com/alexreardon/memoize-one/issues/37
|
||||
import { Component, createContext } from 'react';
|
||||
import * as React from 'react';
|
||||
import { createContext } from 'react';
|
||||
|
||||
import { AppStore } from '../services/';
|
||||
import { RedocRawOptions } from '../services/RedocNormalizedOptions';
|
||||
|
@ -14,7 +14,7 @@ export interface StoreBuilderProps {
|
|||
|
||||
onLoaded?: (e?: Error) => void;
|
||||
|
||||
children: (props: { loading: boolean; store?: AppStore }) => any;
|
||||
children: (props: { loading: boolean; store: AppStore | null }) => any;
|
||||
}
|
||||
|
||||
export interface StoreBuilderState {
|
||||
|
@ -25,79 +25,47 @@ export interface StoreBuilderState {
|
|||
prevSpecUrl?: string;
|
||||
}
|
||||
|
||||
const { Provider, Consumer } = createContext<AppStore | undefined>(undefined);
|
||||
export { Provider as StoreProvider, Consumer as StoreConsumer };
|
||||
const StoreContext = createContext<AppStore | undefined>(undefined);
|
||||
const { Provider, Consumer } = StoreContext;
|
||||
export { Provider as StoreProvider, Consumer as StoreConsumer, StoreContext };
|
||||
|
||||
export class StoreBuilder extends Component<StoreBuilderProps, StoreBuilderState> {
|
||||
static getDerivedStateFromProps(nextProps: StoreBuilderProps, prevState: StoreBuilderState) {
|
||||
if (nextProps.specUrl !== prevState.prevSpecUrl || nextProps.spec !== prevState.prevSpec) {
|
||||
return {
|
||||
loading: true,
|
||||
resolvedSpec: null,
|
||||
prevSpec: nextProps.spec,
|
||||
prevSpecUrl: nextProps.specUrl,
|
||||
};
|
||||
export function StoreBuilder(props: StoreBuilderProps) {
|
||||
const {spec, specUrl, options, onLoaded, children } = props;
|
||||
|
||||
const [resolvedSpec, setResolvedSpec] = React.useState<any>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
async function load() {
|
||||
if (!spec && !specUrl) {
|
||||
return undefined;
|
||||
}
|
||||
setResolvedSpec(null);
|
||||
const resolved = await loadAndBundleSpec(spec || specUrl!);
|
||||
setResolvedSpec(resolved);
|
||||
}
|
||||
load();
|
||||
}, [spec, specUrl])
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
state: StoreBuilderState = {
|
||||
loading: true,
|
||||
resolvedSpec: null,
|
||||
};
|
||||
|
||||
@memoize
|
||||
makeStore(spec, specUrl, options) {
|
||||
if (!spec) {
|
||||
return undefined;
|
||||
}
|
||||
const store = React.useMemo(() => {
|
||||
if (!resolvedSpec) return null;
|
||||
try {
|
||||
return new AppStore(spec, specUrl, options);
|
||||
return new AppStore(resolvedSpec, specUrl, options);
|
||||
} catch (e) {
|
||||
if (this.props.onLoaded) {
|
||||
this.props.onLoaded(e);
|
||||
if (onLoaded) {
|
||||
onLoaded(e);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}, [resolvedSpec, specUrl, options]);
|
||||
|
||||
componentDidMount() {
|
||||
this.load();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.state.resolvedSpec === null) {
|
||||
this.load();
|
||||
} else if (!this.state.loading && this.props.onLoaded) {
|
||||
// may run multiple time
|
||||
this.props.onLoaded();
|
||||
React.useEffect(() => {
|
||||
if (store && onLoaded) {
|
||||
onLoaded();
|
||||
}
|
||||
}
|
||||
}, [store, onLoaded])
|
||||
|
||||
async load() {
|
||||
const { specUrl, spec } = this.props;
|
||||
try {
|
||||
const resolvedSpec = await loadAndBundleSpec(spec || specUrl!);
|
||||
this.setState({ resolvedSpec, loading: false });
|
||||
} catch (e) {
|
||||
if (this.props.onLoaded) {
|
||||
this.props.onLoaded(e);
|
||||
}
|
||||
this.setState({ error: e });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
throw this.state.error;
|
||||
}
|
||||
|
||||
const { specUrl, options } = this.props;
|
||||
const { loading, resolvedSpec } = this.state;
|
||||
return this.props.children({
|
||||
loading,
|
||||
store: this.makeStore(resolvedSpec, specUrl, options),
|
||||
});
|
||||
}
|
||||
return children({
|
||||
loading: !store,
|
||||
store,
|
||||
});
|
||||
}
|
||||
|
|
27
src/components/__tests__/SecurityRequirement.test.tsx
Normal file
27
src/components/__tests__/SecurityRequirement.test.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
import * as React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { OpenAPIParser } from '../../services';
|
||||
import { SecurityRequirementModel } from '../../services/models/SecurityRequirement';
|
||||
import { SecurityRequirement } from '../SecurityRequirement/SecurityRequirement';
|
||||
import { RedocNormalizedOptions } from '../../services/RedocNormalizedOptions';
|
||||
|
||||
const options = new RedocNormalizedOptions({});
|
||||
describe('Components', () => {
|
||||
describe('SecurityRequirement', () => {
|
||||
describe('SecurityRequirement', () => {
|
||||
it('should render \'None\' when empty object in security open api', () => {
|
||||
const parser = new OpenAPIParser({ openapi: '3.0', info: { title: 'test', version: '0' }, paths: {} },
|
||||
undefined,
|
||||
options,
|
||||
);
|
||||
const securityRequirement = new SecurityRequirementModel({}, parser);
|
||||
const securityElement = shallow(
|
||||
<SecurityRequirement key={1} security={securityRequirement} />
|
||||
).getElement();
|
||||
expect(securityElement.props.children.type.target).toEqual('span');
|
||||
expect(securityElement.props.children.props.children).toEqual('None');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -6,6 +6,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
|||
<Field
|
||||
field={
|
||||
FieldModel {
|
||||
"const": "",
|
||||
"deprecated": false,
|
||||
"description": "",
|
||||
"example": undefined,
|
||||
|
@ -17,7 +18,10 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
|||
"required": false,
|
||||
"schema": SchemaModel {
|
||||
"activeOneOf": 0,
|
||||
"const": "",
|
||||
"constraints": Array [],
|
||||
"contentEncoding": undefined,
|
||||
"contentMediaType": undefined,
|
||||
"default": undefined,
|
||||
"deprecated": false,
|
||||
"description": "",
|
||||
|
@ -29,7 +33,8 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
|||
"format": undefined,
|
||||
"isCircular": undefined,
|
||||
"isPrimitive": true,
|
||||
"nullable": false,
|
||||
"maxItems": undefined,
|
||||
"minItems": undefined,
|
||||
"options": "<<<filtered>>>",
|
||||
"pattern": undefined,
|
||||
"pointer": "#/components/schemas/Dog/properties/packSize",
|
||||
|
@ -56,6 +61,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
|||
<Field
|
||||
field={
|
||||
FieldModel {
|
||||
"const": "",
|
||||
"deprecated": false,
|
||||
"description": "",
|
||||
"example": undefined,
|
||||
|
@ -67,7 +73,10 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
|||
"required": true,
|
||||
"schema": SchemaModel {
|
||||
"activeOneOf": 0,
|
||||
"const": "",
|
||||
"constraints": Array [],
|
||||
"contentEncoding": undefined,
|
||||
"contentMediaType": undefined,
|
||||
"default": undefined,
|
||||
"deprecated": false,
|
||||
"description": "",
|
||||
|
@ -79,7 +88,8 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
|||
"format": undefined,
|
||||
"isCircular": undefined,
|
||||
"isPrimitive": true,
|
||||
"nullable": false,
|
||||
"maxItems": undefined,
|
||||
"minItems": undefined,
|
||||
"options": "<<<filtered>>>",
|
||||
"pattern": undefined,
|
||||
"pointer": "#/components/schemas/Dog/properties/type",
|
||||
|
|
|
@ -6,9 +6,9 @@ export {
|
|||
Section,
|
||||
StyledDropdown,
|
||||
SimpleDropdown,
|
||||
DropdownOption,
|
||||
} from './common-elements/';
|
||||
export { OpenAPIEncoding } from './types';
|
||||
export type { DropdownOption } from './common-elements';
|
||||
export type { OpenAPIEncoding } from './types';
|
||||
export * from './services';
|
||||
export * from './utils';
|
||||
|
||||
|
|
|
@ -1,15 +1,2 @@
|
|||
import 'core-js/es/promise';
|
||||
|
||||
import 'core-js/es/array/find';
|
||||
import 'core-js/es/array/includes';
|
||||
import 'core-js/es/object/assign';
|
||||
import 'core-js/es/object/entries';
|
||||
import 'core-js/es/object/is';
|
||||
import 'core-js/es/string/ends-with';
|
||||
import 'core-js/es/string/starts-with';
|
||||
|
||||
import 'core-js/es/map';
|
||||
import 'core-js/es/symbol';
|
||||
|
||||
import 'unfetch/polyfill/index';
|
||||
import 'url-polyfill';
|
||||
import 'core-js/es/symbol';
|
||||
|
|
|
@ -145,7 +145,10 @@ export class AppStore {
|
|||
|
||||
if (idx === -1 && IS_BROWSER) {
|
||||
const $description = document.querySelector('[data-role="redoc-description"]');
|
||||
const $summary = document.querySelector('[data-role="redoc-summary"]');
|
||||
|
||||
if ($description) elements.push($description);
|
||||
if ($summary) elements.push($summary);
|
||||
}
|
||||
|
||||
this.marker.addOnly(elements);
|
||||
|
|
|
@ -6,10 +6,17 @@ export interface LabelsConfig {
|
|||
deprecated: string;
|
||||
example: string;
|
||||
examples: string;
|
||||
nullable: 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>;
|
||||
|
@ -22,10 +29,17 @@ const labels: LabelsConfig = {
|
|||
deprecated: 'Deprecated',
|
||||
example: 'Example',
|
||||
examples: 'Examples',
|
||||
nullable: 'Nullable',
|
||||
recursive: 'Recursive',
|
||||
arrayOf: 'Array of ',
|
||||
webhook: 'Event',
|
||||
const: 'Value',
|
||||
noResultsFound: 'No results found',
|
||||
download: 'Download',
|
||||
downloadSpecification: 'Download OpenAPI specification',
|
||||
responses: 'Responses',
|
||||
callbackResponses: 'Callback responses',
|
||||
requestSamples: 'Request samples',
|
||||
responseSamples: 'Response samples',
|
||||
};
|
||||
|
||||
export function setRedocLabels(_labels?: LabelsConfigRaw) {
|
||||
|
|
|
@ -53,7 +53,7 @@ export class MenuBuilder {
|
|||
const spec = parser.spec;
|
||||
|
||||
const items: ContentItemModel[] = [];
|
||||
const tagsMap = MenuBuilder.getTagsWithOperations(spec);
|
||||
const tagsMap = MenuBuilder.getTagsWithOperations(parser, spec);
|
||||
|
||||
const mdHeadings = MenuBuilder.addMarkdownItems(
|
||||
spec.info.description || '',
|
||||
|
@ -232,24 +232,33 @@ export class MenuBuilder {
|
|||
/**
|
||||
* collects tags and maps each tag to list of operations belonging to this tag
|
||||
*/
|
||||
static getTagsWithOperations(spec: OpenAPISpec): TagsInfoMap {
|
||||
static getTagsWithOperations(parser: OpenAPIParser, spec: OpenAPISpec): TagsInfoMap {
|
||||
const tags: TagsInfoMap = {};
|
||||
const webhooks = spec['x-webhooks'] || spec.webhooks;
|
||||
for (const tag of spec.tags || []) {
|
||||
tags[tag.name] = { ...tag, operations: [] };
|
||||
}
|
||||
|
||||
getTags(spec.paths);
|
||||
if (spec['x-webhooks']) {
|
||||
getTags(spec['x-webhooks'], true);
|
||||
if (webhooks) {
|
||||
getTags(parser, webhooks, true);
|
||||
}
|
||||
|
||||
function getTags(paths: OpenAPIPaths, isWebhook?: boolean) {
|
||||
if (spec.paths){
|
||||
getTags(parser, spec.paths);
|
||||
}
|
||||
|
||||
function getTags(parser: OpenAPIParser, paths: OpenAPIPaths, isWebhook?: boolean) {
|
||||
for (const pathName of Object.keys(paths)) {
|
||||
const path = paths[pathName];
|
||||
const operations = Object.keys(path).filter(isOperationName);
|
||||
for (const operationName of operations) {
|
||||
const operationInfo = path[operationName];
|
||||
let operationTags = operationInfo.tags;
|
||||
if (path.$ref) {
|
||||
const resolvedPaths = parser.deref<OpenAPIPaths>(path as OpenAPIPaths);
|
||||
getTags(parser, { [pathName]: resolvedPaths }, isWebhook);
|
||||
continue;
|
||||
}
|
||||
let operationTags = operationInfo?.tags;
|
||||
|
||||
if (!operationTags || !operationTags.length) {
|
||||
// empty tag
|
||||
|
|
|
@ -211,6 +211,7 @@ export class MenuStore {
|
|||
|
||||
this.deactivate(this.activeItem);
|
||||
if (!item) {
|
||||
this.activeItemIdx = -1;
|
||||
this.history.replace('', rewriteHistory);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -45,9 +45,9 @@ class RefCounter {
|
|||
export class OpenAPIParser {
|
||||
specUrl?: string;
|
||||
spec: OpenAPISpec;
|
||||
mergeRefs: Set<string>;
|
||||
|
||||
private _refCounter: RefCounter = new RefCounter();
|
||||
private allowMergeRefs: boolean = false;
|
||||
|
||||
constructor(
|
||||
spec: OpenAPISpec,
|
||||
|
@ -58,8 +58,7 @@ export class OpenAPIParser {
|
|||
this.preprocess(spec);
|
||||
|
||||
this.spec = spec;
|
||||
|
||||
this.mergeRefs = new Set();
|
||||
this.allowMergeRefs = spec.openapi.startsWith('3.1');
|
||||
|
||||
const href = IS_BROWSER ? window.location.href : '';
|
||||
if (typeof specUrl === 'string') {
|
||||
|
@ -149,7 +148,7 @@ export class OpenAPIParser {
|
|||
* @param obj object to dereference
|
||||
* @param forceCircular whether to dereference even if it is circular ref
|
||||
*/
|
||||
deref<T extends object>(obj: OpenAPIRef | T, forceCircular = false): T {
|
||||
deref<T extends object>(obj: OpenAPIRef | T, forceCircular = false, mergeAsAllOf = false): T {
|
||||
if (this.isRef(obj)) {
|
||||
const schemaName = getDefinitionName(obj.$ref);
|
||||
if (schemaName && this.options.ignoreNamedSchemas.has(schemaName)) {
|
||||
|
@ -165,23 +164,48 @@ export class OpenAPIParser {
|
|||
return Object.assign({}, resolved, { 'x-circular-ref': true });
|
||||
}
|
||||
// deref again in case one more $ref is here
|
||||
let result = resolved;
|
||||
if (this.isRef(resolved)) {
|
||||
const res = this.deref(resolved);
|
||||
result = this.deref(resolved, false, mergeAsAllOf);
|
||||
this.exitRef(resolved);
|
||||
return res;
|
||||
}
|
||||
return resolved;
|
||||
return this.allowMergeRefs ? this.mergeRefs(obj, resolved, mergeAsAllOf) : result;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
shalowDeref<T extends object>(obj: OpenAPIRef | T): T {
|
||||
shallowDeref<T extends unknown>(obj: OpenAPIRef | T): T {
|
||||
if (this.isRef(obj)) {
|
||||
return this.byRef<T>(obj.$ref)!;
|
||||
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
|
||||
const { $ref, ...rest } = ref;
|
||||
const keys = Object.keys(rest);
|
||||
if (keys.length === 0) {
|
||||
return resolved;
|
||||
}
|
||||
if (mergeAsAllOf && keys.some((k) => k !== 'description' && k !== 'title' && k !== 'externalDocs')) {
|
||||
return {
|
||||
allOf: [rest, resolved],
|
||||
};
|
||||
} else {
|
||||
// small optimization
|
||||
return {
|
||||
...resolved,
|
||||
...rest,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge allOf constraints.
|
||||
* @param schema schema with allOF
|
||||
|
@ -225,7 +249,7 @@ export class OpenAPIParser {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const resolved = this.deref(subSchema, forceCircular);
|
||||
const resolved = this.deref(subSchema, forceCircular, true);
|
||||
const subRef = subSchema.$ref || undefined;
|
||||
const subMerged = this.mergeAllOf(resolved, subRef, forceCircular, used$Refs);
|
||||
receiver.parentRefs!.push(...(subMerged.parentRefs || []));
|
||||
|
@ -234,7 +258,7 @@ export class OpenAPIParser {
|
|||
schema: subMerged,
|
||||
};
|
||||
})
|
||||
.filter(child => child !== undefined) as Array<{
|
||||
.filter((child) => child !== undefined) as Array<{
|
||||
$ref: string | undefined;
|
||||
schema: MergedOpenAPISchema;
|
||||
}>;
|
||||
|
@ -265,7 +289,7 @@ export class OpenAPIParser {
|
|||
{ allOf: [receiver.properties[prop], subSchema.properties[prop]] },
|
||||
$ref + '/properties/' + prop,
|
||||
);
|
||||
receiver.properties[prop] = mergedProp
|
||||
receiver.properties[prop] = mergedProp;
|
||||
this.exitParents(mergedProp); // every prop resolution should have separate recursive stack
|
||||
}
|
||||
}
|
||||
|
@ -313,7 +337,7 @@ export class OpenAPIParser {
|
|||
const def = this.deref(schemas[defName]);
|
||||
if (
|
||||
def.allOf !== undefined &&
|
||||
def.allOf.find(obj => obj.$ref !== undefined && $refs.indexOf(obj.$ref) > -1)
|
||||
def.allOf.find((obj) => obj.$ref !== undefined && $refs.indexOf(obj.$ref) > -1)
|
||||
) {
|
||||
res['#/components/schemas/' + defName] = [def['x-discriminator-value'] || defName];
|
||||
}
|
||||
|
@ -339,7 +363,7 @@ export class OpenAPIParser {
|
|||
const beforeAllOf = allOf.slice(0, i);
|
||||
const afterAllOf = allOf.slice(i + 1);
|
||||
return {
|
||||
oneOf: sub.oneOf.map(part => {
|
||||
oneOf: sub.oneOf.map((part) => {
|
||||
const merged = this.mergeAllOf({
|
||||
allOf: [...beforeAllOf, part, ...afterAllOf],
|
||||
});
|
||||
|
|
|
@ -43,14 +43,15 @@ export interface RedocRawOptions {
|
|||
maxDisplayedEnumValues?: number;
|
||||
ignoreNamedSchemas?: string[] | string;
|
||||
hideSchemaPattern?: boolean;
|
||||
generatedPayloadSamplesMaxDepth?: number;
|
||||
}
|
||||
|
||||
function argValueToBoolean(val?: string | boolean, defaultValue?: boolean): boolean {
|
||||
export function argValueToBoolean(val?: string | boolean, defaultValue?: boolean): boolean {
|
||||
if (val === undefined) {
|
||||
return defaultValue || false;
|
||||
}
|
||||
if (typeof val === 'string') {
|
||||
return val === 'false' ? false : true;
|
||||
return val !== 'false';
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
@ -172,6 +173,16 @@ export class RedocNormalizedOptions {
|
|||
return 2;
|
||||
}
|
||||
|
||||
private static normalizeGeneratedPayloadSamplesMaxDepth(
|
||||
value?: number | string | undefined,
|
||||
): number {
|
||||
if (!isNaN(Number(value))) {
|
||||
return Math.max(0, Number(value));
|
||||
}
|
||||
|
||||
return 10;
|
||||
}
|
||||
|
||||
theme: ResolvedThemeInterface;
|
||||
scrollYOffset: () => number;
|
||||
hideHostname: boolean;
|
||||
|
@ -206,6 +217,7 @@ export class RedocNormalizedOptions {
|
|||
|
||||
ignoreNamedSchemas: Set<string>;
|
||||
hideSchemaPattern: boolean;
|
||||
generatedPayloadSamplesMaxDepth: number;
|
||||
|
||||
constructor(raw: RedocRawOptions, defaults: RedocRawOptions = {}) {
|
||||
raw = { ...defaults, ...raw };
|
||||
|
@ -268,5 +280,9 @@ export class RedocNormalizedOptions {
|
|||
: raw.ignoreNamedSchemas?.split(',').map((s) => s.trim());
|
||||
this.ignoreNamedSchemas = new Set(ignoreNamedSchemas);
|
||||
this.hideSchemaPattern = argValueToBoolean(raw.hideSchemaPattern);
|
||||
this.generatedPayloadSamplesMaxDepth =
|
||||
RedocNormalizedOptions.normalizeGeneratedPayloadSamplesMaxDepth(
|
||||
raw.generatedPayloadSamplesMaxDepth,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { OpenAPIExternalDocumentation, OpenAPISpec } from '../types';
|
||||
import { OpenAPIExternalDocumentation, OpenAPIPath, OpenAPISpec, Referenced } from '../types';
|
||||
|
||||
import { ContentItemModel, MenuBuilder } from './MenuBuilder';
|
||||
import { ApiInfoModel } from './models/ApiInfo';
|
||||
|
@ -28,6 +28,7 @@ export class SpecStore {
|
|||
this.externalDocs = this.parser.spec.externalDocs;
|
||||
this.contentItems = MenuBuilder.buildStructure(this.parser, this.options);
|
||||
this.securitySchemes = new SecuritySchemesModel(this.parser);
|
||||
this.webhooks = new WebhookModel(this.parser, options, this.parser.spec['x-webhooks']);
|
||||
const webhookPath: Referenced<OpenAPIPath> = {...this.parser?.spec?.['x-webhooks'], ...this.parser?.spec.webhooks};
|
||||
this.webhooks = new WebhookModel(this.parser, options, webhookPath);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import { OpenAPIParameter, Referenced } from '../../types';
|
||||
|
||||
const opts = new RedocNormalizedOptions({});
|
||||
|
||||
|
@ -13,5 +14,18 @@ describe('Models', () => {
|
|||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
expect(parser.mergeAllOf(spec.components.schemas.test)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should override description from $ref of the referenced component, when sibling description exists ', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const spec = require('./fixtures/siblingRefDescription.json');
|
||||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
const schemaOrRef: Referenced<OpenAPIParameter> = {
|
||||
$ref: '#/components/schemas/Test',
|
||||
description: 'Overriden description',
|
||||
};
|
||||
|
||||
expect(parser.shallowDeref(schemaOrRef)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
@ -86,3 +86,10 @@ Object {
|
|||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Models Schema should override description from $ref of the referenced component, when sibling description exists 1`] = `
|
||||
Object {
|
||||
"description": "Overriden description",
|
||||
"type": "object",
|
||||
}
|
||||
`;
|
||||
|
|
66
src/services/__tests__/fixtures/3.1/pathItems.json
Normal file
66
src/services/__tests__/fixtures/3.1/pathItems.json
Normal file
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"version": "1.0.0",
|
||||
"title": "Swagger Petstore"
|
||||
},
|
||||
"webhooks": {
|
||||
"myWebhook": {
|
||||
"$ref": "#/components/pathItems/catsWebhook",
|
||||
"description": "Overriding description",
|
||||
"summary": "Overriding summary"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"pathItems": {
|
||||
"catsWebhook": {
|
||||
"put": {
|
||||
"summary": "Get a cat details after update",
|
||||
"description": "Get a cat details after update",
|
||||
"operationId": "updatedCat",
|
||||
"tags": [
|
||||
"pet"
|
||||
],
|
||||
"requestBody": {
|
||||
"description": "Information about cat in the system",
|
||||
"content": {
|
||||
"multipart/form-data": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "update Cat details"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"summary": "Create new cat",
|
||||
"description": "Info about new cat",
|
||||
"operationId": "createdCat",
|
||||
"tags": [
|
||||
"pet"
|
||||
],
|
||||
"requestBody": {
|
||||
"description": "Information about cat in the system",
|
||||
"content": {
|
||||
"multipart/form-data": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Pet"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "create Cat details"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
39
src/services/__tests__/fixtures/siblingRefDescription.json
Normal file
39
src/services/__tests__/fixtures/siblingRefDescription.json
Normal file
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "AA",
|
||||
"version": "1.0"
|
||||
},
|
||||
"paths": {
|
||||
"/test": {
|
||||
"get": {
|
||||
"operationId": "test",
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"testAttr": {
|
||||
"description": "Overriden description",
|
||||
"$ref": "#/components/schemas/Test"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Test": {
|
||||
"type": "object",
|
||||
"description": "Refed description"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,5 +34,33 @@ describe('Models', () => {
|
|||
const info = new ApiInfoModel(parser);
|
||||
expect(info.description).toEqual('Test description\nsome text\n');
|
||||
});
|
||||
|
||||
test('should correctly populate summary up to the first md heading', () => {
|
||||
parser.spec = {
|
||||
openapi: '3.1.0',
|
||||
info: {
|
||||
summary: 'Test summary\nsome text\n## Heading\n test',
|
||||
},
|
||||
} as any;
|
||||
|
||||
const info = new ApiInfoModel(parser);
|
||||
expect(info.summary).toEqual('Test summary\nsome text\n## Heading\n test');
|
||||
});
|
||||
|
||||
test('should correctly populate license identifier', () => {
|
||||
parser.spec = {
|
||||
openapi: '3.1.0',
|
||||
info: {
|
||||
license: {
|
||||
name: 'MIT',
|
||||
identifier: 'MIT',
|
||||
url: 'https://opensource.org/licenses/MIT'
|
||||
}
|
||||
},
|
||||
} as any;
|
||||
|
||||
const { license = { identifier: null } } = new ApiInfoModel(parser);
|
||||
expect(license.identifier).toEqual('MIT');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
25
src/services/__tests__/models/MenuBuilder.test.ts
Normal file
25
src/services/__tests__/models/MenuBuilder.test.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
import { MenuBuilder } from '../../MenuBuilder';
|
||||
import { OpenAPIParser } from '../../OpenAPIParser';
|
||||
|
||||
import { RedocNormalizedOptions } from '../../RedocNormalizedOptions';
|
||||
|
||||
const opts = new RedocNormalizedOptions({});
|
||||
|
||||
describe('Models', () => {
|
||||
describe('MenuBuilder', () => {
|
||||
let parser;
|
||||
|
||||
test('should resolve pathItems', () => {
|
||||
const spec = require('../fixtures/3.1/pathItems.json');
|
||||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
const contentItems = MenuBuilder.buildStructure(parser, opts);
|
||||
expect(contentItems).toHaveLength(1);
|
||||
expect(contentItems[0].items).toHaveLength(2);
|
||||
expect(contentItems[0].id).toEqual('tag/pet');
|
||||
expect(contentItems[0].name).toEqual('pet');
|
||||
expect(contentItems[0].type).toEqual('tag');
|
||||
|
||||
});
|
||||
});
|
||||
});
|
27
src/services/__tests__/models/RequestBody.test.ts
Normal file
27
src/services/__tests__/models/RequestBody.test.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { RequestBodyModel } from '../../models/RequestBody';
|
||||
import { OpenAPIParser } from '../../OpenAPIParser';
|
||||
import { RedocNormalizedOptions } from '../../RedocNormalizedOptions';
|
||||
|
||||
const opts = new RedocNormalizedOptions({});
|
||||
describe('Models', () => {
|
||||
describe('RequestBodyModel', () => {
|
||||
let parser, props;
|
||||
|
||||
beforeEach(() => {
|
||||
parser = new OpenAPIParser({ openapi: '3.0.0' } as any, undefined, opts);
|
||||
props = {
|
||||
parser,
|
||||
infoOrRef: {},
|
||||
options: opts,
|
||||
isEvent: false,
|
||||
};
|
||||
});
|
||||
|
||||
test('should work with default props', () => {
|
||||
const consoleError = jest.spyOn(global.console, 'error');
|
||||
const req = new RequestBodyModel(props);
|
||||
expect(consoleError).not.toHaveBeenCalled();
|
||||
expect(req).toEqual({ description: '', required: false });
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,30 +5,38 @@ import { RedocNormalizedOptions } from '../../RedocNormalizedOptions';
|
|||
const opts = new RedocNormalizedOptions({});
|
||||
describe('Models', () => {
|
||||
describe('ResponseModel', () => {
|
||||
let parser;
|
||||
let parser, props;
|
||||
|
||||
beforeEach(() => {
|
||||
parser = new OpenAPIParser({ openapi: '3.0.0' } as any, undefined, opts);
|
||||
props = {
|
||||
parser,
|
||||
defaultAsError: false,
|
||||
infoOrRef: {},
|
||||
options: opts,
|
||||
isEvent: false,
|
||||
code: 'default',
|
||||
};
|
||||
});
|
||||
|
||||
test('should calculate response type based on code', () => {
|
||||
let resp = new ResponseModel(parser, '200', false, {}, opts);
|
||||
let resp = new ResponseModel({...props, code: '200' });
|
||||
expect(resp.type).toEqual('success');
|
||||
resp = new ResponseModel(parser, '120', false, {}, opts);
|
||||
resp = new ResponseModel({...props, code: '120' });
|
||||
expect(resp.type).toEqual('info');
|
||||
resp = new ResponseModel(parser, '301', false, {}, opts);
|
||||
resp = new ResponseModel({...props, code: '301' });
|
||||
expect(resp.type).toEqual('redirect');
|
||||
resp = new ResponseModel(parser, '400', false, {}, opts);
|
||||
resp = new ResponseModel({...props, code: '400' });
|
||||
expect(resp.type).toEqual('error');
|
||||
});
|
||||
|
||||
test('default should be successful by default', () => {
|
||||
const resp = new ResponseModel(parser, 'default', false, {}, opts);
|
||||
const resp = new ResponseModel({...props, code: 'default' });
|
||||
expect(resp.type).toEqual('success');
|
||||
});
|
||||
|
||||
test('default should be error if defaultAsError is true', () => {
|
||||
const resp = new ResponseModel(parser, 'default', true, {}, opts);
|
||||
const resp = new ResponseModel({...props, code: 'default', defaultAsError: true });
|
||||
expect(resp.type).toEqual('error');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@ export class ApiInfoModel implements OpenAPIInfo {
|
|||
version: string;
|
||||
|
||||
description: string;
|
||||
summary: string;
|
||||
termsOfService?: string;
|
||||
contact?: OpenAPIContact;
|
||||
license?: OpenAPILicense;
|
||||
|
@ -17,6 +18,7 @@ export class ApiInfoModel implements OpenAPIInfo {
|
|||
constructor(private parser: OpenAPIParser) {
|
||||
Object.assign(this, parser.spec.info);
|
||||
this.description = parser.spec.info.description || '';
|
||||
this.summary = parser.spec.info.summary || '';
|
||||
|
||||
const firstHeadingLinePos = this.description.search(/^##?\s+/m);
|
||||
if (firstHeadingLinePos > -1) {
|
||||
|
|
|
@ -55,6 +55,7 @@ export class FieldModel {
|
|||
extensions?: Record<string, any>;
|
||||
explode: boolean;
|
||||
style?: OpenAPIParameterStyle;
|
||||
const?: any;
|
||||
|
||||
serializationMime?: string;
|
||||
|
||||
|
@ -111,6 +112,8 @@ export class FieldModel {
|
|||
if (options.showExtensions) {
|
||||
this.extensions = extractExtensions(info, options.showExtensions);
|
||||
}
|
||||
|
||||
this.const = this.schema?.const || info?.const || '';
|
||||
}
|
||||
|
||||
@action
|
||||
|
|
|
@ -14,6 +14,7 @@ export class MediaTypeModel {
|
|||
name: string;
|
||||
isRequestType: boolean;
|
||||
onlyRequiredInSamples: boolean;
|
||||
generatedPayloadSamplesMaxDepth: number;
|
||||
|
||||
/**
|
||||
* @param isRequestType needed to know if skipe RO/RW fields in objects
|
||||
|
@ -29,6 +30,7 @@ export class MediaTypeModel {
|
|||
this.isRequestType = isRequestType;
|
||||
this.schema = info.schema && new SchemaModel(parser, info.schema, '', options);
|
||||
this.onlyRequiredInSamples = options.onlyRequiredInSamples;
|
||||
this.generatedPayloadSamplesMaxDepth = options.generatedPayloadSamplesMaxDepth;
|
||||
if (info.examples !== undefined) {
|
||||
this.examples = mapValues(
|
||||
info.examples,
|
||||
|
@ -38,7 +40,7 @@ export class MediaTypeModel {
|
|||
this.examples = {
|
||||
default: new ExampleModel(
|
||||
parser,
|
||||
{ value: parser.shalowDeref(info.example) },
|
||||
{ value: parser.shallowDeref(info.example) },
|
||||
name,
|
||||
info.encoding,
|
||||
),
|
||||
|
@ -51,14 +53,14 @@ export class MediaTypeModel {
|
|||
generateExample(parser: OpenAPIParser, info: OpenAPIMediaType) {
|
||||
const samplerOptions = {
|
||||
skipReadOnly: this.isRequestType,
|
||||
skipNonRequired: this.isRequestType && this.onlyRequiredInSamples,
|
||||
skipWriteOnly: !this.isRequestType,
|
||||
maxSampleDepth: 10,
|
||||
skipNonRequired: this.isRequestType && this.onlyRequiredInSamples,
|
||||
maxSampleDepth: this.generatedPayloadSamplesMaxDepth,
|
||||
};
|
||||
if (this.schema && this.schema.oneOf) {
|
||||
this.examples = {};
|
||||
for (const subSchema of this.schema.oneOf) {
|
||||
const sample = Sampler.sample(subSchema.rawSchema, samplerOptions, parser.spec);
|
||||
const sample = Sampler.sample(subSchema.rawSchema as any, samplerOptions, parser.spec);
|
||||
|
||||
if (this.schema.discriminatorProp && typeof sample === 'object' && sample) {
|
||||
sample[this.schema.discriminatorProp] = subSchema.title;
|
||||
|
@ -78,7 +80,7 @@ export class MediaTypeModel {
|
|||
default: new ExampleModel(
|
||||
parser,
|
||||
{
|
||||
value: Sampler.sample(info.schema, samplerOptions, parser.spec),
|
||||
value: Sampler.sample(info.schema as any, samplerOptions, parser.spec),
|
||||
},
|
||||
this.name,
|
||||
info.encoding,
|
||||
|
|
|
@ -77,6 +77,7 @@ export class OperationModel implements IMenuItem {
|
|||
isCallback: boolean;
|
||||
isWebhook: boolean;
|
||||
topMargin: boolean;
|
||||
isEvent: boolean;
|
||||
|
||||
constructor(
|
||||
private parser: OpenAPIParser,
|
||||
|
@ -99,7 +100,8 @@ export class OperationModel implements IMenuItem {
|
|||
this.operationId = operationSpec.operationId;
|
||||
this.path = operationSpec.pathName;
|
||||
this.isCallback = isCallback;
|
||||
this.isWebhook = !!operationSpec.isWebhook;
|
||||
this.isWebhook = operationSpec.isWebhook;
|
||||
this.isEvent = this.isCallback || this.isWebhook;
|
||||
|
||||
this.name = getOperationSummary(operationSpec);
|
||||
|
||||
|
@ -172,8 +174,12 @@ export class OperationModel implements IMenuItem {
|
|||
@memoize
|
||||
get requestBody() {
|
||||
return (
|
||||
this.operationSpec.requestBody &&
|
||||
new RequestBodyModel(this.parser, this.operationSpec.requestBody, this.options)
|
||||
this.operationSpec.requestBody && new RequestBodyModel({
|
||||
parser: this.parser,
|
||||
infoOrRef: this.operationSpec.requestBody,
|
||||
options: this.options,
|
||||
isEvent: this.isEvent,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -241,13 +247,14 @@ export class OperationModel implements IMenuItem {
|
|||
return isStatusCode(code);
|
||||
}) // filter out other props (e.g. x-props)
|
||||
.map((code) => {
|
||||
return new ResponseModel(
|
||||
this.parser,
|
||||
return new ResponseModel({
|
||||
parser: this.parser,
|
||||
code,
|
||||
hasSuccessResponses,
|
||||
this.operationSpec.responses[code],
|
||||
this.options,
|
||||
);
|
||||
defaultAsError: hasSuccessResponses,
|
||||
infoOrRef: this.operationSpec.responses[code],
|
||||
options: this.options,
|
||||
isEvent: this.isEvent,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -4,22 +4,27 @@ import { OpenAPIParser } from '../OpenAPIParser';
|
|||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import { MediaContentModel } from './MediaContent';
|
||||
|
||||
type RequestBodyProps = {
|
||||
parser: OpenAPIParser;
|
||||
infoOrRef: Referenced<OpenAPIRequestBody>;
|
||||
options: RedocNormalizedOptions;
|
||||
isEvent: boolean;
|
||||
}
|
||||
|
||||
export class RequestBodyModel {
|
||||
description: string;
|
||||
required: boolean;
|
||||
content?: MediaContentModel;
|
||||
|
||||
constructor(
|
||||
parser: OpenAPIParser,
|
||||
infoOrRef: Referenced<OpenAPIRequestBody>,
|
||||
options: RedocNormalizedOptions,
|
||||
) {
|
||||
constructor(props: RequestBodyProps) {
|
||||
const { parser, infoOrRef, options, isEvent } = props;
|
||||
const isRequest = isEvent ? false : true;
|
||||
const info = parser.deref(infoOrRef);
|
||||
this.description = info.description || '';
|
||||
this.required = !!info.required;
|
||||
parser.exitRef(infoOrRef);
|
||||
if (info.content !== undefined) {
|
||||
this.content = new MediaContentModel(parser, info.content, true, options);
|
||||
this.content = new MediaContentModel(parser, info.content, isRequest, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,15 @@ import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
|||
import { FieldModel } from './Field';
|
||||
import { MediaContentModel } from './MediaContent';
|
||||
|
||||
type ResponseProps = {
|
||||
parser: OpenAPIParser,
|
||||
code: string,
|
||||
defaultAsError: boolean,
|
||||
infoOrRef: Referenced<OpenAPIResponse>,
|
||||
options: RedocNormalizedOptions,
|
||||
isEvent: boolean,
|
||||
}
|
||||
|
||||
export class ResponseModel {
|
||||
@observable
|
||||
expanded: boolean = false;
|
||||
|
@ -19,13 +28,9 @@ export class ResponseModel {
|
|||
type: string;
|
||||
headers: FieldModel[] = [];
|
||||
|
||||
constructor(
|
||||
parser: OpenAPIParser,
|
||||
code: string,
|
||||
defaultAsError: boolean,
|
||||
infoOrRef: Referenced<OpenAPIResponse>,
|
||||
options: RedocNormalizedOptions,
|
||||
) {
|
||||
constructor(props: ResponseProps) {
|
||||
const { parser, code, defaultAsError, infoOrRef, options, isEvent } = props;
|
||||
const isRequest = isEvent ? true : false;
|
||||
makeObservable(this);
|
||||
|
||||
this.expanded = options.expandResponses === 'all' || options.expandResponses[code];
|
||||
|
@ -34,7 +39,7 @@ export class ResponseModel {
|
|||
parser.exitRef(infoOrRef);
|
||||
this.code = code;
|
||||
if (info.content !== undefined) {
|
||||
this.content = new MediaContentModel(parser, info.content, false, options);
|
||||
this.content = new MediaContentModel(parser, info.content, isRequest, options);
|
||||
}
|
||||
|
||||
if (info['x-summary'] !== undefined) {
|
||||
|
|
|
@ -25,7 +25,7 @@ import { l } from '../Labels';
|
|||
export class SchemaModel {
|
||||
pointer: string;
|
||||
|
||||
type: string;
|
||||
type: string | string[];
|
||||
displayType: string;
|
||||
typePrefix: string = '';
|
||||
title: string;
|
||||
|
@ -60,6 +60,11 @@ export class SchemaModel {
|
|||
rawSchema: OpenAPISchema;
|
||||
schema: MergedOpenAPISchema;
|
||||
extensions?: Record<string, any>;
|
||||
const: any;
|
||||
contentEncoding?: string;
|
||||
contentMediaType?: string;
|
||||
minItems?: number;
|
||||
maxItems?: number;
|
||||
|
||||
/**
|
||||
* @param isChild if schema discriminator Child
|
||||
|
@ -75,7 +80,7 @@ export class SchemaModel {
|
|||
makeObservable(this);
|
||||
|
||||
this.pointer = schemaOrRef.$ref || pointer || '';
|
||||
this.rawSchema = parser.deref(schemaOrRef);
|
||||
this.rawSchema = parser.deref(schemaOrRef, false, true);
|
||||
this.schema = parser.mergeAllOf(this.rawSchema, this.pointer, isChild);
|
||||
|
||||
this.init(parser, isChild);
|
||||
|
@ -97,6 +102,10 @@ export class SchemaModel {
|
|||
this.activeOneOf = idx;
|
||||
}
|
||||
|
||||
hasType(type: string) {
|
||||
return this.type === type || (Array.isArray(this.type) && this.type.includes(type));
|
||||
}
|
||||
|
||||
init(parser: OpenAPIParser, isChild: boolean) {
|
||||
const schema = this.schema;
|
||||
this.isCircular = schema['x-circular-ref'];
|
||||
|
@ -106,7 +115,6 @@ export class SchemaModel {
|
|||
this.description = schema.description || '';
|
||||
this.type = schema.type || detectType(schema);
|
||||
this.format = schema.format;
|
||||
this.nullable = !!schema.nullable;
|
||||
this.enum = schema.enum || [];
|
||||
this.example = schema.example;
|
||||
this.deprecated = !!schema.deprecated;
|
||||
|
@ -114,12 +122,28 @@ export class SchemaModel {
|
|||
this.externalDocs = schema.externalDocs;
|
||||
|
||||
this.constraints = humanizeConstraints(schema);
|
||||
this.displayType = this.type;
|
||||
this.displayFormat = this.format;
|
||||
this.isPrimitive = isPrimitiveType(schema, this.type);
|
||||
this.default = schema.default;
|
||||
this.readOnly = !!schema.readOnly;
|
||||
this.writeOnly = !!schema.writeOnly;
|
||||
this.const = schema.const || '';
|
||||
this.contentEncoding = schema.contentEncoding;
|
||||
this.contentMediaType = schema.contentMediaType;
|
||||
this.minItems = schema.minItems;
|
||||
this.maxItems = schema.maxItems;
|
||||
|
||||
if (!!schema.nullable || schema['x-nullable']) {
|
||||
if (Array.isArray(this.type) && !this.type.some((value) => value === null || value === 'null')) {
|
||||
this.type = [...this.type, 'null'];
|
||||
} else if (!Array.isArray(this.type) && (this.type !== null || this.type !== 'null')) {
|
||||
this.type = [this.type, 'null'];
|
||||
}
|
||||
}
|
||||
|
||||
this.displayType = Array.isArray(this.type)
|
||||
? this.type.map(item => item === null ? 'null' : item).join(' or ')
|
||||
: this.type;
|
||||
|
||||
if (this.isCircular) {
|
||||
return;
|
||||
|
@ -154,9 +178,9 @@ export class SchemaModel {
|
|||
return;
|
||||
}
|
||||
|
||||
if (this.type === 'object') {
|
||||
if (this.hasType('object')) {
|
||||
this.fields = buildFields(parser, schema, this.pointer, this.options);
|
||||
} else if (this.type === 'array' && schema.items) {
|
||||
} else if (this.hasType('array') && schema.items) {
|
||||
this.items = new SchemaModel(parser, schema.items, this.pointer + '/items', this.options);
|
||||
this.displayType = pluralizeType(this.items.displayType);
|
||||
this.displayFormat = this.items.format;
|
||||
|
@ -169,6 +193,11 @@ export class SchemaModel {
|
|||
if (this.items.isPrimitive) {
|
||||
this.enum = this.items.enum;
|
||||
}
|
||||
if (Array.isArray(this.type)) {
|
||||
const filteredType = this.type.filter(item => item !== 'array');
|
||||
if (filteredType.length)
|
||||
this.displayType += ` or ${filteredType.join(' or ')}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.enum.length && this.options.sortEnumValuesAlphabetically) {
|
||||
|
@ -178,7 +207,7 @@ export class SchemaModel {
|
|||
|
||||
private initOneOf(oneOf: OpenAPISchema[], parser: OpenAPIParser) {
|
||||
this.oneOf = oneOf!.map((variant, idx) => {
|
||||
const derefVariant = parser.deref(variant);
|
||||
const derefVariant = parser.deref(variant, false, true);
|
||||
|
||||
const merged = parser.mergeAllOf(derefVariant, this.pointer + '/oneOf/' + idx);
|
||||
|
||||
|
@ -186,7 +215,7 @@ export class SchemaModel {
|
|||
const title =
|
||||
isNamedDefinition(variant.$ref) && !merged.title
|
||||
? JsonPointer.baseName(variant.$ref)
|
||||
: merged.title;
|
||||
: `${(merged.title || '')}${(merged.const && JSON.stringify(merged.const)) || ''}`;
|
||||
|
||||
const schema = new SchemaModel(
|
||||
parser,
|
||||
|
@ -334,7 +363,7 @@ function buildFields(
|
|||
): FieldModel[] {
|
||||
const props = schema.properties || {};
|
||||
const additionalProps = schema.additionalProperties;
|
||||
const defaults = schema.default || {};
|
||||
const defaults = schema.default;
|
||||
let fields = Object.keys(props || []).map((fieldName) => {
|
||||
let field = props[fieldName];
|
||||
|
||||
|
@ -355,7 +384,7 @@ function buildFields(
|
|||
required,
|
||||
schema: {
|
||||
...field,
|
||||
default: field.default === undefined ? defaults[fieldName] : field.default,
|
||||
default: field.default === undefined && defaults ? defaults[fieldName] : field.default,
|
||||
},
|
||||
},
|
||||
$ref + '/properties/' + fieldName,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { OpenAPIPath, Referenced } from '../../types';
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import { OperationModel } from './Operation';
|
||||
import { isOperationName } from '../..';
|
||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import { isOperationName } from '../..';
|
||||
|
||||
export class WebhookModel {
|
||||
operations: OperationModel[] = [];
|
||||
|
@ -14,12 +14,21 @@ export class WebhookModel {
|
|||
) {
|
||||
const webhooks = parser.deref<OpenAPIPath>(infoOrRef || {});
|
||||
parser.exitRef(infoOrRef);
|
||||
this.initWebhooks(parser, webhooks, options);
|
||||
}
|
||||
|
||||
initWebhooks(parser: OpenAPIParser, webhooks: OpenAPIPath, options: RedocNormalizedOptions) {
|
||||
for (const webhookName of Object.keys(webhooks)) {
|
||||
const webhook = webhooks[webhookName];
|
||||
const operations = Object.keys(webhook).filter(isOperationName);
|
||||
for (const operationName of operations) {
|
||||
const operationInfo = webhook[operationName];
|
||||
if (webhook.$ref) {
|
||||
const resolvedWebhook = parser.deref<OpenAPIPath>(webhook || {});
|
||||
this.initWebhooks(parser, { [operationName]: resolvedWebhook }, options);
|
||||
}
|
||||
|
||||
if (!operationInfo) continue;
|
||||
const operation = new OperationModel(
|
||||
parser,
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as Enzyme from 'enzyme';
|
||||
import * as Adapter from 'enzyme-adapter-react-16';
|
||||
import * as Adapter from '@wojtekmaj/enzyme-adapter-react-17';
|
||||
|
||||
import 'raf/polyfill';
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
|
|
|
@ -32,7 +32,8 @@ function parseOptionsFromElement(element: Element) {
|
|||
const res = {};
|
||||
for (const attrName in attrMap) {
|
||||
const optionName = attrName.replace(/-(.)/g, (_, $1) => $1.toUpperCase());
|
||||
res[optionName] = attrMap[attrName];
|
||||
const optionValue = attrMap[attrName];
|
||||
res[optionName] = attrName === 'theme' ? JSON.parse(optionValue) : optionValue;
|
||||
// TODO: normalize options
|
||||
}
|
||||
return res;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import * as styledComponents from 'styled-components';
|
||||
|
||||
import { ResolvedThemeInterface } from './theme';
|
||||
import type { ResolvedThemeInterface } from './theme';
|
||||
|
||||
export { ResolvedThemeInterface };
|
||||
export type { ResolvedThemeInterface };
|
||||
|
||||
const {
|
||||
default: styled,
|
||||
|
@ -10,7 +10,7 @@ const {
|
|||
createGlobalStyle,
|
||||
keyframes,
|
||||
ThemeProvider,
|
||||
} = styledComponents as styledComponents.ThemedStyledComponentsModule<ResolvedThemeInterface>;
|
||||
} = styledComponents as unknown as styledComponents.ThemedStyledComponentsModule<ResolvedThemeInterface>;
|
||||
|
||||
export const media = {
|
||||
lessThan(breakpoint, print?: boolean, extra?: string) {
|
||||
|
|
|
@ -10,6 +10,7 @@ export interface OpenAPISpec {
|
|||
tags?: OpenAPITag[];
|
||||
externalDocs?: OpenAPIExternalDocumentation;
|
||||
'x-webhooks'?: OpenAPIPaths;
|
||||
webhooks?: OpenAPIPaths;
|
||||
}
|
||||
|
||||
export interface OpenAPIInfo {
|
||||
|
@ -17,6 +18,7 @@ export interface OpenAPIInfo {
|
|||
version: string;
|
||||
|
||||
description?: string;
|
||||
summary?: string;
|
||||
termsOfService?: string;
|
||||
contact?: OpenAPIContact;
|
||||
license?: OpenAPILicense;
|
||||
|
@ -56,6 +58,7 @@ export interface OpenAPIPath {
|
|||
trace?: OpenAPIOperation;
|
||||
servers?: OpenAPIServer[];
|
||||
parameters?: Array<Referenced<OpenAPIParameter>>;
|
||||
$ref?: string;
|
||||
}
|
||||
|
||||
export interface OpenAPIXCodeSample {
|
||||
|
@ -96,6 +99,7 @@ export interface OpenAPIParameter {
|
|||
examples?: { [media: string]: Referenced<OpenAPIExample> };
|
||||
content?: { [media: string]: OpenAPIMediaType };
|
||||
encoding?: Record<string, OpenAPIEncoding>;
|
||||
const?: any;
|
||||
}
|
||||
|
||||
export interface OpenAPIExample {
|
||||
|
@ -107,7 +111,7 @@ export interface OpenAPIExample {
|
|||
|
||||
export interface OpenAPISchema {
|
||||
$ref?: string;
|
||||
type?: string;
|
||||
type?: string | string[];
|
||||
properties?: { [name: string]: OpenAPISchema };
|
||||
additionalProperties?: boolean | OpenAPISchema;
|
||||
description?: string;
|
||||
|
@ -129,9 +133,9 @@ export interface OpenAPISchema {
|
|||
title?: string;
|
||||
multipleOf?: number;
|
||||
maximum?: number;
|
||||
exclusiveMaximum?: boolean;
|
||||
exclusiveMaximum?: boolean | number;
|
||||
minimum?: number;
|
||||
exclusiveMinimum?: boolean;
|
||||
exclusiveMinimum?: boolean | number;
|
||||
maxLength?: number;
|
||||
minLength?: number;
|
||||
pattern?: string;
|
||||
|
@ -142,6 +146,9 @@ export interface OpenAPISchema {
|
|||
minProperties?: number;
|
||||
enum?: any[];
|
||||
example?: any;
|
||||
const?: string;
|
||||
contentEncoding?: string;
|
||||
contentMediaType?: string;
|
||||
}
|
||||
|
||||
export interface OpenAPIDiscriminator {
|
||||
|
@ -271,4 +278,5 @@ export interface OpenAPIContact {
|
|||
export interface OpenAPILicense {
|
||||
name: string;
|
||||
url?: string;
|
||||
identifier?: string;
|
||||
}
|
4988
src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap
Normal file
4988
src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap
Normal file
File diff suppressed because it is too large
Load Diff
24
src/utils/__tests__/loadAndBundleSpec.test.ts
Normal file
24
src/utils/__tests__/loadAndBundleSpec.test.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import * as yaml from 'js-yaml';
|
||||
import { readFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { loadAndBundleSpec } from '../loadAndBundleSpec';
|
||||
|
||||
describe('#loadAndBundleSpec', () => {
|
||||
it('should load And Bundle Spec demo/openapi.yaml', async () => {
|
||||
const spec = yaml.load(readFileSync(resolve(__dirname, '../../../demo/openapi.yaml'), 'utf-8'));
|
||||
const bundledSpec = await loadAndBundleSpec(spec);
|
||||
expect(bundledSpec).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should load And Bundle Spec demo/openapi-3-1.yaml', async () => {
|
||||
const spec = yaml.load(readFileSync(resolve(__dirname, '../../../demo/openapi-3-1.yaml'), 'utf-8'));
|
||||
const bundledSpec = await loadAndBundleSpec(spec);
|
||||
expect(bundledSpec).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should load And Bundle Spec demo/swagger.yaml', async () => {
|
||||
const spec = yaml.load(readFileSync(resolve(__dirname, '../../../demo/swagger.yaml'), 'utf-8'));
|
||||
const bundledSpec = await loadAndBundleSpec(spec);
|
||||
expect(bundledSpec).toMatchSnapshot();
|
||||
});
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user