From f3862d58a8bab56b54732122ae88cdbe1c3bfb4c Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Thu, 12 May 2022 15:57:12 +0300 Subject: [PATCH 01/19] chore: cli-v0.13.13 (#2004) --- cli/npm-shrinkwrap.json | 124 ++++++++++++++++++---------------------- cli/package.json | 4 +- 2 files changed, 58 insertions(+), 70 deletions(-) diff --git a/cli/npm-shrinkwrap.json b/cli/npm-shrinkwrap.json index 9ac75b6d..b6c6000c 100644 --- a/cli/npm-shrinkwrap.json +++ b/cli/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "redoc-cli", - "version": "0.13.12", + "version": "0.13.13", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "redoc-cli", - "version": "0.13.12", + "version": "0.13.13", "license": "MIT", "dependencies": { "chokidar": "^3.5.1", @@ -17,7 +17,7 @@ "node-libs-browser": "^2.2.1", "react": "^17.0.1", "react-dom": "^17.0.1", - "redoc": "2.0.0-rc.68", + "redoc": "2.0.0-rc.69", "styled-components": "^5.3.0", "yargs": "^17.3.1" }, @@ -216,9 +216,9 @@ } }, "node_modules/@redocly/openapi-core": { - "version": "1.0.0-beta.96", - "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.0.0-beta.96.tgz", - "integrity": "sha512-tcy0q+9PRWV4rcnVx5uHII/9Cq9qpUzWNppupAaVgutxjQRPWH45e24NLinn6lA8Q4por6HuMYkk/0QAJE8d3A==", + "version": "1.0.0-beta.97", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.0.0-beta.97.tgz", + "integrity": "sha512-3WW9/6flosJuRtU3GI0Vw39OYFZqqXMDCp5TLa3EjXOb7Nm6AZTWRb3Y+I/+UdNJ/NTszVJkQczoa1t476ekiQ==", "dependencies": { "@redocly/ajv": "^8.6.4", "@types/node": "^14.11.8", @@ -226,7 +226,7 @@ "js-levenshtein": "^1.1.6", "js-yaml": "^4.1.0", "lodash.isequal": "^4.5.0", - "minimatch": "^3.0.4", + "minimatch": "^5.0.1", "node-fetch": "^2.6.1", "pluralize": "^8.0.0", "yaml-ast-parser": "0.0.43" @@ -236,9 +236,9 @@ } }, "node_modules/@redocly/openapi-core/node_modules/@types/node": { - "version": "14.18.16", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.16.tgz", - "integrity": "sha512-X3bUMdK/VmvrWdoTkz+VCn6nwKwrKCFTHtqwBIaQJNx4RUIBBUFXM00bqPz/DsDd+Icjmzm6/tyYZzeGVqb6/Q==" + "version": "14.18.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.17.tgz", + "integrity": "sha512-oajWz4kOajqpKJMPgnCvBajPq8QAvl2xIWoFjlAJPKGu6n7pjov5SxGE45a+0RxHDoo4ycOMoZw1SCOWtDERbw==" }, "node_modules/@types/chokidar": { "version": "2.1.3", @@ -646,12 +646,11 @@ "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -922,11 +921,6 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "peer": true }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, "node_modules/console-browserify": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", @@ -1253,9 +1247,9 @@ } }, "node_modules/foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", + "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==" }, "node_modules/fsevents": { "version": "2.3.2", @@ -1686,14 +1680,14 @@ "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=10" } }, "node_modules/minimist": { @@ -1932,9 +1926,9 @@ } }, "node_modules/openapi-sampler": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.2.1.tgz", - "integrity": "sha512-mHrYmyvcLD0qrfqPkPRBAL2z16hGT2rW0d0B7nklfoTcc3pmkJLkSZlKSeFgerUM41E5c7jlxf0Y19xrM7mWQQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.2.3.tgz", + "integrity": "sha512-dH2QYXqakorV5dxkP/f1BV3Ku4yNn21YmBsqJunnyrHLw7mnCNZZldftgrEpv/66b1m5oaUAmiJoJN+FqBEkJg==", "dependencies": { "@types/json-schema": "^7.0.7", "json-pointer": "0.6.2" @@ -2207,11 +2201,11 @@ } }, "node_modules/redoc": { - "version": "2.0.0-rc.68", - "resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0-rc.68.tgz", - "integrity": "sha512-sCz52OEhLDu2cIBimy4f6CaVoDxUzD16x63oZx4kpDQOTXYtk0hEOlph1s5VrgNg9pg+rJ9LCCfnwCuzx3Be8w==", + "version": "2.0.0-rc.69", + "resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0-rc.69.tgz", + "integrity": "sha512-AFedbb9h0I98z0lBF2hkkn3M09CsF/zXfRCd07vGL0fZg72/VFPAmHN7CyFRGg/whVEstZ2iUhN2jbN6MmTTDQ==", "dependencies": { - "@redocly/openapi-core": "^1.0.0-beta.95", + "@redocly/openapi-core": "^1.0.0-beta.97", "classnames": "^2.3.1", "decko": "^1.2.0", "dompurify": "^2.2.8", @@ -2221,7 +2215,7 @@ "mark.js": "^8.11.1", "marked": "^4.0.15", "mobx-react": "^7.2.0", - "openapi-sampler": "^1.2.1", + "openapi-sampler": "^1.2.3", "path-browserify": "^1.0.1", "perfect-scrollbar": "^1.5.1", "polished": "^4.1.3", @@ -3135,9 +3129,9 @@ } }, "@redocly/openapi-core": { - "version": "1.0.0-beta.96", - "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.0.0-beta.96.tgz", - "integrity": "sha512-tcy0q+9PRWV4rcnVx5uHII/9Cq9qpUzWNppupAaVgutxjQRPWH45e24NLinn6lA8Q4por6HuMYkk/0QAJE8d3A==", + "version": "1.0.0-beta.97", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.0.0-beta.97.tgz", + "integrity": "sha512-3WW9/6flosJuRtU3GI0Vw39OYFZqqXMDCp5TLa3EjXOb7Nm6AZTWRb3Y+I/+UdNJ/NTszVJkQczoa1t476ekiQ==", "requires": { "@redocly/ajv": "^8.6.4", "@types/node": "^14.11.8", @@ -3145,16 +3139,16 @@ "js-levenshtein": "^1.1.6", "js-yaml": "^4.1.0", "lodash.isequal": "^4.5.0", - "minimatch": "^3.0.4", + "minimatch": "^5.0.1", "node-fetch": "^2.6.1", "pluralize": "^8.0.0", "yaml-ast-parser": "0.0.43" }, "dependencies": { "@types/node": { - "version": "14.18.16", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.16.tgz", - "integrity": "sha512-X3bUMdK/VmvrWdoTkz+VCn6nwKwrKCFTHtqwBIaQJNx4RUIBBUFXM00bqPz/DsDd+Icjmzm6/tyYZzeGVqb6/Q==" + "version": "14.18.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.17.tgz", + "integrity": "sha512-oajWz4kOajqpKJMPgnCvBajPq8QAvl2xIWoFjlAJPKGu6n7pjov5SxGE45a+0RxHDoo4ycOMoZw1SCOWtDERbw==" } } }, @@ -3525,12 +3519,11 @@ "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" }, "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "braces": { @@ -3771,11 +3764,6 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "peer": true }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, "console-browserify": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", @@ -4060,9 +4048,9 @@ } }, "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", + "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==" }, "fsevents": { "version": "2.3.2", @@ -4396,11 +4384,11 @@ "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" } }, "minimist": { @@ -4563,9 +4551,9 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "openapi-sampler": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.2.1.tgz", - "integrity": "sha512-mHrYmyvcLD0qrfqPkPRBAL2z16hGT2rW0d0B7nklfoTcc3pmkJLkSZlKSeFgerUM41E5c7jlxf0Y19xrM7mWQQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.2.3.tgz", + "integrity": "sha512-dH2QYXqakorV5dxkP/f1BV3Ku4yNn21YmBsqJunnyrHLw7mnCNZZldftgrEpv/66b1m5oaUAmiJoJN+FqBEkJg==", "requires": { "@types/json-schema": "^7.0.7", "json-pointer": "0.6.2" @@ -4805,11 +4793,11 @@ } }, "redoc": { - "version": "2.0.0-rc.68", - "resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0-rc.68.tgz", - "integrity": "sha512-sCz52OEhLDu2cIBimy4f6CaVoDxUzD16x63oZx4kpDQOTXYtk0hEOlph1s5VrgNg9pg+rJ9LCCfnwCuzx3Be8w==", + "version": "2.0.0-rc.69", + "resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0-rc.69.tgz", + "integrity": "sha512-AFedbb9h0I98z0lBF2hkkn3M09CsF/zXfRCd07vGL0fZg72/VFPAmHN7CyFRGg/whVEstZ2iUhN2jbN6MmTTDQ==", "requires": { - "@redocly/openapi-core": "^1.0.0-beta.95", + "@redocly/openapi-core": "^1.0.0-beta.97", "classnames": "^2.3.1", "decko": "^1.2.0", "dompurify": "^2.2.8", @@ -4819,7 +4807,7 @@ "mark.js": "^8.11.1", "marked": "^4.0.15", "mobx-react": "^7.2.0", - "openapi-sampler": "^1.2.1", + "openapi-sampler": "^1.2.3", "path-browserify": "^1.0.1", "perfect-scrollbar": "^1.5.1", "polished": "^4.1.3", diff --git a/cli/package.json b/cli/package.json index 9e1857e4..120a9edc 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "redoc-cli", - "version": "0.13.12", + "version": "0.13.13", "description": "ReDoc's Command Line Interface", "main": "index.js", "bin": "index.js", @@ -19,7 +19,7 @@ "node-libs-browser": "^2.2.1", "react": "^17.0.1", "react-dom": "^17.0.1", - "redoc": "2.0.0-rc.68", + "redoc": "2.0.0-rc.69", "styled-components": "^5.3.0", "yargs": "^17.3.1" }, From dfacf989bcd17b9cea17bee45a38068e073bb3d6 Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Thu, 12 May 2022 16:52:16 +0300 Subject: [PATCH 02/19] fix: add node version to docker (#2005) --- config/docker/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/docker/Dockerfile b/config/docker/Dockerfile index bc54ee35..b46474ca 100644 --- a/config/docker/Dockerfile +++ b/config/docker/Dockerfile @@ -5,14 +5,14 @@ # npm i -g http-server # http-server -p 8000 --cors -FROM node:alpine +FROM node:12-alpine RUN apk update && apk add --no-cache git # Install dependencies WORKDIR /build COPY package.json package-lock.json /build/ -RUN npm ci --no-optional --ignore-scripts --force +RUN npm ci --no-optional --ignore-scripts # copy only required for the build files COPY src /build/src From 58cd3cb782b8c297a73d9bf479923f495f2159c0 Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Thu, 12 May 2022 18:09:49 +0300 Subject: [PATCH 03/19] chore: enable cli test for build command with url (#2006) --- cli/__test__/build/configRedoc/url.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cli/__test__/build/configRedoc/url.test.ts b/cli/__test__/build/configRedoc/url.test.ts index 0acc8aa3..7dedddb2 100644 --- a/cli/__test__/build/configRedoc/url.test.ts +++ b/cli/__test__/build/configRedoc/url.test.ts @@ -1,8 +1,7 @@ import { spawnSync } from 'child_process'; describe('build with url', () => { - // FIXME: remove skip after release - it.skip('should not fail on resolving url', () => { + it('should not fail on resolving url', () => { const r = spawnSync( 'ts-node', [ From 660cc857bc86787e16237b407fe5f5d7a493bb48 Mon Sep 17 00:00:00 2001 From: Andrew Tatomyr Date: Fri, 13 May 2022 14:53:03 +0300 Subject: [PATCH 04/19] feat: display patternProperties (#2008) --- src/common-elements/fields-layout.ts | 23 +++++++++++-- src/common-elements/fields.ts | 27 +++++++++++++++- src/common-elements/shelfs.tsx | 1 + src/components/Fields/Field.tsx | 30 +++++++++++------ .../fixtures/3.1/patternProperties.json | 32 +++++++++++++++++++ src/services/__tests__/models/Schema.test.ts | 11 +++++++ src/services/models/Schema.ts | 26 +++++++++++++++ src/types/open-api.ts | 1 + src/utils/openapi.ts | 1 + 9 files changed, 139 insertions(+), 13 deletions(-) create mode 100644 src/services/__tests__/fixtures/3.1/patternProperties.json diff --git a/src/common-elements/fields-layout.ts b/src/common-elements/fields-layout.ts index a1fd214b..9e7fce0b 100644 --- a/src/common-elements/fields-layout.ts +++ b/src/common-elements/fields-layout.ts @@ -1,4 +1,4 @@ -import styled, { extensionsHook, media } from '../styled-components'; +import styled, { extensionsHook, media, css } from '../styled-components'; import { deprecatedCss } from './mixins'; export const PropertiesTableCaption = styled.caption` @@ -72,7 +72,26 @@ export const PropertyNameCell = styled(PropertyCell)` ${deprecatedCss}; } - ${({ kind }) => (kind !== 'field' ? 'font-style: italic' : '')}; + ${({ kind }) => + kind === 'patternProperties' && + css` + > span.property-name { + display: inline-table; + white-space: break-spaces; + margin-right: 20px; + + ::before, + ::after { + content: '/'; + filter: opacity(0.2); + } + } + `} + + ${({ kind = '' }) => + ['field', 'additionalProperties', 'patternProperties'].includes(kind) + ? '' + : 'font-style: italic'}; ${extensionsHook('PropertyNameCell')}; `; diff --git a/src/common-elements/fields.ts b/src/common-elements/fields.ts index 0a7275cf..d894e9a0 100644 --- a/src/common-elements/fields.ts +++ b/src/common-elements/fields.ts @@ -1,6 +1,6 @@ import { transparentize } from 'polished'; -import styled, { extensionsHook } from '../styled-components'; +import styled, { extensionsHook, css } from '../styled-components'; import { PropertyNameCell } from './fields-layout'; import { ShelfIcon } from './shelfs'; @@ -17,6 +17,27 @@ export const ClickablePropertyNameCell = styled(PropertyNameCell)` &:focus { font-weight: ${({ theme }) => theme.typography.fontWeightBold}; } + ${({ kind }) => + kind === 'patternProperties' && + css` + display: inline-flex; + margin-right: 20px; + + > span.property-name { + white-space: break-spaces; + text-align: left; + + ::before, + ::after { + content: '/'; + filter: opacity(0.2); + } + } + + > svg { + align-self: center; + } + `} } ${ShelfIcon} { height: ${({ theme }) => theme.schema.arrow.size}; @@ -56,6 +77,10 @@ export const RequiredLabel = styled(FieldLabel.withComponent('div'))` line-height: 1; `; +export const PropertyLabel = styled(RequiredLabel)` + color: ${props => props.theme.colors.primary.light}; +`; + export const RecursiveLabel = styled(FieldLabel)` color: ${({ theme }) => theme.colors.warning.main}; font-size: 13px; diff --git a/src/common-elements/shelfs.tsx b/src/common-elements/shelfs.tsx index f07c6bfc..18c26ebd 100644 --- a/src/common-elements/shelfs.tsx +++ b/src/common-elements/shelfs.tsx @@ -37,6 +37,7 @@ class IntShelfIcon extends React.PureComponent<{ export const ShelfIcon = styled(IntShelfIcon)` height: ${props => props.size || '18px'}; width: ${props => props.size || '18px'}; + min-width: ${props => props.size || '18px'}; vertical-align: middle; float: ${props => props.float || ''}; transition: transform 0.2s ease-out; diff --git a/src/components/Fields/Field.tsx b/src/components/Fields/Field.tsx index 2fb5865f..0f0d1226 100644 --- a/src/components/Fields/Field.tsx +++ b/src/components/Fields/Field.tsx @@ -1,9 +1,12 @@ import { observer } from 'mobx-react'; import * as React from 'react'; -import { ClickablePropertyNameCell, RequiredLabel } from '../../common-elements/fields'; +import { + ClickablePropertyNameCell, + PropertyLabel, + RequiredLabel, +} from '../../common-elements/fields'; import { FieldDetails } from './FieldDetails'; - import { InnerPropertiesWrap, PropertyBullet, @@ -11,11 +14,10 @@ import { PropertyDetailsCell, PropertyNameCell, } from '../../common-elements/fields-layout'; - import { ShelfIcon } from '../../common-elements/'; - -import { FieldModel } from '../../services/models'; -import { Schema, SchemaOptions } from '../Schema/Schema'; +import { Schema } from '../Schema/Schema'; +import type { SchemaOptions } from '../Schema/Schema'; +import type { FieldModel } from '../../services/models'; export interface FieldProps extends SchemaOptions { className?: string; @@ -52,6 +54,14 @@ export class Field extends React.Component { const expanded = field.expanded === undefined ? expandByDefault : field.expanded; + const labels = ( + <> + {kind === 'additionalProperties' && additional property} + {kind === 'patternProperties' && pattern property} + {required && required} + + ); + const paramName = withSubSchema ? ( { onKeyPress={this.handleKeyPress} aria-label="expand properties" > - {name} + {name} - {required && required } + {labels} ) : ( - {name} - {required && required } + {name} + {labels} ); diff --git a/src/services/__tests__/fixtures/3.1/patternProperties.json b/src/services/__tests__/fixtures/3.1/patternProperties.json new file mode 100644 index 00000000..ec686421 --- /dev/null +++ b/src/services/__tests__/fixtures/3.1/patternProperties.json @@ -0,0 +1,32 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Schema definition with unevaluatedProperties", + "version": "1.0.0" + }, + "servers": [ + { + "url": "example.com" + } + ], + "components": { + "schemas": { + "Patterns": { + "type": "object", + "patternProperties": { + "^S_\\w+\\.[1-9]{2,4}$": { + "type": "string" + }, + "^O_\\w+\\.[1-9]{2,4}$": { + "type": "object", + "properties": { + "x-nestedProperty": { + "type": "string" + } + } + } + } + } + } + } +} diff --git a/src/services/__tests__/models/Schema.test.ts b/src/services/__tests__/models/Schema.test.ts index 966de1af..b4e94d95 100644 --- a/src/services/__tests__/models/Schema.test.ts +++ b/src/services/__tests__/models/Schema.test.ts @@ -76,5 +76,16 @@ describe('Models', () => { expect(schema.fields![1].kind).toEqual('additionalProperties'); expect(schema.fields![1].schema.type).toEqual('boolean'); }); + + test('schemaDefinition should resolve patternProperties', () => { + const spec = require('../fixtures/3.1/patternProperties.json'); + parser = new OpenAPIParser(spec, undefined, opts); + const schema = new SchemaModel(parser, spec.components.schemas.Patterns, '', opts); + expect(schema.fields).toHaveLength(2); + expect(schema.fields![0].kind).toEqual('patternProperties'); + expect(schema.fields![0].schema.type).toEqual('string'); + expect(schema.fields![1].kind).toEqual('patternProperties'); + expect(schema.fields![1].schema.type).toEqual('object'); + }); }); }); diff --git a/src/services/models/Schema.ts b/src/services/models/Schema.ts index c0754b4a..0b1f233f 100644 --- a/src/services/models/Schema.ts +++ b/src/services/models/Schema.ts @@ -364,6 +364,7 @@ function buildFields( options: RedocNormalizedOptions, ): FieldModel[] { const props = schema.properties || {}; + const patternProps = schema.patternProperties || {}; const additionalProps = schema.additionalProperties || schema.unevaluatedProperties; const defaults = schema.default; let fields = Object.keys(props || []).map(fieldName => { @@ -402,6 +403,31 @@ function buildFields( fields = sortByRequired(fields, !options.sortPropsAlphabetically ? schema.required : undefined); } + fields.push( + ...Object.keys(patternProps).map(fieldName => { + let field = patternProps[fieldName]; + + if (!field) { + console.warn( + `Field "${fieldName}" is invalid, skipping.\n Field must be an object but got ${typeof field} at "${$ref}"`, + ); + field = {}; + } + + return new FieldModel( + parser, + { + name: fieldName, + required: false, + schema: field, + kind: 'patternProperties', + }, + `${$ref}/patternProperties/${fieldName}`, + options, + ); + }), + ); + if (typeof additionalProps === 'object' || additionalProps === true) { fields.push( new FieldModel( diff --git a/src/types/open-api.ts b/src/types/open-api.ts index 98ce1f9e..41a65916 100644 --- a/src/types/open-api.ts +++ b/src/types/open-api.ts @@ -113,6 +113,7 @@ export interface OpenAPISchema { $ref?: string; type?: string | string[]; properties?: { [name: string]: OpenAPISchema }; + patternProperties?: { [name: string]: OpenAPISchema }; additionalProperties?: boolean | OpenAPISchema; unevaluatedProperties?: boolean | OpenAPISchema; description?: string; diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index e8a0cc05..0540cecc 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -99,6 +99,7 @@ const schemaKeywordTypes = { additionalProperties: 'object', unevaluatedProperties: 'object', properties: 'object', + patternProperties: 'object', }; export function detectType(schema: OpenAPISchema): string { From ba06485ece27acbb6b846500817f4bff3e4997ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20F=C3=BCrhoff?= Date: Mon, 16 May 2022 10:49:22 +0200 Subject: [PATCH 05/19] feat: theme add links textDecoration options (#1599) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Anastasiia Derymarko Co-authored-by: Loïc Fürhoff --- README.md | 2 ++ package.json | 1 + src/components/Markdown/styled.elements.tsx | 3 ++- .../DiscriminatorDropdown.test.tsx.snap | 20 +++++++++++++++++++ .../__snapshots__/OneOfSchema.test.tsx.snap | 2 +- src/theme.ts | 4 ++++ 6 files changed, 30 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ddc7411d..0b55dece 100644 --- a/README.md +++ b/README.md @@ -289,6 +289,8 @@ You can use all of the following options with the standalone version of the props.theme.typography.links.textDecoration}; color: ${props => props.theme.typography.links.color}; &:visited { @@ -15,6 +15,7 @@ export const linksCss = css` &:hover { color: ${props => props.theme.typography.links.hover}; + text-decoration: ${props => props.theme.typography.links.hoverTextDecoration}; } } `; diff --git a/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap b/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap index 81245601..d2226cc6 100644 --- a/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap @@ -272,6 +272,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "links": Object { "color": "#32329f", "hover": "#6868cf", + "hoverTextDecoration": "auto", + "textDecoration": "auto", "visited": "#32329f", }, "optimizeSpeed": true, @@ -527,6 +529,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "links": Object { "color": "#32329f", "hover": "#6868cf", + "hoverTextDecoration": "auto", + "textDecoration": "auto", "visited": "#32329f", }, "optimizeSpeed": true, @@ -757,6 +761,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "links": Object { "color": "#32329f", "hover": "#6868cf", + "hoverTextDecoration": "auto", + "textDecoration": "auto", "visited": "#32329f", }, "optimizeSpeed": true, @@ -1054,6 +1060,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "links": Object { "color": "#32329f", "hover": "#6868cf", + "hoverTextDecoration": "auto", + "textDecoration": "auto", "visited": "#32329f", }, "optimizeSpeed": true, @@ -1309,6 +1317,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "links": Object { "color": "#32329f", "hover": "#6868cf", + "hoverTextDecoration": "auto", + "textDecoration": "auto", "visited": "#32329f", }, "optimizeSpeed": true, @@ -1539,6 +1549,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "links": Object { "color": "#32329f", "hover": "#6868cf", + "hoverTextDecoration": "auto", + "textDecoration": "auto", "visited": "#32329f", }, "optimizeSpeed": true, @@ -1792,6 +1804,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "links": Object { "color": "#32329f", "hover": "#6868cf", + "hoverTextDecoration": "auto", + "textDecoration": "auto", "visited": "#32329f", }, "optimizeSpeed": true, @@ -2086,6 +2100,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "links": Object { "color": "#32329f", "hover": "#6868cf", + "hoverTextDecoration": "auto", + "textDecoration": "auto", "visited": "#32329f", }, "optimizeSpeed": true, @@ -2341,6 +2357,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "links": Object { "color": "#32329f", "hover": "#6868cf", + "hoverTextDecoration": "auto", + "textDecoration": "auto", "visited": "#32329f", }, "optimizeSpeed": true, @@ -2571,6 +2589,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "links": Object { "color": "#32329f", "hover": "#6868cf", + "hoverTextDecoration": "auto", + "textDecoration": "auto", "visited": "#32329f", }, "optimizeSpeed": true, diff --git a/src/components/__tests__/__snapshots__/OneOfSchema.test.tsx.snap b/src/components/__tests__/__snapshots__/OneOfSchema.test.tsx.snap index aa2d28b9..79b0c2df 100644 --- a/src/components/__tests__/__snapshots__/OneOfSchema.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/OneOfSchema.test.tsx.snap @@ -44,7 +44,7 @@ exports[`Components SchemaView OneOf deprecated should match snapshot 1`] = `
diff --git a/src/theme.ts b/src/theme.ts index e8f2bf2e..791ab4f4 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -128,6 +128,8 @@ const defaultTheme: ThemeInterface = { color: ({ colors }) => colors.primary.main, visited: ({ typography }) => typography.links.color, hover: ({ typography }) => lighten(0.2, typography.links.color), + textDecoration: 'auto', + hoverTextDecoration: 'auto', }, }, sidebar: { @@ -315,6 +317,8 @@ export interface ResolvedThemeInterface { color: string; visited: string; hover: string; + textDecoration: string; + hoverTextDecoration: string; }; }; sidebar: { From ddcc76b5fa38d6f178135d8543b145a6915c1195 Mon Sep 17 00:00:00 2001 From: Frazer Smith Date: Mon, 16 May 2022 14:16:16 +0100 Subject: [PATCH 06/19] ci: update github actions (#2010) --- .github/workflows/e2e-tests.yml | 2 +- .github/workflows/main.yml | 4 ++-- .github/workflows/publish-cli.yml | 34 +++++++++++++++---------------- .github/workflows/publish.yml | 26 +++++++++++------------ .github/workflows/sync.yml | 2 +- .github/workflows/unit-tests.yml | 2 +- 6 files changed, 35 insertions(+), 35 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 514a3193..31f0bff1 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -6,7 +6,7 @@ jobs: build-and-e2e: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - run: npm ci - run: npm run bundle - run: npm run e2e diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ab74307f..71fa9366 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,7 +11,7 @@ jobs: contents: read steps: - name: Check out the repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Login to GitHub Container Registry uses: docker/login-action@v1 with: @@ -48,7 +48,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Docker meta id: docker_meta diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml index 635eae5e..7827253d 100644 --- a/.github/workflows/publish-cli.yml +++ b/.github/workflows/publish-cli.yml @@ -11,10 +11,10 @@ jobs: if: needs.check-version-cli.outputs.changed == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS key: npm-${{ hashFiles('package-lock.json') }} @@ -24,7 +24,7 @@ jobs: - run: npm ci - run: npm run bundle - name: Store bundle artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: bundles-cli path: bundles @@ -34,17 +34,17 @@ jobs: if: needs.check-version-cli.outputs.changed == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - run: npm ci && npm ci --prefix cli - run: npm test e2e-tests: needs: [bundle] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - run: npm ci - name: Download bundled artifact - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: bundles-cli path: bundles @@ -54,10 +54,10 @@ jobs: if: needs.check-version-cli.outputs.changed == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.npm key: npm-${{ hashFiles('package-lock.json') }} @@ -69,7 +69,7 @@ jobs: - name: Bundle run: npm run compile:cli - name: Store bundle artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: cli path: cli @@ -81,9 +81,9 @@ jobs: changed: ${{ steps.check.outputs.changed }} steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Node.js - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 - name: Check if version has been updated id: check uses: EndBug/version-check@v2.0.1 @@ -96,18 +96,18 @@ jobs: if: needs.check-version-cli.outputs.changed == 'true' runs-on: ubuntu-latest steps: - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v3 with: node-version: '14.x' registry-url: 'https://registry.npmjs.org' - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Download cli bundled artifact - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: cli path: cli - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS key: npm-${{ hashFiles('package-lock.json') }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5146cb04..0cdf2500 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,10 +9,10 @@ jobs: bundle: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS key: npm-${{ hashFiles('package-lock.json') }} @@ -22,7 +22,7 @@ jobs: - run: npm ci - run: npm run bundle - name: Store bundle artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: bundles path: bundles @@ -30,17 +30,17 @@ jobs: unit-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - run: npm ci && npm ci --prefix cli - run: npm test e2e-tests: needs: [bundle] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - run: npm ci - name: Download bundled artifact - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: bundles path: bundles @@ -50,7 +50,7 @@ jobs: # needs: [bundle, unit-tests, e2e-tests] # runs-on: ubuntu-latest # steps: - # - uses: actions/checkout@v1 + # - uses: actions/checkout@v3 # - name: Configure AWS Credentials # uses: aws-actions/configure-aws-credentials@v1 # with: @@ -60,7 +60,7 @@ jobs: # - name: Install dependencies # run: npm ci # - name: Download bundled artifacts - # uses: actions/download-artifact@v2 + # uses: actions/download-artifact@v3 # with: # name: bundles # path: bundles @@ -74,18 +74,18 @@ jobs: needs: [bundle, unit-tests, e2e-tests] runs-on: ubuntu-latest steps: - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v3 with: node-version: '14.x' registry-url: 'https://registry.npmjs.org' - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Download bundled artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: bundles path: bundles - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS key: npm-${{ hashFiles('package-lock.json') }} diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index ba673bd0..18819cc7 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repository - uses: actions/checkout@master + uses: actions/checkout@v3 - name: Run GitHub File Sync uses: Redocly/repo-file-sync-action@master with: diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 3df1b925..ed1d672b 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -6,7 +6,7 @@ jobs: build-and-unit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - run: npm ci && npm ci --prefix cli - run: npm run bundle - run: npm test From 291b62a206b68f8b4d98e4b74b71c0cad20a8b9b Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Tue, 17 May 2022 10:56:28 +0300 Subject: [PATCH 07/19] feat: support conditional operators (#1939) * fix: merge type and enum in allOf for 3.1 * chore: add example for OpenApi 3.1 * fix: correct merge constraints in allOf --- demo/openapi-3-1.yaml | 59 +++++++++- src/components/Fields/Field.tsx | 3 +- src/components/Fields/FieldDetails.tsx | 5 +- src/components/JsonViewer/JsonViewer.tsx | 10 +- src/services/OpenAPIParser.ts | 56 +++++---- .../fixtures/3.1/conditionalField.json | 40 +++++++ .../fixtures/3.1/conditionalSchema.json | 40 +++++++ src/services/__tests__/models/Schema.test.ts | 26 +++++ .../models/__snapshots__/Schema.test.ts.snap | 107 ++++++++++++++++++ src/services/models/Schema.ts | 37 ++++++ src/types/open-api.ts | 4 + .../loadAndBundleSpec.test.ts.snap | 93 ++++++++++++++- src/utils/__tests__/openapi.test.ts | 2 +- src/utils/openapi.ts | 4 + 14 files changed, 449 insertions(+), 37 deletions(-) create mode 100644 src/services/__tests__/fixtures/3.1/conditionalField.json create mode 100644 src/services/__tests__/fixtures/3.1/conditionalSchema.json create mode 100644 src/services/__tests__/models/__snapshots__/Schema.test.ts.snap diff --git a/demo/openapi-3-1.yaml b/demo/openapi-3-1.yaml index 484fd33d..9c8d0f07 100644 --- a/demo/openapi-3-1.yaml +++ b/demo/openapi-3-1.yaml @@ -960,6 +960,33 @@ components: schemas: ApiResponse: type: object + patternProperties: + ^S_\\w+\\.[1-9]{2,4}$: + description: The measured skill for hunting + if: + x-displayName: fieldName === 'status' + else: + minLength: 1 + maxLength: 10 + then: + format: url + type: string + enum: + - success + - failed + ^O_\\w+\\.[1-9]{2,4}$: + type: object + properties: + nestedProperty: + type: [string, boolean] + description: The measured skill for hunting + default: lazy + example: adventurous + enum: + - clueless + - lazy + - adventurous + - aggressive properties: code: type: integer @@ -975,7 +1002,7 @@ components: - type: object properties: huntingSkill: - type: string + type: [string, boolean] description: The measured skill for hunting default: lazy example: adventurous @@ -1099,15 +1126,26 @@ components: example: Guru photoUrls: description: The list of URL to a cute photos featuring pet - type: [string, integer, 'null', array] + type: [string, integer, 'null'] minItems: 1 - maxItems: 20 + maxItems: 10 xml: name: photoUrl wrapped: true items: type: string format: url + if: + x-displayName: isString + type: string + then: + minItems: 1 + maxItems: 15 + else: + x-displayName: notString + type: [integer, 'null'] + minItems: 1 + maxItems: 20 friend: $ref: '#/components/schemas/Pet' tags: @@ -1131,6 +1169,12 @@ components: petType: description: Type of a pet type: string + huntingSkill: + type: [integer] + enum: + - 0 + - 1 + - 2 xml: name: Pet Tag: @@ -1198,6 +1242,15 @@ components: type: string contentEncoding: base64 contentMediaType: image/png + if: + title: userStatus === 10 + properties: + userStatus: + enum: [10] + then: + required: ['phone'] + else: + required: [] xml: name: User requestBodies: diff --git a/src/components/Fields/Field.tsx b/src/components/Fields/Field.tsx index 0f0d1226..5c35b04b 100644 --- a/src/components/Fields/Field.tsx +++ b/src/components/Fields/Field.tsx @@ -16,6 +16,7 @@ import { } from '../../common-elements/fields-layout'; import { ShelfIcon } from '../../common-elements/'; import { Schema } from '../Schema/Schema'; + import type { SchemaOptions } from '../Schema/Schema'; import type { FieldModel } from '../../services/models'; @@ -48,7 +49,7 @@ export class Field extends React.Component { }; render() { - const { className, field, isLast, expandByDefault } = this.props; + const { className = '', field, isLast, expandByDefault } = this.props; const { name, deprecated, required, kind } = field; const withSubSchema = !field.schema.isPrimitive && !field.schema.isCircular; diff --git a/src/components/Fields/FieldDetails.tsx b/src/components/Fields/FieldDetails.tsx index c04a5b0f..36a43d3e 100644 --- a/src/components/Fields/FieldDetails.tsx +++ b/src/components/Fields/FieldDetails.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { observer } from 'mobx-react'; import { RecursiveLabel, @@ -24,7 +25,7 @@ import { OptionsContext } from '../OptionsProvider'; import { Pattern } from './Pattern'; import { ArrayItemDetails } from './ArrayItemDetails'; -function FieldDetailsComponent(props: FieldProps) { +export const FieldDetailsComponent = observer((props: FieldProps) => { const { enumSkipQuotes, hideSchemaTitles } = React.useContext(OptionsContext); const { showExamples, field, renderDiscriminatorSwitch } = props; @@ -107,6 +108,6 @@ function FieldDetailsComponent(props: FieldProps) { {(_const && ) || null} ); -} +}); export const FieldDetails = React.memo(FieldDetailsComponent); diff --git a/src/components/JsonViewer/JsonViewer.tsx b/src/components/JsonViewer/JsonViewer.tsx index 8464765f..7bf03de9 100644 --- a/src/components/JsonViewer/JsonViewer.tsx +++ b/src/components/JsonViewer/JsonViewer.tsx @@ -27,20 +27,20 @@ class Json extends React.PureComponent { } renderInner = ({ renderCopyButton }) => { - const showFoldingButtons = this.props.data && Object.values(this.props.data).some( - (value) => typeof value === 'object' && value !== null, - ); + const showFoldingButtons = + this.props.data && + Object.values(this.props.data).some(value => typeof value === 'object' && value !== null); return ( {renderCopyButton()} - {showFoldingButtons && + {showFoldingButtons && ( <> - } + )} {options => ( diff --git a/src/services/OpenAPIParser.ts b/src/services/OpenAPIParser.ts index 66536997..6cf3604d 100644 --- a/src/services/OpenAPIParser.ts +++ b/src/services/OpenAPIParser.ts @@ -268,29 +268,44 @@ export class OpenAPIParser { }>; for (const { $ref: subSchemaRef, schema: subSchema } of allOfSchemas) { - if ( - receiver.type !== subSchema.type && - receiver.type !== undefined && - subSchema.type !== undefined - ) { - console.warn( - `Incompatible types in allOf at "${$ref}": "${receiver.type}" and "${subSchema.type}"`, - ); + const { + type, + enum: enumProperty, + properties, + items, + required, + ...otherConstraints + } = subSchema; + + if (receiver.type !== type && receiver.type !== undefined && type !== undefined) { + console.warn(`Incompatible types in allOf at "${$ref}": "${receiver.type}" and "${type}"`); } - if (subSchema.type !== undefined) { - receiver.type = subSchema.type; + if (type !== undefined) { + if (Array.isArray(type) && Array.isArray(receiver.type)) { + receiver.type = [...type, ...receiver.type]; + } else { + receiver.type = type; + } } - if (subSchema.properties !== undefined) { + if (enumProperty !== undefined) { + if (Array.isArray(enumProperty) && Array.isArray(receiver.enum)) { + receiver.enum = [...enumProperty, ...receiver.enum]; + } else { + receiver.enum = enumProperty; + } + } + + if (properties !== undefined) { receiver.properties = receiver.properties || {}; - for (const prop in subSchema.properties) { + for (const prop in properties) { if (!receiver.properties[prop]) { - receiver.properties[prop] = subSchema.properties[prop]; + receiver.properties[prop] = properties[prop]; } else { // merge inner properties const mergedProp = this.mergeAllOf( - { allOf: [receiver.properties[prop], subSchema.properties[prop]] }, + { allOf: [receiver.properties[prop], properties[prop]] }, $ref + '/properties/' + prop, ); receiver.properties[prop] = mergedProp; @@ -299,22 +314,19 @@ export class OpenAPIParser { } } - if (subSchema.items !== undefined) { + if (items !== undefined) { receiver.items = receiver.items || {}; // merge inner properties - receiver.items = this.mergeAllOf( - { allOf: [receiver.items, subSchema.items] }, - $ref + '/items', - ); + receiver.items = this.mergeAllOf({ allOf: [receiver.items, items] }, $ref + '/items'); } - if (subSchema.required !== undefined) { - receiver.required = (receiver.required || []).concat(subSchema.required); + if (required !== undefined) { + receiver.required = (receiver.required || []).concat(required); } // merge rest of constraints // TODO: do more intelligent merge - receiver = { ...subSchema, ...receiver }; + receiver = { ...receiver, ...otherConstraints }; if (subSchemaRef) { receiver.parentRefs!.push(subSchemaRef); diff --git a/src/services/__tests__/fixtures/3.1/conditionalField.json b/src/services/__tests__/fixtures/3.1/conditionalField.json new file mode 100644 index 00000000..3c010f5d --- /dev/null +++ b/src/services/__tests__/fixtures/3.1/conditionalField.json @@ -0,0 +1,40 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Schema definition field with conditional operators", + "version": "1.0.0" + }, + "components": { + "schemas": { + "Test": { + "type": "object", + "properties": { + "test": { + "type": ["string", "integer", "null"], + "minItems": 1, + "maxItems": 20, + "items": { + "type": "string", + "format": "url" + }, + "if": { + "x-displayName": "isString", + "type": "string" + }, + "then": { + "type": "string", + "minItems": 1, + "maxItems": 20 + }, + "else": { + "x-displayName": "notString", + "minItems": 1, + "maxItems": 10, + "pattern": "\\d+" + } + } + } + } + } + } +} diff --git a/src/services/__tests__/fixtures/3.1/conditionalSchema.json b/src/services/__tests__/fixtures/3.1/conditionalSchema.json new file mode 100644 index 00000000..452747ae --- /dev/null +++ b/src/services/__tests__/fixtures/3.1/conditionalSchema.json @@ -0,0 +1,40 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Schema definition with conditional operators", + "version": "1.0.0" + }, + "components": { + "schemas": { + "Test": { + "type": "object", + "properties": { + "test": { + "description": "The list of URL to a cute photos featuring pet", + "type": ["string", "integer", "null"], + "minItems": 1, + "maxItems": 20, + "items": { + "type": "string", + "format": "url" + } + } + }, + "if": { + "title": "=== 10", + "properties": { + "test": { + "enum": [10] + } + } + }, + "then": { + "maxItems": 2 + }, + "else": { + "maxItems": 20 + } + } + } + } +} diff --git a/src/services/__tests__/models/Schema.test.ts b/src/services/__tests__/models/Schema.test.ts index b4e94d95..d6eb6738 100644 --- a/src/services/__tests__/models/Schema.test.ts +++ b/src/services/__tests__/models/Schema.test.ts @@ -49,6 +49,32 @@ describe('Models', () => { expect(schema.pointer).toBe('#/components/schemas/Child'); }); + test('schemaDefinition should resolve schema with conditional operators', () => { + const spec = require('../fixtures/3.1/conditionalSchema.json'); + parser = new OpenAPIParser(spec, undefined, opts); + const schema = new SchemaModel(parser, spec.components.schemas.Test, '', opts); + expect(schema.oneOf).toHaveLength(2); + + expect(schema.oneOf![0].schema.title).toBe('=== 10'); + expect(schema.oneOf![1].schema.title).toBe('case 2'); + + expect(schema.oneOf![0].schema).toMatchSnapshot(); + expect(schema.oneOf![1].schema).toMatchSnapshot(); + }); + + test('schemaDefinition should resolve field with conditional operators', () => { + const spec = require('../fixtures/3.1/conditionalField.json'); + parser = new OpenAPIParser(spec, undefined, opts); + const schema = new SchemaModel(parser, spec.components.schemas.Test, '', opts); + expect(schema.fields).toHaveLength(1); + expect(schema.fields && schema.fields[0].schema.oneOf).toHaveLength(2); + expect(schema.fields && schema.fields[0].schema.oneOf![0].schema.title).toBe('isString'); + expect(schema.fields && schema.fields[0].schema.oneOf![1].schema.title).toBe('notString'); + + expect(schema.fields && schema.fields[0].schema.oneOf![0].schema).toMatchSnapshot(); + expect(schema.fields && schema.fields[0].schema.oneOf![1].schema).toMatchSnapshot(); + }); + test('schemaDefinition should resolve unevaluatedProperties in properties', () => { const spec = require('../fixtures/3.1/unevaluatedProperties.json'); parser = new OpenAPIParser(spec, undefined, opts); diff --git a/src/services/__tests__/models/__snapshots__/Schema.test.ts.snap b/src/services/__tests__/models/__snapshots__/Schema.test.ts.snap new file mode 100644 index 00000000..b0f6c73e --- /dev/null +++ b/src/services/__tests__/models/__snapshots__/Schema.test.ts.snap @@ -0,0 +1,107 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Models Schema schemaDefinition should resolve field with conditional operators 1`] = ` +Object { + "allOf": undefined, + "default": undefined, + "items": Object { + "allOf": undefined, + "format": "url", + "parentRefs": Array [], + "title": undefined, + "type": "string", + }, + "maxItems": 20, + "minItems": 1, + "parentRefs": Array [], + "title": "isString", + "type": "string", + "x-displayName": "isString", +} +`; + +exports[`Models Schema schemaDefinition should resolve field with conditional operators 2`] = ` +Object { + "allOf": undefined, + "default": undefined, + "items": Object { + "allOf": undefined, + "format": "url", + "parentRefs": Array [], + "title": undefined, + "type": "string", + }, + "maxItems": 10, + "minItems": 1, + "parentRefs": Array [], + "pattern": "\\\\d+", + "title": "notString", + "type": Array [ + "string", + "integer", + "null", + ], + "x-displayName": "notString", +} +`; + +exports[`Models Schema schemaDefinition should resolve schema with conditional operators 1`] = ` +Object { + "allOf": undefined, + "maxItems": 2, + "parentRefs": Array [], + "properties": Object { + "test": Object { + "allOf": undefined, + "description": "The list of URL to a cute photos featuring pet", + "enum": Array [ + 10, + ], + "items": Object { + "allOf": undefined, + "format": "url", + "parentRefs": Array [], + "title": undefined, + "type": "string", + }, + "maxItems": 20, + "minItems": 1, + "parentRefs": Array [], + "title": undefined, + "type": Array [ + "string", + "integer", + "null", + ], + }, + }, + "title": "=== 10", + "type": "object", +} +`; + +exports[`Models Schema schemaDefinition should resolve schema with conditional operators 2`] = ` +Object { + "allOf": undefined, + "maxItems": 20, + "parentRefs": Array [], + "properties": Object { + "test": Object { + "description": "The list of URL to a cute photos featuring pet", + "items": Object { + "format": "url", + "type": "string", + }, + "maxItems": 20, + "minItems": 1, + "type": Array [ + "string", + "integer", + "null", + ], + }, + }, + "title": "case 2", + "type": "object", +} +`; diff --git a/src/services/models/Schema.ts b/src/services/models/Schema.ts index 0b1f233f..71a3a480 100644 --- a/src/services/models/Schema.ts +++ b/src/services/models/Schema.ts @@ -152,6 +152,11 @@ export class SchemaModel { return; } + if ((schema.if && schema.then) || (schema.if && schema.else)) { + this.initConditionalOperators(schema, parser); + return; + } + if (!isChild && getDiscriminator(schema) !== undefined) { this.initDiscriminator(schema, parser); return; @@ -355,6 +360,38 @@ export class SchemaModel { return innerSchema; }); } + + private initConditionalOperators(schema: OpenAPISchema, parser: OpenAPIParser) { + const { + if: ifOperator, + else: elseOperator = {}, + then: thenOperator = {}, + ...restSchema + } = schema; + const groupedOperators = [ + { + allOf: [restSchema, thenOperator, ifOperator], + title: (ifOperator && ifOperator['x-displayName']) || ifOperator?.title || 'case 1', + }, + { + allOf: [restSchema, elseOperator], + title: (elseOperator && elseOperator['x-displayName']) || elseOperator?.title || 'case 2', + }, + ]; + + this.oneOf = groupedOperators.map( + (variant, idx) => + new SchemaModel( + parser, + { + ...variant, + } as OpenAPISchema, + this.pointer + '/oneOf/' + idx, + this.options, + ), + ); + this.oneOfType = 'One of'; + } } function buildFields( diff --git a/src/types/open-api.ts b/src/types/open-api.ts index 41a65916..60fdca26 100644 --- a/src/types/open-api.ts +++ b/src/types/open-api.ts @@ -148,6 +148,10 @@ export interface OpenAPISchema { minProperties?: number; enum?: any[]; example?: any; + + if?: OpenAPISchema; + else?: OpenAPISchema; + then?: OpenAPISchema; examples?: any[]; const?: string; contentEncoding?: string; diff --git a/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap b/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap index 8459b133..a2c8c189 100644 --- a/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap +++ b/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap @@ -1903,6 +1903,46 @@ Object { }, "schemas": Object { "ApiResponse": Object { + "patternProperties": Object { + "^O_\\\\\\\\w+\\\\\\\\.[1-9]{2,4}$": Object { + "properties": Object { + "nestedProperty": Object { + "default": "lazy", + "description": "The measured skill for hunting", + "enum": Array [ + "clueless", + "lazy", + "adventurous", + "aggressive", + ], + "example": "adventurous", + "type": Array [ + "string", + "boolean", + ], + }, + }, + "type": "object", + }, + "^S_\\\\\\\\w+\\\\\\\\.[1-9]{2,4}$": Object { + "description": "The measured skill for hunting", + "else": Object { + "maxLength": 10, + "minLength": 1, + }, + "if": Object { + "x-displayName": "fieldName === 'status'", + }, + "then": Object { + "enum": Array [ + "success", + "failed", + ], + "format": "url", + "type": "string", + }, + }, + }, "properties": Object { "code": Object { "format": "int32", @@ -1934,7 +1974,10 @@ Object { "aggressive", ], "example": "adventurous", - "type": "string", + "type": Array [ + "string", + "boolean", + ], }, }, "required": Array [ @@ -2086,6 +2129,16 @@ Object { "friend": Object { "$ref": "#/components/schemas/Pet", }, + "huntingSkill": Object { + "enum": Array [ + 0, + 1, + 2, + ], + "type": Array [ + "integer", + ], + }, "id": Object { "$ref": "#/components/schemas/Id", "description": "Pet ID", @@ -2105,17 +2158,33 @@ Object { }, "photoUrls": Object { "description": "The list of URL to a cute photos featuring pet", + "else": Object { + "maxItems": 20, + "minItems": 1, + "type": Array [ + "integer", + "null", + ], + "x-displayName": "notString", + }, + "if": Object { + "type": "string", + "x-displayName": "isString", + }, "items": Object { "format": "url", "type": "string", }, - "maxItems": 20, + "maxItems": 10, "minItems": 1, + "then": Object { + "maxItems": 15, + "minItems": 1, + }, "type": Array [ "string", "integer", "null", - "array", ], "xml": Object { "name": "photoUrl", @@ -2173,6 +2242,19 @@ Object { }, }, "User": Object { + "else": Object { + "required": Array [], + }, + "if": Object { + "properties": Object { + "userStatus": Object { + "enum": Array [ + 10, + ], + }, + }, + "title": "userStatus === 10", + }, "properties": Object { "email": Object { "description": "User email address", @@ -2238,6 +2320,11 @@ Object { "type": "string", }, }, + "then": Object { + "required": Array [ + "phone", + ], + }, "type": "object", "xml": Object { "name": "User", diff --git a/src/utils/__tests__/openapi.test.ts b/src/utils/__tests__/openapi.test.ts index e7d73b51..653a9118 100644 --- a/src/utils/__tests__/openapi.test.ts +++ b/src/utils/__tests__/openapi.test.ts @@ -277,7 +277,7 @@ describe('Utils', () => { expect(isPrimitiveType(schema)).toEqual(true); }); - it('Should return false for array of string which include the null value', () => { + it('Should return true for array of string which include the null value', () => { const schema = { type: ['object', 'string', 'null'], }; diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index 0540cecc..accf169f 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -125,6 +125,10 @@ export function isPrimitiveType( return false; } + if ((schema.if && schema.then) || (schema.if && schema.else)) { + return false; + } + let isPrimitive = true; const isArrayType = isArray(type); From 036f97d5211301a3dd7c9869adb556e10df8c871 Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Tue, 17 May 2022 11:07:03 +0300 Subject: [PATCH 08/19] chore: v2.0.0-rc.70 (#2011) --- CHANGELOG.md | 11 +++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4825105..4495018d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# [2.0.0-rc.70](https://github.com/Redocly/redoc/compare/2.0.0-rc.69...2.0.0-rc.70) (2022-05-17) + + +### Features + +* display patternProperties ([#2008](https://github.com/Redocly/redoc/issues/2008)) ([660cc85](https://github.com/Redocly/redoc/commit/660cc857bc86787e16237b407fe5f5d7a493bb48)) +* support conditional operators ([#1939](https://github.com/Redocly/redoc/issues/1939)) ([291b62a](https://github.com/Redocly/redoc/commit/291b62a206b68f8b4d98e4b74b71c0cad20a8b9b)) +* theme add links textDecoration options ([#1599](https://github.com/Redocly/redoc/issues/1599)) ([ba06485](https://github.com/Redocly/redoc/commit/ba06485ece27acbb6b846500817f4bff3e4997ba)) + + + # [2.0.0-rc.69](https://github.com/Redocly/redoc/compare/v2.0.0-rc.68.1...v2.0.0-rc.69) (2022-05-12) diff --git a/package-lock.json b/package-lock.json index bfd4e16a..f638dd3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "redoc", - "version": "2.0.0-rc.69", + "version": "2.0.0-rc.70", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "redoc", - "version": "2.0.0-rc.69", + "version": "2.0.0-rc.70", "license": "MIT", "dependencies": { "@redocly/openapi-core": "^1.0.0-beta.97", diff --git a/package.json b/package.json index d70407b4..f5d9319d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redoc", - "version": "2.0.0-rc.69", + "version": "2.0.0-rc.70", "description": "ReDoc", "repository": { "type": "git", From 6b7283f240876dee5c466c307a4b29186b32da69 Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Tue, 17 May 2022 11:29:44 +0300 Subject: [PATCH 09/19] chore: cli-v0.13.14 (#2012) --- cli/npm-shrinkwrap.json | 18 +++++++++--------- cli/package.json | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cli/npm-shrinkwrap.json b/cli/npm-shrinkwrap.json index b6c6000c..58fbfa27 100644 --- a/cli/npm-shrinkwrap.json +++ b/cli/npm-shrinkwrap.json @@ -1,12 +1,12 @@ { "name": "redoc-cli", - "version": "0.13.13", + "version": "0.13.14", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "redoc-cli", - "version": "0.13.13", + "version": "0.13.14", "license": "MIT", "dependencies": { "chokidar": "^3.5.1", @@ -17,7 +17,7 @@ "node-libs-browser": "^2.2.1", "react": "^17.0.1", "react-dom": "^17.0.1", - "redoc": "2.0.0-rc.69", + "redoc": "2.0.0-rc.70", "styled-components": "^5.3.0", "yargs": "^17.3.1" }, @@ -2201,9 +2201,9 @@ } }, "node_modules/redoc": { - "version": "2.0.0-rc.69", - "resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0-rc.69.tgz", - "integrity": "sha512-AFedbb9h0I98z0lBF2hkkn3M09CsF/zXfRCd07vGL0fZg72/VFPAmHN7CyFRGg/whVEstZ2iUhN2jbN6MmTTDQ==", + "version": "2.0.0-rc.70", + "resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0-rc.70.tgz", + "integrity": "sha512-sdmZ8FX4JjF50hTSjHJ64Ccu9Ewa2O8+Fo8pCLg8GHFrbaFJ2E+KBDK9pGuAqNi61fm3Z5c91Ur7zqpITkUpNg==", "dependencies": { "@redocly/openapi-core": "^1.0.0-beta.97", "classnames": "^2.3.1", @@ -4793,9 +4793,9 @@ } }, "redoc": { - "version": "2.0.0-rc.69", - "resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0-rc.69.tgz", - "integrity": "sha512-AFedbb9h0I98z0lBF2hkkn3M09CsF/zXfRCd07vGL0fZg72/VFPAmHN7CyFRGg/whVEstZ2iUhN2jbN6MmTTDQ==", + "version": "2.0.0-rc.70", + "resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0-rc.70.tgz", + "integrity": "sha512-sdmZ8FX4JjF50hTSjHJ64Ccu9Ewa2O8+Fo8pCLg8GHFrbaFJ2E+KBDK9pGuAqNi61fm3Z5c91Ur7zqpITkUpNg==", "requires": { "@redocly/openapi-core": "^1.0.0-beta.97", "classnames": "^2.3.1", diff --git a/cli/package.json b/cli/package.json index 120a9edc..63433e5f 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "redoc-cli", - "version": "0.13.13", + "version": "0.13.14", "description": "ReDoc's Command Line Interface", "main": "index.js", "bin": "index.js", @@ -19,7 +19,7 @@ "node-libs-browser": "^2.2.1", "react": "^17.0.1", "react-dom": "^17.0.1", - "redoc": "2.0.0-rc.69", + "redoc": "2.0.0-rc.70", "styled-components": "^5.3.0", "yargs": "^17.3.1" }, From 0aafee113102be1439313bba302e2deadf6e2f84 Mon Sep 17 00:00:00 2001 From: redocly-bot Date: Wed, 18 May 2022 09:06:41 +0000 Subject: [PATCH 10/19] sync: Synced local 'docs/' with remote 'docs/redoc/' --- docs/deployment/html.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deployment/html.md b/docs/deployment/html.md index 604f7ce2..53568ab2 100644 --- a/docs/deployment/html.md +++ b/docs/deployment/html.md @@ -97,7 +97,7 @@ Redoc.init(specOrSpecUrl, options, element, callback) ``` - `specOrSpecUrl`: Either a JSON object with the OpenAPI definition or a URL to the definition in JSON or YAML format. -- `options`: See [options object](https://redocly.com/docs/api-reference-docs/configuration/) reference. +- `options`: See [features.openapi object](/docs/api-reference-docs/configuration/functionality.mdx) reference. - `element`: DOM element Redoc will be inserted into. - `callback`(optional): Callback to be called after Redoc has been fully rendered. It is also called on errors with `error` as the first argument. From 82712c5b408dc6bc142307d45fb962de2a43ffba Mon Sep 17 00:00:00 2001 From: Anastasiia Derymarko Date: Fri, 20 May 2022 11:57:04 +0300 Subject: [PATCH 11/19] feat: show minProperties maxProperties (#2015) --- demo/openapi.yaml | 19 +++--- src/components/Parameters/Parameters.tsx | 2 + src/components/Responses/ResponseDetails.tsx | 8 ++- src/components/Schema/OneOfSchema.tsx | 6 +- src/components/__tests__/OneOfSchema.test.tsx | 25 +++++++ src/components/__tests__/Schema.test.tsx | 67 +++++++++++++++++++ .../loadAndBundleSpec.test.ts.snap | 1 + src/utils/__tests__/openapi.test.ts | 2 +- src/utils/openapi.ts | 11 ++- 9 files changed, 128 insertions(+), 13 deletions(-) create mode 100644 src/components/__tests__/Schema.test.tsx diff --git a/demo/openapi.yaml b/demo/openapi.yaml index 5cf19340..c18063e7 100644 --- a/demo/openapi.yaml +++ b/demo/openapi.yaml @@ -88,7 +88,7 @@ paths: parameters: - name: Accept-Language in: header - description: "The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US" + description: 'The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US' example: en-US required: false schema: @@ -254,7 +254,7 @@ paths: required: false schema: type: string - example: "Bearer " + example: 'Bearer ' - name: petId in: path description: Pet id to delete @@ -401,6 +401,7 @@ paths: application/json: schema: type: object + minProperties: 2 additionalProperties: type: integer format: int32 @@ -429,7 +430,7 @@ paths: application/json: example: status: 400 - message: "Invalid Order" + message: 'Invalid Order' requestBody: content: application/json: @@ -877,11 +878,11 @@ paths: type: string examples: response: - value: OK + value: OK text/plain: examples: response: - value: OK + value: OK '400': description: Invalid username/password supplied /user/logout: @@ -1027,8 +1028,8 @@ components: properties: id: externalDocs: - description: "Find more info here" - url: "https://example.com" + description: 'Find more info here' + url: 'https://example.com' description: Pet ID allOf: - $ref: '#/components/schemas/Id' @@ -1201,7 +1202,7 @@ x-webhooks: content: application/json: schema: - $ref: "#/components/schemas/Pet" + $ref: '#/components/schemas/Pet' responses: - "200": + '200': description: Return a 200 status to indicate that the data was received successfully diff --git a/src/components/Parameters/Parameters.tsx b/src/components/Parameters/Parameters.tsx index ed9141bc..6ff7e769 100644 --- a/src/components/Parameters/Parameters.tsx +++ b/src/components/Parameters/Parameters.tsx @@ -10,6 +10,7 @@ import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch'; import { Schema } from '../Schema'; import { Markdown } from '../Markdown/Markdown'; +import { ConstraintsView } from '../Fields/FieldContstraints'; function safePush(obj, prop, item) { if (!obj[prop]) { @@ -79,6 +80,7 @@ export function BodyContent(props: { return ( <> {description !== undefined && } + { render() { @@ -21,7 +22,12 @@ export class ResponseDetails extends React.PureComponent<{ response: ResponseMod {({ schema }) => { - return ; + return ( + <> + + + + ); }} diff --git a/src/components/Schema/OneOfSchema.tsx b/src/components/Schema/OneOfSchema.tsx index b0864c33..311a3258 100644 --- a/src/components/Schema/OneOfSchema.tsx +++ b/src/components/Schema/OneOfSchema.tsx @@ -8,6 +8,7 @@ import { } from '../../common-elements/schema'; import { Badge } from '../../common-elements/shelfs'; import { SchemaModel } from '../../services/models'; +import { ConstraintsView } from '../Fields/FieldContstraints'; import { Schema, SchemaProps } from './Schema'; export interface OneOfButtonProps { @@ -47,6 +48,8 @@ export class OneOfSchema extends React.Component { if (oneOf === undefined) { return null; } + const activeSchema = oneOf[schema.activeOneOf]; + return (
{schema.oneOfType} @@ -58,7 +61,8 @@ export class OneOfSchema extends React.Component {
{oneOf[schema.activeOneOf].deprecated && Deprecated}
- + +
); } diff --git a/src/components/__tests__/OneOfSchema.test.tsx b/src/components/__tests__/OneOfSchema.test.tsx index e9425db7..28b1b265 100644 --- a/src/components/__tests__/OneOfSchema.test.tsx +++ b/src/components/__tests__/OneOfSchema.test.tsx @@ -53,5 +53,30 @@ describe('Components', () => { expect(component.render()).toMatchSnapshot(); }); }); + + describe('Show minProperties/maxProperties constraints oneOf', () => { + const schema = new SchemaModel( + parser, + { + oneOf: [ + { + type: 'object', + description: 'Test description', + minProperties: 1, + maxProperties: 1, + additionalProperties: { + type: 'string', + description: 'The name and value o', + }, + }, + ], + }, + '', + options, + ); + + const component = shallow(withTheme()); + expect(component.html().includes('= 1 properties')).toBe(true); + }); }); }); diff --git a/src/components/__tests__/Schema.test.tsx b/src/components/__tests__/Schema.test.tsx new file mode 100644 index 00000000..919e416f --- /dev/null +++ b/src/components/__tests__/Schema.test.tsx @@ -0,0 +1,67 @@ +/* tslint:disable:no-implicit-dependencies */ + +import { shallow } from 'enzyme'; +import * as React from 'react'; + +import { Schema } from '../'; +import { OpenAPIParser, SchemaModel } from '../../services'; +import { RedocNormalizedOptions } from '../../services/RedocNormalizedOptions'; +import { withTheme } from '../testProviders'; + +const options = new RedocNormalizedOptions({}); +describe('Components', () => { + describe('SchemaView', () => { + const parser = new OpenAPIParser( + { openapi: '3.0', info: { title: 'test', version: '0' }, paths: {} }, + undefined, + options, + ); + + describe('Show minProperties/maxProperties constraints', () => { + const schema = new SchemaModel( + parser, + { + properties: { + name: { + type: 'object', + minProperties: 1, + properties: { + address: { + type: 'string', + }, + }, + }, + }, + }, + '', + options, + ); + const component = shallow(withTheme()); + expect(component.html().includes('non-empty')).toBe(true); + }); + + describe('Show range minProperties/maxProperties constraints', () => { + const schema = new SchemaModel( + parser, + { + properties: { + name: { + type: 'object', + minProperties: 2, + maxProperties: 10, + additionalProperties: { + type: 'string', + }, + }, + }, + }, + '', + options, + ); + it('should includes [ 2 .. 10 ] properties', () => { + const component = shallow(withTheme()); + expect(component.html().includes('[ 2 .. 10 ] properties')).toBe(true); + }); + }); + }); +}); diff --git a/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap b/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap index a2c8c189..0c3f8d4f 100644 --- a/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap +++ b/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap @@ -966,6 +966,7 @@ try { "format": "int32", "type": "integer", }, + "minProperties": 2, "type": "object", }, }, diff --git a/src/utils/__tests__/openapi.test.ts b/src/utils/__tests__/openapi.test.ts index 653a9118..8e7aa3d8 100644 --- a/src/utils/__tests__/openapi.test.ts +++ b/src/utils/__tests__/openapi.test.ts @@ -553,7 +553,7 @@ describe('Utils', () => { }); it('should have a humanized constraint when minItems and maxItems are the same', () => { - expect(humanizeConstraints(itemConstraintSchema(7, 7))).toContain('7 items'); + expect(humanizeConstraints(itemConstraintSchema(7, 7))).toContain('= 7 items'); }); it('should have a humanized constraint when justMinItems is set, and it is equal to 1', () => { diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index accf169f..5c9986c5 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -423,7 +423,7 @@ function humanizeRangeConstraint( let stringRange; if (min !== undefined && max !== undefined) { if (min === max) { - stringRange = `${min} ${description}`; + stringRange = `= ${min} ${description}`; } else { stringRange = `[ ${min} .. ${max} ] ${description}`; } @@ -476,6 +476,15 @@ export function humanizeConstraints(schema: OpenAPISchema): string[] { res.push(arrayRange); } + const propertiesRange = humanizeRangeConstraint( + 'properties', + schema.minProperties, + schema.maxProperties, + ); + if (propertiesRange !== undefined) { + res.push(propertiesRange); + } + const multipleOfConstraint = humanizeMultipleOfConstraint(schema.multipleOf); if (multipleOfConstraint !== undefined) { res.push(multipleOfConstraint); From 1e4ea03d4a9b7eddf3e4cc7cbdbd4d913583e837 Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Thu, 26 May 2022 14:54:03 +0300 Subject: [PATCH 12/19] fix: merge allOf in correct order (#2020) --- src/services/OpenAPIParser.ts | 13 +++- src/services/__tests__/OpenAPIParser.test.ts | 41 ++++++++++- .../__tests__/fixtures/mergeAllOf.json | 70 +++++++++++++++++++ 3 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 src/services/__tests__/fixtures/mergeAllOf.json diff --git a/src/services/OpenAPIParser.ts b/src/services/OpenAPIParser.ts index 6cf3604d..b4d1edb5 100644 --- a/src/services/OpenAPIParser.ts +++ b/src/services/OpenAPIParser.ts @@ -274,6 +274,9 @@ export class OpenAPIParser { properties, items, required, + oneOf, + anyOf, + title, ...otherConstraints } = subSchema; @@ -324,9 +327,17 @@ export class OpenAPIParser { receiver.required = (receiver.required || []).concat(required); } + if (oneOf !== undefined) { + receiver.oneOf = oneOf; + } + + if (anyOf !== undefined) { + receiver.anyOf = anyOf; + } + // merge rest of constraints // TODO: do more intelligent merge - receiver = { ...receiver, ...otherConstraints }; + receiver = { ...receiver, title: receiver.title || title, ...otherConstraints }; if (subSchemaRef) { receiver.parentRefs!.push(subSchemaRef); diff --git a/src/services/__tests__/OpenAPIParser.test.ts b/src/services/__tests__/OpenAPIParser.test.ts index 7942b7b1..24fbc330 100644 --- a/src/services/__tests__/OpenAPIParser.test.ts +++ b/src/services/__tests__/OpenAPIParser.test.ts @@ -9,14 +9,50 @@ describe('Models', () => { let parser; test('should hoist oneOfs when mergin allOf', () => { - // eslint-disable-next-line @typescript-eslint/no-var-requires const spec = require('./fixtures/oneOfHoist.json'); parser = new OpenAPIParser(spec, undefined, opts); expect(parser.mergeAllOf(spec.components.schemas.test)).toMatchSnapshot(); }); + test('should get schema name from named schema', () => { + const spec = require('./fixtures/mergeAllOf.json'); + parser = new OpenAPIParser(spec, undefined, opts); + const schema = parser.mergeAllOf(spec.components.schemas.Case1, '#/components/schemas/Case1'); + expect(schema.title).toEqual('Case1'); + }); + + test('should get schema name from first allOf', () => { + const spec = require('./fixtures/mergeAllOf.json'); + parser = new OpenAPIParser(spec, undefined, opts); + const schema = parser.mergeAllOf( + spec.components.schemas.Case2.properties.a, + '#components/schemas/Case2/properties/a', + ); + expect(schema.title).toEqual('Bar'); + }); + + test('should get schema name from named schema', () => { + const spec = require('./fixtures/mergeAllOf.json'); + parser = new OpenAPIParser(spec, undefined, opts); + const schema = parser.mergeAllOf( + spec.components.schemas.Case3.schemas.Foo, + '#components/schemas/Case3/schemas/Foo', + ); + expect(schema.title).toEqual('Foo'); + }); + + test('should merge oneOff to inside allOff', () => { + // TODO: should hoist + const spec = require('./fixtures/mergeAllOf.json'); + parser = new OpenAPIParser(spec, undefined, opts); + const schema = parser.mergeAllOf(spec.components.schemas.Case4); + expect(schema.title).toEqual('Foo'); + expect(schema.parentRefs).toHaveLength(1); + expect(schema.parentRefs[0]).toEqual('#/components/schemas/Ref'); + expect(schema.oneOf).toEqual([{ title: 'Bar' }, { title: 'Baz' }]); + }); + test('should override description from $ref of the referenced component, when sibling description exists ', () => { - // eslint-disable-next-line @typescript-eslint/no-var-requires const spec = require('./fixtures/siblingRefDescription.json'); parser = new OpenAPIParser(spec, undefined, opts); const schemaOrRef: Referenced = { @@ -28,7 +64,6 @@ describe('Models', () => { }); test('should correct resolve double $ref if no need sibling', () => { - // eslint-disable-next-line @typescript-eslint/no-var-requires const spec = require('./fixtures/3.1/schemaDefinition.json'); parser = new OpenAPIParser(spec, undefined, opts); const schemaOrRef: Referenced = { diff --git a/src/services/__tests__/fixtures/mergeAllOf.json b/src/services/__tests__/fixtures/mergeAllOf.json new file mode 100644 index 00000000..604a015c --- /dev/null +++ b/src/services/__tests__/fixtures/mergeAllOf.json @@ -0,0 +1,70 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "1.0", + "title": "Foo" + }, + "components": { + "schemas": { + "Case1": { + "allOf": [ + { + "title": "Bar" + }, + { + "title": "Baz" + } + ] + }, + "Case2": { + "properties": { + "a": { + "allOf": [ + { + "title": "Bar" + }, + { + "title": "Baz" + } + ] + } + } + }, + "Case3": { + "schemas": { + "Foo": { + "title": "Foo", + "allOf": [ + { + "title": "Bar" + }, + { + "title": "Baz" + } + ] + } + } + }, + "Case4": { + "allOf": [ + { + "title": "Foo" + }, + { + "$ref": "#/components/schemas/Ref" + } + ] + }, + "Ref": { + "oneOf": [ + { + "title": "Bar" + }, + { + "title": "Baz" + } + ] + } + } + } +} From 77104d6c0d6f457aa08a158e93b52a45877be84e Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Mon, 30 May 2022 13:40:19 +0300 Subject: [PATCH 13/19] feat: add Redoc to Redocly CDN (#2026) --- .github/workflows/publish.yml | 19 ++++++++++++++++++ README.md | 10 ++++----- docs/deployment/html.md | 2 +- docs/quickstart.md | 2 +- package.json | 1 + scripts/publish-cdn.sh | 38 +++++++++++++++++++++++++++++++++++ scripts/version.js | 1 + 7 files changed, 66 insertions(+), 7 deletions(-) create mode 100755 scripts/publish-cdn.sh create mode 100644 scripts/version.js diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0cdf2500..34ee6557 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -71,6 +71,7 @@ jobs: # - name: Invalidate # run: aws cloudfront create-invalidation --distribution-id ${{ secrets.CF_DEMO_DISTRIBUTION_ID }} --paths "/*" publish: + name: Publish to NPM needs: [bundle, unit-tests, e2e-tests] runs-on: ubuntu-latest steps: @@ -98,3 +99,21 @@ jobs: run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + publish-cdn: + name: Publish to CDN + needs: [bundle, unit-tests, e2e-tests] + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Configure AWS + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + - name: Download all artifact + uses: actions/download-artifact@v3 + - name: Publish to S3 + run: npm run publish-cdn diff --git a/README.md b/README.md index 0b55dece..5d1c28dc 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Build Status](https://travis-ci.com/Redocly/redoc.svg?branch=master)](https://travis-ci.com/Redocly/redoc) [![Coverage Status](https://coveralls.io/repos/Redocly/redoc/badge.svg?branch=master&service=github)](https://coveralls.io/github/Redocly/redoc?branch=master) [![npm](http://img.shields.io/npm/v/redoc.svg)](https://www.npmjs.com/package/redoc) [![License](https://img.shields.io/npm/l/redoc.svg)](https://github.com/Redocly/redoc/blob/master/LICENSE) - [![bundle size](http://img.badgesize.io/https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js?compression=gzip&max=300000)](https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js) [![npm](https://img.shields.io/npm/dm/redoc.svg)](https://www.npmjs.com/package/redoc) [![](https://data.jsdelivr.com/v1/package/npm/redoc/badge)](https://www.jsdelivr.com/package/npm/redoc) [![Docker Build Status](https://img.shields.io/docker/build/redocly/redoc.svg)](https://hub.docker.com/r/redocly/redoc/) + [![bundle size](http://img.badgesize.io/https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js?compression=gzip&max=300000)](https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js) [![npm](https://img.shields.io/npm/dm/redoc.svg)](https://www.npmjs.com/package/redoc) [![](https://data.jsdelivr.com/v1/package/npm/redoc/badge)](https://www.jsdelivr.com/package/npm/redoc) [![Docker Build Status](https://img.shields.io/docker/build/redocly/redoc.svg)](https://hub.docker.com/r/redocly/redoc/) **This is the README for the `2.x` version of Redoc (React-based).** @@ -102,9 +102,9 @@ Refer to the Redocly's documentation for more information on these products: ![](docs/images/code-samples-demo.gif) ## Releases -**Important:** all the 2.x releases are deployed to npm and can be used with jsdeliver: -- particular release, for example, `v2.0.0-alpha.15`: https://cdn.jsdelivr.net/npm/redoc@2.0.0-alpha.17/bundles/redoc.standalone.js -- `next` release: https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js +**Important:** all the 2.x releases are deployed to npm and can be used with Redocly-cdn: +- particular release, for example, `v2.0.0-rc.70`: https://cdn.redoc.ly/redoc/v2.0.0-rc.70/bundles/redoc.standalone.js +- `latest` release: https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js Additionally, all the 1.x releases are hosted on our GitHub Pages-based CDN **(deprecated)**: - particular release, for example `v1.2.0`: https://rebilly.github.io/ReDoc/releases/v1.2.0/redoc.min.js @@ -166,7 +166,7 @@ replace the `spec-url` attribute with the url or local file address to your defi - + diff --git a/docs/deployment/html.md b/docs/deployment/html.md index 53568ab2..5c392be9 100644 --- a/docs/deployment/html.md +++ b/docs/deployment/html.md @@ -51,7 +51,7 @@ or the files located in your `node modules` folder. To reference the Redoc script with a CDN link: ```html - + ``` ### Node modules link diff --git a/docs/quickstart.md b/docs/quickstart.md index 7607c10f..1b01e0f1 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -38,7 +38,7 @@ replace the `spec-url` attribute with the URL or local file address to your defi - + ``` diff --git a/package.json b/package.json index f5d9319d..3190e111 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "start:demo": "webpack serve --hot --config demo/webpack.config.ts --mode=development", "compile:cli": "tsc custom.d.ts cli/index.ts --target es6 --module commonjs --types yargs", "build:demo": "webpack --mode=production --config demo/webpack.config.ts", + "publish-cdn": "scripts/publish-cdn.sh", "deploy:demo": "aws s3 sync demo/dist s3://production-redoc-demo --acl=public-read", "license-check": "license-checker --production --onlyAllow 'MIT;ISC;Apache-2.0;BSD;BSD-2-Clause;BSD-3-Clause;CC-BY-4.0;Python-2.0' --summary", "docker:build": "docker build -f config/docker/Dockerfile -t redoc .", diff --git a/scripts/publish-cdn.sh b/scripts/publish-cdn.sh new file mode 100755 index 00000000..af930849 --- /dev/null +++ b/scripts/publish-cdn.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +set -e # exit on error + +# TODO: Update script! + +VERSION=$(node scripts/version.js) +VERSION_TAG=v${VERSION:0:1}.x + +copy_to_s3 () { + aws s3 cp bundles "s3://redocly-cdn/redoc/$1/bundles" --recursive "$2" + aws s3 cp CHANGELOG.md "s3://redocly-cdn/redoc/$1/CHANGELOG.md" "$2" + aws s3 cp LICENSE "s3://redocly-cdn/redoc/$1/LICENSE" "$2" + aws s3 cp package.json "s3://redocly-cdn/redoc/$1/package.json" "$2" + aws s3 cp README.md "s3://redocly-cdn/redoc/$1/README.md" "$2" +} + +if aws s3 ls "redocly-cdn/redoc/v$VERSION/" "$@"; then + echo "Version $VERSION already exists" + exit 1 +else + echo Releasing $VERSION + + echo Uploading to S3 $VERSION + copy_to_s3 "v$VERSION" $@ + + echo Uploading to S3 $VERSION_TAG + copy_to_s3 "$VERSION_TAG" $@ + + if [[ "$VERSION_TAG" == "v2.x" ]]; then + echo Uploading to S3 latest + copy_to_s3 latest $@ + fi + + echo + echo Deployed successfully + exit 0 +fi diff --git a/scripts/version.js b/scripts/version.js new file mode 100644 index 00000000..8729268e --- /dev/null +++ b/scripts/version.js @@ -0,0 +1 @@ +console.log(require('../package.json').version); From eb0917d002e57353027fee9c8f07605de8f1ff6f Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Fri, 20 May 2022 10:44:02 +0300 Subject: [PATCH 14/19] fix: constraints label details --- src/components/Fields/ArrayItemDetails.tsx | 13 ++++++++++++- src/components/Parameters/Parameters.tsx | 4 +++- src/components/Responses/ResponseDetails.tsx | 4 +++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/components/Fields/ArrayItemDetails.tsx b/src/components/Fields/ArrayItemDetails.tsx index db8837d2..776d229d 100644 --- a/src/components/Fields/ArrayItemDetails.tsx +++ b/src/components/Fields/ArrayItemDetails.tsx @@ -4,9 +4,20 @@ import { ConstraintsView } from './FieldContstraints'; import { Pattern } from './Pattern'; import { SchemaModel } from '../../services'; import styled from '../../styled-components'; +import { OptionsContext } from '../OptionsProvider'; export function ArrayItemDetails({ schema }: { schema: SchemaModel }) { - if (!schema || (schema.type === 'string' && !schema.constraints.length)) return null; + const { hideSchemaPattern } = React.useContext(OptionsContext); + if ( + !schema || + (schema.type === 'string' && !schema.constraints.length) || + ((!schema?.pattern || hideSchemaPattern) && + !schema.items && + !schema.displayFormat && + !schema.constraints.length) + ) { + return null; + } return ( diff --git a/src/components/Parameters/Parameters.tsx b/src/components/Parameters/Parameters.tsx index 6ff7e769..3f4275df 100644 --- a/src/components/Parameters/Parameters.tsx +++ b/src/components/Parameters/Parameters.tsx @@ -80,7 +80,9 @@ export function BodyContent(props: { return ( <> {description !== undefined && } - + {schema?.type === 'object' && ( + + )} { return ( <> - + {schema?.type === 'object' && ( + + )} ); From 27a9dbaf46aded01a6512645dab27870a85cc73b Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Mon, 23 May 2022 11:32:15 +0300 Subject: [PATCH 15/19] feat: add support prefix items --- demo/openapi-3-1.yaml | 20 +++ demo/openapi.yaml | 20 +++ src/components/Fields/ArrayItemDetails.tsx | 2 +- src/components/Schema/ArraySchema.tsx | 4 + src/services/OpenAPIParser.ts | 16 +- .../__tests__/fixtures/3.1/prefixItems.json | 154 ++++++++++++++++++ .../__tests__/fixtures/arrayItems.json | 154 ++++++++++++++++++ src/services/__tests__/models/Schema.test.ts | 100 ++++++++++++ src/services/models/Schema.ts | 112 +++++++++++-- src/types/open-api.ts | 4 +- .../loadAndBundleSpec.test.ts.snap | 62 +++++++ src/utils/helpers.ts | 8 +- src/utils/openapi.ts | 7 +- 13 files changed, 644 insertions(+), 19 deletions(-) create mode 100644 src/services/__tests__/fixtures/3.1/prefixItems.json create mode 100644 src/services/__tests__/fixtures/arrayItems.json diff --git a/demo/openapi-3-1.yaml b/demo/openapi-3-1.yaml index 9c8d0f07..9c875fc6 100644 --- a/demo/openapi-3-1.yaml +++ b/demo/openapi-3-1.yaml @@ -1242,6 +1242,26 @@ components: type: string contentEncoding: base64 contentMediaType: image/png + addresses: + type: array + minItems: 0 + maxLength: 10 + prefixItems: + - type: object + properties: + city: + type: string + minLength: 0 + country: + type: string + minLength: 0 + street: + description: includes build/apartment number + type: string + minLength: 0 + - type: number + items: + type: string if: title: userStatus === 10 properties: diff --git a/demo/openapi.yaml b/demo/openapi.yaml index c18063e7..4008f2e2 100644 --- a/demo/openapi.yaml +++ b/demo/openapi.yaml @@ -1135,6 +1135,26 @@ components: description: User status type: integer format: int32 + addresses: + type: array + minItems: 0 + maxLength: 10 + items: + - type: object + properties: + city: + type: string + minLength: 0 + country: + type: string + minLength: 0 + street: + description: includes build/apartment number + type: string + minLength: 0 + - type: number + additionalItems: + type: string xml: name: User requestBodies: diff --git a/src/components/Fields/ArrayItemDetails.tsx b/src/components/Fields/ArrayItemDetails.tsx index 776d229d..f56ab0a7 100644 --- a/src/components/Fields/ArrayItemDetails.tsx +++ b/src/components/Fields/ArrayItemDetails.tsx @@ -14,7 +14,7 @@ export function ArrayItemDetails({ schema }: { schema: SchemaModel }) { ((!schema?.pattern || hideSchemaPattern) && !schema.items && !schema.displayFormat && - !schema.constraints.length) + !schema.constraints.length) // return null for cases where all constraints are empty ) { return null; } diff --git a/src/components/Schema/ArraySchema.tsx b/src/components/Schema/ArraySchema.tsx index 25ac633e..84bd716a 100644 --- a/src/components/Schema/ArraySchema.tsx +++ b/src/components/Schema/ArraySchema.tsx @@ -6,6 +6,7 @@ import { ArrayClosingLabel, ArrayOpenningLabel } from '../../common-elements'; import styled from '../../styled-components'; import { humanizeConstraints } from '../../utils'; import { TypeName } from '../../common-elements/fields'; +import { ObjectSchema } from './ObjectSchema'; const PaddedSchema = styled.div` padding-left: ${({ theme }) => theme.spacing.unit * 2}px; @@ -21,6 +22,9 @@ export class ArraySchema extends React.PureComponent { ? '' : `(${humanizeConstraints(schema)})`; + if (schema.fields) { + return ; + } if (schema.displayType && !itemsSchema && !minMaxItems.length) { return (
diff --git a/src/services/OpenAPIParser.ts b/src/services/OpenAPIParser.ts index b4d1edb5..897f1a79 100644 --- a/src/services/OpenAPIParser.ts +++ b/src/services/OpenAPIParser.ts @@ -1,6 +1,6 @@ import { OpenAPIRef, OpenAPISchema, OpenAPISpec, Referenced } from '../types'; -import { appendToMdHeading, isArray, IS_BROWSER } from '../utils/'; +import { appendToMdHeading, isArray, isBoolean, IS_BROWSER } from '../utils/'; import { JsonPointer } from '../utils/JsonPointer'; import { getDefinitionName, @@ -318,9 +318,19 @@ export class OpenAPIParser { } if (items !== undefined) { - receiver.items = receiver.items || {}; + const receiverItems = isBoolean(receiver.items) + ? { items: receiver.items } + : receiver.items + ? (Object.assign({}, receiver.items) as OpenAPISchema) + : {}; + const subSchemaItems = isBoolean(items) + ? { items } + : (Object.assign({}, items) as OpenAPISchema); // merge inner properties - receiver.items = this.mergeAllOf({ allOf: [receiver.items, items] }, $ref + '/items'); + receiver.items = this.mergeAllOf( + { allOf: [receiverItems, subSchemaItems] }, + $ref + '/items', + ); } if (required !== undefined) { diff --git a/src/services/__tests__/fixtures/3.1/prefixItems.json b/src/services/__tests__/fixtures/3.1/prefixItems.json new file mode 100644 index 00000000..63a4d078 --- /dev/null +++ b/src/services/__tests__/fixtures/3.1/prefixItems.json @@ -0,0 +1,154 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Schema definition with prefixItems", + "version": "1.0.0" + }, + "servers": [ + { + "url": "example.com" + } + ], + "components": { + "schemas": { + "Case1": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "prefixItems": [ + { + "type": "string", + "minLength": 0, + "maxLength": 10 + }, + { + "type": "number", + "minimum": 0, + "maximum": 10 + }, + { + "$ref": "#/components/schemas/Cat" + } + ], + "items": false + }, + "Case2": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "prefixItems": [ + { + "type": "string", + "minLength": 0, + "maxLength": 10 + }, + { + "type": "number", + "minimum": 0, + "maximum": 10 + }, + { + "$ref": "#/components/schemas/Cat" + } + ], + "items": true + }, + "Case3": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "prefixItems": [ + { + "type": "string", + "minLength": 0, + "maxLength": 10 + }, + { + "type": "number", + "minimum": 0, + "maximum": 10 + }, + { + "$ref": "#/components/schemas/Cat" + } + ], + "items": { + "$ref": "#/components/schemas/Dog" + } + }, + "Case4": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "prefixItems": [ + { + "type": "string", + "minLength": 0, + "maxLength": 10 + }, + { + "type": "number", + "minimum": 0, + "maximum": 10 + }, + { + "$ref": "#/components/schemas/Cat" + } + ], + "items": { + "type": "object", + "properties": { + "firstItem": { + "type": "string" + } + } + } + }, + "Case5": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "prefixItems": [ + { + "type": "string", + "minLength": 0, + "maxLength": 10 + }, + { + "type": "number", + "minimum": 0, + "maximum": 10 + }, + { + "$ref": "#/components/schemas/Cat" + } + ], + "items": { + "type": "array", + "items": [ + { + "type": "string", + "minLength": 0 + } + ] + } + }, + "Cat": { + "type": "object", + "properties": { + "color": { + "type": "string" + } + } + }, + "Dog": { + "type": "object", + "properties": { + "size": { + "type": "string" + } + } + } + } + } +} diff --git a/src/services/__tests__/fixtures/arrayItems.json b/src/services/__tests__/fixtures/arrayItems.json new file mode 100644 index 00000000..ebe25f34 --- /dev/null +++ b/src/services/__tests__/fixtures/arrayItems.json @@ -0,0 +1,154 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Schema definition with array items", + "version": "1.0.0" + }, + "servers": [ + { + "url": "example.com" + } + ], + "components": { + "schemas": { + "Case1": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "items": [ + { + "type": "string", + "minLength": 0, + "maxLength": 10 + }, + { + "type": "number", + "minimum": 0, + "maximum": 10 + }, + { + "$ref": "#/components/schemas/Cat" + } + ], + "additionalItems": false + }, + "Case2": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "items": [ + { + "type": "string", + "minLength": 0, + "maxLength": 10 + }, + { + "type": "number", + "minimum": 0, + "maximum": 10 + }, + { + "$ref": "#/components/schemas/Cat" + } + ], + "additionalItems": true + }, + "Case3": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "items": [ + { + "type": "string", + "minLength": 0, + "maxLength": 10 + }, + { + "type": "number", + "minimum": 0, + "maximum": 10 + }, + { + "$ref": "#/components/schemas/Cat" + } + ], + "additionalItems": { + "$ref": "#/components/schemas/Dog" + } + }, + "Case4": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "items": [ + { + "type": "string", + "minLength": 0, + "maxLength": 10 + }, + { + "type": "number", + "minimum": 0, + "maximum": 10 + }, + { + "$ref": "#/components/schemas/Cat" + } + ], + "additionalItems": { + "type": "object", + "properties": { + "firstItem": { + "type": "string" + } + } + } + }, + "Case5": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "items": [ + { + "type": "string", + "minLength": 0, + "maxLength": 10 + }, + { + "type": "number", + "minimum": 0, + "maximum": 10 + }, + { + "$ref": "#/components/schemas/Cat" + } + ], + "additionalItems": { + "type": "array", + "items": [ + { + "type": "string", + "minLength": 0 + } + ] + } + }, + "Cat": { + "type": "object", + "properties": { + "color": { + "type": "string" + } + } + }, + "Dog": { + "type": "object", + "properties": { + "size": { + "type": "string" + } + } + } + } + } +} diff --git a/src/services/__tests__/models/Schema.test.ts b/src/services/__tests__/models/Schema.test.ts index d6eb6738..3dfbf6f8 100644 --- a/src/services/__tests__/models/Schema.test.ts +++ b/src/services/__tests__/models/Schema.test.ts @@ -113,5 +113,105 @@ describe('Models', () => { expect(schema.fields![1].kind).toEqual('patternProperties'); expect(schema.fields![1].schema.type).toEqual('object'); }); + + describe('type array', () => { + function testImmutablePart(schema: SchemaModel) { + expect(schema.minItems).toEqual(1); + expect(schema.maxItems).toEqual(10); + expect(schema.fields![0].schema.type).toEqual('string'); + expect(schema.fields![1].schema.type).toEqual('number'); + } + const eachArray = ['../fixtures/3.1/prefixItems.json', '../fixtures/arrayItems.json']; + + test.each(eachArray)( + 'schemaDefinition should resolve prefixItems without additional items', + specFixture => { + const spec = require(specFixture); + const parser = new OpenAPIParser(spec, undefined, opts); + const schema = new SchemaModel(parser, spec.components.schemas.Case1, '', opts); + + testImmutablePart(schema); + + expect(schema.fields).toHaveLength(3); + expect(schema.fields![2].name).toEqual('[2]'); + expect(schema.fields![2].schema.pointer).toEqual('#/components/schemas/Cat'); + expect(schema.fields![2].schema.type).toEqual('object'); + }, + ); + + test.each(eachArray)( + 'schemaDefinition should resolve prefixItems with additional items', + specFixture => { + const spec = require(specFixture); + const parser = new OpenAPIParser(spec, undefined, opts); + const schema = new SchemaModel(parser, spec.components.schemas.Case2, '', opts); + + testImmutablePart(schema); + + expect(schema.fields).toHaveLength(4); + expect(schema.fields![3].name).toEqual('[3...]'); + expect(schema.fields![2].schema.type).toEqual('object'); + expect(schema.fields![2].schema.pointer).toEqual('#/components/schemas/Cat'); + expect(schema.fields![3].schema.type).toEqual('any'); + }, + ); + + test.each(eachArray)( + 'schemaDefinition should resolve prefixItems with additional items with $ref', + specFixture => { + const spec = require(specFixture); + const parser = new OpenAPIParser(spec, undefined, opts); + const schema = new SchemaModel(parser, spec.components.schemas.Case3, '', opts); + + testImmutablePart(schema); + + expect(schema.fields).toHaveLength(4); + expect(schema.fields![3].name).toEqual('[3...]'); + expect(schema.fields![2].schema.type).toEqual('object'); + expect(schema.fields![2].schema.pointer).toEqual('#/components/schemas/Cat'); + expect(schema.fields![3].schema.type).toEqual('object'); + expect(schema.fields![3].schema.pointer).toEqual('#/components/schemas/Dog'); + }, + ); + + test.each(eachArray)( + 'schemaDefinition should resolve prefixItems with additional schema items', + specFixture => { + const spec = require(specFixture); + const parser = new OpenAPIParser(spec, undefined, opts); + const schema = new SchemaModel(parser, spec.components.schemas.Case4, '', opts); + + testImmutablePart(schema); + + expect(schema.fields).toHaveLength(4); + expect(schema.fields![3].name).toEqual('[3...]'); + expect(schema.fields![2].schema.type).toEqual('object'); + expect(schema.fields![2].schema.pointer).toEqual('#/components/schemas/Cat'); + expect(schema.fields![3].schema.type).toEqual('object'); + }, + ); + + test.each(eachArray)( + 'schemaDefinition should resolve prefixItems with additional array items', + specFixture => { + const spec = require(specFixture); + const parser = new OpenAPIParser(spec, undefined, opts); + const schema = new SchemaModel(parser, spec.components.schemas.Case5, '', opts); + + testImmutablePart(schema); + + expect(schema.fields).toHaveLength(4); + expect(schema.fields![3].name).toEqual('[3...]'); + expect(schema.fields![2].schema.type).toEqual('object'); + expect(schema.fields![2].schema.pointer).toEqual('#/components/schemas/Cat'); + expect(schema.fields![3].schema.type).toEqual('array'); + expect(schema.fields![3].schema.fields).toHaveLength(1); + expect(schema.fields![3].schema.fields![0].schema.type).toEqual('string'); + expect(schema.fields![3].schema.fields![0].schema.constraints).toEqual([ + '>= 0 characters', + ]); + }, + ); + }); }); }); diff --git a/src/services/models/Schema.ts b/src/services/models/Schema.ts index 71a3a480..4816a149 100644 --- a/src/services/models/Schema.ts +++ b/src/services/models/Schema.ts @@ -12,7 +12,9 @@ import { extractExtensions, humanizeConstraints, isArray, + isBoolean, isNamedDefinition, + isObject, isPrimitiveType, JsonPointer, pluralizeType, @@ -188,17 +190,31 @@ export class SchemaModel { if (this.hasType('object')) { this.fields = buildFields(parser, schema, this.pointer, this.options); - } else if (this.hasType('array') && schema.items) { - this.items = new SchemaModel(parser, schema.items, this.pointer + '/items', this.options); - this.displayType = pluralizeType(this.items.displayType); - this.displayFormat = this.items.format; - this.typePrefix = this.items.typePrefix + l('arrayOf'); - this.title = this.title || this.items.title; - this.isPrimitive = this.items.isPrimitive; - if (this.example === undefined && this.items.example !== undefined) { + } else if (this.hasType('array')) { + if (isArray(schema.items) || isArray(schema.prefixItems)) { + this.fields = buildFields(parser, schema, this.pointer, this.options); + } else if (isObject(schema.items)) { + this.items = new SchemaModel( + parser, + schema.items as OpenAPISchema, + this.pointer + '/items', + this.options, + ); + } + + this.displayType = + schema.prefixItems || isArray(schema.items) + ? 'items' + : pluralizeType(this.items?.displayType || this.displayType); + this.displayFormat = this.items?.format || ''; + this.typePrefix = this.items?.typePrefix || '' + l('arrayOf'); + this.title = this.title || this.items?.title || ''; + this.isPrimitive = this.items?.isPrimitive || this.isPrimitive; + + if (this.example === undefined && this.items?.example !== undefined) { this.example = [this.items.example]; } - if (this.items.isPrimitive) { + if (this.items?.isPrimitive) { this.enum = this.items.enum; } if (isArray(this.type)) { @@ -400,9 +416,10 @@ function buildFields( $ref: string, options: RedocNormalizedOptions, ): FieldModel[] { - const props = schema.properties || {}; + const props = schema.properties || schema.prefixItems || schema.items || {}; const patternProps = schema.patternProperties || {}; const additionalProps = schema.additionalProperties || schema.unevaluatedProperties; + const itemsProps = schema.prefixItems ? schema.items : schema.additionalItems; const defaults = schema.default; let fields = Object.keys(props || []).map(fieldName => { let field = props[fieldName]; @@ -420,7 +437,7 @@ function buildFields( return new FieldModel( parser, { - name: fieldName, + name: schema.properties ? fieldName : `[${fieldName}]`, required, schema: { ...field, @@ -484,9 +501,82 @@ function buildFields( ); } + fields.push( + ...buildAdditionalItems({ + parser, + schema: itemsProps, + fieldsCount: fields.length, + $ref, + options, + }), + ); + return fields; } +function buildAdditionalItems({ + parser, + schema = false, + fieldsCount, + $ref, + options, +}: { + parser: OpenAPIParser; + schema?: OpenAPISchema | OpenAPISchema[] | boolean; + fieldsCount: number; + $ref: string; + options: RedocNormalizedOptions; +}) { + if (isBoolean(schema)) { + return schema + ? [ + new FieldModel( + parser, + { + name: `[${fieldsCount}...]`, + schema: {}, + }, + `${$ref}/additionalItems`, + options, + ), + ] + : []; + } + + if (isArray(schema)) { + return [ + ...schema.map( + (field, idx) => + new FieldModel( + parser, + { + name: `[${fieldsCount + idx}]`, + schema: field, + }, + `${$ref}/additionalItems`, + options, + ), + ), + ]; + } + + if (isObject(schema)) { + return [ + new FieldModel( + parser, + { + name: `[${fieldsCount}...]`, + schema: schema, + }, + `${$ref}/additionalItems`, + options, + ), + ]; + } + + return []; +} + function getDiscriminator(schema: OpenAPISchema): OpenAPISchema['discriminator'] { return schema.discriminator || schema['x-discriminator']; } diff --git a/src/types/open-api.ts b/src/types/open-api.ts index 60fdca26..afa98642 100644 --- a/src/types/open-api.ts +++ b/src/types/open-api.ts @@ -118,7 +118,7 @@ export interface OpenAPISchema { unevaluatedProperties?: boolean | OpenAPISchema; description?: string; default?: any; - items?: OpenAPISchema; + items?: OpenAPISchema | OpenAPISchema[] | boolean; required?: string[]; readOnly?: boolean; writeOnly?: boolean; @@ -156,6 +156,8 @@ export interface OpenAPISchema { const?: string; contentEncoding?: string; contentMediaType?: string; + prefixItems?: OpenAPISchema[]; + additionalItems?: OpenAPISchema | boolean; } export interface OpenAPIDiscriminator { diff --git a/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap b/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap index 0c3f8d4f..17fda88e 100644 --- a/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap +++ b/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap @@ -352,6 +352,37 @@ Object { }, "User": Object { "properties": Object { + "addresses": Object { + "additionalItems": Object { + "type": "string", + }, + "items": Array [ + Object { + "properties": Object { + "city": Object { + "minLength": 0, + "type": "string", + }, + "country": Object { + "minLength": 0, + "type": "string", + }, + "street": Object { + "description": "includes build/apartment number", + "minLength": 0, + "type": "string", + }, + }, + "type": "object", + }, + Object { + "type": "number", + }, + ], + "maxLength": 10, + "minItems": 0, + "type": "array", + }, "email": Object { "description": "User email address", "example": "john.smith@example.com", @@ -2257,6 +2288,37 @@ Object { "title": "userStatus === 10", }, "properties": Object { + "addresses": Object { + "items": Object { + "type": "string", + }, + "maxLength": 10, + "minItems": 0, + "prefixItems": Array [ + Object { + "properties": Object { + "city": Object { + "minLength": 0, + "type": "string", + }, + "country": Object { + "minLength": 0, + "type": "string", + }, + "street": Object { + "description": "includes build/apartment number", + "minLength": 0, + "type": "string", + }, + }, + "type": "object", + }, + Object { + "type": "number", + }, + ], + "type": "array", + }, "email": Object { "description": "User email address", "example": "john.smith@example.com", diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 35b19e52..16ed57c1 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -107,7 +107,7 @@ export const mergeObjects = (target: any, ...sources: any[]): any => { return mergeObjects(target, ...sources); }; -const isObject = (item: any): boolean => { +export const isObject = (item: unknown): item is Record => { return item !== null && typeof item === 'object'; }; @@ -210,6 +210,10 @@ export function unescapeHTMLChars(str: string): string { .replace(/"/g, '"'); } -export function isArray(value: unknown): value is Array { +export function isArray(value: unknown): value is any[] { return Array.isArray(value); } + +export function isBoolean(value: unknown): value is boolean { + return typeof value === 'boolean'; +} diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index 5c9986c5..89ad4330 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -16,7 +16,7 @@ import { Referenced, } from '../types'; import { IS_BROWSER } from './dom'; -import { isNumeric, removeQueryString, resolveUrl, isArray } from './helpers'; +import { isNumeric, removeQueryString, resolveUrl, isArray, isBoolean } from './helpers'; function isWildcardStatusCode(statusCode: string | number): statusCode is string { return typeof statusCode === 'string' && /\dxx/i.test(statusCode); @@ -139,8 +139,13 @@ export function isPrimitiveType( : schema.additionalProperties === undefined && schema.unevaluatedProperties === undefined; } + if (isArray(schema.items) || isArray(schema.prefixItems)) { + return false; + } + if ( schema.items !== undefined && + !isBoolean(schema.items) && (type === 'array' || (isArrayType && type?.includes('array'))) ) { isPrimitive = isPrimitiveType(schema.items, schema.items.type); From b98c7a0d346d67beb2cfc739bb0014383af800f8 Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Mon, 30 May 2022 16:18:44 +0300 Subject: [PATCH 16/19] chore: update openapi-sampler to v1.3.0 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index f638dd3a..1628b932 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "mark.js": "^8.11.1", "marked": "^4.0.15", "mobx-react": "^7.2.0", - "openapi-sampler": "^1.2.3", + "openapi-sampler": "^1.3.0", "path-browserify": "^1.0.1", "perfect-scrollbar": "^1.5.1", "polished": "^4.1.3", @@ -14217,9 +14217,9 @@ } }, "node_modules/openapi-sampler": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.2.3.tgz", - "integrity": "sha512-dH2QYXqakorV5dxkP/f1BV3Ku4yNn21YmBsqJunnyrHLw7mnCNZZldftgrEpv/66b1m5oaUAmiJoJN+FqBEkJg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.3.0.tgz", + "integrity": "sha512-2QfjK1oM9Sv0q82Ae1RrUe3yfFmAyjF548+6eAeb+h/cL1Uj51TW4UezraBEvwEdzoBgfo4AaTLVFGTKj+yYDw==", "dependencies": { "@types/json-schema": "^7.0.7", "json-pointer": "0.6.2" @@ -29872,9 +29872,9 @@ } }, "openapi-sampler": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.2.3.tgz", - "integrity": "sha512-dH2QYXqakorV5dxkP/f1BV3Ku4yNn21YmBsqJunnyrHLw7mnCNZZldftgrEpv/66b1m5oaUAmiJoJN+FqBEkJg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.3.0.tgz", + "integrity": "sha512-2QfjK1oM9Sv0q82Ae1RrUe3yfFmAyjF548+6eAeb+h/cL1Uj51TW4UezraBEvwEdzoBgfo4AaTLVFGTKj+yYDw==", "requires": { "@types/json-schema": "^7.0.7", "json-pointer": "0.6.2" diff --git a/package.json b/package.json index 3190e111..9afc1851 100644 --- a/package.json +++ b/package.json @@ -150,7 +150,7 @@ "mark.js": "^8.11.1", "marked": "^4.0.15", "mobx-react": "^7.2.0", - "openapi-sampler": "^1.2.3", + "openapi-sampler": "^1.3.0", "path-browserify": "^1.0.1", "perfect-scrollbar": "^1.5.1", "polished": "^4.1.3", From a863302cc803bdf27187c613157ba90af1040fc4 Mon Sep 17 00:00:00 2001 From: Anastasiia Derymarko Date: Mon, 30 May 2022 18:55:39 +0300 Subject: [PATCH 17/19] feat: remove auth section (#2022) --- README.md | 1 - .../SecurityRequirement/OAuthFlow.tsx | 71 ++++++ .../SecurityRequirement/RequiredScopesRow.tsx | 18 ++ .../SecurityRequirement/SecurityDetails.tsx | 65 +++++ .../SecurityRequirement/SecurityHeader.tsx | 43 ++++ .../SecurityRequirement.tsx | 229 +++++++----------- .../SecurityRequirement/styled.elements.ts | 129 ++++++++++ .../SecuritySchemes/SecuritySchemes.tsx | 117 +-------- src/components/SeeMore/SeeMore.tsx | 65 +++++ src/components/StoreBuilder.ts | 6 +- .../__tests__/SecurityRequirement.test.tsx | 66 +++-- .../DiscriminatorDropdown.test.tsx.snap | 20 +- .../SecurityRequirement.test.tsx.snap | 23 ++ .../fixtures/simple-security-fixture.json | 67 +++++ src/services/AppStore.ts | 12 +- src/services/MenuBuilder.ts | 17 +- src/services/MenuStore.ts | 1 + src/services/OpenAPIParser.ts | 30 +-- src/services/RedocNormalizedOptions.ts | 6 +- src/services/models/SecurityRequirement.ts | 3 +- src/services/models/SecuritySchemes.ts | 2 +- src/utils/openapi.ts | 1 - 22 files changed, 653 insertions(+), 339 deletions(-) create mode 100644 src/components/SecurityRequirement/OAuthFlow.tsx create mode 100644 src/components/SecurityRequirement/RequiredScopesRow.tsx create mode 100644 src/components/SecurityRequirement/SecurityDetails.tsx create mode 100644 src/components/SecurityRequirement/SecurityHeader.tsx create mode 100644 src/components/SecurityRequirement/styled.elements.ts create mode 100644 src/components/SeeMore/SeeMore.tsx create mode 100644 src/components/__tests__/__snapshots__/SecurityRequirement.test.tsx.snap create mode 100644 src/components/__tests__/fixtures/simple-security-fixture.json diff --git a/README.md b/README.md index 5d1c28dc..5040347a 100644 --- a/README.md +++ b/README.md @@ -232,7 +232,6 @@ You can use all of the following options with the standalone version of the 50). In this mode ReDoc shows initial screen ASAP and then renders the rest operations asynchronously while showing progress bar on the top. Check out the [demo](\\redocly.github.io/redoc) for the example.~~ * `menuToggle` - if true clicking second time on expanded menu item will collapse it, default `true`. * `nativeScrollbars` - use native scrollbar for sidemenu instead of perfect-scroll (scrolling performance optimization for big specs). -* `noAutoAuth` - do not inject Authentication section automatically. * `onlyRequiredInSamples` - shows only required fields in request samples. * `pathInMiddlePanel` - show path link and HTTP verb in the middle panel instead of the right one. * `requiredPropsFirst` - show required properties first ordered in the same order as in `required` array. diff --git a/src/components/SecurityRequirement/OAuthFlow.tsx b/src/components/SecurityRequirement/OAuthFlow.tsx new file mode 100644 index 00000000..4b20e1f7 --- /dev/null +++ b/src/components/SecurityRequirement/OAuthFlow.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; +import { OpenAPISecurityScheme } from '../../types'; +import { SecurityRow } from './styled.elements'; +import { SeeMore } from '../SeeMore/SeeMore'; +import { Markdown } from '../Markdown/Markdown'; + +export interface OAuthFlowProps { + type: string; + flow: OpenAPISecurityScheme['flows'][keyof OpenAPISecurityScheme['flows']]; + RequiredScopes?: JSX.Element; +} + +export function OAuthFlowComponent(props: OAuthFlowProps) { + const { type, flow, RequiredScopes } = props; + const scopesNames = Object.keys(flow?.scopes || {}); + console.log('rended'); + return ( + <> + + Flow type: + {type} + + {(type === 'implicit' || type === 'authorizationCode') && ( + + Authorization URL: + + + {(flow as any).authorizationUrl} + + + + )} + {(type === 'password' || type === 'clientCredentials' || type === 'authorizationCode') && ( + + Token URL: + {(flow as any).tokenUrl} + + )} + {flow!.refreshUrl && ( + + Refresh URL: + {flow!.refreshUrl} + + )} + {!!scopesNames.length && ( + <> + {RequiredScopes || null} + + Scopes: + + +
    + {scopesNames.map(scope => ( +
  • + {scope} -{' '} + +
  • + ))} +
+
+ + )} + + ); +} + +export const OAuthFlow = React.memo(OAuthFlowComponent); diff --git a/src/components/SecurityRequirement/RequiredScopesRow.tsx b/src/components/SecurityRequirement/RequiredScopesRow.tsx new file mode 100644 index 00000000..8f899b60 --- /dev/null +++ b/src/components/SecurityRequirement/RequiredScopesRow.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; + +export const RequiredScopesRow = ({ scopes }: { scopes: string[] }): JSX.Element | null => { + if (!scopes.length) return null; + + return ( +
+ Required scopes: + {scopes.map((scope, idx) => { + return ( + + {scope}{' '} + + ); + })} +
+ ); +}; diff --git a/src/components/SecurityRequirement/SecurityDetails.tsx b/src/components/SecurityRequirement/SecurityDetails.tsx new file mode 100644 index 00000000..54cd61f8 --- /dev/null +++ b/src/components/SecurityRequirement/SecurityDetails.tsx @@ -0,0 +1,65 @@ +import * as React from 'react'; +import { SecuritySchemeModel } from '../../services'; +import { titleize } from '../../utils'; +import { StyledMarkdownBlock } from '../Markdown/styled.elements'; +import { SecurityRow } from './styled.elements'; +import { OAuthFlow } from './OAuthFlow'; + +interface SecuritySchemaProps { + RequiredScopes?: JSX.Element; + scheme: SecuritySchemeModel; +} +export function SecurityDetails(props: SecuritySchemaProps) { + const { RequiredScopes, scheme } = props; + + return ( + + {scheme.apiKey ? ( + <> + + {titleize(scheme.apiKey.in || '')} parameter name: + {scheme.apiKey.name} + + {RequiredScopes} + + ) : scheme.http ? ( + <> + + HTTP Authorization Scheme: + {scheme.http.scheme} + + + {scheme.http.scheme === 'bearer' && scheme.http.bearerFormat && ( + <> + Bearer format: + {scheme.http.bearerFormat} + + )} + + {RequiredScopes} + + ) : scheme.openId ? ( + <> + + Connect URL: + + + {scheme.openId.connectUrl} + + + + {RequiredScopes} + + ) : scheme.flows ? ( + Object.keys(scheme.flows).map(type => ( + + )) + ) : null} + + ); +} diff --git a/src/components/SecurityRequirement/SecurityHeader.tsx b/src/components/SecurityRequirement/SecurityHeader.tsx new file mode 100644 index 00000000..c542b51c --- /dev/null +++ b/src/components/SecurityRequirement/SecurityHeader.tsx @@ -0,0 +1,43 @@ +import { SecurityRequirementModel } from '../../services/models/SecurityRequirement'; +import { + ScopeName, + SecurityRequirementAndWrap, + SecurityRequirementOrWrap, +} from './styled.elements'; +import * as React from 'react'; +import { AUTH_TYPES } from '../SecuritySchemes/SecuritySchemes'; + +export interface SecurityRequirementProps { + security: SecurityRequirementModel; + showSecuritySchemeType?: boolean; + expanded: boolean; +} + +export function SecurityHeader(props: SecurityRequirementProps) { + const { security, showSecuritySchemeType, expanded } = props; + + const grouping = security.schemes.length > 1; + return ( + + {grouping && '('} + {security.schemes.map(scheme => { + return ( + + {showSecuritySchemeType && `${AUTH_TYPES[scheme.type] || scheme.type}: `} + {scheme.displayName} + {expanded && scheme.scopes.length + ? [ + ' (', + scheme.scopes.map(scope => ( + {scope} + )), + ') ', + ] + : null} + + ); + })} + {grouping && ') '} + + ); +} diff --git a/src/components/SecurityRequirement/SecurityRequirement.tsx b/src/components/SecurityRequirement/SecurityRequirement.tsx index 633bdeaa..7e8642ea 100644 --- a/src/components/SecurityRequirement/SecurityRequirement.tsx +++ b/src/components/SecurityRequirement/SecurityRequirement.tsx @@ -1,153 +1,102 @@ import * as React from 'react'; - -import styled, { media } from '../../styled-components'; - -import { Link, UnderlinedHeader } from '../../common-elements/'; +import { useState } from 'react'; import { SecurityRequirementModel } from '../../services/models/SecurityRequirement'; -import { linksCss } from '../Markdown/styled.elements'; - -const ScopeNameList = styled.ul` - display: inline; - list-style: none; - padding: 0; - - li { - display: inherit; - - &:after { - content: ','; - } - &:last-child:after { - content: none; - } - } -`; - -const ScopeName = styled.code` - font-size: ${props => props.theme.typography.code.fontSize}; - font-family: ${props => props.theme.typography.code.fontFamily}; - border: 1px solid ${({ theme }) => theme.colors.border.dark}; - margin: 0 3px; - padding: 0.2em; - display: inline-block; - line-height: 1; -`; - -const SecurityRequirementAndWrap = styled.span` - &:after { - content: ' AND '; - font-weight: bold; - } - - &:last-child:after { - content: none; - } - - ${linksCss}; -`; - -const SecurityRequirementOrWrap = styled.span` - &:before { - content: '( '; - font-weight: bold; - } - &:after { - content: ' ) OR '; - font-weight: bold; - } - &:last-child:after { - content: ' )'; - } - - &:only-child:before, - &:only-child:after { - content: none; - } - - ${linksCss}; -`; - -export interface SecurityRequirementProps { - security: SecurityRequirementModel; -} - -export class SecurityRequirement extends React.PureComponent { - render() { - const security = this.props.security; - return ( - - {security.schemes.length ? ( - security.schemes.map(scheme => { - return ( - - {scheme.displayName} - {scheme.scopes.length > 0 && ' ('} - - {scheme.scopes.map(scope => ( -
  • - {scope} -
  • - ))} -
    - {scheme.scopes.length > 0 && ') '} -
    - ); - }) - ) : ( - None - )} -
    - ); - } -} - -const AuthHeaderColumn = styled.div` - flex: 1 1 auto; -`; - -const SecuritiesColumn = styled.div` - width: ${props => props.theme.schema.defaultDetailsWidth}; - ${media.lessThan('small')` - margin-top: 10px; - `} -`; - -const AuthHeader = styled(UnderlinedHeader)` - display: inline-block; - margin: 0; -`; - -const Wrap = styled.div` - width: 100%; - display: flex; - margin: 1em 0; - - ${media.lessThan('small')` - flex-direction: column; - `} -`; +import { + AuthHeader, + AuthHeaderColumn, + SecuritiesColumn, + SecurityDetailsStyle, + Wrap, +} from './styled.elements'; +import { useStore } from '../StoreBuilder'; +import { SecurityHeader } from './SecurityHeader'; +import { RequiredScopesRow } from './RequiredScopesRow'; +import { AUTH_TYPES } from '../SecuritySchemes/SecuritySchemes'; +import { Markdown } from '../Markdown/Markdown'; +import { SecurityDetails } from './SecurityDetails'; +import { ShelfIcon } from '../../common-elements'; export interface SecurityRequirementsProps { securities: SecurityRequirementModel[]; } -export class SecurityRequirements extends React.PureComponent { - render() { - const securities = this.props.securities; - if (!securities.length) { - return null; - } - return ( - - - Authorizations: +export function SecurityRequirements(props: SecurityRequirementsProps) { + const store = useStore(); + const showSecuritySchemeType = store?.options.showSecuritySchemeType; + const [expanded, setExpanded] = useState(false); + + const { securities } = props; + + if (!securities?.length) { + return null; + } + + const operationSecuritySchemes = store?.spec.securitySchemes.schemes.filter(({ id }) => { + return securities.find(security => security.schemes.find(scheme => scheme.id === id)); + }); + + return ( + <> + + setExpanded(!expanded)}> + Authorizations: + - + {securities.map((security, idx) => ( - + ))} - ); - } + {expanded && + operationSecuritySchemes?.length && + operationSecuritySchemes.map((scheme, idx) => ( + +
    + {AUTH_TYPES[scheme.type] || scheme.type}: {scheme.id} +
    + + + } + /> +
    + ))} + + ); +} + +const LockIcon = () => ( + + + +); + +function getRequiredScopes(id: string, securities: SecurityRequirementModel[]): string[] { + const allScopes: string[] = []; + let securitiesLength = securities.length; + + while (securitiesLength--) { + const security = securities[securitiesLength]; + let schemesLength = security.schemes.length; + while (schemesLength--) { + const scheme = security.schemes[schemesLength]; + if (scheme.id === id) { + allScopes.push(...scheme.scopes); + } + } + } + + return Array.from(new Set(allScopes)); } diff --git a/src/components/SecurityRequirement/styled.elements.ts b/src/components/SecurityRequirement/styled.elements.ts new file mode 100644 index 00000000..d7e93546 --- /dev/null +++ b/src/components/SecurityRequirement/styled.elements.ts @@ -0,0 +1,129 @@ +import styled from 'styled-components'; +import { linksCss } from '../Markdown/styled.elements'; +import { media } from '../../styled-components'; +import { UnderlinedHeader } from '../../common-elements'; + +export const Header = styled.div` + background-color: #e4e7eb; +`; + +export const ScopeNameList = styled.ul` + display: inline; + list-style: none; + padding: 0; + + li { + display: inherit; + + &:after { + content: ','; + } + &:last-child:after { + content: none; + } + } +`; + +export const ScopeName = styled.code` + font-size: ${props => props.theme.typography.code.fontSize}; + font-family: ${props => props.theme.typography.code.fontFamily}; + margin: 0 3px; + padding: 0.2em; + display: inline-block; + line-height: 1; + + &:after { + content: ','; + font-weight: normal; + } + + &:last-child:after { + content: none; + } +`; + +export const SecurityRequirementAndWrap = styled.span` + &:after { + content: ' and '; + font-weight: normal; + } + + &:last-child:after { + content: none; + } + + ${linksCss}; +`; + +export const SecurityRequirementOrWrap = styled.span<{ expanded?: boolean }>` + ${p => !p.expanded && `white-space: nowrap;`} + &:after { + content: ' or '; + ${p => p.expanded && `content: ' or \\a';`} + white-space: pre; + } + + &:last-child:after, + &:only-child:after { + content: none; + } + + ${linksCss}; +`; + +export const AuthHeaderColumn = styled.div` + flex: 1 1 auto; + cursor: pointer; +`; + +export const SecuritiesColumn = styled.div<{ expanded?: boolean }>` + width: ${props => props.theme.schema.defaultDetailsWidth}; + text-overflow: ellipsis; + border-radius: 4px; + overflow: hidden; + ${p => + p.expanded && + `background: ${p.theme.colors.gray['100']}; + padding: 8px 9.6px; + margin: 20px 0; + width: 100%; + `}; + ${media.lessThan('small')` + margin-top: 10px; + `} +`; + +export const AuthHeader = styled(UnderlinedHeader)` + display: inline-block; + margin: 0; +`; + +export const Wrap = styled.div<{ expanded?: boolean }>` + width: 100%; + display: flex; + margin: 1em 0; + flex-direction: ${p => (p.expanded ? 'column' : 'row')}; + ${media.lessThan('small')` + flex-direction: column; + `} +`; + +export const SecurityRow = styled.div` + margin: 0.5em 0; +`; + +export const SecurityDetailsStyle = styled.div` + border-bottom: 1px solid ${({ theme }) => theme.colors.border.dark}; + margin-bottom: 1.5em; + padding-bottom: 0.7em; + + h5 { + line-height: 1em; + margin: 0 0 0.6em; + font-size: ${({ theme }) => theme.typography.fontSize}; + } + + .redoc-markdown p:first-child { + display: inline; + } +`; diff --git a/src/components/SecuritySchemes/SecuritySchemes.tsx b/src/components/SecuritySchemes/SecuritySchemes.tsx index 817b0666..34a92ada 100644 --- a/src/components/SecuritySchemes/SecuritySchemes.tsx +++ b/src/components/SecuritySchemes/SecuritySchemes.tsx @@ -1,72 +1,18 @@ import * as React from 'react'; -import { SecuritySchemesModel } from '../../services/models'; - -import { H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements'; -import { OpenAPISecurityScheme } from '../../types'; -import { titleize } from '../../utils/helpers'; +import { SecuritySchemesModel } from '../../services'; +import { H2, Row, ShareLink, MiddlePanel, Section } from '../../common-elements'; import { Markdown } from '../Markdown/Markdown'; -import { StyledMarkdownBlock } from '../Markdown/styled.elements'; +import { SecurityDetails } from '../SecurityRequirement/SecurityDetails'; +import { SecurityDetailsStyle, SecurityRow } from '../SecurityRequirement/styled.elements'; -const AUTH_TYPES = { +export const AUTH_TYPES = { oauth2: 'OAuth2', apiKey: 'API Key', http: 'HTTP', openIdConnect: 'OpenID Connect', }; -export interface OAuthFlowProps { - type: string; - flow: OpenAPISecurityScheme['flows'][keyof OpenAPISecurityScheme['flows']]; -} - -export class OAuthFlow extends React.PureComponent { - render() { - const { type, flow } = this.props; - const scopesNames = Object.keys(flow?.scopes || {}); - return ( - - {type} OAuth Flow - - {type === 'implicit' || type === 'authorizationCode' ? ( -
    - Authorization URL: - {(flow as any).authorizationUrl} -
    - ) : null} - {type === 'password' || type === 'clientCredentials' || type === 'authorizationCode' ? ( -
    - Token URL: - {(flow as any).tokenUrl} -
    - ) : null} - {flow!.refreshUrl && ( -
    - Refresh URL: - {flow!.refreshUrl} -
    - )} - {!!scopesNames.length && ( - <> -
    - Scopes: -
    -
      - {scopesNames.map(scope => ( -
    • - {scope} -{' '} - -
    • - ))} -
    - - )} - - - ); - } -} - export interface SecurityDefsProps { securitySchemes: SecuritySchemesModel; } @@ -82,52 +28,13 @@ export class SecurityDefs extends React.PureComponent { {scheme.displayName} - - - - - - - - {scheme.apiKey ? ( - - - - - ) : scheme.http ? ( - [ - - - - , - scheme.http.scheme === 'bearer' && scheme.http.bearerFormat && ( - - - - - ), - ] - ) : scheme.openId ? ( - - - - - ) : scheme.flows ? ( - Object.keys(scheme.flows).map(type => ( - - )) - ) : null} - -
    Security Scheme Type {AUTH_TYPES[scheme.type] || scheme.type}
    {titleize(scheme.apiKey.in || '')} parameter name: {scheme.apiKey.name}
    HTTP Authorization Scheme {scheme.http.scheme}
    Bearer format "{scheme.http.bearerFormat}"
    Connect URL - - {scheme.openId.connectUrl} - -
    -
    + + + Security Scheme Type: + {AUTH_TYPES[scheme.type] || scheme.type} + + + diff --git a/src/components/SeeMore/SeeMore.tsx b/src/components/SeeMore/SeeMore.tsx new file mode 100644 index 00000000..8e3dc2e7 --- /dev/null +++ b/src/components/SeeMore/SeeMore.tsx @@ -0,0 +1,65 @@ +import * as React from 'react'; +import styled from 'styled-components'; + +const TOLERANCE_PX = 20; + +interface SeeMoreProps { + children?: React.ReactNode; + height: string; +} + +export function SeeMore({ children, height }: SeeMoreProps): JSX.Element { + const ref = React.createRef() as React.RefObject; + const [showMore, setShowMore] = React.useState(false); + const [showLink, setShowLink] = React.useState(false); + + React.useEffect(() => { + if (ref.current && ref.current.clientHeight + TOLERANCE_PX < ref.current.scrollHeight) { + setShowLink(true); + } + }, [ref]); + + const onClickMore = () => { + setShowMore(!showMore); + }; + + return ( + <> + + {children} + + + {showLink && ( + + {showMore ? 'See less' : 'See more'} + + )} + + + ); +} + +const Container = styled.div` + overflow-y: hidden; +`; + +const ButtonContainer = styled.div<{ dimmed?: boolean }>` + text-align: center; + line-height: 1.5em; + ${({ dimmed }) => + dimmed && + `background-image: linear-gradient(to bottom, transparent,rgb(255 255 255)); + position: relative; + top: -0.5em; + padding-top: 0.5em; + background-position-y: -1em; + `} +`; + +const ButtonLinkStyled = styled.a` + cursor: pointer; +`; diff --git a/src/components/StoreBuilder.ts b/src/components/StoreBuilder.ts index 99630539..14f8446d 100644 --- a/src/components/StoreBuilder.ts +++ b/src/components/StoreBuilder.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { createContext } from 'react'; +import { createContext, useContext } from 'react'; import { AppStore } from '../services/'; import { RedocRawOptions } from '../services/RedocNormalizedOptions'; @@ -79,3 +79,7 @@ export function StoreBuilder(props: StoreBuilderProps) { store, }); } + +export function useStore(): AppStore | undefined { + return useContext(StoreContext); +} diff --git a/src/components/__tests__/SecurityRequirement.test.tsx b/src/components/__tests__/SecurityRequirement.test.tsx index 097777a6..87a2e511 100644 --- a/src/components/__tests__/SecurityRequirement.test.tsx +++ b/src/components/__tests__/SecurityRequirement.test.tsx @@ -1,28 +1,52 @@ import * as React from 'react'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; -import { OpenAPIParser } from '../../services'; -import { SecurityRequirementModel } from '../../services/models/SecurityRequirement'; -import { SecurityRequirement } from '../SecurityRequirement/SecurityRequirement'; -import { RedocNormalizedOptions } from '../../services/RedocNormalizedOptions'; +import { + createStore, + OpenAPIParser, + OperationModel, + RedocNormalizedOptions, + SecuritySchemesModel, +} from '../../services'; +import { StoreProvider } from '../StoreBuilder'; +import { SecurityRequirements } from '../SecurityRequirement/SecurityRequirement'; +import { withTheme } from '../testProviders'; +import { SecurityDefs } from '../SecuritySchemes/SecuritySchemes'; +import * as simpleSecurityFixture from './fixtures/simple-security-fixture.json'; -const options = new RedocNormalizedOptions({}); -describe('Components', () => { - describe('SecurityRequirement', () => { - describe('SecurityRequirement', () => { - it("should render 'None' when empty object in security open api", () => { - const parser = new OpenAPIParser( - { openapi: '3.0', info: { title: 'test', version: '0' }, paths: {} }, - undefined, - options, +describe('SecurityRequirement', () => { + it('should render authDefinition', async () => { + const store = await createStore(simpleSecurityFixture, undefined, { + showSecuritySchemeType: true, + }); + + store.spec.contentItems.forEach((item: OperationModel) => { + if (item.security) { + const component = mount( + withTheme( + + , + , + ), ); - const securityRequirement = new SecurityRequirementModel({}, parser); - const securityElement = shallow( - , - ).getElement(); - expect(securityElement.props.children.type.target).toEqual('span'); - expect(securityElement.props.children.props.children).toEqual('None'); - }); + expect(component.html()).toMatchSnapshot(); + component.find('svg').simulate('click'); + //Security expanded + expect(component.html()).toMatchSnapshot(); + } }); }); + + it('should render SecurityDefs', async () => { + const parser = new OpenAPIParser( + simpleSecurityFixture, + undefined, + new RedocNormalizedOptions({}), + ); + + const component = mount( + withTheme(), + ); + expect(component.html()).toMatchSnapshot(); + }); }); diff --git a/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap b/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap index d2226cc6..03eb89d5 100644 --- a/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap @@ -96,7 +96,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "menuToggle": true, "minCharacterLengthToInitSearch": 3, "nativeScrollbars": false, - "noAutoAuth": false, "nonce": undefined, "onlyRequiredInSamples": false, "pathInMiddlePanel": false, @@ -106,6 +105,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, + "showSecuritySchemeType": false, "showWebhookVerb": false, "sideNavStyle": "summary-only", "simpleOneOfTypeLabel": false, @@ -353,7 +353,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "menuToggle": true, "minCharacterLengthToInitSearch": 3, "nativeScrollbars": false, - "noAutoAuth": false, "nonce": undefined, "onlyRequiredInSamples": false, "pathInMiddlePanel": false, @@ -363,6 +362,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, + "showSecuritySchemeType": false, "showWebhookVerb": false, "sideNavStyle": "summary-only", "simpleOneOfTypeLabel": false, @@ -585,7 +585,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "menuToggle": true, "minCharacterLengthToInitSearch": 3, "nativeScrollbars": false, - "noAutoAuth": false, "nonce": undefined, "onlyRequiredInSamples": false, "pathInMiddlePanel": false, @@ -595,6 +594,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, + "showSecuritySchemeType": false, "showWebhookVerb": false, "sideNavStyle": "summary-only", "simpleOneOfTypeLabel": false, @@ -884,7 +884,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "menuToggle": true, "minCharacterLengthToInitSearch": 3, "nativeScrollbars": false, - "noAutoAuth": false, "nonce": undefined, "onlyRequiredInSamples": false, "pathInMiddlePanel": false, @@ -894,6 +893,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, + "showSecuritySchemeType": false, "showWebhookVerb": false, "sideNavStyle": "summary-only", "simpleOneOfTypeLabel": false, @@ -1141,7 +1141,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "menuToggle": true, "minCharacterLengthToInitSearch": 3, "nativeScrollbars": false, - "noAutoAuth": false, "nonce": undefined, "onlyRequiredInSamples": false, "pathInMiddlePanel": false, @@ -1151,6 +1150,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, + "showSecuritySchemeType": false, "showWebhookVerb": false, "sideNavStyle": "summary-only", "simpleOneOfTypeLabel": false, @@ -1373,7 +1373,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "menuToggle": true, "minCharacterLengthToInitSearch": 3, "nativeScrollbars": false, - "noAutoAuth": false, "nonce": undefined, "onlyRequiredInSamples": false, "pathInMiddlePanel": false, @@ -1383,6 +1382,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, + "showSecuritySchemeType": false, "showWebhookVerb": false, "sideNavStyle": "summary-only", "simpleOneOfTypeLabel": false, @@ -1628,7 +1628,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "menuToggle": true, "minCharacterLengthToInitSearch": 3, "nativeScrollbars": false, - "noAutoAuth": false, "nonce": undefined, "onlyRequiredInSamples": false, "pathInMiddlePanel": false, @@ -1638,6 +1637,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, + "showSecuritySchemeType": false, "showWebhookVerb": false, "sideNavStyle": "summary-only", "simpleOneOfTypeLabel": false, @@ -1924,7 +1924,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "menuToggle": true, "minCharacterLengthToInitSearch": 3, "nativeScrollbars": false, - "noAutoAuth": false, "nonce": undefined, "onlyRequiredInSamples": false, "pathInMiddlePanel": false, @@ -1934,6 +1933,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, + "showSecuritySchemeType": false, "showWebhookVerb": false, "sideNavStyle": "summary-only", "simpleOneOfTypeLabel": false, @@ -2181,7 +2181,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "menuToggle": true, "minCharacterLengthToInitSearch": 3, "nativeScrollbars": false, - "noAutoAuth": false, "nonce": undefined, "onlyRequiredInSamples": false, "pathInMiddlePanel": false, @@ -2191,6 +2190,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, + "showSecuritySchemeType": false, "showWebhookVerb": false, "sideNavStyle": "summary-only", "simpleOneOfTypeLabel": false, @@ -2413,7 +2413,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "menuToggle": true, "minCharacterLengthToInitSearch": 3, "nativeScrollbars": false, - "noAutoAuth": false, "nonce": undefined, "onlyRequiredInSamples": false, "pathInMiddlePanel": false, @@ -2423,6 +2422,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, + "showSecuritySchemeType": false, "showWebhookVerb": false, "sideNavStyle": "summary-only", "simpleOneOfTypeLabel": false, diff --git a/src/components/__tests__/__snapshots__/SecurityRequirement.test.tsx.snap b/src/components/__tests__/__snapshots__/SecurityRequirement.test.tsx.snap new file mode 100644 index 00000000..506b6dd9 --- /dev/null +++ b/src/components/__tests__/__snapshots__/SecurityRequirement.test.tsx.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SecurityRequirement should render SecurityDefs 1`] = ` +"

    petstore_auth

    Get access to data while protecting your account credentials. +OAuth2 is also a safer and more secure way to give you access.

    +
    Security Scheme Type: OAuth2
    Flow type: implicit
    Scopes:
    • write:pets -

      modify pets in your account

      +
    • read:pets -

      read your pets

      +

    GitLab_PersonalAccessToken

    GitLab Personal Access Token description

    +
    Security Scheme Type: API Key
    Header parameter name: PRIVATE-TOKEN

    GitLab_OpenIdConnect

    GitLab OpenIdConnect description

    +
    Security Scheme Type: OpenID Connect

    basicAuth

    Security Scheme Type: HTTP
    HTTP Authorization Scheme: basic
    " +`; + +exports[`SecurityRequirement should render authDefinition 1`] = `"
    Authorizations:
    (API Key: GitLab_PersonalAccessTokenOpenID Connect: GitLab_OpenIdConnectHTTP: basicAuth) OAuth2: petstore_auth
    ,"`; + +exports[`SecurityRequirement should render authDefinition 2`] = ` +"
    Authorizations:
    (API Key: GitLab_PersonalAccessTokenOpenID Connect: GitLab_OpenIdConnectHTTP: basicAuth) OAuth2: petstore_auth (write:petsread:pets)
    OAuth2: petstore_auth

    Get access to data while protecting your account credentials. +OAuth2 is also a safer and more secure way to give you access.

    +
    Flow type: implicit
    Required scopes: write:pets read:pets
    Scopes:
    • write:pets -

      modify pets in your account

      +
    • read:pets -

      read your pets

      +
    API Key: GitLab_PersonalAccessToken

    GitLab Personal Access Token description

    +
    Header parameter name: PRIVATE-TOKEN
    OpenID Connect: GitLab_OpenIdConnect

    GitLab OpenIdConnect description

    +
    HTTP: basicAuth
    HTTP Authorization Scheme: basic
    ," +`; diff --git a/src/components/__tests__/fixtures/simple-security-fixture.json b/src/components/__tests__/fixtures/simple-security-fixture.json new file mode 100644 index 00000000..084ef84a --- /dev/null +++ b/src/components/__tests__/fixtures/simple-security-fixture.json @@ -0,0 +1,67 @@ +{ + "openapi": "3.0", + "info": { + "title": "test", + "version": "0" + }, + "paths": { + "/pet": { + "put": { + "summary": "Add a new pet to the store", + "description": "Add new pet to the store inventory.", + "operationId": "updatePet", + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "GitLab_PersonalAccessToken": [], + "GitLab_OpenIdConnect": [], + "basicAuth": [] + }, + { + "petstore_auth": ["write:pets", "read:pets"] + } + ] + } + } + }, + "components": { + "securitySchemes": { + "petstore_auth": { + "description": "Get access to data while protecting your account credentials.\nOAuth2 is also a safer and more secure way to give you access.\n", + "type": "oauth2", + "bearerFormat": "", + "flows": { + "implicit": { + "authorizationUrl": "http://petstore.swagger.io/api/oauth/dialog", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } + }, + "GitLab_PersonalAccessToken": { + "description": "GitLab Personal Access Token description", + "type": "apiKey", + "name": "PRIVATE-TOKEN", + "in": "header", + "bearerFormat": "", + "flows": {} + }, + "GitLab_OpenIdConnect": { + "description": "GitLab OpenIdConnect description", + "bearerFormat": "", + "type": "openIdConnect", + "openIdConnectUrl": "https://gitlab.com/.well-known/openid-configuration" + }, + "basicAuth": { + "type": "http", + "scheme": "basic" + } + } + } +} diff --git a/src/services/AppStore.ts b/src/services/AppStore.ts index 76805419..6c3457cd 100644 --- a/src/services/AppStore.ts +++ b/src/services/AppStore.ts @@ -12,11 +12,7 @@ import { SearchStore } from './SearchStore'; import { SchemaDefinition } from '../components/SchemaDefinition/SchemaDefinition'; import { SecurityDefs } from '../components/SecuritySchemes/SecuritySchemes'; -import { - SCHEMA_DEFINITION_JSX_NAME, - SECURITY_DEFINITIONS_COMPONENT_NAME, - SECURITY_DEFINITIONS_JSX_NAME, -} from '../utils/openapi'; +import { SCHEMA_DEFINITION_JSX_NAME, SECURITY_DEFINITIONS_JSX_NAME } from '../utils/openapi'; import { IS_BROWSER } from '../utils'; @@ -158,12 +154,6 @@ export class AppStore { const DEFAULT_OPTIONS: RedocRawOptions = { allowedMdComponents: { - [SECURITY_DEFINITIONS_COMPONENT_NAME]: { - component: SecurityDefs, - propsSelector: (store: AppStore) => ({ - securitySchemes: store.spec.securitySchemes, - }), - }, [SECURITY_DEFINITIONS_JSX_NAME]: { component: SecurityDefs, propsSelector: (store: AppStore) => ({ diff --git a/src/services/MenuBuilder.ts b/src/services/MenuBuilder.ts index 22caa2c9..56c4ad70 100644 --- a/src/services/MenuBuilder.ts +++ b/src/services/MenuBuilder.ts @@ -7,13 +7,7 @@ import { OpenAPIServer, OpenAPIPaths, } from '../types'; -import { - isOperationName, - SECURITY_DEFINITIONS_COMPONENT_NAME, - setSecuritySchemePrefix, - JsonPointer, - alphabeticallyByProp, -} from '../utils'; +import { isOperationName, JsonPointer, alphabeticallyByProp } from '../utils'; import { MarkdownRenderer } from './MarkdownRenderer'; import { GroupModel, OperationModel } from './models'; import { OpenAPIParser } from './OpenAPIParser'; @@ -93,14 +87,7 @@ export class MenuBuilder { if (heading.items) { group.items = mapHeadingsDeep(group, heading.items, depth + 1); } - if ( - MarkdownRenderer.containsComponent( - group.description || '', - SECURITY_DEFINITIONS_COMPONENT_NAME, - ) - ) { - setSecuritySchemePrefix(group.id + '/'); - } + return group; }); diff --git a/src/services/MenuStore.ts b/src/services/MenuStore.ts index 7091244c..75a9956d 100644 --- a/src/services/MenuStore.ts +++ b/src/services/MenuStore.ts @@ -146,6 +146,7 @@ export class MenuStore { let item: IMenuItem | undefined; item = this.flatItems.find(i => i.id === id); + if (item) { this.activateAndScroll(item, false); } else { diff --git a/src/services/OpenAPIParser.ts b/src/services/OpenAPIParser.ts index 897f1a79..15cd5239 100644 --- a/src/services/OpenAPIParser.ts +++ b/src/services/OpenAPIParser.ts @@ -1,14 +1,8 @@ import { OpenAPIRef, OpenAPISchema, OpenAPISpec, Referenced } from '../types'; -import { appendToMdHeading, isArray, isBoolean, IS_BROWSER } from '../utils/'; +import { isArray, isBoolean, IS_BROWSER } from '../utils/'; import { JsonPointer } from '../utils/JsonPointer'; -import { - getDefinitionName, - isNamedDefinition, - SECURITY_DEFINITIONS_COMPONENT_NAME, - SECURITY_DEFINITIONS_JSX_NAME, -} from '../utils/openapi'; -import { buildComponentComment, MarkdownRenderer } from './MarkdownRenderer'; +import { getDefinitionName, isNamedDefinition } from '../utils/openapi'; import { RedocNormalizedOptions } from './RedocNormalizedOptions'; export type MergedOpenAPISchema = OpenAPISchema & { parentRefs?: string[] }; @@ -53,7 +47,6 @@ export class OpenAPIParser { private options: RedocNormalizedOptions = new RedocNormalizedOptions({}), ) { this.validate(spec); - this.preprocess(spec); this.spec = spec; this.allowMergeRefs = spec.openapi.startsWith('3.1'); @@ -70,25 +63,6 @@ export class OpenAPIParser { } } - preprocess(spec: OpenAPISpec) { - if ( - !this.options.noAutoAuth && - spec.info && - spec.components && - spec.components.securitySchemes - ) { - // Automatically inject Authentication section with SecurityDefinitions component - const description = spec.info.description || ''; - if ( - !MarkdownRenderer.containsComponent(description, SECURITY_DEFINITIONS_COMPONENT_NAME) && - !MarkdownRenderer.containsComponent(description, SECURITY_DEFINITIONS_JSX_NAME) - ) { - const comment = buildComponentComment(SECURITY_DEFINITIONS_COMPONENT_NAME); - spec.info.description = appendToMdHeading(description, 'Authentication', comment); - } - } - } - /** * get spec part by JsonPointer ($ref) */ diff --git a/src/services/RedocNormalizedOptions.ts b/src/services/RedocNormalizedOptions.ts index 92bec6b6..9eef2fd9 100644 --- a/src/services/RedocNormalizedOptions.ts +++ b/src/services/RedocNormalizedOptions.ts @@ -21,7 +21,6 @@ export interface RedocRawOptions { sortEnumValuesAlphabetically?: boolean | string; sortOperationsAlphabetically?: boolean | string; sortTagsAlphabetically?: boolean | string; - noAutoAuth?: boolean | string; nativeScrollbars?: boolean | string; pathInMiddlePanel?: boolean | string; untrustedSpec?: boolean | string; @@ -42,6 +41,7 @@ export interface RedocRawOptions { expandSingleSchemaField?: boolean | string; schemaExpansionLevel?: number | string | 'all'; showObjectSchemaExamples?: boolean | string; + showSecuritySchemeType?: boolean; unstable_ignoreMimeParameters?: boolean; @@ -224,7 +224,6 @@ export class RedocNormalizedOptions { sortEnumValuesAlphabetically: boolean; sortOperationsAlphabetically: boolean; sortTagsAlphabetically: boolean; - noAutoAuth: boolean; nativeScrollbars: boolean; pathInMiddlePanel: boolean; untrustedSpec: boolean; @@ -245,6 +244,7 @@ export class RedocNormalizedOptions { expandSingleSchemaField: boolean; schemaExpansionLevel: number; showObjectSchemaExamples: boolean; + showSecuritySchemeType?: boolean; /* tslint:disable-next-line */ unstable_ignoreMimeParameters: boolean; @@ -294,7 +294,6 @@ export class RedocNormalizedOptions { this.sortEnumValuesAlphabetically = argValueToBoolean(raw.sortEnumValuesAlphabetically); this.sortOperationsAlphabetically = argValueToBoolean(raw.sortOperationsAlphabetically); this.sortTagsAlphabetically = argValueToBoolean(raw.sortTagsAlphabetically); - this.noAutoAuth = argValueToBoolean(raw.noAutoAuth); this.nativeScrollbars = argValueToBoolean(raw.nativeScrollbars); this.pathInMiddlePanel = argValueToBoolean(raw.pathInMiddlePanel); this.untrustedSpec = argValueToBoolean(raw.untrustedSpec); @@ -317,6 +316,7 @@ export class RedocNormalizedOptions { this.expandSingleSchemaField = argValueToBoolean(raw.expandSingleSchemaField); this.schemaExpansionLevel = argValueToExpandLevel(raw.schemaExpansionLevel); this.showObjectSchemaExamples = argValueToBoolean(raw.showObjectSchemaExamples); + this.showSecuritySchemeType = argValueToBoolean(raw.showSecuritySchemeType); this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters); diff --git a/src/services/models/SecurityRequirement.ts b/src/services/models/SecurityRequirement.ts index 8525f499..f991abf0 100644 --- a/src/services/models/SecurityRequirement.ts +++ b/src/services/models/SecurityRequirement.ts @@ -1,5 +1,4 @@ import { OpenAPISecurityRequirement, OpenAPISecurityScheme } from '../../types'; -import { SECURITY_SCHEMES_SECTION_PREFIX } from '../../utils/openapi'; import { OpenAPIParser } from '../OpenAPIParser'; export interface SecurityScheme extends OpenAPISecurityScheme { @@ -29,7 +28,7 @@ export class SecurityRequirementModel { return { ...scheme, id, - sectionId: SECURITY_SCHEMES_SECTION_PREFIX + id, + sectionId: id, displayName, scopes, }; diff --git a/src/services/models/SecuritySchemes.ts b/src/services/models/SecuritySchemes.ts index 67055cad..9b157d13 100644 --- a/src/services/models/SecuritySchemes.ts +++ b/src/services/models/SecuritySchemes.ts @@ -1,5 +1,5 @@ import { OpenAPISecurityScheme, Referenced } from '../../types'; -import { SECURITY_SCHEMES_SECTION_PREFIX } from '../../utils/openapi'; +import { SECURITY_SCHEMES_SECTION_PREFIX } from '../../utils'; import { OpenAPIParser } from '../OpenAPIParser'; export class SecuritySchemeModel { diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index 89ad4330..93eeb3d9 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -615,7 +615,6 @@ export function normalizeServers( }); } -export const SECURITY_DEFINITIONS_COMPONENT_NAME = 'security-definitions'; export const SECURITY_DEFINITIONS_JSX_NAME = 'SecurityDefinitions'; export const SCHEMA_DEFINITION_JSX_NAME = 'SchemaDefinition'; From 49cc11d91795653ca870e9276a1e0cd617964e25 Mon Sep 17 00:00:00 2001 From: Anastasiia Derymarko Date: Tue, 31 May 2022 13:20:23 +0300 Subject: [PATCH 18/19] feat: add hideSecuritySection option allowing to disable the Security panel (#2027) --- .../SecurityRequirement.tsx | 2 +- .../__tests__/SecurityRequirement.test.tsx | 20 +++++++++++++++++++ .../DiscriminatorDropdown.test.tsx.snap | 10 ++++++++++ src/services/RedocNormalizedOptions.ts | 3 +++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/components/SecurityRequirement/SecurityRequirement.tsx b/src/components/SecurityRequirement/SecurityRequirement.tsx index 7e8642ea..5ae43782 100644 --- a/src/components/SecurityRequirement/SecurityRequirement.tsx +++ b/src/components/SecurityRequirement/SecurityRequirement.tsx @@ -27,7 +27,7 @@ export function SecurityRequirements(props: SecurityRequirementsProps) { const { securities } = props; - if (!securities?.length) { + if (!securities?.length || store?.options.hideSecuritySection) { return null; } diff --git a/src/components/__tests__/SecurityRequirement.test.tsx b/src/components/__tests__/SecurityRequirement.test.tsx index 87a2e511..8d5e5761 100644 --- a/src/components/__tests__/SecurityRequirement.test.tsx +++ b/src/components/__tests__/SecurityRequirement.test.tsx @@ -49,4 +49,24 @@ describe('SecurityRequirement', () => { ); expect(component.html()).toMatchSnapshot(); }); + + it('should hide authDefinition', async () => { + const store = await createStore(simpleSecurityFixture, undefined, { + hideSecuritySection: true, + }); + + store.spec.contentItems.forEach((item: OperationModel) => { + if (item.security) { + const component = mount( + withTheme( + + , + , + ), + ); + expect(component.html().includes('Authorizations')).toBe(false); + expect(component.html().includes('svg')).toBe(false); + } + }); + }); }); diff --git a/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap b/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap index 03eb89d5..3480ad6f 100644 --- a/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap @@ -89,6 +89,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideHostname": false, "hideSchemaPattern": false, "hideSchemaTitles": false, + "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, "jsonSampleExpandLevel": 2, @@ -346,6 +347,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideHostname": false, "hideSchemaPattern": false, "hideSchemaTitles": false, + "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, "jsonSampleExpandLevel": 2, @@ -578,6 +580,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideHostname": false, "hideSchemaPattern": false, "hideSchemaTitles": false, + "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, "jsonSampleExpandLevel": 2, @@ -877,6 +880,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideHostname": false, "hideSchemaPattern": false, "hideSchemaTitles": false, + "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, "jsonSampleExpandLevel": 2, @@ -1134,6 +1138,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideHostname": false, "hideSchemaPattern": false, "hideSchemaTitles": false, + "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, "jsonSampleExpandLevel": 2, @@ -1366,6 +1371,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideHostname": false, "hideSchemaPattern": false, "hideSchemaTitles": false, + "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, "jsonSampleExpandLevel": 2, @@ -1621,6 +1627,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideHostname": false, "hideSchemaPattern": false, "hideSchemaTitles": false, + "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, "jsonSampleExpandLevel": 2, @@ -1917,6 +1924,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideHostname": false, "hideSchemaPattern": false, "hideSchemaTitles": false, + "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, "jsonSampleExpandLevel": 2, @@ -2174,6 +2182,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideHostname": false, "hideSchemaPattern": false, "hideSchemaTitles": false, + "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, "jsonSampleExpandLevel": 2, @@ -2406,6 +2415,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideHostname": false, "hideSchemaPattern": false, "hideSchemaTitles": false, + "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, "jsonSampleExpandLevel": 2, diff --git a/src/services/RedocNormalizedOptions.ts b/src/services/RedocNormalizedOptions.ts index 9eef2fd9..3efcbac7 100644 --- a/src/services/RedocNormalizedOptions.ts +++ b/src/services/RedocNormalizedOptions.ts @@ -42,6 +42,7 @@ export interface RedocRawOptions { schemaExpansionLevel?: number | string | 'all'; showObjectSchemaExamples?: boolean | string; showSecuritySchemeType?: boolean; + hideSecuritySection?: boolean; unstable_ignoreMimeParameters?: boolean; @@ -245,6 +246,7 @@ export class RedocNormalizedOptions { schemaExpansionLevel: number; showObjectSchemaExamples: boolean; showSecuritySchemeType?: boolean; + hideSecuritySection?: boolean; /* tslint:disable-next-line */ unstable_ignoreMimeParameters: boolean; @@ -317,6 +319,7 @@ export class RedocNormalizedOptions { this.schemaExpansionLevel = argValueToExpandLevel(raw.schemaExpansionLevel); this.showObjectSchemaExamples = argValueToBoolean(raw.showObjectSchemaExamples); this.showSecuritySchemeType = argValueToBoolean(raw.showSecuritySchemeType); + this.hideSecuritySection = argValueToBoolean(raw.hideSecuritySection); this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters); From 16463f9a15f00f0fe765603b055f9e10094bc204 Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Tue, 31 May 2022 15:41:08 +0300 Subject: [PATCH 19/19] chore: v2.0.0-rc.71 (#2029) --- CHANGELOG.md | 19 +++++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4495018d..56cb1429 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +# [2.0.0-rc.71](https://github.com/Redocly/redoc/compare/v2.0.0-rc.70...v2.0.0-rc.71) (2022-05-31) + + +### Bug Fixes + +* constraints label details ([eb0917d](https://github.com/Redocly/redoc/commit/eb0917d002e57353027fee9c8f07605de8f1ff6f)) +* merge allOf in correct order ([#2020](https://github.com/Redocly/redoc/issues/2020)) ([1e4ea03](https://github.com/Redocly/redoc/commit/1e4ea03d4a9b7eddf3e4cc7cbdbd4d913583e837)) + + +### Features + +* add hideSecuritySection option allowing to disable the Security panel ([#2027](https://github.com/Redocly/redoc/issues/2027)) ([49cc11d](https://github.com/Redocly/redoc/commit/49cc11d91795653ca870e9276a1e0cd617964e25)) +* add Redoc to Redocly CDN ([#2026](https://github.com/Redocly/redoc/issues/2026)) ([77104d6](https://github.com/Redocly/redoc/commit/77104d6c0d6f457aa08a158e93b52a45877be84e)) +* add support prefix items ([27a9dba](https://github.com/Redocly/redoc/commit/27a9dbaf46aded01a6512645dab27870a85cc73b)) +* remove auth section ([#2022](https://github.com/Redocly/redoc/issues/2022)) ([a863302](https://github.com/Redocly/redoc/commit/a863302cc803bdf27187c613157ba90af1040fc4)) +* show minProperties maxProperties ([#2015](https://github.com/Redocly/redoc/issues/2015)) ([82712c5](https://github.com/Redocly/redoc/commit/82712c5b408dc6bc142307d45fb962de2a43ffba)) + + + # [2.0.0-rc.70](https://github.com/Redocly/redoc/compare/2.0.0-rc.69...2.0.0-rc.70) (2022-05-17) diff --git a/package-lock.json b/package-lock.json index 1628b932..5bd6d7ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "redoc", - "version": "2.0.0-rc.70", + "version": "2.0.0-rc.71", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "redoc", - "version": "2.0.0-rc.70", + "version": "2.0.0-rc.71", "license": "MIT", "dependencies": { "@redocly/openapi-core": "^1.0.0-beta.97", diff --git a/package.json b/package.json index 9afc1851..954a1bfa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redoc", - "version": "2.0.0-rc.70", + "version": "2.0.0-rc.71", "description": "ReDoc", "repository": { "type": "git",