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

This commit is contained in:
Roberto Fernández 2021-11-18 13:57:38 +01:00
commit 042150c643
105 changed files with 38234 additions and 11137 deletions

22
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View 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.

View 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
View 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
View File

@ -0,0 +1,6 @@
group:
- files:
- source: docs/
dest: docs/redoc
repos: |
Redocly/docs

View File

@ -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
View 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
View 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
View 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
View 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
View 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

View File

@ -9,4 +9,4 @@ jobs:
- uses: actions/checkout@v1
- run: npm ci
- run: npm run bundle
- run: npm test
- run: npm test

View File

@ -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

View File

@ -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
View File

@ -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
[![Build Status](https://travis-ci.com/Redocly/redoc.svg?branch=master)](https://travis-ci.com/Redocly/redoc) [![Coverage Status](https://coveralls.io/repos/Redocly/redoc/badge.svg?branch=master&service=github)](https://coveralls.io/github/Redocly/redoc?branch=master) [![dependencies Status](https://david-dm.org/Redocly/redoc/status.svg)](https://david-dm.org/Redocly/redoc) [![devDependencies Status](https://david-dm.org/Redocly/redoc/dev-status.svg)](https://david-dm.org/Redocly/redoc#info=devDependencies) [![npm](http://img.shields.io/npm/v/redoc.svg)](https://www.npmjs.com/package/redoc) [![License](https://img.shields.io/npm/l/redoc.svg)](https://github.com/Redocly/redoc/blob/master/LICENSE)
[![bundle size](http://img.badgesize.io/https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js?compression=gzip&max=300000)](https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js) [![npm](https://img.shields.io/npm/dm/redoc.svg)](https://www.npmjs.com/package/redoc) [![](https://data.jsdelivr.com/v1/package/npm/redoc/badge)](https://www.jsdelivr.com/package/npm/redoc) [![Docker Build Status](https://img.shields.io/docker/build/redocly/redoc.svg)](https://hub.docker.com/r/redocly/redoc/)
</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 demo](https://raw.githubusercontent.com/Redocly/redoc/master/demo/redoc-demo.png)
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.
![Redoc demo](https://raw.githubusercontent.com/Redocly/redoc/master/demo/redoc-demo.png)
## 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>
![](docs/images/discriminator-demo.gif)
- OpenAPI 3.0 support
- Neat **interactive** documentation for nested objects <br>
![](docs/images/nested-demo.gif)
- Code samples support (via vendor extension) <br>
![](docs/images/code-samples-demo.gif)
- 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>
![](docs/images/nested-demo.gif)
## 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>
![](docs/images/discriminator-demo.gif)
- Code samples support (via vendor extension) <br>
![](docs/images/code-samples-demo.gif)
## 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)

View File

@ -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`.

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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"

View File

@ -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
View 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'));
}

View File

@ -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;
`;

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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
View 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);

View File

@ -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();

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

112
docs/quickstart/cli.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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

View File

@ -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');

View File

@ -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();

View File

@ -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');
})
});

View File

@ -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
View 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

File diff suppressed because it is too large Load Diff

View File

@ -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"
],

View File

@ -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} />);

View File

@ -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);

View File

@ -1,5 +1,3 @@
// import { transparentize } from 'polished';
import styled, { extensionsHook, media } from '../styled-components';
import { deprecatedCss } from './mixins';

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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>;

View File

@ -8,7 +8,7 @@ import { RedocRawOptions } from '../../services/RedocNormalizedOptions';
export interface EnumValuesProps {
values: string[];
type: string;
type: string | string[];
}
export interface EnumValuesState {

View File

@ -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
&gt;{' '}
</TypeFormat>
)}
{schema.contentEncoding && (
<TypeFormat>
{' '}&lt;
{schema.contentEncoding}
&gt;{' '}
</TypeFormat>
)}
{schema.contentMediaType && (
<TypeFormat>
{' '}&lt;
{schema.contentMediaType}
&gt;{' '}
</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>
);
}

View File

@ -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 {

View File

@ -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} />
</>
);
}}

View File

@ -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">

View File

@ -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>
);
}

View File

@ -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}>

View File

@ -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>

View File

@ -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} />;
})}

View File

@ -1,5 +1,3 @@
// import { transparentize } from 'polished';
import { UnderlinedHeader } from '../../common-elements';
import styled from '../../styled-components';
import { ResponseTitle } from './ResponseTitle';

View File

@ -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>

View File

@ -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

View File

@ -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>
);
}

View File

@ -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;
`}

View File

@ -1,4 +1,3 @@
// import { observe } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';

View File

@ -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';

View File

@ -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,
});
}

View 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');
});
});
});
});

View File

@ -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",

View File

@ -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';

View File

@ -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';

View File

@ -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);

View File

@ -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) {

View File

@ -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

View File

@ -211,6 +211,7 @@ export class MenuStore {
this.deactivate(this.activeItem);
if (!item) {
this.activeItemIdx = -1;
this.history.replace('', rewriteHistory);
return;
}

View File

@ -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],
});

View File

@ -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,
);
}
}

View File

@ -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);
}
}

View File

@ -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();
});
});
});

View File

@ -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",
}
`;

View 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"
}
}
}
}
}
}
}

View 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"
}
}
}
}

View File

@ -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');
});
});
});

View 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');
});
});
});

View 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 });
});
});
});

View File

@ -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');
});
});

View File

@ -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) {

View File

@ -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

View File

@ -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,

View File

@ -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,
});
});
}

View File

@ -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);
}
}
}

View File

@ -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) {

View File

@ -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,

View File

@ -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,
{

View File

@ -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() });

View File

@ -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;

View File

@ -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) {

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

View 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