Compare commits

...

38 Commits
v2.1.5 ... main

Author SHA1 Message Date
Alex Varchuk
00bc6edfc4
chore: v2.5.0 (#2684) 2025-04-14 18:17:50 +03:00
Alex Varchuk
45476135ad
chore: fix moderate vulnerabilities (#2683) 2025-04-14 16:45:31 +03:00
dependabot[bot]
05a04c85ed
chore(deps): bump @babel/runtime from 7.23.2 to 7.27.0 (#2679)
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.23.2 to 7.27.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.0/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-version: 7.27.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-14 15:59:27 +03:00
Imamuzzaki Abu Salam
2db293bfb2
fix: enhance accessibility for menu items with keyboard support (#2655) 2025-04-14 15:54:15 +03:00
Imamuzzaki Abu Salam
1b4126fde4
feat: add keyboard navigation support to JsonViewer component (#2654) 2025-04-14 15:48:23 +03:00
dependabot[bot]
1fa13270a1
chore(deps): bump esbuild and esbuild-loader (#2661)
Bumps [esbuild](https://github.com/evanw/esbuild) to 0.25.0 and updates ancestor dependency [esbuild-loader](https://github.com/privatenumber/esbuild-loader). These dependencies need to be updated together.


Updates `esbuild` from 0.17.19 to 0.25.0
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2023.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.17.19...v0.25.0)

Updates `esbuild-loader` from 3.0.1 to 4.3.0
- [Release notes](https://github.com/privatenumber/esbuild-loader/releases)
- [Commits](https://github.com/privatenumber/esbuild-loader/compare/v3.0.1...v4.3.0)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-type: indirect
- dependency-name: esbuild-loader
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: kanoru <kanoru3101@gmail.com>
2025-04-08 15:47:37 +03:00
dependabot[bot]
7cedd59826
chore(deps): bump dompurify from 3.1.3 to 3.2.4 (#2667)
* chore(deps): bump dompurify from 3.1.3 to 3.2.4

Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.1.3 to 3.2.4.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.1.3...3.2.4)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): update dompurify

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: kanoru <kanoru3101@gmail.com>
2025-04-08 15:06:41 +03:00
dependabot[bot]
cab07bad3b
chore(deps): bump prismjs from 1.29.0 to 1.30.0 (#2672)
Bumps [prismjs](https://github.com/PrismJS/prism) from 1.29.0 to 1.30.0.
- [Release notes](https://github.com/PrismJS/prism/releases)
- [Changelog](https://github.com/PrismJS/prism/blob/master/CHANGELOG.md)
- [Commits](https://github.com/PrismJS/prism/compare/v1.29.0...v1.30.0)

---
updated-dependencies:
- dependency-name: prismjs
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-11 10:59:04 +02:00
Ivan Kropyvnytskyi
1243095926
chore: v2.4.0 (#2658) 2025-02-07 14:30:00 +02:00
Alex Varchuk
6fa5a2a57a
fix: update version download/upload artifacts in cicd (#2656) 2025-02-07 13:27:21 +02:00
Alex Varchuk
3a748022be
feat: add supporting react 19 in package.json (#2652) 2025-02-05 18:13:55 +02:00
Alex Varchuk
53a6afc596
fix: unify redoc config (#2647)
---------

Co-authored-by: Ivan Kropyvnytskyi <130547411+ivankropyvnytskyi@users.noreply.github.com>
2025-01-30 14:23:07 +02:00
Alex Varchuk
ae1ae79901
docs: update options for future major release (#2646) 2025-01-30 10:26:09 +02:00
Lucas Akira Uehara
153ec7a0b7
fix: Prototype Pollution Vulnerability Affecting redoc <=2.2.0 (#2638)
https://github.com/Redocly/redoc/issues/2499

Co-authored-by: Lucas Akira Uehara <80917717@telefonicati.onmicrosoft.com>
2025-01-28 23:28:26 +02:00
Alex Varchuk
c765b34ef5
chore: v2.3.0 (#2649) 2025-01-16 13:33:22 +02:00
Alex Varchuk
0e2d595ef7
chore: fix vulnerabilities (#2648) 2025-01-16 13:01:29 +02:00
dependabot[bot]
8caddaf0eb
chore(deps-dev): bump micromatch from 4.0.4 to 4.0.8 (#2578)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.4 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.4...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-16 12:50:06 +02:00
wadakatu
981e4a84fb
chore: move @cfaester/enzyme-adapter-react-18 to devDependencies (#2625) 2025-01-16 12:41:22 +02:00
Alex Varchuk
59ee73fefa
fix: displaying json example when showObjectSchemaExamples enabled (#2635) 2025-01-08 18:49:11 +02:00
Alex Varchuk
d614d2d022
fix: passing inline parameters after support react 18 for response title (#2640) 2025-01-06 13:40:07 +02:00
Alex Varchuk
85b622fc58
fix: displaying nested items with type string (#2634) 2024-12-31 13:09:48 +02:00
Alex Varchuk
639fd2c32c
chore: npm audit for previnting feature vulnerabilities (#2612) 2024-10-28 13:28:36 +02:00
Michael Huynh
aa0879ca02
feat: update pattern styling (#2196) (#2600)
Closes #2196
2024-10-28 13:25:32 +02:00
Dxuian
c3fce4e2b2
chore: updated webpack version to 5.94.0 (#2583) 2024-10-28 13:09:34 +02:00
Taylor Krusen
11912f5d91
docs(all): change admonition type to 'info' (#2608) 2024-10-18 07:47:56 -07:00
Alex Varchuk
1593d01936
fix: docker build (#2607) 2024-10-16 13:29:44 +03:00
Alex Varchuk
4736c54edd
chore: v2.2.0 (#2606) 2024-10-16 12:22:32 +03:00
Alex Varchuk
64f18779e5
feat: add support x-badges (#2605)
* feat: add support x-badges

Co-authored-by: Max Mueller <maxmueller@eaton.com>

* chore: try to fix e2e tests

* chore: try to fix e2e tests part 2

* Update docs/redoc-vendor-extensions.md

---------

Co-authored-by: Max Mueller <maxmueller@eaton.com>
Co-authored-by: Jacek Łękawa <164185257+JLekawa@users.noreply.github.com>
2024-10-16 11:48:21 +03:00
dependabot[bot]
1cceed4b47
chore(deps): bump dompurify from 3.0.6 to 3.1.3 (#2602)
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.0.6 to 3.1.3.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.0.6...3.1.3)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-15 18:24:19 +03:00
dependabot[bot]
c0c68206de
chore(deps): bump body-parser and express (#2603)
Bumps [body-parser](https://github.com/expressjs/body-parser) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `body-parser` from 1.20.2 to 1.20.3
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/1.20.2...1.20.3)

Updates `express` from 4.19.2 to 4.21.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.1)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-15 17:59:12 +03:00
Lorna Jane Mitchell
d193dd2627
chore: Remove the sync job, we don't use it any more (#2591) 2024-09-19 16:10:09 +01:00
Lorna Jane Mitchell
c04b4c8fec
docs: Remove the old zero-dependency claim for Redoc docs (#2590) 2024-09-16 09:12:25 +01:00
Alex Varchuk
c0203be045
chore: upgrade node version for docker (#2589) 2024-09-13 11:17:02 +03:00
Lorna Jane Mitchell
c813eeac04
docs: fix http-server example from in-page feedback (#2580) 2024-09-02 11:48:55 +01:00
Alex Varchuk
60d131b0a9
fix: show siblings schema with oneOf (#2576) 2024-08-29 21:22:07 +03:00
Alex Varchuk
a7607eafdd
docs: update broken link of Openapi Object (#2577) 2024-08-29 19:18:05 +02:00
Roman Hotsiy
e0666776e8 chore: remove heavy bundlesize dev dependency 2024-08-01 15:56:39 +08:00
Roman Hotsiy
d0543bb116 chore: upgrade vulnerable dev dependencies 2024-08-01 14:37:12 +08:00
56 changed files with 2496 additions and 2079 deletions

View File

@ -21,7 +21,7 @@ jobs:
- run: npm ci - run: npm ci
- run: npm run bundle - run: npm run bundle
- name: Store bundle artifact - name: Store bundle artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: bundles name: bundles
path: bundles path: bundles
@ -39,7 +39,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- run: npm ci - run: npm ci
- name: Download bundled artifact - name: Download bundled artifact
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: bundles name: bundles
path: bundles path: bundles
@ -73,7 +73,7 @@ jobs:
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Download bundled artifacts - name: Download bundled artifacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: bundles name: bundles
path: bundles path: bundles
@ -107,7 +107,7 @@ jobs:
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1 aws-region: us-east-1
- name: Download all artifact - name: Download all artifact
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
- name: Publish to S3 - name: Publish to S3
run: npm run publish-cdn run: npm run publish-cdn

View File

@ -1,18 +0,0 @@
name: Sync Files
on:
push:
branches:
- main
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Run GitHub File Sync
uses: Redocly/repo-file-sync-action@main
with:
GH_PAT: ${{ secrets.GH_PAT }}
COMMIT_PREFIX: 'sync:'
SKIP_PR: true

View File

@ -1,3 +1,62 @@
# [2.5.0](https://github.com/Redocly/redoc/compare/v2.4.0...v2.5.0) (2025-04-14)
### Bug Fixes
* enhance accessibility for menu items with keyboard support ([#2655](https://github.com/Redocly/redoc/issues/2655)) ([2db293b](https://github.com/Redocly/redoc/commit/2db293bfb2973497dd33f31dc99e97f5bb90bbe8))
### Features
* add keyboard navigation support to JsonViewer component ([#2654](https://github.com/Redocly/redoc/issues/2654)) ([1b4126f](https://github.com/Redocly/redoc/commit/1b4126fde4531387f49c90f52efbd0c0e5f7b6ea))
# [2.4.0](https://github.com/Redocly/redoc/compare/v2.3.0...v2.4.0) (2025-02-07)
### Bug Fixes
* Prototype Pollution Vulnerability Affecting redoc <=2.2.0 ([#2638](https://github.com/Redocly/redoc/issues/2638)) ([153ec7a](https://github.com/Redocly/redoc/commit/153ec7a0b7245639f404c0b038b612ae7377c7db))
* unify redoc config ([#2647](https://github.com/Redocly/redoc/issues/2647)) ([53a6afc](https://github.com/Redocly/redoc/commit/53a6afc59624fe4591b0a0f1f20f41c0fbb5f1cf))
### Features
* add supporting react 19 in package.json ([#2652](https://github.com/Redocly/redoc/issues/2652)) ([3a74802](https://github.com/Redocly/redoc/commit/3a748022be3a7dc7f98669e1645dd5cda72f1abc))
# [2.3.0](https://github.com/Redocly/redoc/compare/v2.2.0...v2.3.0) (2025-01-16)
### Bug Fixes
* displaying json example when showObjectSchemaExamples enabled ([#2635](https://github.com/Redocly/redoc/issues/2635)) ([59ee73f](https://github.com/Redocly/redoc/commit/59ee73fefa8e8edb398940076bdd721fc284caa3))
* displaying nested items with type string ([#2634](https://github.com/Redocly/redoc/issues/2634)) ([85b622f](https://github.com/Redocly/redoc/commit/85b622fc581eb96303aeb85056aef36c74ea9f9d))
* passing inline parameters after support react 18 for response title ([#2640](https://github.com/Redocly/redoc/issues/2640)) ([d614d2d](https://github.com/Redocly/redoc/commit/d614d2d022df8bd1989cb0eaf76d087b52120d36))
### Features
* update pattern styling ([#2196](https://github.com/Redocly/redoc/issues/2196)) ([#2600](https://github.com/Redocly/redoc/issues/2600)) ([aa0879c](https://github.com/Redocly/redoc/commit/aa0879ca0235112918428fdff8f4c48d2c6c4adf))
# [2.2.0](https://github.com/Redocly/redoc/compare/v2.1.5...v2.2.0) (2024-10-16)
### Bug Fixes
* show siblings schema with oneOf ([#2576](https://github.com/Redocly/redoc/issues/2576)) ([60d131b](https://github.com/Redocly/redoc/commit/60d131b0a9dab4710e900323c9ba81160cecf7d8))
### Features
* add support x-badges ([#2605](https://github.com/Redocly/redoc/issues/2605)) ([64f1877](https://github.com/Redocly/redoc/commit/64f18779e5fe7e03f25862463cbc5062e85c867c))
## [2.1.5](https://github.com/Redocly/redoc/compare/v2.1.4...v2.1.5) (2024-06-10) ## [2.1.5](https://github.com/Redocly/redoc/compare/v2.1.4...v2.1.5) (2024-06-10)

View File

@ -116,15 +116,14 @@ Redoc uses the following [specification extensions](https://redocly.com/docs/api
* [`x-logo`](docs/redoc-vendor-extensions.md#x-logo) - is used to specify API logo * [`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 tags that refer to non-navigation properties like Pagination, Rate-Limits, etc * [`x-traitTag`](docs/redoc-vendor-extensions.md#x-traitTag) - useful for tags that refer to non-navigation properties like Pagination, Rate-Limits, etc
* [`x-codeSamples`](docs/redoc-vendor-extensions.md#x-codeSamples) - specify operation code samples * [`x-codeSamples`](docs/redoc-vendor-extensions.md#x-codeSamples) - specify operation code samples
* [`x-badges`](docs/redoc-vendor-extensions.md#x-badges) - specify operation badges
* [`x-examples`](docs/redoc-vendor-extensions.md#x-examples) - specify JSON example for requests * [`x-examples`](docs/redoc-vendor-extensions.md#x-examples) - specify JSON example for requests
* [`x-nullable`](docs/redoc-vendor-extensions.md#x-nullable) - mark schema param as a nullable * [`x-nullable`](docs/redoc-vendor-extensions.md#x-nullable) - mark schema param as a nullable
* [`x-displayName`](docs/redoc-vendor-extensions.md#x-displayname) - specify human-friendly names for the menu categories * [`x-displayName`](docs/redoc-vendor-extensions.md#x-displayname) - specify human-friendly names for the menu categories
* [`x-tagGroups`](docs/redoc-vendor-extensions.md#x-tagGroups) - group tags by categories in the side menu * [`x-tagGroups`](docs/redoc-vendor-extensions.md#x-tagGroups) - group tags by categories in the side menu
* [`x-servers`](docs/redoc-vendor-extensions.md#x-servers) - ability to specify different servers for API (backported from OpenAPI 3.0) * [`x-servers`](docs/redoc-vendor-extensions.md#x-servers) - ability to specify different servers for API (backported from OpenAPI 3.0)
* [`x-ignoredHeaderParameters`](docs/redoc-vendor-extensions.md#x-ignoredHeaderParameters) - ability to specify header parameter names to ignore
* [`x-additionalPropertiesName`](docs/redoc-vendor-extensions.md#x-additionalPropertiesName) - ability to supply a descriptive name for the additional property keys * [`x-additionalPropertiesName`](docs/redoc-vendor-extensions.md#x-additionalPropertiesName) - ability to supply a descriptive name for the additional property keys
* [`x-summary`](docs/redoc-vendor-extensions.md#x-summary) - for Response object, use as the response button text, with description rendered under the button * [`x-summary`](docs/redoc-vendor-extensions.md#x-summary) - for Response object, use as the response button text, with description rendered under the button
* [`x-extendedDiscriminator`](docs/redoc-vendor-extensions.md#x-extendedDiscriminator) - in Schemas, uses this to solve name-clash issues with the standard discriminator
* [`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 * [`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
## Releases ## Releases

View File

@ -5,7 +5,7 @@
# npm i -g http-server # npm i -g http-server
# http-server -p 8000 --cors # http-server -p 8000 --cors
FROM node:12-alpine FROM node:18-alpine
RUN apk update && apk add --no-cache git RUN apk update && apk add --no-cache git
@ -13,6 +13,7 @@ RUN apk update && apk add --no-cache git
WORKDIR /build WORKDIR /build
COPY package.json package-lock.json /build/ COPY package.json package-lock.json /build/
RUN npm ci --no-optional --ignore-scripts RUN npm ci --no-optional --ignore-scripts
RUN npm explore esbuild -- npm run postinstall
# copy only required for the build files # copy only required for the build files
COPY src /build/src COPY src /build/src

View File

@ -122,7 +122,7 @@ class DemoApp extends React.Component<
<RedocStandalone <RedocStandalone
spec={this.state.spec} spec={this.state.spec}
specUrl={proxiedUrl} specUrl={proxiedUrl}
options={{ scrollYOffset: 'nav', untrustedSpec: true }} options={{ scrollYOffset: 'nav', sanitize: true }}
/> />
</> </>
); );

View File

@ -22,6 +22,10 @@ paths:
operationId: getMuseumHours operationId: getMuseumHours
tags: tags:
- Operations - Operations
x-badges:
- name: 'Beta'
position: before
color: purple
parameters: parameters:
- $ref: '#/components/parameters/StartDate' - $ref: '#/components/parameters/StartDate'
- $ref: '#/components/parameters/PaginationPage' - $ref: '#/components/parameters/PaginationPage'
@ -64,6 +68,9 @@ paths:
summary: Create special event summary: Create special event
tags: tags:
- Events - Events
x-badges:
- name: 'Alpha'
color: purple
requestBody: requestBody:
required: true required: true
content: content:
@ -92,6 +99,8 @@ paths:
description: Return a list of upcoming special events at the museum. description: Return a list of upcoming special events at the museum.
security: [] security: []
operationId: listSpecialEvents operationId: listSpecialEvents
x-badges:
- name: 'Gamma'
tags: tags:
- Events - Events
parameters: parameters:
@ -300,6 +309,9 @@ components:
enum: enum:
- event - event
- general - general
x-enumDescriptions:
event: Special event ticket
general: General museum entry ticket
example: event example: event
Date: Date:
type: string type: string
@ -767,6 +779,9 @@ x-tagGroups:
- name: Purchases - name: Purchases
tags: tags:
- Tickets - Tickets
- name: Entities
tags:
- Schemas
security: security:
- MuseumPlaceholderAuth: [] - MuseumPlaceholderAuth: []

View File

@ -106,6 +106,10 @@ paths:
post: post:
tags: tags:
- pet - pet
x-badges:
- name: 'Beta'
position: before
color: purple
summary: Add a new pet to the store summary: Add a new pet to the store
description: Add new pet to the store inventory. description: Add new pet to the store inventory.
operationId: addPet operationId: addPet
@ -150,6 +154,9 @@ paths:
put: put:
tags: tags:
- pet - pet
x-badges:
- name: 'Alpha'
color: purple
summary: Update an existing pet summary: Update an existing pet
description: '' description: ''
operationId: updatePet operationId: updatePet
@ -183,6 +190,8 @@ paths:
get: get:
tags: tags:
- pet - pet
x-badges:
- name: 'Gamma'
summary: Find pet by ID summary: Find pet by ID
description: Returns a single pet description: Returns a single pet
operationId: getPetById operationId: getPetById
@ -1074,6 +1083,10 @@ components:
- available - available
- pending - pending
- sold - sold
x-enumDescriptions:
available: Available status
pending: Pending status
sold: Sold status
petType: petType:
description: Type of a pet description: Type of a pet
type: string type: string

View File

@ -11,7 +11,11 @@ const userUrl = window.location.search.match(/url=(.*)$/);
const specUrl = const specUrl =
(userUrl && userUrl[1]) || (swagger ? 'museum.yaml' : big ? 'big-openapi.json' : 'museum.yaml'); (userUrl && userUrl[1]) || (swagger ? 'museum.yaml' : big ? 'big-openapi.json' : 'museum.yaml');
const options: RedocRawOptions = { nativeScrollbars: false, maxDisplayedEnumValues: 3 }; const options: RedocRawOptions = {
nativeScrollbars: false,
maxDisplayedEnumValues: 3,
schemaDefinitionsTagName: 'schemas',
};
const container = document.getElementById('example'); const container = document.getElementById('example');
const root = createRoot(container!); const root = createRoot(container!);

View File

@ -26,59 +26,19 @@ Sets the minimum amount of characters that need to be typed into the search dial
_Default: 3_ _Default: 3_
### expandDefaultServerVariables ### hideDownloadButtons
Enables or disables expanding default server variables.
### expandResponses
Controls which responses to expand by default. Specify one or more responses by providing their response codes as a comma-separated list without spaces, for example `expandResponses='200,201'`. Special value 'all' expands all responses by default. Be careful: this option can slow down documentation rendering time.
### expandSingleSchemaField
Automatically expands the single field in a schema.
### hideDownloadButton
Hides the 'Download' button for saving the API definition source file. **This setting does not make the API definition private**; it just hides the button. Hides the 'Download' button for saving the API definition source file. **This setting does not make the API definition private**; it just hides the button.
### hideHostname
If set to `true`, the protocol and hostname are not shown in the operation definition.
### hideLoading ### hideLoading
Hides the loading animation. Does not apply to CLI or Workflows-rendered docs. Hides the loading animation. Does not apply to CLI or Workflows-rendered docs.
### hideRequestPayloadSample
Hides request payload examples.
### hideOneOfDescription
If set to `true`, the description for `oneOf`/`anyOf` object is not shown in the schema.
### hideSchemaPattern
If set to `true`, the pattern is not shown in the schema.
### hideSchemaTitles ### hideSchemaTitles
Hides the schema title next to to the type. Hides the schema title next to to the type.
### hideSecuritySection ### jsonSamplesExpandLevel
Hides the Security panel section.
### hideSingleRequestSampleTab
Hides the request sample tab for requests with only one sample.
### htmlTemplate
Sets the path to the optional HTML file used to modify the layout of the reference docs page.
### jsonSampleExpandLevel
Sets the default expand level for JSON payload samples (response and request body). The default value is 2, and the maximum supported value is '+Infinity'. It can also be configured as a string with the special value `all` that expands all levels. Sets the default expand level for JSON payload samples (response and request body). The default value is 2, and the maximum supported value is '+Infinity'. It can also be configured as a string with the special value `all` that expands all levels.
@ -88,35 +48,17 @@ _Default: 2_
Displays only the specified number of enum values. The remaining values are hidden in an expandable area. If not set, all values are displayed. Displays only the specified number of enum values. The remaining values are hidden in an expandable area. If not set, all values are displayed.
### menuToggle
If set to `true`, selecting an expanded item in the sidebar twice collapses it.
_Default: true_
### nativeScrollbars
If set to `true`, the sidebar uses the native scrollbar instead of perfect-scroll. This setting is a scrolling performance optimization for big API definitions.
### onlyRequiredInSamples ### onlyRequiredInSamples
Shows only required fields in request samples. Shows only required fields in request samples.
### pathInMiddlePanel ### sortRequiredPropsFirst
Shows the path link and HTTP verb in the middle panel instead of the right panel.
### payloadSampleIdx
If set, the payload sample is inserted at the specified index. If there are `N` payload samples and the value configured here is bigger than `N`, the payload sample is inserted last. Indexes start from 0.
### requiredPropsFirst
Shows required properties in schemas first, ordered in the same order as in the required array. Shows required properties in schemas first, ordered in the same order as in the required array.
### schemaExpansionLevel ### schemasExpansionLevel
Specifies whether to automatically expand schemas in Reference docs. Set it to `all` to expand all schemas regardless of their level, or set it to a number to expand schemas up to the specified level. For example, `schemaExpansionLevel: 3` expands schemas up to three levels deep. The default value is `0`, meaning no schemas are expanded automatically. Specifies whether to automatically expand schemas in Reference docs. Set it to `all` to expand all schemas regardless of their level, or set it to a number to expand schemas up to the specified level. For example, `schemasExpansionLevel: 3` expands schemas up to three levels deep. The default value is `0`, meaning no schemas are expanded automatically.
### scrollYOffset ### scrollYOffset
@ -132,6 +74,111 @@ Note that you can specify the `scrollYOffset` value in any of the following ways
Shows specification extensions ('x-' fields). Extensions used by Redoc are ignored. The value can be boolean or an array of strings with names of extensions to display. When used as boolean and set to `true`, all specification extensions are shown. Shows specification extensions ('x-' fields). Extensions used by Redoc are ignored. The value can be boolean or an array of strings with names of extensions to display. When used as boolean and set to `true`, all specification extensions are shown.
### sanitize
If set to `true`, the API definition is considered untrusted and all HTML/Markdown is sanitized to prevent XSS.
### downloadUrls
Set the URLs used to download the OpenAPI description or other documentation related files from the API documentation page.
### schemaDefinitionsTagName
If a value is set, all of the schemas are rendered with the designated tag name. The schemas then render and display in the sidebar navigation (with that associated tag name).
### generatedSamplesMaxDepth
The generatedSamplesMaxDepth option controls how many schema levels automatically generated for payload samples. The default is 8 which works well for most APIs, but you can adjust it if necessary for your use case.
### hidePropertiesPrefix
In complex data structures or object schemas where properties are nested within parent objects the hidePropertiesPrefix option enables the hiding of parent names for nested properties within the documentation.
_Default: true_
## Deprecated Functional settings
### hideDownloadButton
Hides the 'Download' button for saving the API definition source file. **This setting does not make the API definition private**; it just hides the button.
### downloadFileName
Sets the filename for the downloaded API definition source file.
### downloadDefinitionUrl
Sets the URL for the downloaded API definition source file.
### requiredPropsFirst
Shows required properties in schemas first, ordered in the same order as in the required array.
### jsonSampleExpandLevel
Sets the default expand level for JSON payload samples (response and request body). The default value is 2, and the maximum supported value is '+Infinity'. It can also be configured as a string with the special value `all` that expands all levels.
_Default: 2_
### schemaExpansionLevel
Specifies whether to automatically expand schemas in Reference docs. Set it to `all` to expand all schemas regardless of their level, or set it to a number to expand schemas up to the specified level. For example, `schemaExpansionLevel: 3` expands schemas up to three levels deep. The default value is `0`, meaning no schemas are expanded automatically.
### expandDefaultServerVariables
Enables or disables expanding default server variables.
### expandResponses
Controls which responses to expand by default. Specify one or more responses by providing their response codes as a comma-separated list without spaces, for example `expandResponses='200,201'`. Special value 'all' expands all responses by default. Be careful: this option can slow down documentation rendering time.
### expandSingleSchemaField
Automatically expands the single field in a schema.
### hideHostname
If set to `true`, the protocol and hostname are not shown in the operation definition.
### hideRequestPayloadSample
Hides request payload examples.
### hideOneOfDescription
If set to `true`, the description for `oneOf`/`anyOf` object is not shown in the schema.
### hideSchemaPattern
If set to `true`, the pattern is not shown in the schema.
### hideSecuritySection
Hides the Security panel section.
### hideSingleRequestSampleTab
Hides the request sample tab for requests with only one sample.
### menuToggle
If set to `true`, selecting an expanded item in the sidebar twice collapses it.
_Default: true_
### nativeScrollbars
If set to `true`, the sidebar uses the native scrollbar instead of perfect-scroll. This setting is a scrolling performance optimization for big API definitions.
### pathInMiddlePanel
Shows the path link and HTTP verb in the middle panel instead of the right panel.
### payloadSampleIdx
If set, the payload sample is inserted at the specified index. If there are `N` payload samples and the value configured here is bigger than `N`, the payload sample is inserted last. Indexes start from 0.
### showObjectSchemaExamples ### showObjectSchemaExamples
Shows object schema example in the properties; default `false`. Shows object schema example in the properties; default `false`.
@ -162,12 +209,12 @@ When set to true, sorts tags in the navigation sidebar and in the middle panel a
_Default: false_ _Default: false_
### untrustedDefinition ### untrustedSpec
If set to `true`, the API definition is considered untrusted and all HTML/Markdown is sanitized to prevent XSS. If set to `true`, the API definition is considered untrusted and all HTML/Markdown is sanitized to prevent XSS.
## Theme settings ## Theme settings
Change styles for the API documentation page. **Supported in Redoc CE 2.x**.
* `spacing` * `spacing`
* `unit`: 5 # main spacing unit used in autocomputed theme values later * `unit`: 5 # main spacing unit used in autocomputed theme values later
* `sectionHorizontal`: 40 # Horizontal section padding. COMPUTED: spacing.unit * 8 * `sectionHorizontal`: 40 # Horizontal section padding. COMPUTED: spacing.unit * 8
@ -248,7 +295,7 @@ For more information, refer to [Security definitions injection](./security-defin
### OpenAPI specification extensions ### OpenAPI specification extensions
Redoc uses the following [specification extensions](https://redocly.com/docs/api-reference-docs/spec-extensions/): Redoc uses the following [specification extensions](https://redocly.com/docs-legacy/api-reference-docs/spec-extensions/):
* [`x-logo`](./redoc-vendor-extensions.md#x-logo) - is used to specify API logo * [`x-logo`](./redoc-vendor-extensions.md#x-logo) - is used to specify API logo
* [`x-traitTag`](./redoc-vendor-extensions.md#x-traittag) - useful for handling out common things like Pagination, Rate-Limits, etc * [`x-traitTag`](./redoc-vendor-extensions.md#x-traittag) - useful for handling out common things like Pagination, Rate-Limits, etc
* [`x-codeSamples`](./redoc-vendor-extensions.md#x-codesamples) - specify operation code samples * [`x-codeSamples`](./redoc-vendor-extensions.md#x-codesamples) - specify operation code samples
@ -257,10 +304,8 @@ Redoc uses the following [specification extensions](https://redocly.com/docs/api
* [`x-displayName`](./redoc-vendor-extensions.md#x-displayname) - specify human-friendly names for the menu categories * [`x-displayName`](./redoc-vendor-extensions.md#x-displayname) - specify human-friendly names for the menu categories
* [`x-tagGroups`](./redoc-vendor-extensions.md#x-taggroups) - group tags by categories in the side menu * [`x-tagGroups`](./redoc-vendor-extensions.md#x-taggroups) - group tags by categories in the side menu
* [`x-servers`](./redoc-vendor-extensions.md#x-servers) - ability to specify different servers for API (backported from OpenAPI 3.0) * [`x-servers`](./redoc-vendor-extensions.md#x-servers) - ability to specify different servers for API (backported from OpenAPI 3.0)
* [`x-ignoredHeaderParameters`](./redoc-vendor-extensions.md#x-ignoredheaderparameters) - ability to specify header parameter names to ignore
* [`x-additionalPropertiesName`](./redoc-vendor-extensions.md#x-additionalpropertiesname) - ability to supply a descriptive name for the additional property keys * [`x-additionalPropertiesName`](./redoc-vendor-extensions.md#x-additionalpropertiesname) - ability to supply a descriptive name for the additional property keys
* [`x-summary`](./redoc-vendor-extensions.md#x-summary) - For Response object, use as the response button text, with description rendered under the button * [`x-summary`](./redoc-vendor-extensions.md#x-summary) - For Response object, use as the response button text, with description rendered under the button
* [`x-extendedDiscriminator`](./redoc-vendor-extensions.md#x-extendeddiscriminator) - In Schemas, uses this to solve name-clash issues with the standard discriminator
* [`x-explicitMappingOnly`](./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 * [`x-explicitMappingOnly`](./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

View File

@ -6,7 +6,7 @@ seo:
# How to use the Redocly CLI # How to use the Redocly CLI
With Redocly CLI, you can bundle your OpenAPI definition and API documentation With Redocly CLI, you can bundle your OpenAPI definition and API documentation
(made with Redoc) into a zero-dependency HTML file and render it locally. (made with Redoc) into an HTML file and render it locally.
## Step 1 - Install Redocly CLI ## Step 1 - Install Redocly CLI
@ -18,9 +18,9 @@ Or you can install it during [runtime](../../cli/installation.md#use-npx-at-runt
## Step 2 - Build the HTML file ## Step 2 - Build the HTML file
The Redocly CLI `build-docs` command builds Redoc into a zero-dependency HTML file. The Redocly CLI `build-docs` command builds Redoc into an HTML file.
To build a zero-dependency HTML file using Redocly CLI, enter the following command, To build an HTML file using Redocly CLI, enter the following command,
replacing `apis/openapi.yaml` with your API definition file's name and path: replacing `apis/openapi.yaml` with your API definition file's name and path:
```bash ```bash

View File

@ -104,7 +104,7 @@ npm install -g http-server
Then, `cd` into your project directory and run the following command: Then, `cd` into your project directory and run the following command:
```node ```node
http - server; http-server
``` ```
The output after entering the command provides the local URL where the preview can be accessed. The output after entering the command provides the local URL where the preview can be accessed.

View File

@ -67,7 +67,7 @@ theme:
openapi: openapi:
disableSearch: true disableSearch: true
expandResponses: 200,202 expandResponses: 200,202
jsonSampleExpandLevel: 1 jsonSamplesExpandLevel: 1
theme: theme:
sidebar: sidebar:

View File

@ -44,7 +44,7 @@ replace the `spec-url` attribute with the URL or local file address to your defi
</html> </html>
``` ```
{% admonition type="attention" name="Redoc requires an HTTP server to run locally" %} {% admonition type="info" name="Redoc requires an HTTP server to run locally" %}
Loading local OpenAPI definitions is impossible without running a web server because of issues with 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 [same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) and
other security reasons. Refer to [Running Redoc locally](./deployment/intro.md#how-to-run-redoc-locally) for more information. other security reasons. Refer to [Running Redoc locally](./deployment/intro.md#how-to-run-redoc-locally) for more information.

View File

@ -10,9 +10,6 @@ You can use the following [vendor extensions](https://redocly.com/docs/openapi-v
- [Tag Group Object](#tag-group-object) - [Tag Group Object](#tag-group-object)
- [Fixed fields](#fixed-fields) - [Fixed fields](#fixed-fields)
- [x-tagGroups example](#x-taggroups-example) - [x-tagGroups example](#x-taggroups-example)
- [x-ignoredHeaderParameters](#x-ignoredheaderparameters)
- [How to use with Redoc](#how-to-use-with-redoc-1)
- [x-ignoredHeaderParameters example](#x-ignoredheaderparameters-example)
- [Info Object](#info-object) - [Info Object](#info-object)
- [x-logo](#x-logo) - [x-logo](#x-logo)
- [How to use with Redoc](#how-to-use-with-redoc-2) - [How to use with Redoc](#how-to-use-with-redoc-2)
@ -39,21 +36,21 @@ You can use the following [vendor extensions](https://redocly.com/docs/openapi-v
- [Schema Object](#schema-object) - [Schema Object](#schema-object)
- [x-nullable](#x-nullable) - [x-nullable](#x-nullable)
- [How to use with Redoc](#how-to-use-with-redoc-7) - [How to use with Redoc](#how-to-use-with-redoc-7)
- [x-extendedDiscriminator](#x-extendeddiscriminator)
- [How to use with Redoc](#how-to-use-with-redoc-8)
- [x-extendedDiscriminator example](#x-extendeddiscriminator-example)
- [x-additionalPropertiesName](#x-additionalpropertiesname) - [x-additionalPropertiesName](#x-additionalpropertiesname)
- [How to use with Redoc](#how-to-use-with-redoc-9) - [How to use with Redoc](#how-to-use-with-redoc-9)
- [x-additionalPropertiesName example](#x-additionalpropertiesname-example) - [x-additionalPropertiesName example](#x-additionalpropertiesname-example)
- [x-explicitMappingOnly](#x-explicitmappingonly) - [x-explicitMappingOnly](#x-explicitmappingonly)
- [How to use with Redoc](#how-to-use-with-redoc-10) - [How to use with Redoc](#how-to-use-with-redoc-10)
- [x-explicitMappingOnly example](#x-explicitmappingonly-example) - [x-explicitMappingOnly example](#x-explicitmappingonly-example)
- [x-enumDescriptions](#x-enumdescriptions)
- [How to use with Redoc](#how-to-use-with-redoc-11)
- [x-enumDescriptions example](#x-enumdescriptions-example)
## Swagger Object ## Swagger Object
Extends the OpenAPI root [OpenAPI Object](https://redocly.com/docs/openapi-visual-reference/openapi-1/) Extends the OpenAPI root [OpenAPI Object](https://redocly.com/docs/openapi-visual-reference/openapi)
### x-servers ### x-servers
Backported from OpenAPI 3.0 [`servers`](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#serverObject). Currently doesn't support templates. Backported from OpenAPI 3.0 [`servers`](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#server-object). Currently doesn't support templates.
### x-tagGroups ### x-tagGroups
@ -105,29 +102,6 @@ x-tagGroups:
- Secondary Stats - Secondary Stats
``` ```
### x-ignoredHeaderParameters
| Field Name | Type | Description |
| :-------------------------- | :-----------: | :---------- |
| x-ignoredHeaderParameters | [ string ] | A list of ignored headers |
#### How to use with Redoc
Use `x-ignoredHeaderParameters` to specify header parameter names which are ignored by Redoc.
#### x-ignoredHeaderParameters example
```yaml
swagger: '2.0'
info:
...
tags: [...]
x-ignoredHeaderParameters:
- Accept
- User-Agent
- X-Test-Header
```
## Info Object ## Info Object
Extends the OpenAPI [Info Object](https://redocly.com/docs/openapi-visual-reference/info/) Extends the OpenAPI [Info Object](https://redocly.com/docs/openapi-visual-reference/info/)
@ -252,6 +226,11 @@ lang: JavaScript
source: console.log('Hello World'); source: console.log('Hello World');
``` ```
### x-badges
| Field Name | Type | Description |
| :------------- | :------: | :---------- |
| x-badges | [[Badge Object](https://redocly.com/docs/realm/author/reference/openapi-extensions/x-badges#badge-object)] | A list of badges associated with the operation |
## Parameter Object ## Parameter Object
Extends the OpenAPI [Parameter Object](https://redocly.com/docs/openapi-visual-reference/parameter/) Extends the OpenAPI [Parameter Object](https://redocly.com/docs/openapi-visual-reference/parameter/)
@ -285,59 +264,6 @@ Extends the OpenAPI [Schema Object](https://redocly.com/docs/openapi-visual-refe
#### How to use with Redoc #### How to use with Redoc
Schemas marked as `x-nullable` are marked in Redoc with the label Nullable. Schemas marked as `x-nullable` are marked in Redoc with the label Nullable.
### x-extendedDiscriminator
**ATTENTION**: This is a Redoc-specific vendor extension, and is not supported by other tools.
| Field Name | Type | Description |
| :------------- | :------: | :---------- |
| x-extendedDiscriminator | string | specifies extended discriminator |
#### How to use with Redoc
Redoc uses this vendor extension to solve name-clash issues with the standard `discriminator`.
Value of this field specifies the field which is used as an extended discriminator.
Redoc displays definition with selectpicker using which user can select value of the `x-extendedDiscriminator`-marked field.
Redoc displays the definition derived from the current (using `allOf`) and has `enum` with only one value which is the same as the selected value of the field specified as `x-extendedDiscriminator`.
#### x-extendedDiscriminator example
```yaml
Payment:
x-extendedDiscriminator: type
type: object
required:
- type
properties:
type:
type: string
name:
type: string
CashPayment:
allOf:
- $ref: "#/definitions/Payment"
- properties:
type:
type: string
enum:
- cash
currency:
type: string
PayPalPayment:
allOf:
- $ref: "#/definitions/Payment"
- properties:
type:
type: string
enum:
- paypal
userEmail:
type: string
```
In the example above, the names of definitions (`PayPalPayment`) are named differently than names in the payload (`paypal`) which is not supported by default `discriminator`.
### x-additionalPropertiesName ### x-additionalPropertiesName
**Attention**: This is a Redoc-specific vendor extension, and is not supported by other tools. **Attention**: This is a Redoc-specific vendor extension, and is not supported by other tools.
@ -397,3 +323,31 @@ Pet:
``` ```
Shows in the selectpicker only the items `cat` and `bee`, even though the `Dog` class inherits from the `Pet` class. Shows in the selectpicker only the items `cat` and `bee`, even though the `Dog` class inherits from the `Pet` class.
### x-enumDescriptions
| Field Name | Type | Description |
| :------------- | :------: | :---------- |
| x-enumDescriptions | [[Enum Description Object](https://redocly.com/docs/realm/author/reference/openapi-extensions/x-enum-descriptions#enum-description-object)] | A list of the enum values and descriptions to include in the documentation. |
#### How to use with Redoc
The enum (short for "enumeration") fields in OpenAPI allow you to restrict the value of a field to a list of allowed values. These values need to be short and machine-readable, but that can make them harder for humans to parse and work with.
Add x-enumDescriptions to your OpenAPI description to show a helpful table of enum options and an explanation of what each one means. This field supports Markdown.
#### x-enumDescriptions example
The following example shows a schema with two short-named options, and the x-enumDescriptions entry to list all enum entries and give additional context for each:
```yaml
components:
schemas:
TicketType:
description: Type of ticket being purchased. Use `general` for regular museum entry and `event` for tickets to special events.
type: string
enum:
- event
- general
x-enumDescriptions:
event: Event Tickets _(timed entry)_
general: General Admission
example: event
```

View File

@ -52,6 +52,31 @@ describe('Menu', () => {
cy.location('hash').should('equal', '#schema/Cat'); cy.location('hash').should('equal', '#schema/Cat');
}); });
it('should contains badge schema from x-badges', () => {
cy.contains('h2', 'Add a new pet to the store').scrollIntoView();
cy.contains('h2 > span', 'Beta')
.scrollIntoView()
.wait(100)
.get('[role=menuitem] > label.active')
.children('span[type="badge"]')
.should('have.text', 'Beta');
cy.contains('h2 > span', 'Alpha')
.scrollIntoView()
.wait(100)
.get('[role=menuitem] > label.active')
.children('span[type="badge"]')
.should('have.text', 'Alpha');
cy.contains('h2 > span', 'Gamma')
.scrollIntoView()
.wait(100)
.get('[role=menuitem] > label.active')
.children('span[type="badge"]')
.should('have.text', 'Gamma');
});
it('should contains Cat schema in Pet using x-tags', () => { it('should contains Cat schema in Pet using x-tags', () => {
cy.contains('[role=menuitem] > label.-depth1', 'pet').click({ force: true }); cy.contains('[role=menuitem] > label.-depth1', 'pet').click({ force: true });
cy.location('hash').should('equal', '#tag/pet'); cy.location('hash').should('equal', '#tag/pet');

3162
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "redoc", "name": "redoc",
"version": "2.1.5", "version": "2.5.0",
"description": "ReDoc", "description": "ReDoc",
"repository": { "repository": {
"type": "git", "type": "git",
@ -61,8 +61,9 @@
"pre-commit": "pretty-quick --staged && npm run lint" "pre-commit": "pretty-quick --staged && npm run lint"
}, },
"devDependencies": { "devDependencies": {
"@cfaester/enzyme-adapter-react-18": "^0.8.0",
"@cypress/webpack-preprocessor": "^5.17.1", "@cypress/webpack-preprocessor": "^5.17.1",
"@size-limit/preset-app": "^8.2.6", "@size-limit/file": "^11.1.4",
"@types/chai": "^4.2.18", "@types/chai": "^4.2.18",
"@types/dompurify": "^2.2.2", "@types/dompurify": "^2.2.2",
"@types/enzyme": "^3.10.5", "@types/enzyme": "^3.10.5",
@ -92,7 +93,7 @@
"cypress": "^13.8.1", "cypress": "^13.8.1",
"enzyme": "^3.11.0", "enzyme": "^3.11.0",
"enzyme-to-json": "^3.6.2", "enzyme-to-json": "^3.6.2",
"esbuild-loader": "^3.0.1", "esbuild-loader": "^4.3.0",
"eslint": "^7.27.0", "eslint": "^7.27.0",
"eslint-plugin-import": "^2.23.4", "eslint-plugin-import": "^2.23.4",
"eslint-plugin-react": "^7.34.2", "eslint-plugin-react": "^7.34.2",
@ -114,7 +115,7 @@
"react-dom": "^18.0.0", "react-dom": "^18.0.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"shelljs": "^0.8.4", "shelljs": "^0.8.4",
"size-limit": "^8.2.6", "size-limit": "^11.1.4",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"styled-components": "^5.3.0", "styled-components": "^5.3.0",
"ts-jest": "^29.1.1", "ts-jest": "^29.1.1",
@ -123,7 +124,7 @@
"typescript": "^4.9.0", "typescript": "^4.9.0",
"unfetch": "^4.2.0", "unfetch": "^4.2.0",
"url": "^0.11.1", "url": "^0.11.1",
"webpack": "^5.88.2", "webpack": "^5.94.0",
"webpack-cli": "^5.1.4", "webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1", "webpack-dev-server": "^4.15.1",
"webpack-node-externals": "^3.0.0", "webpack-node-externals": "^3.0.0",
@ -132,16 +133,15 @@
"peerDependencies": { "peerDependencies": {
"core-js": "^3.1.4", "core-js": "^3.1.4",
"mobx": "^6.0.4", "mobx": "^6.0.4",
"react": "^16.8.4 || ^17.0.0 || ^18.0.0", "react": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0", "react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"styled-components": "^4.1.1 || ^5.1.1 || ^6.0.5" "styled-components": "^4.1.1 || ^5.1.1 || ^6.0.5"
}, },
"dependencies": { "dependencies": {
"@cfaester/enzyme-adapter-react-18": "^0.8.0",
"@redocly/openapi-core": "^1.4.0", "@redocly/openapi-core": "^1.4.0",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"decko": "^1.2.0", "decko": "^1.2.0",
"dompurify": "^3.0.6", "dompurify": "^3.2.4",
"eventemitter3": "^5.0.1", "eventemitter3": "^5.0.1",
"json-pointer": "^0.6.2", "json-pointer": "^0.6.2",
"lunr": "^2.3.9", "lunr": "^2.3.9",

View File

@ -97,9 +97,11 @@ export const RecursiveLabel = styled(FieldLabel)`
export const PatternLabel = styled(FieldLabel)` export const PatternLabel = styled(FieldLabel)`
color: #0e7c86; color: #0e7c86;
font-family: ${props => props.theme.typography.code.fontFamily};
font-size: 12px;
&::before, &::before,
&::after { &::after {
font-weight: bold; content: ' ';
} }
`; `;

View File

@ -47,11 +47,11 @@ export const ShelfIcon = styled(IntShelfIcon)`
} }
`; `;
export const Badge = styled.span<{ type: string }>` export const Badge = styled.span<{ type: string; color?: string }>`
display: inline-block; display: inline-block;
padding: 2px 8px; padding: 2px 8px;
margin: 0; margin: 0;
background-color: ${props => props.theme.colors[props.type].main}; background-color: ${props => props.color || props.theme.colors[props.type].main};
color: ${props => props.theme.colors[props.type].contrastText}; color: ${props => props.theme.colors[props.type].contrastText};
font-size: ${props => props.theme.typography.code.fontSize}; font-size: ${props => props.theme.typography.code.fontSize};
vertical-align: middle; vertical-align: middle;

View File

@ -22,20 +22,13 @@ export interface ApiInfoProps {
@observer @observer
export class ApiInfo extends React.Component<ApiInfoProps> { export class ApiInfo extends React.Component<ApiInfoProps> {
handleDownloadClick = e => {
if (!e.target.href) {
e.target.href = this.props.store.spec.info.downloadLink;
}
};
render() { render() {
const { store } = this.props; const { store } = this.props;
const { info, externalDocs } = store.spec; const { info, externalDocs } = store.spec;
const hideDownloadButton = store.options.hideDownloadButton; const hideDownloadButtons = store.options.hideDownloadButtons;
const downloadFilename = info.downloadFileName;
const downloadLink = info.downloadLink;
const downloadUrls = info.downloadUrls;
const downloadFileName = info.downloadFileName;
const license = const license =
(info.license && ( (info.license && (
<InfoSpan> <InfoSpan>
@ -83,17 +76,22 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
<ApiHeader> <ApiHeader>
{info.title} {version} {info.title} {version}
</ApiHeader> </ApiHeader>
{!hideDownloadButton && ( {!hideDownloadButtons && (
<p> <p>
{l('downloadSpecification')}: {l('downloadSpecification')}:
{downloadUrls?.map(({ title, url }) => {
return (
<DownloadButton <DownloadButton
download={downloadFilename || true} download={downloadFileName || true}
target="_blank" target="_blank"
href={downloadLink} href={url}
onClick={this.handleDownloadClick} rel="noreferrer"
key={url}
> >
{l('download')} {title}
</DownloadButton> </DownloadButton>
);
})}
</p> </p>
)} )}
<StyledMarkdownBlock> <StyledMarkdownBlock>

View File

@ -18,14 +18,6 @@ export function ArrayItemDetails({ schema }: { schema: SchemaModel }) {
return null; return null;
} }
if (schema.type === 'string' && schema.pattern) {
return (
<Wrapper>
[<Pattern schema={schema} />]
</Wrapper>
);
}
return ( return (
<Wrapper> <Wrapper>
[ items [ items

View File

@ -5,17 +5,29 @@ import { l } from '../../services/Labels';
import { OptionsContext } from '../OptionsProvider'; import { OptionsContext } from '../OptionsProvider';
import styled from '../../styled-components'; import styled from '../../styled-components';
import { RedocRawOptions } from '../../services/RedocNormalizedOptions'; import { RedocRawOptions } from '../../services/RedocNormalizedOptions';
import { StyledMarkdownBlock } from '../Markdown/styled.elements';
import { Markdown } from '../Markdown/Markdown';
export interface EnumValuesProps { export interface EnumValuesProps {
values: string[]; values?: string[] | { [name: string]: string };
isArrayType: boolean; type: string | string[];
} }
export interface EnumValuesState { export interface EnumValuesState {
collapsed: boolean; collapsed: boolean;
} }
const DescriptionEnumsBlock = styled(StyledMarkdownBlock)`
table {
margin-bottom: 0.2em;
}
`;
export class EnumValues extends React.PureComponent<EnumValuesProps, EnumValuesState> { export class EnumValues extends React.PureComponent<EnumValuesProps, EnumValuesState> {
constructor(props: EnumValuesProps) {
super(props);
this.toggle = this.toggle.bind(this);
}
state: EnumValuesState = { state: EnumValuesState = {
collapsed: true, collapsed: true,
}; };
@ -27,35 +39,79 @@ export class EnumValues extends React.PureComponent<EnumValuesProps, EnumValuesS
} }
render() { render() {
const { values, isArrayType } = this.props; const { values, type } = this.props;
const { collapsed } = this.state; const { collapsed } = this.state;
const isDescriptionEnum = !Array.isArray(values);
const enums =
(Array.isArray(values) && values) ||
Object.entries(values || {}).map(([value, description]) => ({
value,
description,
}));
// TODO: provide context interface in more elegant way // TODO: provide context interface in more elegant way
const { enumSkipQuotes, maxDisplayedEnumValues } = this.context as RedocRawOptions; const { enumSkipQuotes, maxDisplayedEnumValues } = this.context as RedocRawOptions;
if (!values.length) { if (!enums.length) {
return null; return null;
} }
const displayedItems = const displayedItems =
this.state.collapsed && maxDisplayedEnumValues this.state.collapsed && maxDisplayedEnumValues
? values.slice(0, maxDisplayedEnumValues) ? enums.slice(0, maxDisplayedEnumValues)
: values; : enums;
const showToggleButton = maxDisplayedEnumValues const showToggleButton = maxDisplayedEnumValues ? enums.length > maxDisplayedEnumValues : false;
? values.length > maxDisplayedEnumValues
: false;
const toggleButtonText = maxDisplayedEnumValues const toggleButtonText = maxDisplayedEnumValues
? collapsed ? collapsed
? `${values.length - maxDisplayedEnumValues} more` ? `${enums.length - maxDisplayedEnumValues} more`
: 'Hide' : 'Hide'
: ''; : '';
return ( return (
<div> <div>
{isDescriptionEnum ? (
<>
<DescriptionEnumsBlock>
<table>
<thead>
<tr>
<th>
<FieldLabel> <FieldLabel>
{isArrayType ? l('enumArray') : ''}{' '} {type === 'array' ? l('enumArray') : ''}{' '}
{enums.length === 1 ? l('enumSingleValue') : l('enum')}
</FieldLabel>{' '}
</th>
<th>
<strong>Description</strong>
</th>
</tr>
</thead>
<tbody>
{(displayedItems as { value: string; description: string }[]).map(
({ description, value }) => {
return (
<tr key={value}>
<td>{value}</td>
<td>
<Markdown source={description} compact inline />
</td>
</tr>
);
},
)}
</tbody>
</table>
</DescriptionEnumsBlock>
{showToggleButton ? (
<ToggleButton onClick={this.toggle}>{toggleButtonText}</ToggleButton>
) : null}
</>
) : (
<>
<FieldLabel>
{type === 'array' ? l('enumArray') : ''}{' '}
{values.length === 1 ? l('enumSingleValue') : l('enum')}: {values.length === 1 ? l('enumSingleValue') : l('enum')}:
</FieldLabel>{' '} </FieldLabel>{' '}
{displayedItems.map((value, idx) => { {displayedItems.map((value, idx) => {
@ -67,14 +123,10 @@ export class EnumValues extends React.PureComponent<EnumValuesProps, EnumValuesS
); );
})} })}
{showToggleButton ? ( {showToggleButton ? (
<ToggleButton <ToggleButton onClick={this.toggle}>{toggleButtonText}</ToggleButton>
onClick={() => {
this.toggle();
}}
>
{toggleButtonText}
</ToggleButton>
) : null} ) : null}
</>
)}
</div> </div>
); );
} }

View File

@ -19,6 +19,8 @@ import { Schema } from '../Schema/Schema';
import type { SchemaOptions } from '../Schema/Schema'; import type { SchemaOptions } from '../Schema/Schema';
import type { FieldModel } from '../../services/models'; import type { FieldModel } from '../../services/models';
import { OptionsContext } from '../OptionsProvider';
import { RedocNormalizedOptions } from '../../services/RedocNormalizedOptions';
export interface FieldProps extends SchemaOptions { export interface FieldProps extends SchemaOptions {
className?: string; className?: string;
@ -27,12 +29,15 @@ export interface FieldProps extends SchemaOptions {
field: FieldModel; field: FieldModel;
expandByDefault?: boolean; expandByDefault?: boolean;
fieldParentsName?: string[];
renderDiscriminatorSwitch?: (opts: FieldProps) => JSX.Element; renderDiscriminatorSwitch?: (opts: FieldProps) => JSX.Element;
} }
@observer @observer
export class Field extends React.Component<FieldProps> { export class Field extends React.Component<FieldProps> {
static contextType = OptionsContext;
context: RedocNormalizedOptions;
toggle = () => { toggle = () => {
if (this.props.field.expanded === undefined && this.props.expandByDefault) { if (this.props.field.expanded === undefined && this.props.expandByDefault) {
this.props.field.collapse(); this.props.field.collapse();
@ -49,12 +54,12 @@ export class Field extends React.Component<FieldProps> {
}; };
render() { render() {
const { className = '', field, isLast, expandByDefault } = this.props; const { hidePropertiesPrefix } = this.context;
const { className = '', field, isLast, expandByDefault, fieldParentsName = [] } = this.props;
const { name, deprecated, required, kind } = field; const { name, deprecated, required, kind } = field;
const withSubSchema = !field.schema.isPrimitive && !field.schema.isCircular; const withSubSchema = !field.schema.isPrimitive && !field.schema.isCircular;
const expanded = field.expanded === undefined ? expandByDefault : field.expanded; const expanded = field.expanded === undefined ? expandByDefault : field.expanded;
const labels = ( const labels = (
<> <>
{kind === 'additionalProperties' && <PropertyLabel>additional property</PropertyLabel>} {kind === 'additionalProperties' && <PropertyLabel>additional property</PropertyLabel>}
@ -75,6 +80,10 @@ export class Field extends React.Component<FieldProps> {
onKeyPress={this.handleKeyPress} onKeyPress={this.handleKeyPress}
aria-label={`expand ${name}`} aria-label={`expand ${name}`}
> >
{!hidePropertiesPrefix &&
fieldParentsName.map(
name => name + '.\u200B', // zero-width space, a special character is used for correct line breaking
)}
<span className="property-name">{name}</span> <span className="property-name">{name}</span>
<ShelfIcon direction={expanded ? 'down' : 'right'} /> <ShelfIcon direction={expanded ? 'down' : 'right'} />
</button> </button>
@ -83,6 +92,10 @@ export class Field extends React.Component<FieldProps> {
) : ( ) : (
<PropertyNameCell className={deprecated ? 'deprecated' : undefined} kind={kind} title={name}> <PropertyNameCell className={deprecated ? 'deprecated' : undefined} kind={kind} title={name}>
<PropertyBullet /> <PropertyBullet />
{!hidePropertiesPrefix &&
fieldParentsName.map(
name => name + '.\u200B', // zero-width space, a special character is used for correct line breaking
)}
<span className="property-name">{name}</span> <span className="property-name">{name}</span>
{labels} {labels}
</PropertyNameCell> </PropertyNameCell>
@ -102,6 +115,7 @@ export class Field extends React.Component<FieldProps> {
<InnerPropertiesWrap> <InnerPropertiesWrap>
<Schema <Schema
schema={field.schema} schema={field.schema}
fieldParentsName={[...(fieldParentsName || []), field.name]}
skipReadOnly={this.props.skipReadOnly} skipReadOnly={this.props.skipReadOnly}
skipWriteOnly={this.props.skipWriteOnly} skipWriteOnly={this.props.skipWriteOnly}
showTitle={this.props.showTitle} showTitle={this.props.showTitle}

View File

@ -8,7 +8,7 @@ import {
TypePrefix, TypePrefix,
TypeTitle, TypeTitle,
} from '../../common-elements/fields'; } from '../../common-elements/fields';
import { getSerializedValue, isObject } from '../../utils'; import { getSerializedValue, isArray, isObject } from '../../utils';
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation'; import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
import { Markdown } from '../Markdown/Markdown'; import { Markdown } from '../Markdown/Markdown';
import { EnumValues } from './EnumValues'; import { EnumValues } from './EnumValues';
@ -30,7 +30,8 @@ export const FieldDetailsComponent = observer((props: FieldProps) => {
const { showExamples, field, renderDiscriminatorSwitch } = props; const { showExamples, field, renderDiscriminatorSwitch } = props;
const { schema, description, deprecated, extensions, in: _in, const: _const } = field; const { schema, description, deprecated, extensions, in: _in, const: _const } = field;
const isArrayType = schema.type === 'array'; const isArrayType =
schema.type === 'array' || (isArray(schema.type) && schema.type.includes('array'));
const rawDefault = enumSkipQuotes || _in === 'header'; // having quotes around header field default values is confusing and inappropriate const rawDefault = enumSkipQuotes || _in === 'header'; // having quotes around header field default values is confusing and inappropriate
@ -98,7 +99,7 @@ export const FieldDetailsComponent = observer((props: FieldProps) => {
)} )}
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={defaultValue} /> <FieldDetail raw={rawDefault} label={l('default') + ':'} value={defaultValue} />
{!renderDiscriminatorSwitch && ( {!renderDiscriminatorSwitch && (
<EnumValues isArrayType={isArrayType} values={schema.enum} /> <EnumValues type={schema.type} values={schema['x-enumDescriptions'] || schema.enum} />
)}{' '} )}{' '}
{renderedExamples} {renderedExamples}
<Extensions extensions={{ ...extensions, ...schema.extensions }} /> <Extensions extensions={{ ...extensions, ...schema.extensions }} />

View File

@ -41,11 +41,12 @@ const Json = (props: JsonProps) => {
<OptionsContext.Consumer> <OptionsContext.Consumer>
{options => ( {options => (
<PrismDiv <PrismDiv
tabIndex={0}
className={props.className} className={props.className}
// tslint:disable-next-line // tslint:disable-next-line
ref={node => setNode(node!)} ref={node => setNode(node!)}
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: jsonToHTML(props.data, options.jsonSampleExpandLevel), __html: jsonToHTML(props.data, options.jsonSamplesExpandLevel),
}} }}
/> />
)} )}

View File

@ -6,11 +6,14 @@ import { StylingMarkdownProps } from './Markdown';
import { StyledMarkdownBlock } from './styled.elements'; import { StyledMarkdownBlock } from './styled.elements';
import styled from 'styled-components'; import styled from 'styled-components';
const StyledMarkdownSpan = styled(props => <StyledMarkdownBlock {...props} />)` // Workaround for DOMPurify type issues (https://github.com/cure53/DOMPurify/issues/1034)
const dompurify = DOMPurify['default'] as DOMPurify.DOMPurify;
const StyledMarkdownSpan = styled(StyledMarkdownBlock)`
display: inline; display: inline;
`; `;
const sanitize = (untrustedSpec, html) => (untrustedSpec ? DOMPurify.sanitize(html) : html); const sanitize = (sanitize, html) => (sanitize ? dompurify.sanitize(html) : html);
export function SanitizedMarkdownHTML({ export function SanitizedMarkdownHTML({
inline, inline,
@ -25,7 +28,7 @@ export function SanitizedMarkdownHTML({
<Wrap <Wrap
className={'redoc-markdown ' + (rest.className || '')} className={'redoc-markdown ' + (rest.className || '')}
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: sanitize(options.untrustedSpec, rest.html), __html: sanitize(options.sanitize, rest.html),
}} }}
data-role={rest['data-role']} data-role={rest['data-role']}
{...rest} {...rest}

View File

@ -28,9 +28,20 @@ export interface OperationProps {
} }
export const Operation = observer(({ operation }: OperationProps): JSX.Element => { export const Operation = observer(({ operation }: OperationProps): JSX.Element => {
const { name: summary, description, deprecated, externalDocs, isWebhook, httpVerb } = operation; const {
name: summary,
description,
deprecated,
externalDocs,
isWebhook,
httpVerb,
badges,
} = operation;
const hasDescription = !!(description || externalDocs); const hasDescription = !!(description || externalDocs);
const { showWebhookVerb } = React.useContext(OptionsContext); const { showWebhookVerb } = React.useContext(OptionsContext);
const badgesBefore = badges.filter(({ position }) => position === 'before');
const badgesAfter = badges.filter(({ position }) => position === 'after');
return ( return (
<OptionsContext.Consumer> <OptionsContext.Consumer>
{options => ( {options => (
@ -38,6 +49,11 @@ export const Operation = observer(({ operation }: OperationProps): JSX.Element =
<MiddlePanel> <MiddlePanel>
<H2> <H2>
<ShareLink to={operation.id} /> <ShareLink to={operation.id} />
{badgesBefore.map(({ name, color }) => (
<Badge type="primary" key={name} color={color}>
{name}
</Badge>
))}
{summary} {deprecated && <Badge type="warning"> Deprecated </Badge>} {summary} {deprecated && <Badge type="warning"> Deprecated </Badge>}
{isWebhook && ( {isWebhook && (
<Badge type="primary"> <Badge type="primary">
@ -45,6 +61,11 @@ export const Operation = observer(({ operation }: OperationProps): JSX.Element =
Webhook {showWebhookVerb && httpVerb && '| ' + httpVerb.toUpperCase()} Webhook {showWebhookVerb && httpVerb && '| ' + httpVerb.toUpperCase()}
</Badge> </Badge>
)} )}
{badgesAfter.map(({ name, color }) => (
<Badge type="primary" key={name} color={color}>
{name}
</Badge>
))}
</H2> </H2>
{options.pathInMiddlePanel && !isWebhook && ( {options.pathInMiddlePanel && !isWebhook && (
<Endpoint operation={operation} inverted={true} /> <Endpoint operation={operation} inverted={true} />

View File

@ -16,14 +16,24 @@ export class ArraySchema extends React.PureComponent<SchemaProps> {
render() { render() {
const schema = this.props.schema; const schema = this.props.schema;
const itemsSchema = schema.items; const itemsSchema = schema.items;
const fieldParentsName = this.props.fieldParentsName;
const minMaxItems = const minMaxItems =
schema.minItems === undefined && schema.maxItems === undefined schema.minItems === undefined && schema.maxItems === undefined
? '' ? ''
: `(${humanizeConstraints(schema)})`; : `(${humanizeConstraints(schema)})`;
const updatedParentsArray = fieldParentsName
? [...fieldParentsName.slice(0, -1), fieldParentsName[fieldParentsName.length - 1] + '[]']
: fieldParentsName;
if (schema.fields) { if (schema.fields) {
return <ObjectSchema {...(this.props as any)} level={this.props.level} />; return (
<ObjectSchema
{...(this.props as any)}
level={this.props.level}
fieldParentsName={updatedParentsArray}
/>
);
} }
if (schema.displayType && !itemsSchema && !minMaxItems.length) { if (schema.displayType && !itemsSchema && !minMaxItems.length) {
return ( return (
@ -37,7 +47,7 @@ export class ArraySchema extends React.PureComponent<SchemaProps> {
<div> <div>
<ArrayOpenningLabel> Array {minMaxItems}</ArrayOpenningLabel> <ArrayOpenningLabel> Array {minMaxItems}</ArrayOpenningLabel>
<PaddedSchema> <PaddedSchema>
<Schema {...this.props} schema={itemsSchema} /> <Schema {...this.props} schema={itemsSchema} fieldParentsName={updatedParentsArray} />
</PaddedSchema> </PaddedSchema>
<ArrayClosingLabel /> <ArrayClosingLabel />
</div> </div>

View File

@ -16,6 +16,7 @@ export interface ObjectSchemaProps extends SchemaProps {
fieldName: string; fieldName: string;
parentSchema: SchemaModel; parentSchema: SchemaModel;
}; };
fieldParentsName?: string[];
} }
export const ObjectSchema = observer( export const ObjectSchema = observer(
@ -26,8 +27,9 @@ export const ObjectSchema = observer(
skipReadOnly, skipReadOnly,
skipWriteOnly, skipWriteOnly,
level, level,
fieldParentsName,
}: ObjectSchemaProps) => { }: ObjectSchemaProps) => {
const { expandSingleSchemaField, showObjectSchemaExamples, schemaExpansionLevel } = const { expandSingleSchemaField, showObjectSchemaExamples, schemasExpansionLevel } =
React.useContext(OptionsContext); React.useContext(OptionsContext);
const filteredFields = React.useMemo( const filteredFields = React.useMemo(
@ -45,7 +47,7 @@ export const ObjectSchema = observer(
); );
const expandByDefault = const expandByDefault =
(expandSingleSchemaField && filteredFields.length === 1) || schemaExpansionLevel >= level!; (expandSingleSchemaField && filteredFields.length === 1) || schemasExpansionLevel >= level!;
return ( return (
<PropertiesTable> <PropertiesTable>
@ -58,6 +60,7 @@ export const ObjectSchema = observer(
isLast={isLast} isLast={isLast}
field={field} field={field}
expandByDefault={expandByDefault} expandByDefault={expandByDefault}
fieldParentsName={Number(level) > 1 ? fieldParentsName : []}
renderDiscriminatorSwitch={ renderDiscriminatorSwitch={
discriminator?.fieldName === field.name discriminator?.fieldName === field.name
? () => ( ? () => (

View File

@ -21,6 +21,7 @@ export interface SchemaOptions {
export interface SchemaProps extends SchemaOptions { export interface SchemaProps extends SchemaOptions {
schema: SchemaModel; schema: SchemaModel;
fieldParentsName?: string[];
} }
@observer @observer

View File

@ -2,14 +2,14 @@ import { observer } from 'mobx-react';
import * as React from 'react'; import * as React from 'react';
import { ShelfIcon } from '../../common-elements/shelfs'; import { ShelfIcon } from '../../common-elements/shelfs';
import type { IMenuItem } from '../../services';
import { OperationModel } from '../../services'; import { OperationModel } from '../../services';
import { shortenHTTPVerb } from '../../utils/openapi';
import { MenuItems } from './MenuItems';
import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements';
import { l } from '../../services/Labels'; import { l } from '../../services/Labels';
import { scrollIntoViewIfNeeded } from '../../utils'; import { scrollIntoViewIfNeeded } from '../../utils';
import { shortenHTTPVerb } from '../../utils/openapi';
import { OptionsContext } from '../OptionsProvider'; import { OptionsContext } from '../OptionsProvider';
import type { IMenuItem } from '../../services'; import { MenuItems } from './MenuItems';
import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements';
export interface MenuItemProps { export interface MenuItemProps {
item: IMenuItem; item: IMenuItem;
@ -47,9 +47,18 @@ export class MenuItem extends React.Component<MenuItemProps> {
<MenuItemLi <MenuItemLi
tabIndex={0} tabIndex={0}
onClick={this.activate} onClick={this.activate}
onKeyDown={evt => {
// Space or Enter key will activate the menu item
if (evt.key === 'Enter' || evt.key === ' ') {
this.props.onActivate!(this.props.item);
evt.stopPropagation();
}
}}
depth={item.depth} depth={item.depth}
data-item-id={item.id} data-item-id={item.id}
role="menuitem" role="menuitem"
aria-label={item.sidebarLabel}
aria-expanded={item.expanded}
> >
{item.type === 'operation' ? ( {item.type === 'operation' ? (
<OperationMenuItemContent {...this.props} item={item as OperationModel} /> <OperationMenuItemContent {...this.props} item={item as OperationModel} />
@ -101,6 +110,12 @@ export const OperationMenuItemContent = observer((props: OperationMenuItemConten
$deprecated={item.deprecated} $deprecated={item.deprecated}
ref={ref} ref={ref}
> >
{item.badges &&
item.badges?.map(({ name, color }) => (
<OperationBadge type="badge" color={color} key={name}>
{name}
</OperationBadge>
))}
{item.isWebhook ? ( {item.isWebhook ? (
<OperationBadge type="hook"> <OperationBadge type="hook">
{showWebhookVerb ? item.httpVerb : l('webhook')} {showWebhookVerb ? item.httpVerb : l('webhook')}

View File

@ -4,14 +4,14 @@ import { darken } from 'polished';
import { deprecatedCss, ShelfIcon } from '../../common-elements'; import { deprecatedCss, ShelfIcon } from '../../common-elements';
import styled, { css, media, ResolvedThemeInterface } from '../../styled-components'; import styled, { css, media, ResolvedThemeInterface } from '../../styled-components';
export const OperationBadge = styled.span.attrs((props: { type: string }) => ({ export const OperationBadge = styled.span.attrs((props: { type: string; color?: string }) => ({
className: `operation-type ${props.type}`, className: `operation-type ${props.type}`,
}))<{ type: string }>` }))<{ type: string; color?: string }>`
width: 9ex; width: 9ex;
display: inline-block; display: inline-block;
height: ${props => props.theme.typography.code.fontSize}; height: ${props => props.theme.typography.code.fontSize};
line-height: ${props => props.theme.typography.code.fontSize}; line-height: ${props => props.theme.typography.code.fontSize};
background-color: #333; background-color: ${props => props.color || '#333'};
border-radius: 3px; border-radius: 3px;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: 6px 4px; background-position: 6px 4px;

View File

@ -56,6 +56,7 @@ export function StoreBuilder(props: StoreBuilderProps) {
} }
} }
load(); load();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [spec, specUrl]); }, [spec, specUrl]);
const store = React.useMemo(() => { const store = React.useMemo(() => {

View File

@ -90,7 +90,7 @@ describe('FieldDetailsComponent', () => {
items: { items: {
type: 'string', type: 'string',
pattern: '^see regex[0-9]$', pattern: '^see regex[0-9]$',
constraints: [''], constraints: ['<= 128 characters'],
externalDocs: undefined, externalDocs: undefined,
}, },
} as any as SchemaModel, } as any as SchemaModel,

View File

@ -2,6 +2,7 @@
import { mount, ReactWrapper } from 'enzyme'; import { mount, ReactWrapper } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { act } from 'react';
import { JsonViewer } from '../'; import { JsonViewer } from '../';
import { withTheme } from '../testProviders'; import { withTheme } from '../testProviders';
@ -50,5 +51,54 @@ describe('Components', () => {
expect(flatDataComponent.html()).not.toContain('Expand all'); expect(flatDataComponent.html()).not.toContain('Expand all');
expect(flatDataComponent.html()).not.toContain('Collapse all'); expect(flatDataComponent.html()).not.toContain('Collapse all');
}); });
describe('Keyboard Navigation', () => {
let component: ReactWrapper;
const data = {
a: 1,
b: {
c:
// Long string to test horizontal scrolling
Array(100).fill('hello').join(''),
},
};
beforeEach(() => {
component = mount(withTheme(<JsonViewer data={data} />));
ClipboardService.copySelected = origCopySelected;
});
test('should handle arrow key navigation', () => {
const prismDiv = component.find('div[tabIndex=0]');
const divElement = prismDiv.getDOMNode();
// Mock scrollLeft before events
Object.defineProperty(divElement, 'scrollLeft', {
get: jest.fn(() => 0),
set: jest.fn(),
});
// Trigger events inside act()
act(() => {
divElement.dispatchEvent(
new KeyboardEvent('keydown', {
key: 'ArrowRight',
bubbles: true,
}),
);
});
act(() => {
divElement.dispatchEvent(
new KeyboardEvent('keydown', {
key: 'ArrowLeft',
bubbles: true,
}),
);
});
expect(divElement.scrollLeft).toBe(0);
});
});
}); });
}); });

View File

@ -79,21 +79,23 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"disableSearch": false, "disableSearch": false,
"downloadDefinitionUrl": undefined, "downloadDefinitionUrl": undefined,
"downloadFileName": undefined, "downloadFileName": undefined,
"downloadUrls": undefined,
"enumSkipQuotes": false, "enumSkipQuotes": false,
"expandDefaultServerVariables": false, "expandDefaultServerVariables": false,
"expandResponses": {}, "expandResponses": {},
"expandSingleSchemaField": false, "expandSingleSchemaField": false,
"generatedPayloadSamplesMaxDepth": 10, "generatedSamplesMaxDepth": 10,
"hideDownloadButton": false, "hideDownloadButtons": false,
"hideFab": false, "hideFab": false,
"hideHostname": false, "hideHostname": false,
"hidePropertiesPrefix": true,
"hideRequestPayloadSample": false, "hideRequestPayloadSample": false,
"hideSchemaPattern": false, "hideSchemaPattern": false,
"hideSchemaTitles": false, "hideSchemaTitles": false,
"hideSecuritySection": false, "hideSecuritySection": false,
"hideSingleRequestSampleTab": false, "hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {}, "ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2, "jsonSamplesExpandLevel": 2,
"maxDisplayedEnumValues": undefined, "maxDisplayedEnumValues": undefined,
"menuToggle": true, "menuToggle": true,
"minCharacterLengthToInitSearch": 3, "minCharacterLengthToInitSearch": 3,
@ -102,8 +104,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"onlyRequiredInSamples": false, "onlyRequiredInSamples": false,
"pathInMiddlePanel": false, "pathInMiddlePanel": false,
"payloadSampleIdx": 0, "payloadSampleIdx": 0,
"requiredPropsFirst": false, "sanitize": false,
"schemaExpansionLevel": 0, "schemaDefinitionsTagName": undefined,
"schemasExpansionLevel": 0,
"scrollYOffset": [Function], "scrollYOffset": [Function],
"showExtensions": false, "showExtensions": false,
"showObjectSchemaExamples": false, "showObjectSchemaExamples": false,
@ -114,6 +117,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sortEnumValuesAlphabetically": false, "sortEnumValuesAlphabetically": false,
"sortOperationsAlphabetically": false, "sortOperationsAlphabetically": false,
"sortPropsAlphabetically": false, "sortPropsAlphabetically": false,
"sortRequiredPropsFirst": false,
"sortTagsAlphabetically": false, "sortTagsAlphabetically": false,
"theme": { "theme": {
"breakpoints": { "breakpoints": {
@ -292,7 +296,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
}, },
}, },
"unstable_ignoreMimeParameters": false, "unstable_ignoreMimeParameters": false,
"untrustedSpec": false,
}, },
"pattern": undefined, "pattern": undefined,
"pointer": "#/components/schemas/Dog/properties/packSize", "pointer": "#/components/schemas/Dog/properties/packSize",
@ -313,6 +316,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"type": "number", "type": "number",
"typePrefix": "", "typePrefix": "",
"writeOnly": false, "writeOnly": false,
"x-enumDescriptions": undefined,
}, },
}, },
FieldModel { FieldModel {
@ -351,21 +355,23 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"disableSearch": false, "disableSearch": false,
"downloadDefinitionUrl": undefined, "downloadDefinitionUrl": undefined,
"downloadFileName": undefined, "downloadFileName": undefined,
"downloadUrls": undefined,
"enumSkipQuotes": false, "enumSkipQuotes": false,
"expandDefaultServerVariables": false, "expandDefaultServerVariables": false,
"expandResponses": {}, "expandResponses": {},
"expandSingleSchemaField": false, "expandSingleSchemaField": false,
"generatedPayloadSamplesMaxDepth": 10, "generatedSamplesMaxDepth": 10,
"hideDownloadButton": false, "hideDownloadButtons": false,
"hideFab": false, "hideFab": false,
"hideHostname": false, "hideHostname": false,
"hidePropertiesPrefix": true,
"hideRequestPayloadSample": false, "hideRequestPayloadSample": false,
"hideSchemaPattern": false, "hideSchemaPattern": false,
"hideSchemaTitles": false, "hideSchemaTitles": false,
"hideSecuritySection": false, "hideSecuritySection": false,
"hideSingleRequestSampleTab": false, "hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {}, "ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2, "jsonSamplesExpandLevel": 2,
"maxDisplayedEnumValues": undefined, "maxDisplayedEnumValues": undefined,
"menuToggle": true, "menuToggle": true,
"minCharacterLengthToInitSearch": 3, "minCharacterLengthToInitSearch": 3,
@ -374,8 +380,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"onlyRequiredInSamples": false, "onlyRequiredInSamples": false,
"pathInMiddlePanel": false, "pathInMiddlePanel": false,
"payloadSampleIdx": 0, "payloadSampleIdx": 0,
"requiredPropsFirst": false, "sanitize": false,
"schemaExpansionLevel": 0, "schemaDefinitionsTagName": undefined,
"schemasExpansionLevel": 0,
"scrollYOffset": [Function], "scrollYOffset": [Function],
"showExtensions": false, "showExtensions": false,
"showObjectSchemaExamples": false, "showObjectSchemaExamples": false,
@ -386,6 +393,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sortEnumValuesAlphabetically": false, "sortEnumValuesAlphabetically": false,
"sortOperationsAlphabetically": false, "sortOperationsAlphabetically": false,
"sortPropsAlphabetically": false, "sortPropsAlphabetically": false,
"sortRequiredPropsFirst": false,
"sortTagsAlphabetically": false, "sortTagsAlphabetically": false,
"theme": { "theme": {
"breakpoints": { "breakpoints": {
@ -564,7 +572,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
}, },
}, },
"unstable_ignoreMimeParameters": false, "unstable_ignoreMimeParameters": false,
"untrustedSpec": false,
}, },
"pattern": undefined, "pattern": undefined,
"pointer": "#/components/schemas/Dog/properties/type", "pointer": "#/components/schemas/Dog/properties/type",
@ -597,6 +604,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"type": "string", "type": "string",
"typePrefix": "", "typePrefix": "",
"writeOnly": false, "writeOnly": false,
"x-enumDescriptions": undefined,
}, },
}, },
], ],
@ -610,21 +618,23 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"disableSearch": false, "disableSearch": false,
"downloadDefinitionUrl": undefined, "downloadDefinitionUrl": undefined,
"downloadFileName": undefined, "downloadFileName": undefined,
"downloadUrls": undefined,
"enumSkipQuotes": false, "enumSkipQuotes": false,
"expandDefaultServerVariables": false, "expandDefaultServerVariables": false,
"expandResponses": {}, "expandResponses": {},
"expandSingleSchemaField": false, "expandSingleSchemaField": false,
"generatedPayloadSamplesMaxDepth": 10, "generatedSamplesMaxDepth": 10,
"hideDownloadButton": false, "hideDownloadButtons": false,
"hideFab": false, "hideFab": false,
"hideHostname": false, "hideHostname": false,
"hidePropertiesPrefix": true,
"hideRequestPayloadSample": false, "hideRequestPayloadSample": false,
"hideSchemaPattern": false, "hideSchemaPattern": false,
"hideSchemaTitles": false, "hideSchemaTitles": false,
"hideSecuritySection": false, "hideSecuritySection": false,
"hideSingleRequestSampleTab": false, "hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {}, "ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2, "jsonSamplesExpandLevel": 2,
"maxDisplayedEnumValues": undefined, "maxDisplayedEnumValues": undefined,
"menuToggle": true, "menuToggle": true,
"minCharacterLengthToInitSearch": 3, "minCharacterLengthToInitSearch": 3,
@ -633,8 +643,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"onlyRequiredInSamples": false, "onlyRequiredInSamples": false,
"pathInMiddlePanel": false, "pathInMiddlePanel": false,
"payloadSampleIdx": 0, "payloadSampleIdx": 0,
"requiredPropsFirst": false, "sanitize": false,
"schemaExpansionLevel": 0, "schemaDefinitionsTagName": undefined,
"schemasExpansionLevel": 0,
"scrollYOffset": [Function], "scrollYOffset": [Function],
"showExtensions": false, "showExtensions": false,
"showObjectSchemaExamples": false, "showObjectSchemaExamples": false,
@ -645,6 +656,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sortEnumValuesAlphabetically": false, "sortEnumValuesAlphabetically": false,
"sortOperationsAlphabetically": false, "sortOperationsAlphabetically": false,
"sortPropsAlphabetically": false, "sortPropsAlphabetically": false,
"sortRequiredPropsFirst": false,
"sortTagsAlphabetically": false, "sortTagsAlphabetically": false,
"theme": { "theme": {
"breakpoints": { "breakpoints": {
@ -823,7 +835,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
}, },
}, },
"unstable_ignoreMimeParameters": false, "unstable_ignoreMimeParameters": false,
"untrustedSpec": false,
}, },
"pattern": undefined, "pattern": undefined,
"pointer": "#/components/schemas/Dog", "pointer": "#/components/schemas/Dog",
@ -878,6 +889,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"type": "object", "type": "object",
"typePrefix": "", "typePrefix": "",
"writeOnly": false, "writeOnly": false,
"x-enumDescriptions": undefined,
}, },
SchemaModel { SchemaModel {
"activeOneOf": 0, "activeOneOf": 0,
@ -931,21 +943,23 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"disableSearch": false, "disableSearch": false,
"downloadDefinitionUrl": undefined, "downloadDefinitionUrl": undefined,
"downloadFileName": undefined, "downloadFileName": undefined,
"downloadUrls": undefined,
"enumSkipQuotes": false, "enumSkipQuotes": false,
"expandDefaultServerVariables": false, "expandDefaultServerVariables": false,
"expandResponses": {}, "expandResponses": {},
"expandSingleSchemaField": false, "expandSingleSchemaField": false,
"generatedPayloadSamplesMaxDepth": 10, "generatedSamplesMaxDepth": 10,
"hideDownloadButton": false, "hideDownloadButtons": false,
"hideFab": false, "hideFab": false,
"hideHostname": false, "hideHostname": false,
"hidePropertiesPrefix": true,
"hideRequestPayloadSample": false, "hideRequestPayloadSample": false,
"hideSchemaPattern": false, "hideSchemaPattern": false,
"hideSchemaTitles": false, "hideSchemaTitles": false,
"hideSecuritySection": false, "hideSecuritySection": false,
"hideSingleRequestSampleTab": false, "hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {}, "ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2, "jsonSamplesExpandLevel": 2,
"maxDisplayedEnumValues": undefined, "maxDisplayedEnumValues": undefined,
"menuToggle": true, "menuToggle": true,
"minCharacterLengthToInitSearch": 3, "minCharacterLengthToInitSearch": 3,
@ -954,8 +968,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"onlyRequiredInSamples": false, "onlyRequiredInSamples": false,
"pathInMiddlePanel": false, "pathInMiddlePanel": false,
"payloadSampleIdx": 0, "payloadSampleIdx": 0,
"requiredPropsFirst": false, "sanitize": false,
"schemaExpansionLevel": 0, "schemaDefinitionsTagName": undefined,
"schemasExpansionLevel": 0,
"scrollYOffset": [Function], "scrollYOffset": [Function],
"showExtensions": false, "showExtensions": false,
"showObjectSchemaExamples": false, "showObjectSchemaExamples": false,
@ -966,6 +981,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sortEnumValuesAlphabetically": false, "sortEnumValuesAlphabetically": false,
"sortOperationsAlphabetically": false, "sortOperationsAlphabetically": false,
"sortPropsAlphabetically": false, "sortPropsAlphabetically": false,
"sortRequiredPropsFirst": false,
"sortTagsAlphabetically": false, "sortTagsAlphabetically": false,
"theme": { "theme": {
"breakpoints": { "breakpoints": {
@ -1144,7 +1160,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
}, },
}, },
"unstable_ignoreMimeParameters": false, "unstable_ignoreMimeParameters": false,
"untrustedSpec": false,
}, },
"pattern": undefined, "pattern": undefined,
"pointer": "#/components/schemas/Cat/properties/type", "pointer": "#/components/schemas/Cat/properties/type",
@ -1177,6 +1192,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"type": "string", "type": "string",
"typePrefix": "", "typePrefix": "",
"writeOnly": false, "writeOnly": false,
"x-enumDescriptions": undefined,
}, },
}, },
FieldModel { FieldModel {
@ -1215,21 +1231,23 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"disableSearch": false, "disableSearch": false,
"downloadDefinitionUrl": undefined, "downloadDefinitionUrl": undefined,
"downloadFileName": undefined, "downloadFileName": undefined,
"downloadUrls": undefined,
"enumSkipQuotes": false, "enumSkipQuotes": false,
"expandDefaultServerVariables": false, "expandDefaultServerVariables": false,
"expandResponses": {}, "expandResponses": {},
"expandSingleSchemaField": false, "expandSingleSchemaField": false,
"generatedPayloadSamplesMaxDepth": 10, "generatedSamplesMaxDepth": 10,
"hideDownloadButton": false, "hideDownloadButtons": false,
"hideFab": false, "hideFab": false,
"hideHostname": false, "hideHostname": false,
"hidePropertiesPrefix": true,
"hideRequestPayloadSample": false, "hideRequestPayloadSample": false,
"hideSchemaPattern": false, "hideSchemaPattern": false,
"hideSchemaTitles": false, "hideSchemaTitles": false,
"hideSecuritySection": false, "hideSecuritySection": false,
"hideSingleRequestSampleTab": false, "hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {}, "ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2, "jsonSamplesExpandLevel": 2,
"maxDisplayedEnumValues": undefined, "maxDisplayedEnumValues": undefined,
"menuToggle": true, "menuToggle": true,
"minCharacterLengthToInitSearch": 3, "minCharacterLengthToInitSearch": 3,
@ -1238,8 +1256,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"onlyRequiredInSamples": false, "onlyRequiredInSamples": false,
"pathInMiddlePanel": false, "pathInMiddlePanel": false,
"payloadSampleIdx": 0, "payloadSampleIdx": 0,
"requiredPropsFirst": false, "sanitize": false,
"schemaExpansionLevel": 0, "schemaDefinitionsTagName": undefined,
"schemasExpansionLevel": 0,
"scrollYOffset": [Function], "scrollYOffset": [Function],
"showExtensions": false, "showExtensions": false,
"showObjectSchemaExamples": false, "showObjectSchemaExamples": false,
@ -1250,6 +1269,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sortEnumValuesAlphabetically": false, "sortEnumValuesAlphabetically": false,
"sortOperationsAlphabetically": false, "sortOperationsAlphabetically": false,
"sortPropsAlphabetically": false, "sortPropsAlphabetically": false,
"sortRequiredPropsFirst": false,
"sortTagsAlphabetically": false, "sortTagsAlphabetically": false,
"theme": { "theme": {
"breakpoints": { "breakpoints": {
@ -1428,7 +1448,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
}, },
}, },
"unstable_ignoreMimeParameters": false, "unstable_ignoreMimeParameters": false,
"untrustedSpec": false,
}, },
"pattern": undefined, "pattern": undefined,
"pointer": "#/components/schemas/Cat/properties/packSize", "pointer": "#/components/schemas/Cat/properties/packSize",
@ -1457,6 +1476,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"type": "number", "type": "number",
"typePrefix": "", "typePrefix": "",
"writeOnly": false, "writeOnly": false,
"x-enumDescriptions": undefined,
}, },
}, },
], ],
@ -1470,21 +1490,23 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"disableSearch": false, "disableSearch": false,
"downloadDefinitionUrl": undefined, "downloadDefinitionUrl": undefined,
"downloadFileName": undefined, "downloadFileName": undefined,
"downloadUrls": undefined,
"enumSkipQuotes": false, "enumSkipQuotes": false,
"expandDefaultServerVariables": false, "expandDefaultServerVariables": false,
"expandResponses": {}, "expandResponses": {},
"expandSingleSchemaField": false, "expandSingleSchemaField": false,
"generatedPayloadSamplesMaxDepth": 10, "generatedSamplesMaxDepth": 10,
"hideDownloadButton": false, "hideDownloadButtons": false,
"hideFab": false, "hideFab": false,
"hideHostname": false, "hideHostname": false,
"hidePropertiesPrefix": true,
"hideRequestPayloadSample": false, "hideRequestPayloadSample": false,
"hideSchemaPattern": false, "hideSchemaPattern": false,
"hideSchemaTitles": false, "hideSchemaTitles": false,
"hideSecuritySection": false, "hideSecuritySection": false,
"hideSingleRequestSampleTab": false, "hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {}, "ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2, "jsonSamplesExpandLevel": 2,
"maxDisplayedEnumValues": undefined, "maxDisplayedEnumValues": undefined,
"menuToggle": true, "menuToggle": true,
"minCharacterLengthToInitSearch": 3, "minCharacterLengthToInitSearch": 3,
@ -1493,8 +1515,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"onlyRequiredInSamples": false, "onlyRequiredInSamples": false,
"pathInMiddlePanel": false, "pathInMiddlePanel": false,
"payloadSampleIdx": 0, "payloadSampleIdx": 0,
"requiredPropsFirst": false, "sanitize": false,
"schemaExpansionLevel": 0, "schemaDefinitionsTagName": undefined,
"schemasExpansionLevel": 0,
"scrollYOffset": [Function], "scrollYOffset": [Function],
"showExtensions": false, "showExtensions": false,
"showObjectSchemaExamples": false, "showObjectSchemaExamples": false,
@ -1505,6 +1528,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sortEnumValuesAlphabetically": false, "sortEnumValuesAlphabetically": false,
"sortOperationsAlphabetically": false, "sortOperationsAlphabetically": false,
"sortPropsAlphabetically": false, "sortPropsAlphabetically": false,
"sortRequiredPropsFirst": false,
"sortTagsAlphabetically": false, "sortTagsAlphabetically": false,
"theme": { "theme": {
"breakpoints": { "breakpoints": {
@ -1683,7 +1707,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
}, },
}, },
"unstable_ignoreMimeParameters": false, "unstable_ignoreMimeParameters": false,
"untrustedSpec": false,
}, },
"pattern": undefined, "pattern": undefined,
"pointer": "#/components/schemas/Cat", "pointer": "#/components/schemas/Cat",
@ -1743,6 +1766,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"type": "object", "type": "object",
"typePrefix": "", "typePrefix": "",
"writeOnly": false, "writeOnly": false,
"x-enumDescriptions": undefined,
}, },
], ],
"options": RedocNormalizedOptions { "options": RedocNormalizedOptions {
@ -1750,21 +1774,23 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"disableSearch": false, "disableSearch": false,
"downloadDefinitionUrl": undefined, "downloadDefinitionUrl": undefined,
"downloadFileName": undefined, "downloadFileName": undefined,
"downloadUrls": undefined,
"enumSkipQuotes": false, "enumSkipQuotes": false,
"expandDefaultServerVariables": false, "expandDefaultServerVariables": false,
"expandResponses": {}, "expandResponses": {},
"expandSingleSchemaField": false, "expandSingleSchemaField": false,
"generatedPayloadSamplesMaxDepth": 10, "generatedSamplesMaxDepth": 10,
"hideDownloadButton": false, "hideDownloadButtons": false,
"hideFab": false, "hideFab": false,
"hideHostname": false, "hideHostname": false,
"hidePropertiesPrefix": true,
"hideRequestPayloadSample": false, "hideRequestPayloadSample": false,
"hideSchemaPattern": false, "hideSchemaPattern": false,
"hideSchemaTitles": false, "hideSchemaTitles": false,
"hideSecuritySection": false, "hideSecuritySection": false,
"hideSingleRequestSampleTab": false, "hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {}, "ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2, "jsonSamplesExpandLevel": 2,
"maxDisplayedEnumValues": undefined, "maxDisplayedEnumValues": undefined,
"menuToggle": true, "menuToggle": true,
"minCharacterLengthToInitSearch": 3, "minCharacterLengthToInitSearch": 3,
@ -1773,8 +1799,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"onlyRequiredInSamples": false, "onlyRequiredInSamples": false,
"pathInMiddlePanel": false, "pathInMiddlePanel": false,
"payloadSampleIdx": 0, "payloadSampleIdx": 0,
"requiredPropsFirst": false, "sanitize": false,
"schemaExpansionLevel": 0, "schemaDefinitionsTagName": undefined,
"schemasExpansionLevel": 0,
"scrollYOffset": [Function], "scrollYOffset": [Function],
"showExtensions": false, "showExtensions": false,
"showObjectSchemaExamples": false, "showObjectSchemaExamples": false,
@ -1785,6 +1812,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sortEnumValuesAlphabetically": false, "sortEnumValuesAlphabetically": false,
"sortOperationsAlphabetically": false, "sortOperationsAlphabetically": false,
"sortPropsAlphabetically": false, "sortPropsAlphabetically": false,
"sortRequiredPropsFirst": false,
"sortTagsAlphabetically": false, "sortTagsAlphabetically": false,
"theme": { "theme": {
"breakpoints": { "breakpoints": {
@ -1963,7 +1991,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
}, },
}, },
"unstable_ignoreMimeParameters": false, "unstable_ignoreMimeParameters": false,
"untrustedSpec": false,
}, },
"pattern": undefined, "pattern": undefined,
"pointer": "#/components/schemas/Pet", "pointer": "#/components/schemas/Pet",
@ -2003,6 +2030,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"type": "object", "type": "object",
"typePrefix": "", "typePrefix": "",
"writeOnly": false, "writeOnly": false,
"x-enumDescriptions": undefined,
}, },
} }
} }
@ -2060,21 +2088,23 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"disableSearch": false, "disableSearch": false,
"downloadDefinitionUrl": undefined, "downloadDefinitionUrl": undefined,
"downloadFileName": undefined, "downloadFileName": undefined,
"downloadUrls": undefined,
"enumSkipQuotes": false, "enumSkipQuotes": false,
"expandDefaultServerVariables": false, "expandDefaultServerVariables": false,
"expandResponses": {}, "expandResponses": {},
"expandSingleSchemaField": false, "expandSingleSchemaField": false,
"generatedPayloadSamplesMaxDepth": 10, "generatedSamplesMaxDepth": 10,
"hideDownloadButton": false, "hideDownloadButtons": false,
"hideFab": false, "hideFab": false,
"hideHostname": false, "hideHostname": false,
"hidePropertiesPrefix": true,
"hideRequestPayloadSample": false, "hideRequestPayloadSample": false,
"hideSchemaPattern": false, "hideSchemaPattern": false,
"hideSchemaTitles": false, "hideSchemaTitles": false,
"hideSecuritySection": false, "hideSecuritySection": false,
"hideSingleRequestSampleTab": false, "hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {}, "ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2, "jsonSamplesExpandLevel": 2,
"maxDisplayedEnumValues": undefined, "maxDisplayedEnumValues": undefined,
"menuToggle": true, "menuToggle": true,
"minCharacterLengthToInitSearch": 3, "minCharacterLengthToInitSearch": 3,
@ -2083,8 +2113,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"onlyRequiredInSamples": false, "onlyRequiredInSamples": false,
"pathInMiddlePanel": false, "pathInMiddlePanel": false,
"payloadSampleIdx": 0, "payloadSampleIdx": 0,
"requiredPropsFirst": false, "sanitize": false,
"schemaExpansionLevel": 0, "schemaDefinitionsTagName": undefined,
"schemasExpansionLevel": 0,
"scrollYOffset": [Function], "scrollYOffset": [Function],
"showExtensions": false, "showExtensions": false,
"showObjectSchemaExamples": false, "showObjectSchemaExamples": false,
@ -2095,6 +2126,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sortEnumValuesAlphabetically": false, "sortEnumValuesAlphabetically": false,
"sortOperationsAlphabetically": false, "sortOperationsAlphabetically": false,
"sortPropsAlphabetically": false, "sortPropsAlphabetically": false,
"sortRequiredPropsFirst": false,
"sortTagsAlphabetically": false, "sortTagsAlphabetically": false,
"theme": { "theme": {
"breakpoints": { "breakpoints": {
@ -2273,7 +2305,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
}, },
}, },
"unstable_ignoreMimeParameters": false, "unstable_ignoreMimeParameters": false,
"untrustedSpec": false,
}, },
"pattern": undefined, "pattern": undefined,
"pointer": "#/components/schemas/Dog/properties/packSize", "pointer": "#/components/schemas/Dog/properties/packSize",
@ -2294,6 +2325,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"type": "number", "type": "number",
"typePrefix": "", "typePrefix": "",
"writeOnly": false, "writeOnly": false,
"x-enumDescriptions": undefined,
}, },
}, },
FieldModel { FieldModel {
@ -2332,21 +2364,23 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"disableSearch": false, "disableSearch": false,
"downloadDefinitionUrl": undefined, "downloadDefinitionUrl": undefined,
"downloadFileName": undefined, "downloadFileName": undefined,
"downloadUrls": undefined,
"enumSkipQuotes": false, "enumSkipQuotes": false,
"expandDefaultServerVariables": false, "expandDefaultServerVariables": false,
"expandResponses": {}, "expandResponses": {},
"expandSingleSchemaField": false, "expandSingleSchemaField": false,
"generatedPayloadSamplesMaxDepth": 10, "generatedSamplesMaxDepth": 10,
"hideDownloadButton": false, "hideDownloadButtons": false,
"hideFab": false, "hideFab": false,
"hideHostname": false, "hideHostname": false,
"hidePropertiesPrefix": true,
"hideRequestPayloadSample": false, "hideRequestPayloadSample": false,
"hideSchemaPattern": false, "hideSchemaPattern": false,
"hideSchemaTitles": false, "hideSchemaTitles": false,
"hideSecuritySection": false, "hideSecuritySection": false,
"hideSingleRequestSampleTab": false, "hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {}, "ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2, "jsonSamplesExpandLevel": 2,
"maxDisplayedEnumValues": undefined, "maxDisplayedEnumValues": undefined,
"menuToggle": true, "menuToggle": true,
"minCharacterLengthToInitSearch": 3, "minCharacterLengthToInitSearch": 3,
@ -2355,8 +2389,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"onlyRequiredInSamples": false, "onlyRequiredInSamples": false,
"pathInMiddlePanel": false, "pathInMiddlePanel": false,
"payloadSampleIdx": 0, "payloadSampleIdx": 0,
"requiredPropsFirst": false, "sanitize": false,
"schemaExpansionLevel": 0, "schemaDefinitionsTagName": undefined,
"schemasExpansionLevel": 0,
"scrollYOffset": [Function], "scrollYOffset": [Function],
"showExtensions": false, "showExtensions": false,
"showObjectSchemaExamples": false, "showObjectSchemaExamples": false,
@ -2367,6 +2402,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sortEnumValuesAlphabetically": false, "sortEnumValuesAlphabetically": false,
"sortOperationsAlphabetically": false, "sortOperationsAlphabetically": false,
"sortPropsAlphabetically": false, "sortPropsAlphabetically": false,
"sortRequiredPropsFirst": false,
"sortTagsAlphabetically": false, "sortTagsAlphabetically": false,
"theme": { "theme": {
"breakpoints": { "breakpoints": {
@ -2545,7 +2581,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
}, },
}, },
"unstable_ignoreMimeParameters": false, "unstable_ignoreMimeParameters": false,
"untrustedSpec": false,
}, },
"pattern": undefined, "pattern": undefined,
"pointer": "#/components/schemas/Dog/properties/type", "pointer": "#/components/schemas/Dog/properties/type",
@ -2578,6 +2613,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"type": "string", "type": "string",
"typePrefix": "", "typePrefix": "",
"writeOnly": false, "writeOnly": false,
"x-enumDescriptions": undefined,
}, },
}, },
], ],
@ -2591,21 +2627,23 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"disableSearch": false, "disableSearch": false,
"downloadDefinitionUrl": undefined, "downloadDefinitionUrl": undefined,
"downloadFileName": undefined, "downloadFileName": undefined,
"downloadUrls": undefined,
"enumSkipQuotes": false, "enumSkipQuotes": false,
"expandDefaultServerVariables": false, "expandDefaultServerVariables": false,
"expandResponses": {}, "expandResponses": {},
"expandSingleSchemaField": false, "expandSingleSchemaField": false,
"generatedPayloadSamplesMaxDepth": 10, "generatedSamplesMaxDepth": 10,
"hideDownloadButton": false, "hideDownloadButtons": false,
"hideFab": false, "hideFab": false,
"hideHostname": false, "hideHostname": false,
"hidePropertiesPrefix": true,
"hideRequestPayloadSample": false, "hideRequestPayloadSample": false,
"hideSchemaPattern": false, "hideSchemaPattern": false,
"hideSchemaTitles": false, "hideSchemaTitles": false,
"hideSecuritySection": false, "hideSecuritySection": false,
"hideSingleRequestSampleTab": false, "hideSingleRequestSampleTab": false,
"ignoreNamedSchemas": Set {}, "ignoreNamedSchemas": Set {},
"jsonSampleExpandLevel": 2, "jsonSamplesExpandLevel": 2,
"maxDisplayedEnumValues": undefined, "maxDisplayedEnumValues": undefined,
"menuToggle": true, "menuToggle": true,
"minCharacterLengthToInitSearch": 3, "minCharacterLengthToInitSearch": 3,
@ -2614,8 +2652,9 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"onlyRequiredInSamples": false, "onlyRequiredInSamples": false,
"pathInMiddlePanel": false, "pathInMiddlePanel": false,
"payloadSampleIdx": 0, "payloadSampleIdx": 0,
"requiredPropsFirst": false, "sanitize": false,
"schemaExpansionLevel": 0, "schemaDefinitionsTagName": undefined,
"schemasExpansionLevel": 0,
"scrollYOffset": [Function], "scrollYOffset": [Function],
"showExtensions": false, "showExtensions": false,
"showObjectSchemaExamples": false, "showObjectSchemaExamples": false,
@ -2626,6 +2665,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"sortEnumValuesAlphabetically": false, "sortEnumValuesAlphabetically": false,
"sortOperationsAlphabetically": false, "sortOperationsAlphabetically": false,
"sortPropsAlphabetically": false, "sortPropsAlphabetically": false,
"sortRequiredPropsFirst": false,
"sortTagsAlphabetically": false, "sortTagsAlphabetically": false,
"theme": { "theme": {
"breakpoints": { "breakpoints": {
@ -2804,7 +2844,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
}, },
}, },
"unstable_ignoreMimeParameters": false, "unstable_ignoreMimeParameters": false,
"untrustedSpec": false,
}, },
"pattern": undefined, "pattern": undefined,
"pointer": "#/components/schemas/Dog", "pointer": "#/components/schemas/Dog",
@ -2859,6 +2898,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
"type": "object", "type": "object",
"typePrefix": "", "typePrefix": "",
"writeOnly": false, "writeOnly": false,
"x-enumDescriptions": undefined,
} }
} }
/> />
@ -2921,9 +2961,11 @@ exports[`Components SchemaView discriminator should correctly render discriminat
"type": "number", "type": "number",
"typePrefix": "", "typePrefix": "",
"writeOnly": false, "writeOnly": false,
"x-enumDescriptions": undefined,
}, },
} }
} }
fieldParentsName={[]}
isLast={false} isLast={false}
key="packSize" key="packSize"
showExamples={false} showExamples={false}
@ -2994,9 +3036,11 @@ exports[`Components SchemaView discriminator should correctly render discriminat
"type": "string", "type": "string",
"typePrefix": "", "typePrefix": "",
"writeOnly": false, "writeOnly": false,
"x-enumDescriptions": undefined,
}, },
} }
} }
fieldParentsName={[]}
isLast={true} isLast={true}
key="type" key="type"
renderDiscriminatorSwitch={[Function]} renderDiscriminatorSwitch={[Function]}

View File

@ -159,11 +159,19 @@ exports[`FieldDetailsComponent renders correctly when field items have string ty
</span> </span>
</span> </span>
<span <span
class="sc-kpDqfm sc-dAlyuH sc-dxcDKg cGRfjn gHomYR gXntsr" class="sc-kpDqfm sc-dAlyuH sc-gvZAcH cGRfjn gHomYR eXivNJ"
> >
[ [ items
<span>
<span <span
class="sc-kpDqfm sc-eDPEul cGRfjn erJHow" class="sc-kpDqfm sc-gFqAkR cGRfjn fYEICH"
>
&lt;= 128 characters
</span>
</span>
<span
class="sc-kpDqfm sc-eDPEul cGRfjn cCKYVD"
> >
^see regex[0-9]$ ^see regex[0-9]$
</span> </span>

View File

@ -3,21 +3,21 @@
exports[`SecurityRequirement should render SecurityDefs 1`] = ` exports[`SecurityRequirement should render SecurityDefs 1`] = `
"<div id="section/Authentication/petstore_auth" data-section-id="section/Authentication/petstore_auth" class="sc-dcJsrY bBkGhy"><div class="sc-kAyceB hBQWIZ"><div class="sc-fqkvVR oJKYx"><h2 class="sc-jXbUNg fWnwAh">petstore_auth</h2><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><p>Get access to data while protecting your account credentials. "<div id="section/Authentication/petstore_auth" data-section-id="section/Authentication/petstore_auth" class="sc-dcJsrY bBkGhy"><div class="sc-kAyceB hBQWIZ"><div class="sc-fqkvVR oJKYx"><h2 class="sc-jXbUNg fWnwAh">petstore_auth</h2><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><p>Get access to data while protecting your account credentials.
OAuth2 is also a safer and more secure way to give you access.</p> OAuth2 is also a safer and more secure way to give you access.</p>
</div><div class="sc-ejfMa-d a-DjBE"><div class="sc-dkmUuB hFwAIA"><b>Security Scheme Type: </b><span>OAuth2</span></div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-dkmUuB hFwAIA"><b>Flow type: </b><code>implicit </code></div><div class="sc-dkmUuB hFwAIA"><strong> Authorization URL: </strong><code><a target="_blank" rel="noopener noreferrer" href="http://petstore.swagger.io/api/oauth/dialog">http://petstore.swagger.io/api/oauth/dialog</a></code></div><div class="sc-dkmUuB hFwAIA"><b> Scopes: </b></div><div class="sc-iEXKAA blExNw container" style="height: 4em;"><ul><li><code>write:pets</code> - <div class="sc-eeDRCY sc-eBMEME gTGgei fMmru sc-fhzFiK hXtrri redoc-markdown"><p>modify pets in your account</p> </div><div class="sc-iEXKAA ebCiwb"><div class="sc-ejfMa-d bdDYxc"><b>Security Scheme Type: </b><span>OAuth2</span></div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-ejfMa-d bdDYxc"><b>Flow type: </b><code>implicit </code></div><div class="sc-ejfMa-d bdDYxc"><strong> Authorization URL: </strong><code><a target="_blank" rel="noopener noreferrer" href="http://petstore.swagger.io/api/oauth/dialog">http://petstore.swagger.io/api/oauth/dialog</a></code></div><div class="sc-ejfMa-d bdDYxc"><b> Scopes: </b></div><div class="sc-EgOXT kRIdPi container" style="height: 4em;"><ul><li><code>write:pets</code> - <div class="sc-eeDRCY sc-eBMEME sc-fhzFiK gTGgei iCmQdS hXtrri redoc-markdown"><p>modify pets in your account</p>
</div></li><li><code>read:pets</code> - <div class="sc-eeDRCY sc-eBMEME gTGgei fMmru sc-fhzFiK hXtrri redoc-markdown"><p>read your pets</p> </div></li><li><code>read:pets</code> - <div class="sc-eeDRCY sc-eBMEME sc-fhzFiK gTGgei iCmQdS hXtrri redoc-markdown"><p>read your pets</p>
</div></li></ul></div><div class="sc-EgOXT bNSpXO"></div></div></div></div></div></div><div id="section/Authentication/GitLab_PersonalAccessToken" data-section-id="section/Authentication/GitLab_PersonalAccessToken" class="sc-dcJsrY bBkGhy"><div class="sc-kAyceB hBQWIZ"><div class="sc-fqkvVR oJKYx"><h2 class="sc-jXbUNg fWnwAh">GitLab_PersonalAccessToken</h2><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><p>GitLab Personal Access Token description</p> </div></li></ul></div><div class="sc-eZYNyq dIKkVb"></div></div></div></div></div></div><div id="section/Authentication/GitLab_PersonalAccessToken" data-section-id="section/Authentication/GitLab_PersonalAccessToken" class="sc-dcJsrY bBkGhy"><div class="sc-kAyceB hBQWIZ"><div class="sc-fqkvVR oJKYx"><h2 class="sc-jXbUNg fWnwAh">GitLab_PersonalAccessToken</h2><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><p>GitLab Personal Access Token description</p>
</div><div class="sc-ejfMa-d a-DjBE"><div class="sc-dkmUuB hFwAIA"><b>Security Scheme Type: </b><span>API Key</span></div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-dkmUuB hFwAIA"><b>Header parameter name: </b><code>PRIVATE-TOKEN</code></div></div></div></div></div></div><div id="section/Authentication/GitLab_OpenIdConnect" data-section-id="section/Authentication/GitLab_OpenIdConnect" class="sc-dcJsrY bBkGhy"><div class="sc-kAyceB hBQWIZ"><div class="sc-fqkvVR oJKYx"><h2 class="sc-jXbUNg fWnwAh">GitLab_OpenIdConnect</h2><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><p>GitLab OpenIdConnect description</p> </div><div class="sc-iEXKAA ebCiwb"><div class="sc-ejfMa-d bdDYxc"><b>Security Scheme Type: </b><span>API Key</span></div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-ejfMa-d bdDYxc"><b>Header parameter name: </b><code>PRIVATE-TOKEN</code></div></div></div></div></div></div><div id="section/Authentication/GitLab_OpenIdConnect" data-section-id="section/Authentication/GitLab_OpenIdConnect" class="sc-dcJsrY bBkGhy"><div class="sc-kAyceB hBQWIZ"><div class="sc-fqkvVR oJKYx"><h2 class="sc-jXbUNg fWnwAh">GitLab_OpenIdConnect</h2><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><p>GitLab OpenIdConnect description</p>
</div><div class="sc-ejfMa-d a-DjBE"><div class="sc-dkmUuB hFwAIA"><b>Security Scheme Type: </b><span>OpenID Connect</span></div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-dkmUuB hFwAIA"><b>Connect URL: </b><code><a target="_blank" rel="noopener noreferrer" href="https://gitlab.com/.well-known/openid-configuration">https://gitlab.com/.well-known/openid-configuration</a></code></div></div></div></div></div></div><div id="section/Authentication/basicAuth" data-section-id="section/Authentication/basicAuth" class="sc-dcJsrY bBkGhy"><div class="sc-kAyceB hBQWIZ"><div class="sc-fqkvVR oJKYx"><h2 class="sc-jXbUNg fWnwAh">basicAuth</h2><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"></div><div class="sc-ejfMa-d a-DjBE"><div class="sc-dkmUuB hFwAIA"><b>Security Scheme Type: </b><span>HTTP</span></div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-dkmUuB hFwAIA"><b>HTTP Authorization Scheme: </b><code>basic</code></div><div class="sc-dkmUuB hFwAIA"></div></div></div></div></div></div>" </div><div class="sc-iEXKAA ebCiwb"><div class="sc-ejfMa-d bdDYxc"><b>Security Scheme Type: </b><span>OpenID Connect</span></div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-ejfMa-d bdDYxc"><b>Connect URL: </b><code><a target="_blank" rel="noopener noreferrer" href="https://gitlab.com/.well-known/openid-configuration">https://gitlab.com/.well-known/openid-configuration</a></code></div></div></div></div></div></div><div id="section/Authentication/basicAuth" data-section-id="section/Authentication/basicAuth" class="sc-dcJsrY bBkGhy"><div class="sc-kAyceB hBQWIZ"><div class="sc-fqkvVR oJKYx"><h2 class="sc-jXbUNg fWnwAh">basicAuth</h2><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"></div><div class="sc-iEXKAA ebCiwb"><div class="sc-ejfMa-d bdDYxc"><b>Security Scheme Type: </b><span>HTTP</span></div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-ejfMa-d bdDYxc"><b>HTTP Authorization Scheme: </b><code>basic</code></div><div class="sc-ejfMa-d bdDYxc"></div></div></div></div></div></div>"
`; `;
exports[`SecurityRequirement should render authDefinition 1`] = `"<div class="sc-bDumWk iWBBny"><div class="sc-sLsrZ hgeUJn"><h5 class="sc-dAlyuH sc-fifgRP jbQuod kWJur">Authorizations:</h5><svg class="sc-cwHptR iZRiKW" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-dBmzty eoFcYg"><span class="sc-kbousE cpXQuZ">(<span class="sc-gfoqjT kbvnry">API Key: <i>GitLab_PersonalAccessToken</i></span><span class="sc-gfoqjT kbvnry">OpenID Connect: <i>GitLab_OpenIdConnect</i></span><span class="sc-gfoqjT kbvnry">HTTP: <i>basicAuth</i></span>) </span><span class="sc-kbousE cpXQuZ"><span class="sc-gfoqjT kbvnry">OAuth2: <i>petstore_auth</i></span></span></div></div>,"`; exports[`SecurityRequirement should render authDefinition 1`] = `"<div class="sc-dkmUuB fUBzjk"><div class="sc-dBmzty iDyBRL"><h5 class="sc-dAlyuH sc-bDumWk jbQuod feBYnB">Authorizations:</h5><svg class="sc-cwHptR iZRiKW" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-fifgRP eqIYDA"><span class="sc-sLsrZ jmro">(<span class="sc-kbousE iMnLRS">API Key: <i>GitLab_PersonalAccessToken</i></span><span class="sc-kbousE iMnLRS">OpenID Connect: <i>GitLab_OpenIdConnect</i></span><span class="sc-kbousE iMnLRS">HTTP: <i>basicAuth</i></span>) </span><span class="sc-sLsrZ jmro"><span class="sc-kbousE iMnLRS">OAuth2: <i>petstore_auth</i></span></span></div></div>,"`;
exports[`SecurityRequirement should render authDefinition 2`] = ` exports[`SecurityRequirement should render authDefinition 2`] = `
"<div class="sc-bDumWk gtsPcy"><div class="sc-sLsrZ hgeUJn"><h5 class="sc-dAlyuH sc-fifgRP jbQuod kWJur">Authorizations:</h5><svg class="sc-cwHptR dSJqIk" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-dBmzty llvZdI"><span class="sc-kbousE dOwJQz">(<span class="sc-gfoqjT kbvnry">API Key: <i>GitLab_PersonalAccessToken</i></span><span class="sc-gfoqjT kbvnry">OpenID Connect: <i>GitLab_OpenIdConnect</i></span><span class="sc-gfoqjT kbvnry">HTTP: <i>basicAuth</i></span>) </span><span class="sc-kbousE dOwJQz"><span class="sc-gfoqjT kbvnry">OAuth2: <i>petstore_auth</i> (<code class="sc-eyvILC bzHwfc">write:pets</code><code class="sc-eyvILC bzHwfc">read:pets</code>) </span></span></div></div><div class="sc-ejfMa-d a-DjBE"><h5><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="11" height="11"><path fill="currentColor" d="M18 10V6A6 6 0 0 0 6 6v4H3v14h18V10h-3zM8 6c0-2.206 1.794-4 4-4s4 1.794 4 4v4H8V6zm11 16H5V12h14v10z"></path></svg> OAuth2: petstore_auth</h5><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><p>Get access to data while protecting your account credentials. "<div class="sc-dkmUuB KTEsk"><div class="sc-dBmzty iDyBRL"><h5 class="sc-dAlyuH sc-bDumWk jbQuod feBYnB">Authorizations:</h5><svg class="sc-cwHptR dSJqIk" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0" aria-hidden="true"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-fifgRP gNcumo"><span class="sc-sLsrZ iTheFK">(<span class="sc-kbousE iMnLRS">API Key: <i>GitLab_PersonalAccessToken</i></span><span class="sc-kbousE iMnLRS">OpenID Connect: <i>GitLab_OpenIdConnect</i></span><span class="sc-kbousE iMnLRS">HTTP: <i>basicAuth</i></span>) </span><span class="sc-sLsrZ iTheFK"><span class="sc-kbousE iMnLRS">OAuth2: <i>petstore_auth</i> (<code class="sc-gfoqjT dapMvh">write:pets</code><code class="sc-gfoqjT dapMvh">read:pets</code>) </span></span></div></div><div class="sc-iEXKAA ebCiwb"><h5><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="11" height="11"><path fill="currentColor" d="M18 10V6A6 6 0 0 0 6 6v4H3v14h18V10h-3zM8 6c0-2.206 1.794-4 4-4s4 1.794 4 4v4H8V6zm11 16H5V12h14v10z"></path></svg> OAuth2: petstore_auth</h5><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><p>Get access to data while protecting your account credentials.
OAuth2 is also a safer and more secure way to give you access.</p> OAuth2 is also a safer and more secure way to give you access.</p>
</div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-dkmUuB hFwAIA"><b>Flow type: </b><code>implicit </code></div><div class="sc-dkmUuB hFwAIA"><strong> Authorization URL: </strong><code><a target="_blank" rel="noopener noreferrer" href="http://petstore.swagger.io/api/oauth/dialog">http://petstore.swagger.io/api/oauth/dialog</a></code></div><div><b>Required scopes: </b><code>write:pets</code> <code>read:pets</code> </div><div class="sc-dkmUuB hFwAIA"><b> Scopes: </b></div><div class="sc-iEXKAA blExNw container" style="height: 4em;"><ul><li><code>write:pets</code> - <div class="sc-eeDRCY sc-eBMEME gTGgei fMmru sc-fhzFiK hXtrri redoc-markdown"><p>modify pets in your account</p> </div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-ejfMa-d bdDYxc"><b>Flow type: </b><code>implicit </code></div><div class="sc-ejfMa-d bdDYxc"><strong> Authorization URL: </strong><code><a target="_blank" rel="noopener noreferrer" href="http://petstore.swagger.io/api/oauth/dialog">http://petstore.swagger.io/api/oauth/dialog</a></code></div><div><b>Required scopes: </b><code>write:pets</code> <code>read:pets</code> </div><div class="sc-ejfMa-d bdDYxc"><b> Scopes: </b></div><div class="sc-EgOXT kRIdPi container" style="height: 4em;"><ul><li><code>write:pets</code> - <div class="sc-eeDRCY sc-eBMEME sc-fhzFiK gTGgei iCmQdS hXtrri redoc-markdown"><p>modify pets in your account</p>
</div></li><li><code>read:pets</code> - <div class="sc-eeDRCY sc-eBMEME gTGgei fMmru sc-fhzFiK hXtrri redoc-markdown"><p>read your pets</p> </div></li><li><code>read:pets</code> - <div class="sc-eeDRCY sc-eBMEME sc-fhzFiK gTGgei iCmQdS hXtrri redoc-markdown"><p>read your pets</p>
</div></li></ul></div><div class="sc-EgOXT bNSpXO"></div></div></div><div class="sc-ejfMa-d a-DjBE"><h5><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="11" height="11"><path fill="currentColor" d="M18 10V6A6 6 0 0 0 6 6v4H3v14h18V10h-3zM8 6c0-2.206 1.794-4 4-4s4 1.794 4 4v4H8V6zm11 16H5V12h14v10z"></path></svg> API Key: GitLab_PersonalAccessToken</h5><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><p>GitLab Personal Access Token description</p> </div></li></ul></div><div class="sc-eZYNyq dIKkVb"></div></div></div><div class="sc-iEXKAA ebCiwb"><h5><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="11" height="11"><path fill="currentColor" d="M18 10V6A6 6 0 0 0 6 6v4H3v14h18V10h-3zM8 6c0-2.206 1.794-4 4-4s4 1.794 4 4v4H8V6zm11 16H5V12h14v10z"></path></svg> API Key: GitLab_PersonalAccessToken</h5><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><p>GitLab Personal Access Token description</p>
</div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-dkmUuB hFwAIA"><b>Header parameter name: </b><code>PRIVATE-TOKEN</code></div></div></div><div class="sc-ejfMa-d a-DjBE"><h5><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="11" height="11"><path fill="currentColor" d="M18 10V6A6 6 0 0 0 6 6v4H3v14h18V10h-3zM8 6c0-2.206 1.794-4 4-4s4 1.794 4 4v4H8V6zm11 16H5V12h14v10z"></path></svg> OpenID Connect: GitLab_OpenIdConnect</h5><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><p>GitLab OpenIdConnect description</p> </div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-ejfMa-d bdDYxc"><b>Header parameter name: </b><code>PRIVATE-TOKEN</code></div></div></div><div class="sc-iEXKAA ebCiwb"><h5><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="11" height="11"><path fill="currentColor" d="M18 10V6A6 6 0 0 0 6 6v4H3v14h18V10h-3zM8 6c0-2.206 1.794-4 4-4s4 1.794 4 4v4H8V6zm11 16H5V12h14v10z"></path></svg> OpenID Connect: GitLab_OpenIdConnect</h5><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><p>GitLab OpenIdConnect description</p>
</div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-dkmUuB hFwAIA"><b>Connect URL: </b><code><a target="_blank" rel="noopener noreferrer" href="https://gitlab.com/.well-known/openid-configuration">https://gitlab.com/.well-known/openid-configuration</a></code></div></div></div><div class="sc-ejfMa-d a-DjBE"><h5><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="11" height="11"><path fill="currentColor" d="M18 10V6A6 6 0 0 0 6 6v4H3v14h18V10h-3zM8 6c0-2.206 1.794-4 4-4s4 1.794 4 4v4H8V6zm11 16H5V12h14v10z"></path></svg> HTTP: basicAuth</h5><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"></div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-dkmUuB hFwAIA"><b>HTTP Authorization Scheme: </b><code>basic</code></div><div class="sc-dkmUuB hFwAIA"></div></div></div>," </div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-ejfMa-d bdDYxc"><b>Connect URL: </b><code><a target="_blank" rel="noopener noreferrer" href="https://gitlab.com/.well-known/openid-configuration">https://gitlab.com/.well-known/openid-configuration</a></code></div></div></div><div class="sc-iEXKAA ebCiwb"><h5><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="11" height="11"><path fill="currentColor" d="M18 10V6A6 6 0 0 0 6 6v4H3v14h18V10h-3zM8 6c0-2.206 1.794-4 4-4s4 1.794 4 4v4H8V6zm11 16H5V12h14v10z"></path></svg> HTTP: basicAuth</h5><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"></div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-ejfMa-d bdDYxc"><b>HTTP Authorization Scheme: </b><code>basic</code></div><div class="sc-ejfMa-d bdDYxc"></div></div></div>,"
`; `;

View File

@ -1,4 +1,4 @@
import type { OpenAPISpec, OpenAPIPaths, OpenAPITag, OpenAPISchema } from '../types'; import type { OpenAPIPaths, OpenAPITag, OpenAPISchema } from '../types';
import { isOperationName, JsonPointer, alphabeticallyByProp } from '../utils'; import { isOperationName, JsonPointer, alphabeticallyByProp } from '../utils';
import { MarkdownRenderer } from './MarkdownRenderer'; import { MarkdownRenderer } from './MarkdownRenderer';
import { GroupModel, OperationModel } from './models'; import { GroupModel, OperationModel } from './models';
@ -17,9 +17,19 @@ export class MenuBuilder {
options: RedocNormalizedOptions, options: RedocNormalizedOptions,
): ContentItemModel[] { ): ContentItemModel[] {
const spec = parser.spec; const spec = parser.spec;
const { schemaDefinitionsTagName } = options;
const items: ContentItemModel[] = []; const items: ContentItemModel[] = [];
const tagsMap = MenuBuilder.getTagsWithOperations(parser, spec); const tags = [...(spec.tags || [])];
const hasAutogenerated = tags.find(
tag => tag?.name === schemaDefinitionsTagName,
);
if (!hasAutogenerated && schemaDefinitionsTagName) {
tags.push({ name: schemaDefinitionsTagName });
}
const tagsMap = MenuBuilder.getTagsWithOperations(parser, tags);
items.push(...MenuBuilder.addMarkdownItems(spec.info.description || '', undefined, 1, options)); items.push(...MenuBuilder.addMarkdownItems(spec.info.description || '', undefined, 1, options));
if (spec['x-tagGroups'] && spec['x-tagGroups'].length > 0) { if (spec['x-tagGroups'] && spec['x-tagGroups'].length > 0) {
items.push( items.push(
@ -28,6 +38,7 @@ export class MenuBuilder {
} else { } else {
items.push(...MenuBuilder.getTagsItems(parser, tagsMap, undefined, undefined, options)); items.push(...MenuBuilder.getTagsItems(parser, tagsMap, undefined, undefined, options));
} }
return items; return items;
} }
@ -141,6 +152,7 @@ export class MenuBuilder {
parser, parser,
tag, tag,
parent: item, parent: item,
schemaDefinitionsTagName: options.schemaDefinitionsTagName,
}); });
item.items = [ item.items = [
@ -195,10 +207,11 @@ export class MenuBuilder {
/** /**
* collects tags and maps each tag to list of operations belonging to this tag * collects tags and maps each tag to list of operations belonging to this tag
*/ */
static getTagsWithOperations(parser: OpenAPIParser, spec: OpenAPISpec): TagsInfoMap { static getTagsWithOperations(parser: OpenAPIParser, explicitTags: OpenAPITag[]): TagsInfoMap {
const { spec } = parser;
const tags: TagsInfoMap = {}; const tags: TagsInfoMap = {};
const webhooks = spec['x-webhooks'] || spec.webhooks; const webhooks = spec['x-webhooks'] || spec.webhooks;
for (const tag of spec.tags || []) { for (const tag of explicitTags || []) {
tags[tag.name] = { ...tag, operations: [] }; tags[tag.name] = { ...tag, operations: [] };
} }
@ -260,14 +273,18 @@ export class MenuBuilder {
parser, parser,
tag, tag,
parent, parent,
schemaDefinitionsTagName,
}: { }: {
parser: OpenAPIParser; parser: OpenAPIParser;
tag: TagInfo; tag: TagInfo;
parent: GroupModel; parent: GroupModel;
schemaDefinitionsTagName?: string;
}): GroupModel[] { }): GroupModel[] {
const defaultTags = schemaDefinitionsTagName ? [schemaDefinitionsTagName] : [];
return Object.entries(parser.spec.components?.schemas || {}) return Object.entries(parser.spec.components?.schemas || {})
.map(([schemaName, schema]) => { .map(([schemaName, schema]) => {
const schemaTags = schema['x-tags']; const schemaTags = schema['x-tags'] || defaultTags;
if (!schemaTags?.includes(tag.name)) return null; if (!schemaTags?.includes(tag.name)) return null;
const item = new GroupModel( const item = new GroupModel(

View File

@ -364,14 +364,18 @@ export class OpenAPIParser {
const allOf = schema.allOf; const allOf = schema.allOf;
for (let i = 0; i < allOf.length; i++) { for (let i = 0; i < allOf.length; i++) {
const sub = allOf[i]; const { oneOf, ...sub } = allOf[i];
if (Array.isArray(sub.oneOf)) { if (!oneOf) {
continue;
}
if (Array.isArray(oneOf)) {
const beforeAllOf = allOf.slice(0, i); const beforeAllOf = allOf.slice(0, i);
const afterAllOf = allOf.slice(i + 1); const afterAllOf = allOf.slice(i + 1);
const siblingValues = Object.keys(sub).length > 0 ? [sub] : [];
return { return {
oneOf: sub.oneOf.map((part: OpenAPISchema) => { oneOf: oneOf.map((part: OpenAPISchema) => {
return { return {
allOf: [...beforeAllOf, part, ...afterAllOf], allOf: [...beforeAllOf, ...siblingValues, part, ...afterAllOf],
'x-refsStack': refsStack, 'x-refsStack': refsStack,
}; };
}), }),

View File

@ -6,23 +6,32 @@ import { setRedocLabels } from './Labels';
import { SideNavStyleEnum } from './types'; import { SideNavStyleEnum } from './types';
import type { LabelsConfigRaw, MDXComponentMeta } from './types'; import type { LabelsConfigRaw, MDXComponentMeta } from './types';
export type DownloadUrlsConfig = {
title?: string;
url: string;
}[];
export interface RedocRawOptions { export interface RedocRawOptions {
theme?: ThemeInterface; theme?: ThemeInterface;
scrollYOffset?: number | string | (() => number); scrollYOffset?: number | string | (() => number);
hideHostname?: boolean | string; hideHostname?: boolean | string;
expandResponses?: string | 'all'; expandResponses?: string | 'all';
requiredPropsFirst?: boolean | string; requiredPropsFirst?: boolean | string; // remove in next major release
sortRequiredPropsFirst?: boolean | string;
sortPropsAlphabetically?: boolean | string; sortPropsAlphabetically?: boolean | string;
sortEnumValuesAlphabetically?: boolean | string; sortEnumValuesAlphabetically?: boolean | string;
sortOperationsAlphabetically?: boolean | string; sortOperationsAlphabetically?: boolean | string;
sortTagsAlphabetically?: boolean | string; sortTagsAlphabetically?: boolean | string;
nativeScrollbars?: boolean | string; nativeScrollbars?: boolean | string;
pathInMiddlePanel?: boolean | string; pathInMiddlePanel?: boolean | string;
untrustedSpec?: boolean | string; untrustedSpec?: boolean | string; // remove in next major release
sanitize?: boolean | string;
hideLoading?: boolean | string; hideLoading?: boolean | string;
hideDownloadButton?: boolean | string; hideDownloadButton?: boolean | string; // remove in next major release
hideDownloadButtons?: boolean | string;
downloadFileName?: string; downloadFileName?: string;
downloadDefinitionUrl?: string; downloadDefinitionUrl?: string;
downloadUrls?: DownloadUrlsConfig;
disableSearch?: boolean | string; disableSearch?: boolean | string;
onlyRequiredInSamples?: boolean | string; onlyRequiredInSamples?: boolean | string;
showExtensions?: boolean | string | string[]; showExtensions?: boolean | string | string[];
@ -30,12 +39,15 @@ export interface RedocRawOptions {
hideSingleRequestSampleTab?: boolean | string; hideSingleRequestSampleTab?: boolean | string;
hideRequestPayloadSample?: boolean; hideRequestPayloadSample?: boolean;
menuToggle?: boolean | string; menuToggle?: boolean | string;
jsonSampleExpandLevel?: number | string | 'all'; jsonSampleExpandLevel?: number | string | 'all'; // remove in next major release
jsonSamplesExpandLevel?: number | string | 'all';
hideSchemaTitles?: boolean | string; hideSchemaTitles?: boolean | string;
simpleOneOfTypeLabel?: boolean | string; simpleOneOfTypeLabel?: boolean | string;
payloadSampleIdx?: number; payloadSampleIdx?: number;
expandSingleSchemaField?: boolean | string; expandSingleSchemaField?: boolean | string;
schemaExpansionLevel?: number | string | 'all'; schemaExpansionLevel?: number | string | 'all'; // remove in next major release
schemasExpansionLevel?: number | string | 'all';
schemaDefinitionsTagName?: string;
showObjectSchemaExamples?: boolean | string; showObjectSchemaExamples?: boolean | string;
showSecuritySchemeType?: boolean; showSecuritySchemeType?: boolean;
hideSecuritySection?: boolean; hideSecuritySection?: boolean;
@ -52,11 +64,13 @@ export interface RedocRawOptions {
maxDisplayedEnumValues?: number; maxDisplayedEnumValues?: number;
ignoreNamedSchemas?: string[] | string; ignoreNamedSchemas?: string[] | string;
hideSchemaPattern?: boolean; hideSchemaPattern?: boolean;
generatedPayloadSamplesMaxDepth?: number; generatedPayloadSamplesMaxDepth?: number; // remove in next major release
generatedSamplesMaxDepth?: number;
nonce?: string; nonce?: string;
hideFab?: boolean; hideFab?: boolean;
minCharacterLengthToInitSearch?: number; minCharacterLengthToInitSearch?: number;
showWebhookVerb?: boolean; showWebhookVerb?: boolean;
hidePropertiesPrefix?: boolean;
} }
export function argValueToBoolean(val?: string | boolean, defaultValue?: boolean): boolean { export function argValueToBoolean(val?: string | boolean, defaultValue?: boolean): boolean {
@ -216,17 +230,18 @@ export class RedocNormalizedOptions {
scrollYOffset: () => number; scrollYOffset: () => number;
hideHostname: boolean; hideHostname: boolean;
expandResponses: { [code: string]: boolean } | 'all'; expandResponses: { [code: string]: boolean } | 'all';
requiredPropsFirst: boolean; sortRequiredPropsFirst: boolean;
sortPropsAlphabetically: boolean; sortPropsAlphabetically: boolean;
sortEnumValuesAlphabetically: boolean; sortEnumValuesAlphabetically: boolean;
sortOperationsAlphabetically: boolean; sortOperationsAlphabetically: boolean;
sortTagsAlphabetically: boolean; sortTagsAlphabetically: boolean;
nativeScrollbars: boolean; nativeScrollbars: boolean;
pathInMiddlePanel: boolean; pathInMiddlePanel: boolean;
untrustedSpec: boolean; sanitize: boolean;
hideDownloadButton: boolean; hideDownloadButtons: boolean;
downloadFileName?: string; downloadFileName?: string;
downloadDefinitionUrl?: string; downloadDefinitionUrl?: string;
downloadUrls?: DownloadUrlsConfig;
disableSearch: boolean; disableSearch: boolean;
onlyRequiredInSamples: boolean; onlyRequiredInSamples: boolean;
showExtensions: boolean | string[]; showExtensions: boolean | string[];
@ -234,13 +249,14 @@ export class RedocNormalizedOptions {
hideSingleRequestSampleTab: boolean; hideSingleRequestSampleTab: boolean;
hideRequestPayloadSample: boolean; hideRequestPayloadSample: boolean;
menuToggle: boolean; menuToggle: boolean;
jsonSampleExpandLevel: number; jsonSamplesExpandLevel: number;
enumSkipQuotes: boolean; enumSkipQuotes: boolean;
hideSchemaTitles: boolean; hideSchemaTitles: boolean;
simpleOneOfTypeLabel: boolean; simpleOneOfTypeLabel: boolean;
payloadSampleIdx: number; payloadSampleIdx: number;
expandSingleSchemaField: boolean; expandSingleSchemaField: boolean;
schemaExpansionLevel: number; schemasExpansionLevel: number;
schemaDefinitionsTagName?: string;
showObjectSchemaExamples: boolean; showObjectSchemaExamples: boolean;
showSecuritySchemeType?: boolean; showSecuritySchemeType?: boolean;
hideSecuritySection?: boolean; hideSecuritySection?: boolean;
@ -254,10 +270,11 @@ export class RedocNormalizedOptions {
ignoreNamedSchemas: Set<string>; ignoreNamedSchemas: Set<string>;
hideSchemaPattern: boolean; hideSchemaPattern: boolean;
generatedPayloadSamplesMaxDepth: number; generatedSamplesMaxDepth: number;
hideFab: boolean; hideFab: boolean;
minCharacterLengthToInitSearch: number; minCharacterLengthToInitSearch: number;
showWebhookVerb: boolean; showWebhookVerb: boolean;
hidePropertiesPrefix?: boolean;
nonce?: string; nonce?: string;
@ -288,17 +305,20 @@ export class RedocNormalizedOptions {
this.scrollYOffset = RedocNormalizedOptions.normalizeScrollYOffset(raw.scrollYOffset); this.scrollYOffset = RedocNormalizedOptions.normalizeScrollYOffset(raw.scrollYOffset);
this.hideHostname = RedocNormalizedOptions.normalizeHideHostname(raw.hideHostname); this.hideHostname = RedocNormalizedOptions.normalizeHideHostname(raw.hideHostname);
this.expandResponses = RedocNormalizedOptions.normalizeExpandResponses(raw.expandResponses); this.expandResponses = RedocNormalizedOptions.normalizeExpandResponses(raw.expandResponses);
this.requiredPropsFirst = argValueToBoolean(raw.requiredPropsFirst); this.sortRequiredPropsFirst = argValueToBoolean(
raw.sortRequiredPropsFirst || raw.requiredPropsFirst,
);
this.sortPropsAlphabetically = argValueToBoolean(raw.sortPropsAlphabetically); this.sortPropsAlphabetically = argValueToBoolean(raw.sortPropsAlphabetically);
this.sortEnumValuesAlphabetically = argValueToBoolean(raw.sortEnumValuesAlphabetically); this.sortEnumValuesAlphabetically = argValueToBoolean(raw.sortEnumValuesAlphabetically);
this.sortOperationsAlphabetically = argValueToBoolean(raw.sortOperationsAlphabetically); this.sortOperationsAlphabetically = argValueToBoolean(raw.sortOperationsAlphabetically);
this.sortTagsAlphabetically = argValueToBoolean(raw.sortTagsAlphabetically); this.sortTagsAlphabetically = argValueToBoolean(raw.sortTagsAlphabetically);
this.nativeScrollbars = argValueToBoolean(raw.nativeScrollbars); this.nativeScrollbars = argValueToBoolean(raw.nativeScrollbars);
this.pathInMiddlePanel = argValueToBoolean(raw.pathInMiddlePanel); this.pathInMiddlePanel = argValueToBoolean(raw.pathInMiddlePanel);
this.untrustedSpec = argValueToBoolean(raw.untrustedSpec); this.sanitize = argValueToBoolean(raw.sanitize || raw.untrustedSpec);
this.hideDownloadButton = argValueToBoolean(raw.hideDownloadButton); this.hideDownloadButtons = argValueToBoolean(raw.hideDownloadButtons || raw.hideDownloadButton);
this.downloadFileName = raw.downloadFileName; this.downloadFileName = raw.downloadFileName;
this.downloadDefinitionUrl = raw.downloadDefinitionUrl; this.downloadDefinitionUrl = raw.downloadDefinitionUrl;
this.downloadUrls = raw.downloadUrls;
this.disableSearch = argValueToBoolean(raw.disableSearch); this.disableSearch = argValueToBoolean(raw.disableSearch);
this.onlyRequiredInSamples = argValueToBoolean(raw.onlyRequiredInSamples); this.onlyRequiredInSamples = argValueToBoolean(raw.onlyRequiredInSamples);
this.showExtensions = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions); this.showExtensions = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions);
@ -306,15 +326,18 @@ export class RedocNormalizedOptions {
this.hideSingleRequestSampleTab = argValueToBoolean(raw.hideSingleRequestSampleTab); this.hideSingleRequestSampleTab = argValueToBoolean(raw.hideSingleRequestSampleTab);
this.hideRequestPayloadSample = argValueToBoolean(raw.hideRequestPayloadSample); this.hideRequestPayloadSample = argValueToBoolean(raw.hideRequestPayloadSample);
this.menuToggle = argValueToBoolean(raw.menuToggle, true); this.menuToggle = argValueToBoolean(raw.menuToggle, true);
this.jsonSampleExpandLevel = RedocNormalizedOptions.normalizeJsonSampleExpandLevel( this.jsonSamplesExpandLevel = RedocNormalizedOptions.normalizeJsonSampleExpandLevel(
raw.jsonSampleExpandLevel, raw.jsonSamplesExpandLevel || raw.jsonSampleExpandLevel,
); );
this.enumSkipQuotes = argValueToBoolean(raw.enumSkipQuotes); this.enumSkipQuotes = argValueToBoolean(raw.enumSkipQuotes);
this.hideSchemaTitles = argValueToBoolean(raw.hideSchemaTitles); this.hideSchemaTitles = argValueToBoolean(raw.hideSchemaTitles);
this.simpleOneOfTypeLabel = argValueToBoolean(raw.simpleOneOfTypeLabel); this.simpleOneOfTypeLabel = argValueToBoolean(raw.simpleOneOfTypeLabel);
this.payloadSampleIdx = RedocNormalizedOptions.normalizePayloadSampleIdx(raw.payloadSampleIdx); this.payloadSampleIdx = RedocNormalizedOptions.normalizePayloadSampleIdx(raw.payloadSampleIdx);
this.expandSingleSchemaField = argValueToBoolean(raw.expandSingleSchemaField); this.expandSingleSchemaField = argValueToBoolean(raw.expandSingleSchemaField);
this.schemaExpansionLevel = argValueToExpandLevel(raw.schemaExpansionLevel); this.schemasExpansionLevel = argValueToExpandLevel(
raw.schemasExpansionLevel || raw.schemaExpansionLevel,
);
this.schemaDefinitionsTagName = raw.schemaDefinitionsTagName;
this.showObjectSchemaExamples = argValueToBoolean(raw.showObjectSchemaExamples); this.showObjectSchemaExamples = argValueToBoolean(raw.showObjectSchemaExamples);
this.showSecuritySchemeType = argValueToBoolean(raw.showSecuritySchemeType); this.showSecuritySchemeType = argValueToBoolean(raw.showSecuritySchemeType);
this.hideSecuritySection = argValueToBoolean(raw.hideSecuritySection); this.hideSecuritySection = argValueToBoolean(raw.hideSecuritySection);
@ -330,13 +353,13 @@ export class RedocNormalizedOptions {
: raw.ignoreNamedSchemas?.split(',').map(s => s.trim()); : raw.ignoreNamedSchemas?.split(',').map(s => s.trim());
this.ignoreNamedSchemas = new Set(ignoreNamedSchemas); this.ignoreNamedSchemas = new Set(ignoreNamedSchemas);
this.hideSchemaPattern = argValueToBoolean(raw.hideSchemaPattern); this.hideSchemaPattern = argValueToBoolean(raw.hideSchemaPattern);
this.generatedPayloadSamplesMaxDepth = this.generatedSamplesMaxDepth = RedocNormalizedOptions.normalizeGeneratedPayloadSamplesMaxDepth(
RedocNormalizedOptions.normalizeGeneratedPayloadSamplesMaxDepth( raw.generatedSamplesMaxDepth || raw.generatedPayloadSamplesMaxDepth,
raw.generatedPayloadSamplesMaxDepth,
); );
this.nonce = raw.nonce; this.nonce = raw.nonce;
this.hideFab = argValueToBoolean(raw.hideFab); this.hideFab = argValueToBoolean(raw.hideFab);
this.minCharacterLengthToInitSearch = argValueToNumber(raw.minCharacterLengthToInitSearch) || 3; this.minCharacterLengthToInitSearch = argValueToNumber(raw.minCharacterLengthToInitSearch) || 3;
this.showWebhookVerb = argValueToBoolean(raw.showWebhookVerb); this.showWebhookVerb = argValueToBoolean(raw.showWebhookVerb);
this.hidePropertiesPrefix = argValueToBoolean(raw.hidePropertiesPrefix, true);
} }
} }

View File

@ -21,6 +21,14 @@ exports[`Models Schema should hoist oneOfs when mergin allOf 1`] = `
"oneOf": [ "oneOf": [
{ {
"allOf": [ "allOf": [
{
"properties": {
"id": {
"description": "The user's ID",
"type": "integer",
},
},
},
{ {
"properties": { "properties": {
"username": { "username": {
@ -61,6 +69,14 @@ exports[`Models Schema should hoist oneOfs when mergin allOf 1`] = `
}, },
{ {
"allOf": [ "allOf": [
{
"properties": {
"id": {
"description": "The user's ID",
"type": "integer",
},
},
},
{ {
"properties": { "properties": {
"email": { "email": {
@ -99,6 +115,55 @@ exports[`Models Schema should hoist oneOfs when mergin allOf 1`] = `
], ],
"x-refsStack": undefined, "x-refsStack": undefined,
}, },
{
"allOf": [
{
"properties": {
"id": {
"description": "The user's ID",
"type": "integer",
},
},
},
{
"properties": {
"id": {
"description": "The user's ID",
"format": "uuid",
"type": "string",
},
},
},
{
"properties": {
"extra": {
"type": "string",
},
},
},
{
"oneOf": [
{
"properties": {
"password": {
"description": "The user's password",
"type": "string",
},
},
},
{
"properties": {
"mobile": {
"description": "The user's mobile",
"type": "string",
},
},
},
],
},
],
"x-refsStack": undefined,
},
], ],
} }
`; `;

View File

@ -0,0 +1,24 @@
{
"openapi": "3.0.0",
"info": {
"version": "1.0",
"title": "Test"
},
"components": {
"schemas": {
"Test": {
"type": "array",
"description": "test description",
"items": {
"type": "string",
"description": "test description",
"enum": ["authorize", "do-nothing"],
"x-enumDescriptions": {
"authorize-and-void": "Will create an authorize transaction in the amount/currency of the request, followed by a void",
"do-nothing": "Will do nothing, and return an approved `setup` transaction. This is the default behavior."
}
}
}
}
}
}

View File

@ -9,6 +9,12 @@
"test": { "test": {
"allOf": [ "allOf": [
{ {
"properties": {
"id": {
"description": "The user's ID",
"type": "integer"
}
},
"oneOf": [ "oneOf": [
{ {
"properties": { "properties": {
@ -25,6 +31,15 @@
"type": "string" "type": "string"
} }
} }
},
{
"properties": {
"id": {
"description": "The user's ID",
"type": "string",
"format": "uuid"
}
}
} }
] ]
}, },

View File

@ -139,10 +139,18 @@ describe('Models', () => {
} as any; } as any;
const opts = new RedocNormalizedOptions({ const opts = new RedocNormalizedOptions({
downloadDefinitionUrl: 'https:test.com/filename.yaml', downloadUrls: [{ title: 'Openapi description', url: 'https:test.com/filename.yaml' }],
}); });
const info = new ApiInfoModel(parser, opts); const info = new ApiInfoModel(parser, opts);
expect(info.downloadLink).toEqual('https:test.com/filename.yaml'); expect(info.downloadUrls).toMatchInlineSnapshot(`
[
{
"title": "Openapi description",
"url": "https:test.com/filename.yaml",
},
]
`);
expect(info.downloadFileName).toMatchInlineSnapshot(`"openapi.json"`);
}); });
test('should correctly populate download link and download file name', () => { test('should correctly populate download link and download file name', () => {
@ -158,8 +166,29 @@ describe('Models', () => {
downloadFileName: 'test.yaml', downloadFileName: 'test.yaml',
}); });
const info = new ApiInfoModel(parser, opts); const info = new ApiInfoModel(parser, opts);
expect(info.downloadLink).toEqual('https:test.com/filename.yaml'); expect(info.downloadUrls).toMatchInlineSnapshot(`
expect(info.downloadFileName).toEqual('test.yaml'); [
{
"title": "Download",
"url": "https:test.com/filename.yaml",
},
]
`);
expect(info.downloadFileName).toMatchInlineSnapshot(`"test.yaml"`);
const opts2 = new RedocNormalizedOptions({
downloadUrls: [{ title: 'Download file', url: 'https:test.com/filename.yaml' }],
});
const info2 = new ApiInfoModel(parser, opts2);
expect(info2.downloadUrls).toMatchInlineSnapshot(`
[
{
"title": "Download file",
"url": "https:test.com/filename.yaml",
},
]
`);
expect(info2.downloadFileName).toMatchInlineSnapshot(`"openapi.json"`);
}); });
}); });
}); });

View File

@ -13,6 +13,17 @@ describe('Models', () => {
describe('Schema', () => { describe('Schema', () => {
let parser; let parser;
test('parsing nested x-enumDescription', () => {
const spec = require('../fixtures/nestedEnumDescroptionSample.json');
parser = new OpenAPIParser(spec, undefined, opts);
const testSchema = spec.components.schemas.Test;
const schemaModel = new SchemaModel(parser, testSchema, '', opts);
expect(schemaModel['x-enumDescriptions']).toStrictEqual(
testSchema.items['x-enumDescriptions'],
);
});
test('discriminator with one field', () => { test('discriminator with one field', () => {
const spec = require('../fixtures/discriminator.json'); const spec = require('../fixtures/discriminator.json');
parser = new OpenAPIParser(spec, undefined, opts); parser = new OpenAPIParser(spec, undefined, opts);

View File

@ -1,5 +1,6 @@
import type { OpenAPIContact, OpenAPIInfo, OpenAPILicense } from '../../types'; import type { OpenAPIContact, OpenAPIInfo, OpenAPILicense } from '../../types';
import { IS_BROWSER } from '../../utils/'; import { IS_BROWSER } from '../../utils/';
import { l } from '../Labels';
import type { OpenAPIParser } from '../OpenAPIParser'; import type { OpenAPIParser } from '../OpenAPIParser';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
@ -13,7 +14,10 @@ export class ApiInfoModel implements OpenAPIInfo {
contact?: OpenAPIContact; contact?: OpenAPIContact;
license?: OpenAPILicense; license?: OpenAPILicense;
downloadLink?: string; downloadUrls: {
title?: string;
url?: string;
}[];
downloadFileName?: string; downloadFileName?: string;
constructor( constructor(
@ -29,13 +33,28 @@ export class ApiInfoModel implements OpenAPIInfo {
this.description = this.description.substring(0, firstHeadingLinePos); this.description = this.description.substring(0, firstHeadingLinePos);
} }
this.downloadLink = this.getDownloadLink(); this.downloadUrls = this.getDownloadUrls();
this.downloadFileName = this.getDownloadFileName(); this.downloadFileName = this.getDownloadFileName();
} }
private getDownloadUrls() {
return (
!this.options.downloadUrls
? [
{
title: l('download'),
url: this.getDownloadLink(this.options.downloadDefinitionUrl),
},
]
: this.options.downloadUrls.map(({ title, url }) => ({
title: title || l('download'),
url: this.getDownloadLink(url),
}))
).filter(({ title, url }) => title && url);
}
private getDownloadLink(): string | undefined { private getDownloadLink(url?: string): string | undefined {
if (this.options.downloadDefinitionUrl) { if (url) {
return this.options.downloadDefinitionUrl; return url;
} }
if (this.parser.specUrl) { if (this.parser.specUrl) {

View File

@ -14,7 +14,7 @@ export class MediaTypeModel {
name: string; name: string;
isRequestType: boolean; isRequestType: boolean;
onlyRequiredInSamples: boolean; onlyRequiredInSamples: boolean;
generatedPayloadSamplesMaxDepth: number; generatedSamplesMaxDepth: number;
/** /**
* @param isRequestType needed to know if skipe RO/RW fields in objects * @param isRequestType needed to know if skipe RO/RW fields in objects
@ -30,7 +30,7 @@ export class MediaTypeModel {
this.isRequestType = isRequestType; this.isRequestType = isRequestType;
this.schema = info.schema && new SchemaModel(parser, info.schema, '', options); this.schema = info.schema && new SchemaModel(parser, info.schema, '', options);
this.onlyRequiredInSamples = options.onlyRequiredInSamples; this.onlyRequiredInSamples = options.onlyRequiredInSamples;
this.generatedPayloadSamplesMaxDepth = options.generatedPayloadSamplesMaxDepth; this.generatedSamplesMaxDepth = options.generatedSamplesMaxDepth;
if (info.examples !== undefined) { if (info.examples !== undefined) {
this.examples = mapValues( this.examples = mapValues(
info.examples, info.examples,
@ -55,7 +55,7 @@ export class MediaTypeModel {
skipReadOnly: this.isRequestType, skipReadOnly: this.isRequestType,
skipWriteOnly: !this.isRequestType, skipWriteOnly: !this.isRequestType,
skipNonRequired: this.isRequestType && this.onlyRequiredInSamples, skipNonRequired: this.isRequestType && this.onlyRequiredInSamples,
maxSampleDepth: this.generatedPayloadSamplesMaxDepth, maxSampleDepth: this.generatedSamplesMaxDepth,
}; };
if (this.schema && this.schema.oneOf) { if (this.schema && this.schema.oneOf) {
this.examples = {}; this.examples = {};

View File

@ -20,7 +20,12 @@ import { RequestBodyModel } from './RequestBody';
import { ResponseModel } from './Response'; import { ResponseModel } from './Response';
import { SideNavStyleEnum } from '../types'; import { SideNavStyleEnum } from '../types';
import type { OpenAPIExternalDocumentation, OpenAPIServer, OpenAPIXCodeSample } from '../../types'; import type {
OpenAPIExternalDocumentation,
OpenAPIServer,
OpenAPIXBadges,
OpenAPIXCodeSample,
} from '../../types';
import type { OpenAPIParser } from '../OpenAPIParser'; import type { OpenAPIParser } from '../OpenAPIParser';
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import type { MediaContentModel } from './MediaContent'; import type { MediaContentModel } from './MediaContent';
@ -71,6 +76,7 @@ export class OperationModel implements IMenuItem {
operationId?: string; operationId?: string;
operationHash?: string; operationHash?: string;
httpVerb: string; httpVerb: string;
badges: OpenAPIXBadges[];
deprecated: boolean; deprecated: boolean;
path: string; path: string;
servers: OpenAPIServer[]; servers: OpenAPIServer[];
@ -112,6 +118,12 @@ export class OperationModel implements IMenuItem {
: options.sideNavStyle === SideNavStyleEnum.PathOnly : options.sideNavStyle === SideNavStyleEnum.PathOnly
? this.path ? this.path
: this.name; : this.name;
this.badges =
operationSpec['x-badges']?.map(({ name, color, position }) => ({
name,
color: color,
position: position || 'after',
})) || [];
if (this.isCallback) { if (this.isCallback) {
// NOTE: Callbacks by default should not inherit the specification's global `security` definition. // NOTE: Callbacks by default should not inherit the specification's global `security` definition.
@ -235,7 +247,7 @@ export class OperationModel implements IMenuItem {
if (this.options.sortPropsAlphabetically) { if (this.options.sortPropsAlphabetically) {
return sortByField(_parameters, 'name'); return sortByField(_parameters, 'name');
} }
if (this.options.requiredPropsFirst) { if (this.options.sortRequiredPropsFirst) {
return sortByRequired(_parameters); return sortByRequired(_parameters);
} }

View File

@ -65,6 +65,7 @@ export class SchemaModel {
rawSchema: OpenAPISchema; rawSchema: OpenAPISchema;
schema: MergedOpenAPISchema; schema: MergedOpenAPISchema;
extensions?: Record<string, any>; extensions?: Record<string, any>;
'x-enumDescriptions': { [name: string]: string };
const: any; const: any;
contentEncoding?: string; contentEncoding?: string;
contentMediaType?: string; contentMediaType?: string;
@ -122,6 +123,7 @@ export class SchemaModel {
this.type = schema.type || detectType(schema); this.type = schema.type || detectType(schema);
this.format = schema.format; this.format = schema.format;
this.enum = schema.enum || []; this.enum = schema.enum || [];
this['x-enumDescriptions'] = schema['x-enumDescriptions'];
this.example = schema.example; this.example = schema.example;
this.examples = schema.examples; this.examples = schema.examples;
this.deprecated = !!schema.deprecated; this.deprecated = !!schema.deprecated;
@ -221,6 +223,7 @@ export class SchemaModel {
} }
if (this.items?.isPrimitive) { if (this.items?.isPrimitive) {
this.enum = this.items.enum; this.enum = this.items.enum;
this['x-enumDescriptions'] = this.items['x-enumDescriptions'];
} }
if (isArray(this.type)) { if (isArray(this.type)) {
const filteredType = this.type.filter(item => item !== 'array'); const filteredType = this.type.filter(item => item !== 'array');
@ -463,7 +466,7 @@ function buildFields(
if (options.sortPropsAlphabetically) { if (options.sortPropsAlphabetically) {
fields = sortByField(fields, 'name'); fields = sortByField(fields, 'name');
} }
if (options.requiredPropsFirst) { if (options.sortRequiredPropsFirst) {
// if not sort alphabetically sort in the order from required keyword // if not sort alphabetically sort in the order from required keyword
fields = sortByRequired(fields, !options.sortPropsAlphabetically ? schema.required : undefined); fields = sortByRequired(fields, !options.sortPropsAlphabetically ? schema.required : undefined);
} }

View File

@ -70,6 +70,12 @@ export interface OpenAPIXCodeSample {
source: string; source: string;
} }
export interface OpenAPIXBadges {
name: string;
color?: string;
position?: 'before' | 'after';
}
export interface OpenAPIOperation { export interface OpenAPIOperation {
tags?: string[]; tags?: string[];
summary?: string; summary?: string;
@ -85,6 +91,7 @@ export interface OpenAPIOperation {
servers?: OpenAPIServer[]; servers?: OpenAPIServer[];
'x-codeSamples'?: OpenAPIXCodeSample[]; 'x-codeSamples'?: OpenAPIXCodeSample[];
'x-code-samples'?: OpenAPIXCodeSample[]; // deprecated 'x-code-samples'?: OpenAPIXCodeSample[]; // deprecated
'x-badges'?: OpenAPIXBadges[];
} }
export interface OpenAPIParameter { export interface OpenAPIParameter {

View File

@ -311,6 +311,11 @@ exports[`#loadAndBundleSpec should load And Bundle Spec demo/openapi.yaml 1`] =
"sold", "sold",
], ],
"type": "string", "type": "string",
"x-enumDescriptions": {
"available": "Available status",
"pending": "Pending status",
"sold": "Sold status",
},
}, },
"tags": { "tags": {
"description": "Tags attached to the pet", "description": "Tags attached to the pet",
@ -581,6 +586,13 @@ and standard method from web, mobile and desktop applications.
"tags": [ "tags": [
"pet", "pet",
], ],
"x-badges": [
{
"color": "purple",
"name": "Beta",
"position": "before",
},
],
"x-codeSamples": [ "x-codeSamples": [
{ {
"lang": "C#", "lang": "C#",
@ -645,6 +657,12 @@ try {
"tags": [ "tags": [
"pet", "pet",
], ],
"x-badges": [
{
"color": "purple",
"name": "Alpha",
},
],
"x-codeSamples": [ "x-codeSamples": [
{ {
"lang": "PHP", "lang": "PHP",
@ -883,6 +901,11 @@ try {
"tags": [ "tags": [
"pet", "pet",
], ],
"x-badges": [
{
"name": "Gamma",
},
],
}, },
"post": { "post": {
"description": "", "description": "",

View File

@ -71,6 +71,30 @@ describe('Utils', () => {
const obj2 = { a: ['C'], b: ['D'] }; const obj2 = { a: ['C'], b: ['D'] };
expect(mergeObjects({}, obj1, obj2)).toEqual({ a: ['C'], b: ['D'] }); expect(mergeObjects({}, obj1, obj2)).toEqual({ a: ['C'], b: ['D'] });
}); });
test('should prevent prototype pollution', () => {
const target = {};
const source = JSON.parse('{"__proto__": {"polluted": "yes"}}');
mergeObjects(target, source);
expect(({} as any).polluted).toBeUndefined();
});
test('should merge objects correctly', () => {
const target = { a: 1 };
const source = { b: 2 };
const result = mergeObjects(target, source);
expect(result).toEqual({ a: 1, b: 2 });
});
test('should handle nested objects', () => {
const target = { a: { b: 1 } };
const source = { a: { c: 2 } };
const result = mergeObjects(target, source);
expect(result).toEqual({ a: { b: 1, c: 2 } });
});
}); });
describe('titleize', () => { describe('titleize', () => {

View File

@ -81,7 +81,6 @@ export function appendToMdHeading(md: string, heading: string, content: string)
} }
} }
// credits https://stackoverflow.com/a/46973278/1749888
export const mergeObjects = (target: any, ...sources: any[]): any => { export const mergeObjects = (target: any, ...sources: any[]): any => {
if (!sources.length) { if (!sources.length) {
return target; return target;
@ -93,6 +92,7 @@ export const mergeObjects = (target: any, ...sources: any[]): any => {
if (isMergebleObject(target) && isMergebleObject(source)) { if (isMergebleObject(target) && isMergebleObject(source)) {
Object.keys(source).forEach((key: string) => { Object.keys(source).forEach((key: string) => {
if (Object.prototype.hasOwnProperty.call(source, key) && key !== '__proto__') {
if (isMergebleObject(source[key])) { if (isMergebleObject(source[key])) {
if (!target[key]) { if (!target[key]) {
target[key] = {}; target[key] = {};
@ -101,6 +101,7 @@ export const mergeObjects = (target: any, ...sources: any[]): any => {
} else { } else {
target[key] = source[key]; target[key] = source[key];
} }
}
}); });
} }

View File

@ -393,7 +393,7 @@ export function getSerializedValue(field: FieldModel, example: any) {
// decode for better readability in examples: see https://github.com/Redocly/redoc/issues/1138 // decode for better readability in examples: see https://github.com/Redocly/redoc/issues/1138
return decodeURIComponent(serializeParameterValue(field, example)); return decodeURIComponent(serializeParameterValue(field, example));
} else { } else {
return String(example); return typeof example === 'object' ? example : String(example);
} }
} }
@ -654,12 +654,13 @@ export function isRedocExtension(key: string): boolean {
'x-codeSamples': true, 'x-codeSamples': true,
'x-displayName': true, 'x-displayName': true,
'x-examples': true, 'x-examples': true,
'x-ignoredHeaderParameters': true, 'x-enumDescriptions': true,
'x-logo': true, 'x-logo': true,
'x-nullable': true, 'x-nullable': true,
'x-servers': true, 'x-servers': true,
'x-tagGroups': true, 'x-tagGroups': true,
'x-traitTag': true, 'x-traitTag': true,
'x-badges': true,
'x-additionalPropertiesName': true, 'x-additionalPropertiesName': true,
'x-explicitMappingOnly': true, 'x-explicitMappingOnly': true,
}; };